mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-25 20:54:18 +00:00
Compare commits
22 Commits
explorer-c
...
v-yiqcao/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34f53709c8 | ||
|
|
ed7a8dafcf | ||
|
|
2e83adc7db | ||
|
|
6e619175c6 | ||
|
|
08e8bf4bcf | ||
|
|
bbddeddbc7 | ||
|
|
89dc0f394b | ||
|
|
e28b6cd44a | ||
|
|
30e0001b7f | ||
|
|
4a8f408112 | ||
|
|
e801364800 | ||
|
|
a55f2d0de9 | ||
|
|
d40b1aa9b5 | ||
|
|
cc63cdc1fd | ||
|
|
c3058ee5a9 | ||
|
|
b000631a0c | ||
|
|
e8f4c8f93c | ||
|
|
16bde97e47 | ||
|
|
6da43ee27b | ||
|
|
ebae484b8f | ||
|
|
dfb1b50621 | ||
|
|
f54e8eb692 |
@@ -14,7 +14,6 @@ src/Common/DataAccessUtilityBase.ts
|
|||||||
src/Common/DeleteFeedback.ts
|
src/Common/DeleteFeedback.ts
|
||||||
src/Common/DocumentClientUtilityBase.ts
|
src/Common/DocumentClientUtilityBase.ts
|
||||||
src/Common/EditableUtility.ts
|
src/Common/EditableUtility.ts
|
||||||
src/Common/EnvironmentUtility.ts
|
|
||||||
src/Common/HashMap.test.ts
|
src/Common/HashMap.test.ts
|
||||||
src/Common/HashMap.ts
|
src/Common/HashMap.ts
|
||||||
src/Common/HeadersUtility.test.ts
|
src/Common/HeadersUtility.test.ts
|
||||||
|
|||||||
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
@@ -101,6 +101,7 @@ jobs:
|
|||||||
PLATFORM: "Emulator"
|
PLATFORM: "Emulator"
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
@@ -159,13 +160,14 @@ jobs:
|
|||||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -189,7 +191,7 @@ jobs:
|
|||||||
nugetmpac:
|
nugetmpac:
|
||||||
name: Publish Nuget MPAC
|
name: Publish Nuget MPAC
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -211,3 +213,28 @@ jobs:
|
|||||||
name: packages
|
name: packages
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
path: "*.nupkg"
|
||||||
|
nugetie:
|
||||||
|
name: Publish Nuget IE
|
||||||
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
|
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
||||||
|
steps:
|
||||||
|
- uses: nuget/setup-nuget@v1
|
||||||
|
with:
|
||||||
|
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
||||||
|
- name: Download Dist Folder
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
- run: cp ./configs/prod.json config.json
|
||||||
|
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.IE/g' DataExplorer.nuspec
|
||||||
|
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
|
||||||
|
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
||||||
|
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
name: packages
|
||||||
|
with:
|
||||||
|
path: "*.nupkg"
|
||||||
|
|||||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
37
README.md
37
README.md
@@ -13,29 +13,18 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
|
|||||||
|
|
||||||
### Watch mode
|
### Watch mode
|
||||||
|
|
||||||
Run `npm run watch` to start the development server and automatically rebuild on changes
|
Run `npm start` to start the development server and automatically rebuild on changes
|
||||||
|
|
||||||
### Specifying Development Platform
|
### Hosted Development (https://cosmos.azure.com)
|
||||||
|
|
||||||
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options:
|
- Visit: `https://localhost:1234/hostedExplorer.html`
|
||||||
|
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
|
||||||
- Hosted
|
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
||||||
- Emulator
|
|
||||||
- Portal
|
|
||||||
|
|
||||||
`PLATFORM=Emulator npm run watch`
|
|
||||||
|
|
||||||
### Hosted Development
|
|
||||||
|
|
||||||
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
|
||||||
|
|
||||||
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
|
|
||||||
|
|
||||||
### Emulator Development
|
### Emulator Development
|
||||||
|
|
||||||
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
- Start the Cosmos Emulator
|
||||||
|
- Visit: https://localhost:1234/index.html
|
||||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
|
||||||
|
|
||||||
#### Setting up a Remote Emulator
|
#### Setting up a Remote Emulator
|
||||||
|
|
||||||
@@ -55,16 +44,8 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
|
|||||||
|
|
||||||
### Portal Development
|
### Portal Development
|
||||||
|
|
||||||
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment
|
- Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
|
||||||
You can however load a local running instance of data explorer in the production portal.
|
|
||||||
|
|
||||||
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
|
||||||
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
|
||||||
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
|
||||||
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
|
||||||
|
|
||||||
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
7
canvas/README.md
Normal file
7
canvas/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Why?
|
||||||
|
|
||||||
|
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
|
||||||
|
|
||||||
|
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
|
||||||
|
|
||||||
|
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved
|
||||||
1
canvas/index.js
Normal file
1
canvas/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = {}
|
||||||
11
canvas/package.json
Normal file
11
canvas/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "canvas",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
@@ -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,32 +161,31 @@
|
|||||||
**************************************************************************************/
|
**************************************************************************************/
|
||||||
|
|
||||||
@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,
|
||||||
th, td {
|
td {
|
||||||
|
&:nth-child(2) {
|
||||||
&:nth-child(2) {
|
width: @IETableDataWidth;
|
||||||
width: @IETableDataWidth;
|
}
|
||||||
}
|
|
||||||
|
&:nth-child(3) {
|
||||||
&:nth-child(3) {
|
width: 50%;
|
||||||
width: 50%;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************************************
|
/********************************************************************************************
|
||||||
@@ -194,15 +193,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************************************************
|
/************************************************************************************************
|
||||||
@@ -212,63 +211,87 @@
|
|||||||
@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;
|
||||||
}
|
}
|
||||||
|
|||||||
3827
less/documentDB.less
3827
less/documentDB.less
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,12 @@
|
|||||||
@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 {
|
||||||
|
|||||||
220
package-lock.json
generated
220
package-lock.json
generated
@@ -5393,11 +5393,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
|
||||||
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
||||||
},
|
},
|
||||||
"abbrev": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
|
||||||
},
|
|
||||||
"abort-controller": {
|
"abort-controller": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
@@ -5641,6 +5636,7 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delegates": "^1.0.0",
|
"delegates": "^1.0.0",
|
||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
@@ -6883,14 +6879,7 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"canvas": {
|
"canvas": {
|
||||||
"version": "2.6.1",
|
"version": "file:canvas"
|
||||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
|
|
||||||
"requires": {
|
|
||||||
"nan": "^2.14.0",
|
|
||||||
"node-pre-gyp": "^0.11.0",
|
|
||||||
"simple-get": "^3.0.3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"capture-exit": {
|
"capture-exit": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -7454,7 +7443,8 @@
|
|||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"constants-browserify": {
|
"constants-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -8435,6 +8425,7 @@
|
|||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mimic-response": "^2.0.0"
|
"mimic-response": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -8460,7 +8451,8 @@
|
|||||||
"deep-extend": {
|
"deep-extend": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"deep-is": {
|
"deep-is": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
@@ -8652,7 +8644,8 @@
|
|||||||
"delegates": {
|
"delegates": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -8688,7 +8681,8 @@
|
|||||||
"detect-libc": {
|
"detect-libc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"detect-newline": {
|
"detect-newline": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -10674,14 +10668,6 @@
|
|||||||
"universalify": "^0.1.0"
|
"universalify": "^0.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs-minipass": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
|
||||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs-observable": {
|
"fs-observable": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/fs-observable/-/fs-observable-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/fs-observable/-/fs-observable-4.1.14.tgz",
|
||||||
@@ -10823,6 +10809,7 @@
|
|||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"aproba": "^1.0.3",
|
"aproba": "^1.0.3",
|
||||||
"console-control-strings": "^1.0.0",
|
"console-control-strings": "^1.0.0",
|
||||||
@@ -10837,12 +10824,14 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -10851,6 +10840,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@@ -10861,6 +10851,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -11350,7 +11341,8 @@
|
|||||||
"has-unicode": {
|
"has-unicode": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"has-value": {
|
"has-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -11832,14 +11824,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
|
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
|
||||||
},
|
},
|
||||||
"ignore-walk": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
|
||||||
"requires": {
|
|
||||||
"minimatch": "^3.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"image-size": {
|
"image-size": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
@@ -15544,7 +15528,8 @@
|
|||||||
"mimic-response": {
|
"mimic-response": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"min-document": {
|
"min-document": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
@@ -15601,15 +15586,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"minipass": {
|
|
||||||
"version": "2.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
|
||||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minipass-collect": {
|
"minipass-collect": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
|
||||||
@@ -15679,14 +15655,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minizlib": {
|
|
||||||
"version": "1.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
|
||||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mississippi": {
|
"mississippi": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
@@ -15861,7 +15829,8 @@
|
|||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.14.2",
|
"version": "2.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
|
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
@@ -15911,26 +15880,6 @@
|
|||||||
"semver": "^5.4.1"
|
"semver": "^5.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"needle": {
|
|
||||||
"version": "2.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz",
|
|
||||||
"integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "^3.2.6",
|
|
||||||
"iconv-lite": "^0.4.4",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "3.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
|
||||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "^2.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"negotiator": {
|
"negotiator": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
@@ -16099,41 +16048,6 @@
|
|||||||
"which": "^1.3.0"
|
"which": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-pre-gyp": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
|
|
||||||
"requires": {
|
|
||||||
"detect-libc": "^1.0.2",
|
|
||||||
"mkdirp": "^0.5.1",
|
|
||||||
"needle": "^2.2.1",
|
|
||||||
"nopt": "^4.0.1",
|
|
||||||
"npm-packlist": "^1.1.6",
|
|
||||||
"npmlog": "^4.0.2",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"rimraf": "^2.6.1",
|
|
||||||
"semver": "^5.3.0",
|
|
||||||
"tar": "^4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"mkdirp": {
|
|
||||||
"version": "0.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
|
||||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rimraf": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
|
||||||
"requires": {
|
|
||||||
"glob": "^7.1.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "1.1.66",
|
"version": "1.1.66",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz",
|
||||||
@@ -16146,15 +16060,6 @@
|
|||||||
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=",
|
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"nopt": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
|
||||||
"requires": {
|
|
||||||
"abbrev": "1",
|
|
||||||
"osenv": "^0.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"normalize-package-data": {
|
"normalize-package-data": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||||
@@ -16179,29 +16084,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
||||||
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
||||||
},
|
},
|
||||||
"npm-bundled": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
|
||||||
"requires": {
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-normalize-package-bin": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
|
||||||
},
|
|
||||||
"npm-packlist": {
|
|
||||||
"version": "1.4.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
|
||||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
|
||||||
"requires": {
|
|
||||||
"ignore-walk": "^3.0.1",
|
|
||||||
"npm-bundled": "^1.0.1",
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-run-path": {
|
"npm-run-path": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
@@ -16214,6 +16096,7 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"are-we-there-yet": "~1.1.2",
|
"are-we-there-yet": "~1.1.2",
|
||||||
"console-control-strings": "~1.1.0",
|
"console-control-strings": "~1.1.0",
|
||||||
@@ -16605,7 +16488,8 @@
|
|||||||
"os-homedir": {
|
"os-homedir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"os-locale": {
|
"os-locale": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
@@ -16624,20 +16508,6 @@
|
|||||||
"windows-release": "^3.1.0"
|
"windows-release": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"os-tmpdir": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
|
||||||
},
|
|
||||||
"osenv": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
|
||||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
|
||||||
"requires": {
|
|
||||||
"os-homedir": "^1.0.0",
|
|
||||||
"os-tmpdir": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-defer": {
|
"p-defer": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||||
@@ -17690,6 +17560,7 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"deep-extend": "^0.6.0",
|
"deep-extend": "^0.6.0",
|
||||||
"ini": "~1.3.0",
|
"ini": "~1.3.0",
|
||||||
@@ -19116,12 +18987,14 @@
|
|||||||
"simple-concat": {
|
"simple-concat": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"simple-get": {
|
"simple-get": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
|
||||||
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
|
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"decompress-response": "^4.2.0",
|
"decompress-response": "^4.2.0",
|
||||||
"once": "^1.3.1",
|
"once": "^1.3.1",
|
||||||
@@ -20113,30 +19986,6 @@
|
|||||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tar": {
|
|
||||||
"version": "4.4.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
|
||||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
|
||||||
"requires": {
|
|
||||||
"chownr": "^1.1.1",
|
|
||||||
"fs-minipass": "^1.2.5",
|
|
||||||
"minipass": "^2.8.6",
|
|
||||||
"minizlib": "^1.2.1",
|
|
||||||
"mkdirp": "^0.5.0",
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"mkdirp": {
|
|
||||||
"version": "0.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
|
||||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tar-fs": {
|
"tar-fs": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||||
@@ -22192,6 +22041,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"string-width": "^1.0.2 || 2"
|
"string-width": "^1.0.2 || 2"
|
||||||
},
|
},
|
||||||
@@ -22199,12 +22049,14 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-fullwidth-code-point": "^2.0.0",
|
"is-fullwidth-code-point": "^2.0.0",
|
||||||
"strip-ansi": "^4.0.0"
|
"strip-ansi": "^4.0.0"
|
||||||
@@ -22214,6 +22066,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^3.0.0"
|
"ansi-regex": "^3.0.0"
|
||||||
}
|
}
|
||||||
@@ -22383,7 +22236,8 @@
|
|||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "13.3.2",
|
"version": "13.3.2",
|
||||||
|
|||||||
@@ -44,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": "2.6.1",
|
"canvas": "file:./canvas",
|
||||||
"clean-webpack-plugin": "0.1.19",
|
"clean-webpack-plugin": "0.1.19",
|
||||||
"copy-webpack-plugin": "6.0.2",
|
"copy-webpack-plugin": "6.0.2",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export class Features {
|
|||||||
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
import { ConflictDefinition, FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import Q from "q";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
|
||||||
import * as Constants from "./Constants";
|
|
||||||
import { client } from "./CosmosClient";
|
|
||||||
|
|
||||||
export function getCommonQueryOptions(options: FeedOptions): any {
|
|
||||||
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
|
||||||
options = options || {};
|
|
||||||
options.populateQueryMetrics = true;
|
|
||||||
options.enableScanInQuery = options.enableScanInQuery || true;
|
|
||||||
if (!options.partitionKey) {
|
|
||||||
options.forceQueryPlan = true;
|
|
||||||
}
|
|
||||||
options.maxItemCount =
|
|
||||||
options.maxItemCount ||
|
|
||||||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
|
||||||
Constants.Queries.itemsPerPage;
|
|
||||||
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryDocuments(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
|
||||||
options = getCommonQueryOptions(options);
|
|
||||||
const documentsIterator = client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(containerId)
|
|
||||||
.items.query(query, options);
|
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
|
||||||
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
|
||||||
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
|
||||||
|
|
||||||
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
|
||||||
if (!partitionKeyDefinition) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partitionKeyValue === undefined) {
|
|
||||||
return [{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [partitionKeyValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.replace(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: StoredProcedure,
|
|
||||||
partitionKeyValue: any,
|
|
||||||
params: any[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
|
||||||
const deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id())
|
|
||||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
|
||||||
.then(response =>
|
|
||||||
deferred.resolve({
|
|
||||||
result: response.resource,
|
|
||||||
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch(error => deferred.reject(error));
|
|
||||||
|
|
||||||
return deferred.promise.timeout(
|
|
||||||
Constants.ClientDefaults.requestTimeoutMs,
|
|
||||||
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.items.create(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.read()
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteConflict(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
conflictId: ConflictId,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<any> {
|
|
||||||
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.conflict(conflictId.id())
|
|
||||||
.delete(options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
const documentsIterator = client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(containerId)
|
|
||||||
.conflicts.query(query, options);
|
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import Q from "q";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
|
||||||
import * as Constants from "./Constants";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
|
||||||
export function queryDocuments(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
|
||||||
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEntityName() {
|
|
||||||
const defaultExperience =
|
|
||||||
window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
|
|
||||||
if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
|
|
||||||
return "document";
|
|
||||||
}
|
|
||||||
return "item";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: StoredProcedure,
|
|
||||||
partitionKeyValue: any,
|
|
||||||
params: any[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
|
||||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
deferred.resolve(response);
|
|
||||||
logConsoleInfo(
|
|
||||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"ExecuteStoredProcedure",
|
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryDocumentsPage(
|
|
||||||
resourceName: string,
|
|
||||||
documentsIterator: MinimalQueryIterator,
|
|
||||||
firstItemIndex: number,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<ViewModels.QueryResults> {
|
|
||||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
|
||||||
Q(nextPage(documentsIterator, firstItemIndex))
|
|
||||||
.then(
|
|
||||||
(result: ViewModels.QueryResults) => {
|
|
||||||
const itemCount = (result.documents && result.documents.length) || 0;
|
|
||||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
|
||||||
deferred.resolve(result);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
|
||||||
.then(
|
|
||||||
(document: any) => {
|
|
||||||
deferred.resolve(document);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
|
||||||
.then(
|
|
||||||
(updatedDocument: any) => {
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.resolve(updatedDocument);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
|
||||||
.then(
|
|
||||||
(savedDocument: any) => {
|
|
||||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
|
||||||
deferred.resolve(savedDocument);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteConflict(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
conflictId: ConflictId,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
|
||||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
10
src/Common/DocumentUtility.ts
Normal file
10
src/Common/DocumentUtility.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
export const getEntityName = (): string => {
|
||||||
|
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
||||||
|
return "document";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "item";
|
||||||
|
};
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export default class EnvironmentUtility {
|
export function normalizeArmEndpoint(uri: string): string {
|
||||||
public static normalizeArmEndpointUri(uri: string): string {
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
return `${uri}/`;
|
||||||
return `${uri}/`;
|
|
||||||
}
|
|
||||||
return uri;
|
|
||||||
}
|
}
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import * as _ from "underscore";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { QueryUtils } from "../Utils/QueryUtils";
|
import { QueryUtils } from "../Utils/QueryUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "./dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "./dataAccess/deleteDocument";
|
||||||
|
import { queryDocuments } from "./dataAccess/queryDocuments";
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -31,10 +33,7 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queriesCollection.rawDataModel);
|
return Promise.resolve(queriesCollection.rawDataModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
"Setting up account for saving queries"
|
|
||||||
);
|
|
||||||
return createCollection({
|
return createCollection({
|
||||||
collectionId: SavedQueries.CollectionName,
|
collectionId: SavedQueries.CollectionName,
|
||||||
createNewDatabase: true,
|
createNewDatabase: true,
|
||||||
@@ -45,10 +44,7 @@ export class QueriesClient {
|
|||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
|
||||||
ConsoleDataType.Info,
|
|
||||||
"Successfully set up account for saving queries"
|
|
||||||
);
|
|
||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -56,17 +52,14 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveQuery(query: DataModels.Query): Promise<void> {
|
public async saveQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +67,16 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Saving query ${query.queryName}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Saving query ${query.queryName}`
|
|
||||||
);
|
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
return createDocument(queriesCollection, query)
|
return createDocument(queriesCollection, query)
|
||||||
.then(
|
.then(
|
||||||
(savedQuery: DataModels.Query) => {
|
(savedQuery: DataModels.Query) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully saved query ${query.queryName}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully saved query ${query.queryName}`
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -103,74 +87,65 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getQueries(): Promise<DataModels.Query[]> {
|
public async getQueries(): Promise<DataModels.Query[]> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: any = { enableCrossPartitionQuery: true };
|
const options: any = { enableCrossPartitionQuery: true };
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
||||||
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
|
SavedQueries.DatabaseName,
|
||||||
|
SavedQueries.CollectionName,
|
||||||
|
this.fetchQueriesQuery(),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
||||||
|
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
||||||
|
return QueryUtils.queryAllPages(fetchQueries)
|
||||||
.then(
|
.then(
|
||||||
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
(results: ViewModels.QueryResults) => {
|
||||||
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
||||||
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
if (!document) {
|
||||||
return QueryUtils.queryAllPages(fetchQueries).then(
|
return undefined;
|
||||||
(results: ViewModels.QueryResults) => {
|
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
|
||||||
if (!document) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const { id, resourceId, query, queryName } = document;
|
|
||||||
const parsedQuery: DataModels.Query = {
|
|
||||||
resourceId: resourceId,
|
|
||||||
queryName: queryName,
|
|
||||||
query: query,
|
|
||||||
id: id
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
this.validateQuery(parsedQuery);
|
|
||||||
return parsedQuery;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries");
|
|
||||||
return Promise.resolve(queries);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
);
|
const { id, resourceId, query, queryName } = document;
|
||||||
|
const parsedQuery: DataModels.Query = {
|
||||||
|
resourceId: resourceId,
|
||||||
|
queryName: queryName,
|
||||||
|
query: query,
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
this.validateQuery(parsedQuery);
|
||||||
|
return parsedQuery;
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||||
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
// should never get into this state but we handle this regardless
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,16 +153,10 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to delete query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting query ${query.queryName}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting query ${query.queryName}`
|
|
||||||
);
|
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
const documentId = new DocumentId(
|
const documentId = new DocumentId(
|
||||||
{
|
{
|
||||||
@@ -201,10 +170,7 @@ export class QueriesClient {
|
|||||||
return deleteDocument(queriesCollection, documentId)
|
return deleteDocument(queriesCollection, documentId)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted query ${query.queryName}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted query ${query.queryName}`
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -212,7 +178,7 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getResourceId(): string {
|
public getResourceId(): string {
|
||||||
|
|||||||
@@ -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,9 +42,10 @@ export class Splitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public initialize() {
|
public initialize() {
|
||||||
this.splitter = document.getElementById(this.splitterId);
|
if (document.getElementById(this.splitterId) !== null && document.getElementById(this.leftSideId) != null) {
|
||||||
this.leftSide = document.getElementById(this.leftSideId);
|
this.splitter = <HTMLElement>document.getElementById(this.splitterId);
|
||||||
|
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,6 +1,5 @@
|
|||||||
jest.mock("../../Utils/arm/request");
|
jest.mock("../../Utils/arm/request");
|
||||||
jest.mock("../CosmosClient");
|
jest.mock("../CosmosClient");
|
||||||
jest.mock("../DataAccessUtilityBase");
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|||||||
25
src/Common/dataAccess/createDocument.ts
Normal file
25
src/Common/dataAccess/createDocument.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
|
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.items.create(newDocument);
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
36
src/Common/dataAccess/deleteConflict.ts
Normal file
36
src/Common/dataAccess/deleteConflict.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import ConflictId from "../../Explorer/Tree/ConflictId";
|
||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { RequestOptions } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
|
export const deleteConflict = async (collection: CollectionBase, conflictId: ConflictId): Promise<void> => {
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
partitionKey: getPartitionKeyHeaderForConflict(conflictId)
|
||||||
|
};
|
||||||
|
|
||||||
|
await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.conflict(conflictId.id())
|
||||||
|
.delete(options as RequestOptions);
|
||||||
|
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPartitionKeyHeaderForConflict = (conflictId: ConflictId): unknown => {
|
||||||
|
if (!conflictId.partitionKey) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflictId.partitionKeyValue === undefined ? [{}] : [conflictId.partitionKeyValue];
|
||||||
|
};
|
||||||
25
src/Common/dataAccess/deleteDocument.ts
Normal file
25
src/Common/dataAccess/deleteDocument.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
||||||
|
const entityName: string = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.delete();
|
||||||
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
48
src/Common/dataAccess/executeStoredProcedure.ts
Normal file
48
src/Common/dataAccess/executeStoredProcedure.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Collection } from "../../Contracts/ViewModels";
|
||||||
|
import { ClientDefaults, HttpHeaders } from "../Constants";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
|
||||||
|
|
||||||
|
export interface ExecuteSprocResult {
|
||||||
|
result: StoredProcedure;
|
||||||
|
scriptLogs: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const executeStoredProcedure = async (
|
||||||
|
collection: Collection,
|
||||||
|
storedProcedure: StoredProcedure,
|
||||||
|
partitionKeyValue: string,
|
||||||
|
params: string[]
|
||||||
|
): Promise<ExecuteSprocResult> => {
|
||||||
|
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
throw Error(`Request timed out while executing stored procedure ${storedProcedure.id()}`);
|
||||||
|
}, ClientDefaults.requestTimeoutMs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id())
|
||||||
|
.execute(partitionKeyValue, params, { enableScriptLogging: true });
|
||||||
|
clearTimeout(timeout);
|
||||||
|
logConsoleInfo(
|
||||||
|
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
result: response.resource,
|
||||||
|
scriptLogs: response.headers[HttpHeaders.scriptLogResults] as string
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
handleError(
|
||||||
|
error,
|
||||||
|
"ExecuteStoredProcedure",
|
||||||
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
14
src/Common/dataAccess/queryConflicts.ts
Normal file
14
src/Common/dataAccess/queryConflicts.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
|
||||||
|
export const queryConflicts = (
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: FeedOptions
|
||||||
|
): QueryIterator<ConflictDefinition & Resource> => {
|
||||||
|
return client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.conflicts.query(query, options);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getCommonQueryOptions } from "./DataAccessUtilityBase";
|
import { getCommonQueryOptions } from "./queryDocuments";
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
|
||||||
describe("getCommonQueryOptions", () => {
|
describe("getCommonQueryOptions", () => {
|
||||||
it("builds the correct default options objects", () => {
|
it("builds the correct default options objects", () => {
|
||||||
34
src/Common/dataAccess/queryDocuments.ts
Normal file
34
src/Common/dataAccess/queryDocuments.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Queries } from "../Constants";
|
||||||
|
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
|
||||||
|
export const queryDocuments = (
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: FeedOptions
|
||||||
|
): QueryIterator<ItemDefinition & Resource> => {
|
||||||
|
options = getCommonQueryOptions(options);
|
||||||
|
return client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.items.query(query, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
|
||||||
|
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
||||||
|
options = options || {};
|
||||||
|
options.populateQueryMetrics = true;
|
||||||
|
options.enableScanInQuery = options.enableScanInQuery || true;
|
||||||
|
if (!options.partitionKey) {
|
||||||
|
options.forceQueryPlan = true;
|
||||||
|
}
|
||||||
|
options.maxItemCount =
|
||||||
|
options.maxItemCount ||
|
||||||
|
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
||||||
|
Queries.itemsPerPage;
|
||||||
|
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
26
src/Common/dataAccess/queryDocumentsPage.ts
Normal file
26
src/Common/dataAccess/queryDocumentsPage.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { QueryResults } from "../../Contracts/ViewModels";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
|
||||||
|
export const queryDocumentsPage = async (
|
||||||
|
resourceName: string,
|
||||||
|
documentsIterator: MinimalQueryIterator,
|
||||||
|
firstItemIndex: number
|
||||||
|
): Promise<QueryResults> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex);
|
||||||
|
const itemCount = (result.documents && result.documents.length) || 0;
|
||||||
|
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
27
src/Common/dataAccess/readDocument.ts
Normal file
27
src/Common/dataAccess/readDocument.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Item } from "@azure/cosmos";
|
||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.read();
|
||||||
|
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
32
src/Common/dataAccess/updateDocument.ts
Normal file
32
src/Common/dataAccess/updateDocument.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { Item } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const updateDocument = async (
|
||||||
|
collection: CollectionBase,
|
||||||
|
documentId: DocumentId,
|
||||||
|
newDocument: Item
|
||||||
|
): Promise<Item> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.replace(newDocument);
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -362,7 +362,7 @@ export enum CollectionTabKind {
|
|||||||
Gallery = 17,
|
Gallery = 17,
|
||||||
NotebookViewer = 18,
|
NotebookViewer = 18,
|
||||||
Schema = 19,
|
Schema = 19,
|
||||||
SettingsV2 = 19
|
SettingsV2 = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
|
|||||||
@@ -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.collection.partitionKey ||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
|
(!this.collection.partitionKey || this.collection.partitionKey.systemKey);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
throughput: undefined,
|
throughput: undefined,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { IColumn, Text } from "office-ui-fabric-react";
|
||||||
import {
|
import {
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
getEstimatedSpendElement,
|
getEstimatedSpendingElement,
|
||||||
getEstimatedAutoscaleSpendElement,
|
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
@@ -19,11 +19,37 @@ import {
|
|||||||
mongoIndexingPolicyDisclaimer,
|
mongoIndexingPolicyDisclaimer,
|
||||||
mongoIndexingPolicyAADError,
|
mongoIndexingPolicyAADError,
|
||||||
mongoIndexTransformationRefreshingMessage,
|
mongoIndexTransformationRefreshingMessage,
|
||||||
renderMongoIndexTransformationRefreshMessage
|
renderMongoIndexTransformationRefreshMessage,
|
||||||
|
ManualEstimatedSpendingDisplayProps,
|
||||||
|
PriceBreakdown,
|
||||||
|
getRuPriceBreakdown
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{ key: "costType", name: "", fieldName: "costType", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "hourly", name: "Hourly", fieldName: "hourly", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "daily", name: "Daily", fieldName: "daily", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true }
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
hourly: <Text>$ 1.02</Text>,
|
||||||
|
daily: <Text>$ 24.48</Text>,
|
||||||
|
monthly: <Text>$ 744.6</Text>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const priceBreakdown: PriceBreakdown = {
|
||||||
|
hourlyPrice: 1.02,
|
||||||
|
dailyPrice: 24.48,
|
||||||
|
monthlyPrice: 744.6,
|
||||||
|
pricePerRu: 0.00051,
|
||||||
|
currency: "RMB",
|
||||||
|
currencySign: "¥"
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getAutoPilotV3SpendElement(1000, false)}
|
{getAutoPilotV3SpendElement(1000, false)}
|
||||||
@@ -31,9 +57,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{getAutoPilotV3SpendElement(1000, true)}
|
{getAutoPilotV3SpendElement(1000, true)}
|
||||||
{getAutoPilotV3SpendElement(undefined, true)}
|
{getAutoPilotV3SpendElement(undefined, true)}
|
||||||
|
|
||||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
{getEstimatedSpendingElement(estimatedSpendingColumns, estimatedSpendingItems, 1000, 2, priceBreakdown, false)}
|
||||||
|
|
||||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
|
||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
@@ -69,4 +93,14 @@ describe("SettingsUtils functions", () => {
|
|||||||
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return correct price breakdown for a manual RU setting of 500, 1 region, multimaster disabled", () => {
|
||||||
|
const prices = getRuPriceBreakdown(500, "", 1, false, false);
|
||||||
|
expect(prices.hourlyPrice).toBe(0.04);
|
||||||
|
expect(prices.dailyPrice).toBe(0.96);
|
||||||
|
expect(prices.monthlyPrice).toBe(29.2);
|
||||||
|
expect(prices.pricePerRu).toBe(0.00008);
|
||||||
|
expect(prices.currency).toBe("USD");
|
||||||
|
expect(prices.currencySign).toBe("$");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||||
import {
|
import {
|
||||||
computeAutoscaleUsagePriceHourly,
|
|
||||||
getPriceCurrency,
|
getPriceCurrency,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
getPricePerRu,
|
getPricePerRu,
|
||||||
calculateEstimateNumber
|
estimatedCostDisclaimer
|
||||||
} from "../../../Utils/PricingUtils";
|
} from "../../../Utils/PricingUtils";
|
||||||
import {
|
import {
|
||||||
ITextFieldStyles,
|
ITextFieldStyles,
|
||||||
@@ -32,11 +31,42 @@ import {
|
|||||||
MessageBarType,
|
MessageBarType,
|
||||||
Stack,
|
Stack,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize
|
SpinnerSize,
|
||||||
|
DetailsList,
|
||||||
|
IColumn,
|
||||||
|
SelectionMode,
|
||||||
|
DetailsListLayoutMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
|
IDetailsColumnStyles
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
export interface EstimatedSpendingDisplayProps {
|
||||||
|
costType: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ManualEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||||
|
hourly: JSX.Element;
|
||||||
|
daily: JSX.Element;
|
||||||
|
monthly: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoscaleEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||||
|
minPerMonth: JSX.Element;
|
||||||
|
maxPerMonth: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceBreakdown {
|
||||||
|
hourlyPrice: number;
|
||||||
|
dailyPrice: number;
|
||||||
|
monthlyPrice: number;
|
||||||
|
pricePerRu: number;
|
||||||
|
currency: string;
|
||||||
|
currencySign: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
@@ -104,6 +134,16 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
background: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
root: {
|
root: {
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -126,10 +166,17 @@ export const separatorStyles: Partial<ISeparatorStyles> = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
export const messageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
|
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,
|
||||||
@@ -165,63 +212,61 @@ export const getAutoPilotV3SpendElement = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEstimatedAutoscaleSpendElement = (
|
export const getRuPriceBreakdown = (
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
numberOfRegions: number,
|
||||||
multimaster: boolean
|
isMultimaster: boolean,
|
||||||
): JSX.Element => {
|
isAutoscale: boolean
|
||||||
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
): PriceBreakdown => {
|
||||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
const currency: string = getPriceCurrency(serverId);
|
serverId: serverId,
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
requestUnits: throughput,
|
||||||
const pricePerRu =
|
numberOfRegions: numberOfRegions,
|
||||||
getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
|
multimasterEnabled: isMultimaster,
|
||||||
getMultimasterMultiplier(regions, multimaster);
|
isAutoscale: isAutoscale
|
||||||
|
});
|
||||||
return (
|
const basePricePerRu: number = isAutoscale
|
||||||
<Text id="autoscaleSpendElement">
|
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
||||||
Estimated monthly cost ({currency}) is{" "}
|
: getPricePerRu(serverId);
|
||||||
<b>
|
return {
|
||||||
{currencySign}
|
hourlyPrice: hourlyPrice,
|
||||||
{calculateEstimateNumber(monthlyPrice / 10)}
|
dailyPrice: hourlyPrice * 24,
|
||||||
{` - `}
|
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||||
{currencySign}
|
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
||||||
{calculateEstimateNumber(monthlyPrice)}{" "}
|
currency: getPriceCurrency(serverId),
|
||||||
</b>
|
currencySign: getCurrencySign(serverId)
|
||||||
({"regions: "} {regions}, {throughput / 10} - {throughput} RU/s, {currencySign}
|
};
|
||||||
{pricePerRu}/RU)
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEstimatedSpendElement = (
|
export const getEstimatedSpendingElement = (
|
||||||
|
estimatedSpendingColumns: IColumn[],
|
||||||
|
estimatedSpendingItems: EstimatedSpendingDisplayProps[],
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
numberOfRegions: number,
|
||||||
regions: number,
|
priceBreakdown: PriceBreakdown,
|
||||||
multimaster: boolean
|
isAutoscale: boolean
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
|
||||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
|
||||||
const currency: string = getPriceCurrency(serverId);
|
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
|
||||||
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text id="throughputSpendElement">
|
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
Estimated cost ({currency}):{" "}
|
<DetailsList
|
||||||
<b>
|
disableSelectionZone
|
||||||
{currencySign}
|
items={estimatedSpendingItems}
|
||||||
{calculateEstimateNumber(hourlyPrice)} hourly {` / `}
|
columns={estimatedSpendingColumns}
|
||||||
{currencySign}
|
selectionMode={SelectionMode.none}
|
||||||
{calculateEstimateNumber(dailyPrice)} daily {` / `}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
{currencySign}
|
onRenderRow={onRenderRow}
|
||||||
{calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
/>
|
||||||
</b>
|
<Text id="throughputSpendElement">
|
||||||
({"regions: "} {regions}, {throughput}RU/s, {currencySign}
|
({"regions: "} {numberOfRegions}, {ruRange}
|
||||||
{pricePerRu}/RU)
|
{throughput} RU/s, {priceBreakdown.currencySign}
|
||||||
</Text>
|
{priceBreakdown.pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>{estimatedCostDisclaimer}</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,6 +310,13 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const saveThroughputWarningMessage: JSX.Element = (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
||||||
|
before saving your changes
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
const getCurrentThroughput = (
|
const getCurrentThroughput = (
|
||||||
isAutoscale: boolean,
|
isAutoscale: boolean,
|
||||||
throughput: number,
|
throughput: number,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Text,
|
Text,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
IDetailsRowProps,
|
|
||||||
DetailsRow,
|
|
||||||
IColumn,
|
IColumn,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
@@ -21,11 +19,11 @@ import {
|
|||||||
mongoIndexingPolicyDisclaimer,
|
mongoIndexingPolicyDisclaimer,
|
||||||
mediumWidthStackStyles,
|
mediumWidthStackStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
transparentDetailsRowStyles,
|
|
||||||
createAndAddMongoIndexStackProps,
|
createAndAddMongoIndexStackProps,
|
||||||
separatorStyles,
|
separatorStyles,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
infoAndToolTipTextStyle
|
infoAndToolTipTextStyle,
|
||||||
|
onRenderRow
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import {
|
import {
|
||||||
@@ -140,10 +138,6 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
|
||||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||||
return isCurrentIndex ? (
|
return isCurrentIndex ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -253,7 +247,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={initialIndexes}
|
items={initialIndexes}
|
||||||
columns={this.initialIndexesColumns}
|
columns={this.initialIndexesColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={this.onRenderRow}
|
onRenderRow={onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
{this.renderIndexesToBeAdded()}
|
{this.renderIndexesToBeAdded()}
|
||||||
@@ -279,7 +273,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={indexesToBeDropped}
|
items={indexesToBeDropped}
|
||||||
columns={this.indexesToBeDroppedColumns}
|
columns={this.indexesToBeDroppedColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={this.onRenderRow}
|
onRenderRow={onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
import { Link, 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 {
|
||||||
@@ -176,6 +176,7 @@ 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}
|
||||||
@@ -190,9 +191,37 @@ 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,16 +13,7 @@ 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 {
|
import { Label, Text, TextField, Stack, IChoiceGroupOption, ChoiceGroup, MessageBar } from "office-ui-fabric-react";
|
||||||
Label,
|
|
||||||
Text,
|
|
||||||
TextField,
|
|
||||||
Stack,
|
|
||||||
IChoiceGroupOption,
|
|
||||||
ChoiceGroup,
|
|
||||||
MessageBar,
|
|
||||||
MessageBarType
|
|
||||||
} from "office-ui-fabric-react";
|
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
@@ -190,7 +181,10 @@ 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 messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
<MessageBar
|
||||||
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
|
styles={messageBarStyles}
|
||||||
|
>
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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,
|
||||||
@@ -54,7 +55,6 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
||||||
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("autopilot input visible", () => {
|
it("autopilot input visible", () => {
|
||||||
@@ -72,8 +72,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
|
|
||||||
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(true);
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("spendAck checkbox visible", () => {
|
it("spendAck checkbox visible", () => {
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import {
|
|||||||
checkBoxAndInputStackProps,
|
checkBoxAndInputStackProps,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
messageBarStyles,
|
messageBarStyles,
|
||||||
getEstimatedSpendElement,
|
getEstimatedSpendingElement,
|
||||||
getEstimatedAutoscaleSpendElement,
|
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
manualToAutoscaleDisclaimerElement
|
manualToAutoscaleDisclaimerElement,
|
||||||
|
saveThroughputWarningMessage,
|
||||||
|
ManualEstimatedSpendingDisplayProps,
|
||||||
|
AutoscaleEstimatedSpendingDisplayProps,
|
||||||
|
PriceBreakdown,
|
||||||
|
getRuPriceBreakdown,
|
||||||
|
transparentDetailsHeaderStyle
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
@@ -23,7 +28,8 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType
|
FontIcon,
|
||||||
|
IColumn
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||||
@@ -32,7 +38,7 @@ import * as DataModels from "../../../../../Contracts/DataModels";
|
|||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||||
import { usageInGB } from "../../../../../Utils/PricingUtils";
|
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
||||||
import { Features } from "../../../../../Common/Constants";
|
import { Features } from "../../../../../Common/Constants";
|
||||||
|
|
||||||
export interface ThroughputInputAutoPilotV3Props {
|
export interface ThroughputInputAutoPilotV3Props {
|
||||||
@@ -51,6 +57,7 @@ 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;
|
||||||
@@ -69,6 +76,7 @@ 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<
|
||||||
@@ -142,7 +150,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
public constructor(props: ThroughputInputAutoPilotV3Props) {
|
public constructor(props: ThroughputInputAutoPilotV3Props) {
|
||||||
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;
|
||||||
@@ -165,33 +175,243 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
|
||||||
const serverId: string = this.props.serverId;
|
const serverId: string = this.props.serverId;
|
||||||
const offerThroughput: number = this.props.throughput;
|
|
||||||
|
|
||||||
const regions = account?.properties?.readLocations?.length || 1;
|
const regions = account?.properties?.readLocations?.length || 1;
|
||||||
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
||||||
|
|
||||||
let estimatedSpend: JSX.Element;
|
let estimatedSpend: JSX.Element;
|
||||||
|
|
||||||
if (!this.props.isAutoPilotSelected) {
|
if (!this.props.isAutoPilotSelected) {
|
||||||
estimatedSpend = getEstimatedSpendElement(
|
estimatedSpend = this.getEstimatedManualSpendElement(
|
||||||
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
||||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
isDirty ? this.props.throughput : undefined
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
|
||||||
this.props.maxAutoPilotThroughput,
|
this.props.maxAutoPilotThroughputBaseline,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
isDirty ? this.props.maxAutoPilotThroughput : undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return estimatedSpend;
|
return estimatedSpend;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getEstimatedAutoscaleSpendElement = (
|
||||||
|
throughput: number,
|
||||||
|
serverId: string,
|
||||||
|
numberOfRegions: number,
|
||||||
|
isMultimaster: boolean,
|
||||||
|
newThroughput?: number
|
||||||
|
): JSX.Element => {
|
||||||
|
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{
|
||||||
|
key: "costType",
|
||||||
|
name: "",
|
||||||
|
fieldName: "costType",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "minPerMonth",
|
||||||
|
name: "Min Per Month",
|
||||||
|
fieldName: "minPerMonth",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "maxPerMonth",
|
||||||
|
name: "Max Per Month",
|
||||||
|
fieldName: "maxPerMonth",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
minPerMonth: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
maxPerMonth: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (newThroughput) {
|
||||||
|
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||||
|
newThroughput,
|
||||||
|
serverId,
|
||||||
|
numberOfRegions,
|
||||||
|
isMultimaster,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
estimatedSpendingItems.unshift({
|
||||||
|
costType: (
|
||||||
|
<Text>
|
||||||
|
<b>Updated Cost</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
minPerMonth: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
maxPerMonth: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEstimatedSpendingElement(
|
||||||
|
estimatedSpendingColumns,
|
||||||
|
estimatedSpendingItems,
|
||||||
|
newThroughput ?? throughput,
|
||||||
|
numberOfRegions,
|
||||||
|
prices,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private getEstimatedManualSpendElement = (
|
||||||
|
throughput: number,
|
||||||
|
serverId: string,
|
||||||
|
numberOfRegions: number,
|
||||||
|
isMultimaster: boolean,
|
||||||
|
newThroughput?: number
|
||||||
|
): JSX.Element => {
|
||||||
|
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{
|
||||||
|
key: "costType",
|
||||||
|
name: "",
|
||||||
|
fieldName: "costType",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "hourly",
|
||||||
|
name: "Hourly",
|
||||||
|
fieldName: "hourly",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "daily",
|
||||||
|
name: "Daily",
|
||||||
|
fieldName: "daily",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "monthly",
|
||||||
|
name: "Monthly",
|
||||||
|
fieldName: "monthly",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
hourly: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.hourlyPrice)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
daily: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.dailyPrice)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
monthly: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (newThroughput) {
|
||||||
|
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||||
|
newThroughput,
|
||||||
|
serverId,
|
||||||
|
numberOfRegions,
|
||||||
|
isMultimaster,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
estimatedSpendingItems.unshift({
|
||||||
|
costType: (
|
||||||
|
<Text>
|
||||||
|
<b>Updated Cost</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
hourly: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.hourlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
daily: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.dailyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
monthly: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEstimatedSpendingElement(
|
||||||
|
estimatedSpendingColumns,
|
||||||
|
estimatedSpendingItems,
|
||||||
|
newThroughput ?? throughput,
|
||||||
|
numberOfRegions,
|
||||||
|
prices,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private getAutoPilotUsageCost = (): JSX.Element => {
|
private getAutoPilotUsageCost = (): JSX.Element => {
|
||||||
if (!this.props.maxAutoPilotThroughput) {
|
if (!this.props.maxAutoPilotThroughput) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -207,7 +427,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, this.autoPilotInputMaxValue);
|
const newThroughput = getSanitizedInputValue(newValue);
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -215,10 +435,11 @@ 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, this.throughputInputMaxValue);
|
const newThroughput = getSanitizedInputValue(newValue);
|
||||||
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -262,7 +483,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
{this.overrideWithProvisionedThroughputSettings() && (
|
{this.overrideWithProvisionedThroughputSettings() && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
<MessageBar
|
||||||
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
|
styles={messageBarStyles}
|
||||||
|
>
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
@@ -318,6 +542,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
|
|
||||||
private renderThroughputInput = (): JSX.Element => (
|
private renderThroughputInput = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||||
|
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
type="number"
|
type="number"
|
||||||
@@ -333,8 +563,21 @@ 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 messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
<MessageBar
|
||||||
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
|
styles={messageBarStyles}
|
||||||
|
>
|
||||||
{this.props.getThroughputWarningMessage()}
|
{this.props.getThroughputWarningMessage()}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
@@ -349,13 +592,32 @@ 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,6 +8,26 @@ 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"
|
||||||
@@ -19,7 +39,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,12 +50,21 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
/>
|
/>
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
<StyledMessageBarBase
|
<StyledMessageBarBase
|
||||||
messageBarType={5}
|
messageBarIconProps={
|
||||||
|
Object {
|
||||||
|
"className": "messageBarInfoIcon",
|
||||||
|
"iconName": "InfoSolid",
|
||||||
|
}
|
||||||
|
}
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
|
"backgroundColor": "white",
|
||||||
"marginTop": "5px",
|
"marginTop": "5px",
|
||||||
},
|
},
|
||||||
|
"text": Object {
|
||||||
|
"fontSize": 14,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -44,7 +73,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +185,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,6 +243,19 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
capacity calculator
|
||||||
|
|
||||||
|
<Component
|
||||||
|
iconName="NavigateExternalInline"
|
||||||
|
/>
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -239,38 +281,142 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
USD
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
<b>
|
0.19
|
||||||
$
|
</Text>,
|
||||||
0.0080
|
"hourly": <Text>
|
||||||
hourly
|
$
|
||||||
/
|
|
||||||
$
|
|
||||||
0.19
|
|
||||||
daily
|
|
||||||
/
|
|
||||||
$
|
|
||||||
5.84
|
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
0.0080
|
||||||
(
|
</Text>,
|
||||||
regions:
|
"monthly": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
1
|
5.84
|
||||||
,
|
</Text>,
|
||||||
100
|
},
|
||||||
RU/s,
|
]
|
||||||
$
|
}
|
||||||
0.00008
|
layoutMode={1}
|
||||||
/RU)
|
onRenderRow={[Function]}
|
||||||
</Text>
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>
|
||||||
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
|
</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
@@ -288,6 +434,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<br />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
@@ -311,7 +458,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,6 +516,19 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
capacity calculator
|
||||||
|
|
||||||
|
<Component
|
||||||
|
iconName="NavigateExternalInline"
|
||||||
|
/>
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -394,38 +554,143 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
USD
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
<b>
|
0.19
|
||||||
$
|
</Text>,
|
||||||
0.0080
|
"hourly": <Text>
|
||||||
hourly
|
$
|
||||||
/
|
|
||||||
$
|
|
||||||
0.19
|
|
||||||
daily
|
|
||||||
/
|
|
||||||
$
|
|
||||||
5.84
|
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
0.0080
|
||||||
(
|
</Text>,
|
||||||
regions:
|
"monthly": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
1
|
5.84
|
||||||
,
|
</Text>,
|
||||||
100
|
},
|
||||||
RU/s,
|
]
|
||||||
$
|
}
|
||||||
0.00008
|
layoutMode={1}
|
||||||
/RU)
|
onRenderRow={[Function]}
|
||||||
</Text>
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>
|
||||||
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
|
</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<br />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,7 +412,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,7 +952,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1228,7 +1228,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 Math.min(newValue, max);
|
return max ? Math.min(newValue, max) : newValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ 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],
|
||||||
@@ -104,6 +105,7 @@ 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],
|
||||||
@@ -591,6 +593,7 @@ 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],
|
||||||
@@ -665,6 +668,7 @@ 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],
|
||||||
@@ -731,7 +735,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -951,6 +954,7 @@ 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],
|
||||||
@@ -1023,7 +1027,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -1049,7 +1052,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -1178,11 +1180,9 @@ 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 {
|
||||||
@@ -1329,6 +1329,7 @@ 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],
|
||||||
@@ -1378,6 +1379,7 @@ 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],
|
||||||
@@ -1865,6 +1867,7 @@ 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],
|
||||||
@@ -1939,6 +1942,7 @@ 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],
|
||||||
@@ -2005,7 +2009,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2225,6 +2228,7 @@ 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],
|
||||||
@@ -2297,7 +2301,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2323,7 +2326,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -2452,11 +2454,9 @@ 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 {
|
||||||
@@ -2616,6 +2616,7 @@ 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],
|
||||||
@@ -2665,6 +2666,7 @@ 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],
|
||||||
@@ -3152,6 +3154,7 @@ 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],
|
||||||
@@ -3226,6 +3229,7 @@ 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],
|
||||||
@@ -3292,7 +3296,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3512,6 +3515,7 @@ 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],
|
||||||
@@ -3584,7 +3588,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3610,7 +3613,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -3739,11 +3741,9 @@ 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 {
|
||||||
@@ -3890,6 +3890,7 @@ 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],
|
||||||
@@ -3939,6 +3940,7 @@ 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],
|
||||||
@@ -4426,6 +4428,7 @@ 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],
|
||||||
@@ -4500,6 +4503,7 @@ 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],
|
||||||
@@ -4566,7 +4570,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4786,6 +4789,7 @@ 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],
|
||||||
@@ -4858,7 +4862,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4884,7 +4887,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -5013,11 +5015,9 @@ 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,72 +60,106 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
RMB
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$ 24.48
|
||||||
|
</Text>,
|
||||||
|
"hourly": <Text>
|
||||||
|
$ 1.02
|
||||||
|
</Text>,
|
||||||
|
"monthly": <Text>
|
||||||
|
$ 744.6
|
||||||
|
</Text>,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
layoutMode={1}
|
||||||
|
onRenderRow={[Function]}
|
||||||
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
<b>
|
2
|
||||||
|
,
|
||||||
|
1000
|
||||||
|
RU/s,
|
||||||
¥
|
¥
|
||||||
1.02
|
0.00051
|
||||||
hourly
|
/RU)
|
||||||
/
|
</Text>
|
||||||
¥
|
<Text>
|
||||||
24.48
|
<em>
|
||||||
daily
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
/
|
</em>
|
||||||
¥
|
</Text>
|
||||||
744.60
|
</Stack>
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
2
|
|
||||||
,
|
|
||||||
1000
|
|
||||||
RU/s,
|
|
||||||
¥
|
|
||||||
0.00051
|
|
||||||
/RU)
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
id="autoscaleSpendElement"
|
|
||||||
>
|
|
||||||
Estimated monthly cost (
|
|
||||||
RMB
|
|
||||||
) is
|
|
||||||
|
|
||||||
<b>
|
|
||||||
¥
|
|
||||||
111.69
|
|
||||||
-
|
|
||||||
¥
|
|
||||||
1116.90
|
|
||||||
|
|
||||||
</b>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
2
|
|
||||||
,
|
|
||||||
100
|
|
||||||
-
|
|
||||||
1000
|
|
||||||
RU/s,
|
|
||||||
¥
|
|
||||||
0.000765
|
|
||||||
/RU)
|
|
||||||
</Text>
|
|
||||||
<Text
|
<Text
|
||||||
id="manualToAutoscaleDisclaimerElement"
|
id="manualToAutoscaleDisclaimerElement"
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +176,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +195,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +207,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +219,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +230,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +249,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +268,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,7 +299,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +310,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +329,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +371,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +386,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +402,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 12,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ 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 {
|
||||||
@@ -165,6 +167,10 @@ 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();
|
||||||
@@ -219,6 +225,16 @@ 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() {
|
||||||
|
|||||||
@@ -132,6 +132,14 @@
|
|||||||
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
||||||
>
|
>
|
||||||
</p>
|
</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="
|
||||||
@@ -154,6 +162,11 @@
|
|||||||
/>
|
/>
|
||||||
</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/DocumentClientUtilityBase";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import GraphTab from ".././Tabs/GraphTab";
|
|||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
@@ -95,12 +95,15 @@ export class ContainerSampleGenerator {
|
|||||||
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
||||||
} else {
|
} else {
|
||||||
// For SQL all queries are executed at the same time
|
// For SQL all queries are executed at the same time
|
||||||
this.sampleDataFile.data.map(doc => {
|
await Promise.all(
|
||||||
const subPromise = createDocument(collection, doc);
|
this.sampleDataFile.data.map(async doc => {
|
||||||
subPromise.catch(reason => NotificationConsoleUtils.logConsoleError(reason));
|
try {
|
||||||
promises.push(subPromise);
|
await createDocument(collection, doc);
|
||||||
});
|
} catch (error) {
|
||||||
await Promise.all(promises);
|
NotificationConsoleUtils.logConsoleError(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPa
|
|||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
import EnvironmentUtility from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import hasher from "hasher";
|
import hasher from "hasher";
|
||||||
import NewVertexPane from "./Panes/NewVertexPane";
|
import NewVertexPane from "./Panes/NewVertexPane";
|
||||||
@@ -121,7 +121,6 @@ export default class Explorer {
|
|||||||
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
||||||
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
||||||
public subscriptionType: ko.Observable<SubscriptionType>;
|
public subscriptionType: ko.Observable<SubscriptionType>;
|
||||||
public quotaId: ko.Observable<string>;
|
|
||||||
public defaultExperience: ko.Observable<string>;
|
public defaultExperience: ko.Observable<string>;
|
||||||
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
||||||
public isPreferredApiCassandra: ko.Computed<boolean>;
|
public isPreferredApiCassandra: ko.Computed<boolean>;
|
||||||
@@ -135,12 +134,10 @@ export default class Explorer {
|
|||||||
public canSaveQueries: ko.Computed<boolean>;
|
public canSaveQueries: ko.Computed<boolean>;
|
||||||
public features: ko.Observable<any>;
|
public features: ko.Observable<any>;
|
||||||
public serverId: ko.Observable<string>;
|
public serverId: ko.Observable<string>;
|
||||||
public armEndpoint: ko.Observable<string>;
|
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
|
|
||||||
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
||||||
|
|
||||||
// Notification Console
|
// Notification Console
|
||||||
@@ -210,6 +207,7 @@ export default class Explorer {
|
|||||||
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 shouldShowShareDialogContents: ko.Observable<boolean>;
|
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||||
@@ -278,7 +276,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
||||||
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
||||||
this.quotaId = ko.observable<string>("");
|
|
||||||
let firstInitialization = true;
|
let firstInitialization = true;
|
||||||
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
||||||
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
||||||
@@ -318,9 +315,9 @@ export default class Explorer {
|
|||||||
if (isAccountReady) {
|
if (isAccountReady) {
|
||||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
RouteHandler.getInstance().initHandler();
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
|
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
||||||
this.arcadiaWorkspaces = ko.observableArray();
|
this.arcadiaWorkspaces = ko.observableArray();
|
||||||
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
|
this._arcadiaManager = new ArcadiaResourceManager();
|
||||||
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
||||||
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
||||||
);
|
);
|
||||||
@@ -370,7 +367,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
this.serverId = ko.observable<string>();
|
this.serverId = ko.observable<string>();
|
||||||
this.armEndpoint = ko.observable<string>(undefined);
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
||||||
|
|
||||||
@@ -407,6 +403,7 @@ export default class Explorer {
|
|||||||
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);
|
||||||
|
|
||||||
@@ -1012,9 +1009,7 @@ export default class Explorer {
|
|||||||
this.isSynapseLinkUpdating(true);
|
this.isSynapseLinkUpdating(true);
|
||||||
this._closeSynapseLinkModalDialog();
|
this._closeSynapseLinkModalDialog();
|
||||||
|
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(this.databaseAccount().id);
|
||||||
this.databaseAccount().id
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
||||||
@@ -1754,61 +1749,59 @@ export default class Explorer {
|
|||||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
this.initDataExplorerWithFrameInputs(inputs);
|
||||||
|
|
||||||
initPromise.then(() => {
|
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
if (!!openAction) {
|
||||||
if (!!openAction) {
|
if (this.isRefreshingExplorer()) {
|
||||||
if (this.isRefreshingExplorer()) {
|
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||||
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
|
||||||
subscription.dispose();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
}
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
}
|
}
|
||||||
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
}
|
||||||
handleCachedDataMessage(message);
|
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
||||||
return;
|
handleCachedDataMessage(message);
|
||||||
}
|
return;
|
||||||
if (message.type) {
|
}
|
||||||
switch (message.type) {
|
if (message.type) {
|
||||||
case MessageTypes.UpdateLocationHash:
|
switch (message.type) {
|
||||||
if (!message.locationHash) {
|
case MessageTypes.UpdateLocationHash:
|
||||||
break;
|
if (!message.locationHash) {
|
||||||
}
|
break;
|
||||||
hasher.replaceHash(message.locationHash);
|
}
|
||||||
RouteHandler.getInstance().parseHash(message.locationHash);
|
hasher.replaceHash(message.locationHash);
|
||||||
break;
|
RouteHandler.getInstance().parseHash(message.locationHash);
|
||||||
case MessageTypes.SendNotification:
|
break;
|
||||||
if (!message.message) {
|
case MessageTypes.SendNotification:
|
||||||
break;
|
if (!message.message) {
|
||||||
}
|
break;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
}
|
||||||
message.consoleDataType || ConsoleDataType.Info,
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
message.message,
|
message.consoleDataType || ConsoleDataType.Info,
|
||||||
message.id
|
message.message,
|
||||||
);
|
message.id
|
||||||
break;
|
);
|
||||||
case MessageTypes.ClearNotification:
|
break;
|
||||||
if (!message.id) {
|
case MessageTypes.ClearNotification:
|
||||||
break;
|
if (!message.id) {
|
||||||
}
|
break;
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
}
|
||||||
break;
|
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
||||||
case MessageTypes.LoadingStatus:
|
break;
|
||||||
if (!message.text) {
|
case MessageTypes.LoadingStatus:
|
||||||
break;
|
if (!message.text) {
|
||||||
}
|
break;
|
||||||
this._setLoadingStatusText(message.text, message.title);
|
}
|
||||||
break;
|
this._setLoadingStatusText(message.text, message.title);
|
||||||
}
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.splashScreenAdapter.forceRender();
|
this.splashScreenAdapter.forceRender();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
public findSelectedDatabase(): ViewModels.Database {
|
||||||
@@ -1848,8 +1841,14 @@ export default class Explorer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
|
||||||
if (inputs != null) {
|
if (inputs != null) {
|
||||||
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
|
// This allows webpack hot reload to funciton properly
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
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;
|
||||||
@@ -1858,25 +1857,18 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
this.features(inputs.features);
|
this.features(inputs.features);
|
||||||
this.serverId(inputs.serverId);
|
this.serverId(inputs.serverId);
|
||||||
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
this.quotaId(inputs.quotaId);
|
|
||||||
this.hasWriteAccess(inputs.hasWriteAccess);
|
this.hasWriteAccess(inputs.hasWriteAccess);
|
||||||
this.flight(inputs.addCollectionDefaultFlight);
|
this.flight(inputs.addCollectionDefaultFlight);
|
||||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||||
this.setFeatureFlagsFromFlights(inputs.flights);
|
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||||
|
|
||||||
if (!!inputs.dataExplorerVersion) {
|
|
||||||
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
||||||
ARM_ENDPOINT: this.armEndpoint()
|
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT)
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -1885,7 +1877,8 @@ export default class Explorer {
|
|||||||
databaseAccount,
|
databaseAccount,
|
||||||
resourceGroup: inputs.resourceGroup,
|
resourceGroup: inputs.resourceGroup,
|
||||||
subscriptionId: inputs.subscriptionId,
|
subscriptionId: inputs.subscriptionId,
|
||||||
subscriptionType: inputs.subscriptionType
|
subscriptionType: inputs.subscriptionType,
|
||||||
|
quotaId: inputs.quotaId
|
||||||
});
|
});
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabaseAccount,
|
Action.LoadDatabaseAccount,
|
||||||
@@ -1899,13 +1892,15 @@ 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.MongoIndexing) !== -1) {
|
||||||
|
this.isMongoIndexingEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
@@ -2562,7 +2557,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = this.armEndpoint();
|
const armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2571,7 +2566,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -2591,7 +2586,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = this.armEndpoint();
|
const armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2599,7 +2594,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -3024,4 +3019,25 @@ 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
jest.mock("../../../Common/DocumentClientUtilityBase");
|
jest.mock("../../../Common/dataAccess/queryDocuments");
|
||||||
|
jest.mock("../../../Common/dataAccess/queryDocumentsPage");
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
@@ -12,7 +13,8 @@ import * as DataModels from "../../../Contracts/DataModels";
|
|||||||
import * as StorageUtility from "../../../Shared/StorageUtility";
|
import * as StorageUtility from "../../../Shared/StorageUtility";
|
||||||
import GraphTab from "../../Tabs/GraphTab";
|
import GraphTab from "../../Tabs/GraphTab";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||||
|
|
||||||
describe("Check whether query result is vertex array", () => {
|
describe("Check whether query result is vertex array", () => {
|
||||||
it("should reject null as vertex array", () => {
|
it("should reject null as vertex array", () => {
|
||||||
@@ -299,12 +301,12 @@ describe("GraphExplorer", () => {
|
|||||||
ignoreD3Update: boolean
|
ignoreD3Update: boolean
|
||||||
): GraphExplorer => {
|
): GraphExplorer => {
|
||||||
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
||||||
return Q.resolve({
|
return {
|
||||||
_query: query,
|
_query: query,
|
||||||
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
||||||
hasMoreResults: () => false,
|
hasMoreResults: () => false,
|
||||||
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
|
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
|
||||||
});
|
};
|
||||||
});
|
});
|
||||||
(queryDocumentsPage as jest.Mock).mockImplementation(
|
(queryDocumentsPage as jest.Mock).mockImplementation(
|
||||||
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
|
|
||||||
export interface GraphAccessor {
|
export interface GraphAccessor {
|
||||||
applyFilter: () => void;
|
applyFilter: () => void;
|
||||||
@@ -725,26 +727,32 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Execute DocDB query and get all results
|
* Execute DocDB query and get all results
|
||||||
*/
|
*/
|
||||||
public executeNonPagedDocDbQuery(query: string): Q.Promise<DataModels.DocumentId[]> {
|
public async executeNonPagedDocDbQuery(query: string): Promise<DataModels.DocumentId[]> {
|
||||||
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
try {
|
||||||
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
||||||
maxItemCount: GraphExplorer.PAGE_ALL,
|
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
enableCrossPartitionQuery:
|
this.props.databaseId,
|
||||||
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.IsCrossPartitionQueryEnabled) ===
|
this.props.collectionId,
|
||||||
"true"
|
query,
|
||||||
}).then(
|
{
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
maxItemCount: GraphExplorer.PAGE_ALL,
|
||||||
return iterator.fetchNext().then(response => response.resources);
|
enableCrossPartitionQuery:
|
||||||
},
|
StorageUtility.LocalStorageUtility.getEntryString(
|
||||||
(reason: any) => {
|
StorageUtility.StorageKey.IsCrossPartitionQueryEnabled
|
||||||
GraphExplorer.reportToConsole(
|
) === "true"
|
||||||
ConsoleDataType.Error,
|
} as FeedOptions
|
||||||
`Failed to execute non-paged query ${query}. Reason:${reason}`,
|
);
|
||||||
reason
|
const response = await iterator.fetchNext();
|
||||||
);
|
|
||||||
return null;
|
return response?.resources;
|
||||||
}
|
} catch (error) {
|
||||||
);
|
GraphExplorer.reportToConsole(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to execute non-paged query ${query}. Reason:${error}`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -864,7 +872,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* User executes query
|
* User executes query
|
||||||
*/
|
*/
|
||||||
public submitQuery(query: string): void {
|
public async submitQuery(query: string): Promise<void> {
|
||||||
// Clear any progress indicator
|
// Clear any progress indicator
|
||||||
this.executeCounter = 0;
|
this.executeCounter = 0;
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -882,24 +890,22 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// Remember query
|
// Remember query
|
||||||
this.pushToLatestQueryFragments(query);
|
this.pushToLatestQueryFragments(query);
|
||||||
|
|
||||||
let backendPromise;
|
try {
|
||||||
|
let result: UserQueryResult;
|
||||||
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
||||||
backendPromise = this.executeDocDbGVQuery();
|
result = await this.executeDocDbGVQuery();
|
||||||
} else {
|
} else {
|
||||||
backendPromise = this.executeGremlinQuery(query);
|
result = await this.executeGremlinQuery(query);
|
||||||
}
|
|
||||||
|
|
||||||
backendPromise.then(
|
|
||||||
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
|
|
||||||
(error: any) => {
|
|
||||||
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
|
||||||
this.setState({
|
|
||||||
filterQueryError: errorMsg
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
this.queryTotalRequestCharge = result.requestCharge;
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
||||||
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
|
this.setState({
|
||||||
|
filterQueryError: errorMsg
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1390,7 +1396,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Update possible vertices to display in UI
|
* Update possible vertices to display in UI
|
||||||
*/
|
*/
|
||||||
private updatePossibleVertices(): Q.Promise<PossibleVertex[]> {
|
private updatePossibleVertices(): Promise<PossibleVertex[]> {
|
||||||
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
||||||
|
|
||||||
const q = `SELECT c.id, c["${this.props.graphConfigUiData.nodeCaptionChoice() ||
|
const q = `SELECT c.id, c["${this.props.graphConfigUiData.nodeCaptionChoice() ||
|
||||||
@@ -1721,85 +1727,81 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeDocDbGVQuery(): Q.Promise<UserQueryResult> {
|
private async executeDocDbGVQuery(): Promise<UserQueryResult> {
|
||||||
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
||||||
if (this.props.collectionPartitionKeyProperty) {
|
if (this.props.collectionPartitionKeyProperty) {
|
||||||
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
try {
|
||||||
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
enableCrossPartitionQuery: LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
this.props.databaseId,
|
||||||
})
|
this.props.collectionId,
|
||||||
.then(
|
query,
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
{
|
||||||
this.currentDocDBQueryInfo = {
|
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
||||||
iterator: iterator,
|
enableCrossPartitionQuery:
|
||||||
index: 0,
|
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
||||||
query: query
|
} as FeedOptions
|
||||||
};
|
);
|
||||||
},
|
this.currentDocDBQueryInfo = {
|
||||||
(reason: any) => {
|
iterator: iterator,
|
||||||
GraphExplorer.reportToConsole(
|
index: 0,
|
||||||
ConsoleDataType.Error,
|
query: query
|
||||||
`Failed to execute CosmosDB query: ${query} reason:${reason}`
|
};
|
||||||
);
|
return await this.loadMoreRootNodes();
|
||||||
}
|
} catch (error) {
|
||||||
)
|
GraphExplorer.reportToConsole(
|
||||||
.then(() => this.loadMoreRootNodes());
|
ConsoleDataType.Error,
|
||||||
|
`Failed to execute CosmosDB query: ${query} reason:${error}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadMoreRootNodes(): Q.Promise<UserQueryResult> {
|
private async loadMoreRootNodes(): Promise<UserQueryResult> {
|
||||||
if (!this.currentDocDBQueryInfo) {
|
if (!this.currentDocDBQueryInfo) {
|
||||||
return Q.resolve(null);
|
return undefined;
|
||||||
}
|
}
|
||||||
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
|
||||||
|
|
||||||
|
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
||||||
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${this
|
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${this
|
||||||
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
|
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
|
||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
||||||
|
|
||||||
return queryDocumentsPage(
|
try {
|
||||||
this.props.collectionId,
|
const results: ViewModels.QueryResults = await queryDocumentsPage(
|
||||||
this.currentDocDBQueryInfo.iterator,
|
this.props.collectionId,
|
||||||
this.currentDocDBQueryInfo.index,
|
this.currentDocDBQueryInfo.iterator,
|
||||||
{
|
this.currentDocDBQueryInfo.index
|
||||||
enableCrossPartitionQuery:
|
);
|
||||||
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
|
||||||
}
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
)
|
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
||||||
.then((results: ViewModels.QueryResults) => {
|
this.setState({ hasMoreRoots: results.hasMoreResults });
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
RU = results.requestCharge.toString();
|
||||||
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
GraphExplorer.reportToConsole(
|
||||||
this.setState({ hasMoreRoots: results.hasMoreResults });
|
ConsoleDataType.Info,
|
||||||
RU = results.requestCharge.toString();
|
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
||||||
GraphExplorer.reportToConsole(
|
);
|
||||||
ConsoleDataType.Info,
|
const pkIds: string[] = (results.documents || []).map((item: DataModels.DocumentId) =>
|
||||||
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty)
|
||||||
);
|
);
|
||||||
const documents = results.documents || [];
|
|
||||||
return documents.map(
|
const arg = pkIds.join(",");
|
||||||
(item: DataModels.DocumentId) => {
|
await this.executeGremlinQuery(`g.V(${arg})`);
|
||||||
return GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty);
|
|
||||||
},
|
return { requestCharge: RU };
|
||||||
(reason: any) => {
|
} catch (error) {
|
||||||
// Failure
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
|
||||||
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${reason}`;
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
this.setState({
|
||||||
this.setState({
|
filterQueryError: errorMsg
|
||||||
filterQueryError: errorMsg
|
});
|
||||||
});
|
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
||||||
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
throw error;
|
||||||
throw reason;
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then((pkIds: string[]) => {
|
|
||||||
const arg = pkIds.join(",");
|
|
||||||
return this.executeGremlinQuery(`g.V(${arg})`);
|
|
||||||
})
|
|
||||||
.then(() => ({ requestCharge: RU }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
||||||
|
|||||||
@@ -152,7 +152,8 @@
|
|||||||
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
|
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
showAutoPilot: !isFreeTierAccount()
|
showAutoPilot: !isFreeTierAccount(),
|
||||||
|
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
||||||
}">
|
}">
|
||||||
</throughput-input-autopilot-v3>
|
</throughput-input-autopilot-v3>
|
||||||
</div>
|
</div>
|
||||||
@@ -333,7 +334,8 @@
|
|||||||
maxAutoPilotThroughputSet: autoPilotThroughput,
|
maxAutoPilotThroughputSet: autoPilotThroughput,
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
showAutoPilot: !isFixedStorageSelected()
|
showAutoPilot: !isFixedStorageSelected(),
|
||||||
|
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
||||||
}">
|
}">
|
||||||
</throughput-input-autopilot-v3>
|
</throughput-input-autopilot-v3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe("Add Collection Pane", () => {
|
|||||||
explorer.databaseAccount(mockFreeTierDatabaseAccount);
|
explorer.databaseAccount(mockFreeTierDatabaseAccount);
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
||||||
expect(addCollectionPane.isFreeTierAccount()).toBe(true);
|
expect(addCollectionPane.isFreeTierAccount()).toBe(true);
|
||||||
expect(addCollectionPane.upsellMessage()).toContain("With free tier discount");
|
expect(addCollectionPane.upsellMessage()).toContain("With free tier");
|
||||||
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
||||||
expect(addCollectionPane.upsellAnchorText()).toBe("Learn more");
|
expect(addCollectionPane.upsellAnchorText()).toBe("Learn more");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
|||||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||||
isPreferredApiTable: ko.Computed<boolean>;
|
isPreferredApiTable: ko.Computed<boolean>;
|
||||||
@@ -88,9 +89,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
public isSynapseLinkUpdating: ko.Computed<boolean>;
|
public isSynapseLinkUpdating: ko.Computed<boolean>;
|
||||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||||
public ruToolTipText: ko.Computed<string>;
|
public ruToolTipText: ko.Computed<string>;
|
||||||
|
public freeTierExceedThroughputTooltip: ko.Computed<string>;
|
||||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||||
public showUpsellMessage: ko.PureComputed<boolean>;
|
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||||
public shouldCreateMongoWildcardIndex: ko.Observable<boolean>;
|
public shouldCreateMongoWildcardIndex: ko.Computed<boolean>;
|
||||||
|
|
||||||
private _isSynapseLinkEnabled: ko.Computed<boolean>;
|
private _isSynapseLinkEnabled: ko.Computed<boolean>;
|
||||||
|
|
||||||
@@ -98,7 +100,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
super(options);
|
super(options);
|
||||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||||
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
|
||||||
this.formWarnings = ko.observable<string>();
|
this.formWarnings = ko.observable<string>();
|
||||||
this.collectionId = ko.observable<string>();
|
this.collectionId = ko.observable<string>();
|
||||||
this.databaseId = ko.observable<string>();
|
this.databaseId = ko.observable<string>();
|
||||||
@@ -480,8 +481,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
this.resetData();
|
this.resetData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.freeTierExceedThroughputTooltip = ko.pureComputed<string>(() =>
|
||||||
|
this.isFreeTierAccount() && !this.container.isFirstResourceCreated()
|
||||||
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
this.upsellMessage = ko.pureComputed<string>(() => {
|
this.upsellMessage = ko.pureComputed<string>(() => {
|
||||||
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
|
return PricingUtils.getUpsellMessage(
|
||||||
|
this.container.serverId(),
|
||||||
|
this.isFreeTierAccount(),
|
||||||
|
this.container.isFirstResourceCreated(),
|
||||||
|
this.container.defaultExperience(),
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
||||||
@@ -533,6 +546,23 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return isFreeTierAccount;
|
return isFreeTierAccount;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showUpsellMessage = ko.pureComputed(() => {
|
||||||
|
if (this.container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isFreeTierAccount() &&
|
||||||
|
!this.databaseCreateNew() &&
|
||||||
|
this.databaseHasSharedOffer() &&
|
||||||
|
!this.collectionWithThroughputInShared()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
this.showIndexingOptionsForSharedThroughput = ko.computed<boolean>(() => {
|
this.showIndexingOptionsForSharedThroughput = ko.computed<boolean>(() => {
|
||||||
const newDatabaseWithSharedOffer = this.databaseCreateNew() && this.databaseCreateNewShared();
|
const newDatabaseWithSharedOffer = this.databaseCreateNew() && this.databaseCreateNewShared();
|
||||||
const existingDatabaseWithSharedOffer = !this.databaseCreateNew() && this.databaseHasSharedOffer();
|
const existingDatabaseWithSharedOffer = !this.databaseCreateNew() && this.databaseHasSharedOffer();
|
||||||
@@ -624,7 +654,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shouldCreateMongoWildcardIndex = ko.observable(false);
|
this.shouldCreateMongoWildcardIndex = ko.computed(function() {
|
||||||
|
return this.container.isMongoIndexingEnabled();
|
||||||
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSharedThroughputDefault(): boolean {
|
public getSharedThroughputDefault(): boolean {
|
||||||
@@ -668,7 +700,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
databaseId: this.databaseId()
|
databaseId: this.databaseId()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: this._getThroughput(),
|
throughput: this._getThroughput(),
|
||||||
@@ -770,7 +802,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -844,7 +876,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -878,7 +910,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
},
|
},
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
|
|||||||
@@ -114,7 +114,8 @@
|
|||||||
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
|
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
|
||||||
autoPilotUsageCost: autoPilotUsageCost,
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
showAutoPilot: !isFreeTierAccount()
|
showAutoPilot: !isFreeTierAccount(),
|
||||||
|
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
|
||||||
}">
|
}">
|
||||||
</throughput-input-autopilot-v3>
|
</throughput-input-autopilot-v3>
|
||||||
<p data-bind="visible: canRequestSupport">
|
<p data-bind="visible: canRequestSupport">
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe("Add Database Pane", () => {
|
|||||||
explorer.databaseAccount(mockFreeTierDatabaseAccount);
|
explorer.databaseAccount(mockFreeTierDatabaseAccount);
|
||||||
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
|
||||||
expect(addDatabasePane.isFreeTierAccount()).toBe(true);
|
expect(addDatabasePane.isFreeTierAccount()).toBe(true);
|
||||||
expect(addDatabasePane.upsellMessage()).toContain("With free tier discount");
|
expect(addDatabasePane.upsellMessage()).toContain("With free tier");
|
||||||
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
|
||||||
expect(addDatabasePane.upsellAnchorText()).toBe("Learn more");
|
expect(addDatabasePane.upsellAnchorText()).toBe("Learn more");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
@@ -43,6 +44,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
public autoPilotUsageCost: ko.Computed<string>;
|
public autoPilotUsageCost: ko.Computed<string>;
|
||||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||||
public ruToolTipText: ko.Computed<string>;
|
public ruToolTipText: ko.Computed<string>;
|
||||||
|
public freeTierExceedThroughputTooltip: ko.Computed<string>;
|
||||||
public isFreeTierAccount: ko.Computed<boolean>;
|
public isFreeTierAccount: ko.Computed<boolean>;
|
||||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||||
public showUpsellMessage: ko.PureComputed<boolean>;
|
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||||
@@ -53,7 +55,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.databaseId = ko.observable<string>();
|
this.databaseId = ko.observable<string>();
|
||||||
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
|
||||||
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||||
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
|
||||||
|
|
||||||
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
|
||||||
|
|
||||||
@@ -181,6 +182,18 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
return isFreeTierAccount;
|
return isFreeTierAccount;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.showUpsellMessage = ko.pureComputed(() => {
|
||||||
|
if (this.container.isServerlessEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isFreeTierAccount()) {
|
||||||
|
return this.databaseCreateNewShared();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
this.maxThroughputRUText = ko.pureComputed(() => {
|
this.maxThroughputRUText = ko.pureComputed(() => {
|
||||||
return this.maxThroughputRU().toLocaleString();
|
return this.maxThroughputRU().toLocaleString();
|
||||||
});
|
});
|
||||||
@@ -218,8 +231,20 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.resetData();
|
this.resetData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.freeTierExceedThroughputTooltip = ko.pureComputed<string>(() =>
|
||||||
|
this.isFreeTierAccount() && !this.container.isFirstResourceCreated()
|
||||||
|
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
this.upsellMessage = ko.pureComputed<string>(() => {
|
this.upsellMessage = ko.pureComputed<string>(() => {
|
||||||
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
|
return PricingUtils.getUpsellMessage(
|
||||||
|
this.container.serverId(),
|
||||||
|
this.isFreeTierAccount(),
|
||||||
|
this.container.isFirstResourceCreated(),
|
||||||
|
this.container.defaultExperience(),
|
||||||
|
false
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
|
||||||
@@ -250,7 +275,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
@@ -278,7 +303,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -342,7 +367,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -366,7 +391,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { HashMap } from "../../Common/HashMap";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
public createTableQuery: ko.Observable<string>;
|
public createTableQuery: ko.Observable<string>;
|
||||||
@@ -299,7 +300,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
databaseId: this.keyspaceId()
|
databaseId: this.keyspaceId()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -353,7 +354,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -399,7 +400,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -429,7 +430,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
},
|
},
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
|
|||||||
@@ -421,53 +421,47 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
* Note that this also means that we can get less entities than the requested download size in a successful call.
|
* Note that this also means that we can get less entities than the requested download size in a successful call.
|
||||||
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
||||||
*/
|
*/
|
||||||
private prefetchData(
|
private async prefetchData(
|
||||||
tableQuery: Entities.ITableQuery,
|
tableQuery: Entities.ITableQuery,
|
||||||
downloadSize: number,
|
downloadSize: number,
|
||||||
currentRetry: number = 0
|
currentRetry: number = 0
|
||||||
): Q.Promise<any> {
|
): Promise<IListTableEntitiesSegmentedResult> {
|
||||||
if (!this.cache.serverCallInProgress) {
|
if (!this.cache.serverCallInProgress) {
|
||||||
this.cache.serverCallInProgress = true;
|
this.cache.serverCallInProgress = true;
|
||||||
this.allDownloaded = false;
|
this.allDownloaded = false;
|
||||||
this.lastPrefetchTime = new Date().getTime();
|
this.lastPrefetchTime = new Date().getTime();
|
||||||
var time = this.lastPrefetchTime;
|
const time = this.lastPrefetchTime;
|
||||||
|
|
||||||
var promise: Q.Promise<IListTableEntitiesSegmentedResult>;
|
|
||||||
if (this._documentIterator && this.continuationToken) {
|
if (this._documentIterator && this.continuationToken) {
|
||||||
// TODO handle Cassandra case
|
// TODO handle Cassandra case
|
||||||
|
const response = await this._documentIterator.fetchNext();
|
||||||
|
const entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(response?.resources);
|
||||||
|
|
||||||
promise = Q(this._documentIterator.fetchNext().then(response => response.resources)).then(
|
return {
|
||||||
(documents: any[]) => {
|
Results: entities,
|
||||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
ContinuationToken: this._documentIterator.hasMoreResults()
|
||||||
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
};
|
||||||
Results: entities,
|
|
||||||
ContinuationToken: this._documentIterator.hasMoreResults()
|
|
||||||
};
|
|
||||||
return Q.resolve(finalEntities);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
|
|
||||||
promise = this.queryTablesTab.container.tableDataClient.queryDocuments(
|
|
||||||
this.queryTablesTab.collection,
|
|
||||||
this.cqlQuery(),
|
|
||||||
true,
|
|
||||||
this.continuationToken
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let query = this.sqlQuery();
|
|
||||||
if (this.queryTablesTab.container.isPreferredApiCassandra()) {
|
|
||||||
query = this.cqlQuery();
|
|
||||||
}
|
|
||||||
promise = this.queryTablesTab.container.tableDataClient.queryDocuments(
|
|
||||||
this.queryTablesTab.collection,
|
|
||||||
query,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return promise
|
|
||||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
try {
|
||||||
|
let documents: IListTableEntitiesSegmentedResult;
|
||||||
|
if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
|
||||||
|
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||||
|
this.queryTablesTab.collection,
|
||||||
|
this.cqlQuery(),
|
||||||
|
true,
|
||||||
|
this.continuationToken
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const query = this.queryTablesTab.container.isPreferredApiCassandra() ? this.cqlQuery() : this.sqlQuery();
|
||||||
|
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||||
|
this.queryTablesTab.collection,
|
||||||
|
query,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
if (!this._documentIterator) {
|
if (!this._documentIterator) {
|
||||||
this._documentIterator = result.iterator;
|
this._documentIterator = documents.iterator;
|
||||||
}
|
}
|
||||||
var actualDownloadSize: number = 0;
|
var actualDownloadSize: number = 0;
|
||||||
|
|
||||||
@@ -478,11 +472,11 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
return Q.resolve(null);
|
return Q.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = result.Results;
|
var entities = documents.Results;
|
||||||
actualDownloadSize = entities.length;
|
actualDownloadSize = entities.length;
|
||||||
|
|
||||||
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
||||||
this.continuationToken = this.isCancelled ? null : result.ContinuationToken;
|
this.continuationToken = this.isCancelled ? null : documents.ContinuationToken;
|
||||||
|
|
||||||
if (!this.continuationToken) {
|
if (!this.continuationToken) {
|
||||||
this.allDownloaded = true;
|
this.allDownloaded = true;
|
||||||
@@ -514,20 +508,22 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
|
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
|
||||||
// For #2.2, go to next round prefetch.
|
// For #2.2, go to next round prefetch.
|
||||||
if (this.allDownloaded || nextDownloadSize === 0) {
|
if (this.allDownloaded || nextDownloadSize === 0) {
|
||||||
return Q.resolve(result);
|
return documents;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
||||||
result.ExceedMaximumRetries = true;
|
documents.ExceedMaximumRetries = true;
|
||||||
return Q.resolve(result);
|
return documents;
|
||||||
}
|
}
|
||||||
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
|
||||||
})
|
return await this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
||||||
.catch((error: Error) => {
|
}
|
||||||
this.cache.serverCallInProgress = false;
|
} catch (error) {
|
||||||
return Q.reject(error);
|
this.cache.serverCallInProgress = false;
|
||||||
});
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Q from "q";
|
|||||||
import { displayTokenRenewalPromptForStatus, getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { displayTokenRenewalPromptForStatus, getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as Entities from "./Entities";
|
import * as Entities from "./Entities";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
@@ -12,9 +13,12 @@ import * as TableConstants from "./Constants";
|
|||||||
import * as TableEntityProcessor from "./TableEntityProcessor";
|
import * as TableEntityProcessor from "./TableEntityProcessor";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
|
||||||
export interface CassandraTableKeys {
|
export interface CassandraTableKeys {
|
||||||
partitionKeys: CassandraTableKey[];
|
partitionKeys: CassandraTableKey[];
|
||||||
@@ -38,19 +42,19 @@ export abstract class TableDataClient {
|
|||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
newEntity: Entities.ITableEntity
|
newEntity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity>;
|
): Promise<Entities.ITableEntity>;
|
||||||
|
|
||||||
public abstract queryDocuments(
|
public abstract queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string,
|
query: string,
|
||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string
|
paginationToken?: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult>;
|
): Promise<Entities.IListTableEntitiesResult>;
|
||||||
|
|
||||||
public abstract deleteDocuments(
|
public abstract deleteDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
entitiesToDelete: Entities.ITableEntity[]
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
): Q.Promise<any>;
|
): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TablesAPIDataClient extends TableDataClient {
|
export class TablesAPIDataClient extends TableDataClient {
|
||||||
@@ -74,77 +78,63 @@ export class TablesAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDocument(
|
public async updateDocument(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
entity: Entities.ITableEntity
|
entity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity> {
|
): Promise<Entities.ITableEntity> {
|
||||||
const deferred = Q.defer<Entities.ITableEntity>();
|
try {
|
||||||
|
const newDocument = await updateDocument(
|
||||||
updateDocument(
|
collection,
|
||||||
collection,
|
originalDocument,
|
||||||
originalDocument,
|
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
|
||||||
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
|
);
|
||||||
).then(
|
return TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
|
||||||
(newDocument: any) => {
|
} catch (error) {
|
||||||
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
|
handleError(error, "TablesAPIDataClient/updateDocument");
|
||||||
deferred.resolve(newEntity);
|
throw error;
|
||||||
},
|
}
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public queryDocuments(
|
public async queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string
|
query: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
|
try {
|
||||||
|
const options = {
|
||||||
|
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey()
|
||||||
|
} as FeedOptions;
|
||||||
|
const iterator = queryDocuments(collection.databaseId, collection.id(), query, options);
|
||||||
|
const response = await iterator.fetchNext();
|
||||||
|
const documents = response?.resources;
|
||||||
|
const entities = TableEntityProcessor.convertDocumentsToEntities(documents);
|
||||||
|
|
||||||
let options: any = {};
|
return {
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
Results: entities,
|
||||||
queryDocuments(collection.databaseId, collection.id(), query, options).then(
|
ContinuationToken: iterator.hasMoreResults(),
|
||||||
iterator => {
|
iterator: iterator
|
||||||
iterator
|
};
|
||||||
.fetchNext()
|
} catch (error) {
|
||||||
.then(response => response.resources)
|
handleError(error, "TablesAPIDataClient/queryDocuments", "Query documents failed");
|
||||||
.then(
|
throw error;
|
||||||
(documents: any[] = []) => {
|
}
|
||||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
|
||||||
let finalEntities: Entities.IListTableEntitiesResult = <Entities.IListTableEntitiesResult>{
|
|
||||||
Results: entities,
|
|
||||||
ContinuationToken: iterator.hasMoreResults(),
|
|
||||||
iterator: iterator
|
|
||||||
};
|
|
||||||
deferred.resolve(finalEntities);
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
|
public async deleteDocuments(
|
||||||
let documentsToDelete: any[] = TableEntityProcessor.convertEntitiesToDocuments(
|
collection: ViewModels.Collection,
|
||||||
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
|
): Promise<any> {
|
||||||
|
const documentsToDelete: any[] = TableEntityProcessor.convertEntitiesToDocuments(
|
||||||
<Entities.ITableEntityForTablesAPI[]>entitiesToDelete,
|
<Entities.ITableEntityForTablesAPI[]>entitiesToDelete,
|
||||||
collection
|
collection
|
||||||
);
|
);
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
|
||||||
documentsToDelete &&
|
await Promise.all(
|
||||||
documentsToDelete.forEach(document => {
|
documentsToDelete?.map(async document => {
|
||||||
document.id = ko.observable<string>(document.id);
|
document.id = ko.observable<string>(document.id);
|
||||||
let promise: Q.Promise<any> = deleteDocument(collection, document);
|
await deleteDocument(collection, document);
|
||||||
promiseArray.push(promise);
|
})
|
||||||
});
|
);
|
||||||
return Q.all(promiseArray);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,10 +170,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
(data: any) => {
|
(data: any) => {
|
||||||
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
|
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
|
||||||
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
|
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully added new row to table ${collection.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully added new row to table ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(entity);
|
deferred.resolve(entity);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -197,181 +184,149 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDocument(
|
public async updateDocument(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
newEntity: Entities.ITableEntity
|
newEntity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity> {
|
): Promise<Entities.ITableEntity> {
|
||||||
const notificationId = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Updating row ${originalDocument.RowKey._}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating row ${originalDocument.RowKey._}`
|
try {
|
||||||
);
|
let whereSegment = " WHERE";
|
||||||
const deferred = Q.defer<Entities.ITableEntity>();
|
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
collection.cassandraKeys.clusteringKeys
|
||||||
let query = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
);
|
||||||
let isChange: boolean = false;
|
for (let keyIndex in keys) {
|
||||||
for (let property in newEntity) {
|
const key = keys[keyIndex].property;
|
||||||
if (!originalDocument[property] || newEntity[property]._.toString() !== originalDocument[property]._.toString()) {
|
const keyType = keys[keyIndex].type;
|
||||||
if (this.isStringType(newEntity[property].$)) {
|
whereSegment += this.isStringType(keyType)
|
||||||
query = `${query} SET ${property} = '${newEntity[property]._}',`;
|
? ` ${key} = '${newEntity[key]._}' AND`
|
||||||
} else {
|
: ` ${key} = ${newEntity[key]._} AND`;
|
||||||
query = `${query} SET ${property} = ${newEntity[property]._},`;
|
}
|
||||||
|
whereSegment = whereSegment.slice(0, whereSegment.length - 4);
|
||||||
|
|
||||||
|
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
||||||
|
let isPropertyUpdated = false;
|
||||||
|
for (let property in newEntity) {
|
||||||
|
if (
|
||||||
|
!originalDocument[property] ||
|
||||||
|
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
||||||
|
) {
|
||||||
|
updateQuery += this.isStringType(newEntity[property].$)
|
||||||
|
? ` SET ${property} = '${newEntity[property]._}',`
|
||||||
|
: ` SET ${property} = ${newEntity[property]._},`;
|
||||||
|
isPropertyUpdated = true;
|
||||||
}
|
}
|
||||||
isChange = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
query = query.slice(0, query.length - 1);
|
if (isPropertyUpdated) {
|
||||||
let whereSegment = " WHERE";
|
updateQuery = updateQuery.slice(0, updateQuery.length - 1);
|
||||||
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
updateQuery += whereSegment;
|
||||||
collection.cassandraKeys.clusteringKeys
|
await this.queryDocuments(collection, updateQuery);
|
||||||
);
|
|
||||||
for (let keyIndex in keys) {
|
|
||||||
const key = keys[keyIndex].property;
|
|
||||||
const keyType = keys[keyIndex].type;
|
|
||||||
if (this.isStringType(keyType)) {
|
|
||||||
whereSegment = `${whereSegment} ${key} = '${newEntity[key]._}' AND`;
|
|
||||||
} else {
|
|
||||||
whereSegment = `${whereSegment} ${key} = ${newEntity[key]._} AND`;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
whereSegment = whereSegment.slice(0, whereSegment.length - 4);
|
let deleteQuery = `DELETE `;
|
||||||
query = query + whereSegment;
|
let isPropertyDeleted = false;
|
||||||
if (isChange) {
|
for (let property in originalDocument) {
|
||||||
promiseArray.push(this.queryDocuments(collection, query));
|
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
|
||||||
}
|
deleteQuery += ` ${property},`;
|
||||||
query = `DELETE `;
|
isPropertyDeleted = true;
|
||||||
for (let property in originalDocument) {
|
|
||||||
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
|
|
||||||
query = `${query} ${property},`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (query.length > 7) {
|
|
||||||
query = query.slice(0, query.length - 1);
|
|
||||||
query = `${query} FROM ${collection.databaseId}.${collection.id()}${whereSegment}`;
|
|
||||||
promiseArray.push(this.queryDocuments(collection, query));
|
|
||||||
}
|
|
||||||
Q.all(promiseArray)
|
|
||||||
.then(
|
|
||||||
(data: any) => {
|
|
||||||
newEntity[TableConstants.EntityKeyNames.RowKey] = originalDocument[TableConstants.EntityKeyNames.RowKey];
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated row ${newEntity.RowKey._}`
|
|
||||||
);
|
|
||||||
deferred.resolve(newEntity);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
handleError(error, "UpdateRowCassandra", `Failed to update row ${newEntity.RowKey._}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
if (isPropertyDeleted) {
|
||||||
});
|
deleteQuery = deleteQuery.slice(0, deleteQuery.length - 1);
|
||||||
return deferred.promise;
|
deleteQuery += ` FROM ${collection.databaseId}.${collection.id()}${whereSegment}`;
|
||||||
|
await this.queryDocuments(collection, deleteQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntity[TableConstants.EntityKeyNames.RowKey] = originalDocument[TableConstants.EntityKeyNames.RowKey];
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(`Successfully updated row ${newEntity.RowKey._}`);
|
||||||
|
return newEntity;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateRowCassandra", "Failed to update row ${newEntity.RowKey._}");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public queryDocuments(
|
public async queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string,
|
query: string,
|
||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string
|
paginationToken?: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
let notificationId: string;
|
const clearMessage =
|
||||||
if (shouldNotify) {
|
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
||||||
notificationId = NotificationConsoleUtils.logConsoleMessage(
|
try {
|
||||||
ConsoleDataType.InProgress,
|
const authType = window.authType;
|
||||||
`Querying rows for table ${collection.id()}`
|
const apiEndpoint: string =
|
||||||
);
|
authType === AuthType.EncryptedToken
|
||||||
}
|
? Constants.CassandraBackend.guestQueryApi
|
||||||
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
|
: Constants.CassandraBackend.queryApi;
|
||||||
const authType = window.authType;
|
const data: any = await $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
||||||
const apiEndpoint: string =
|
type: "POST",
|
||||||
authType === AuthType.EncryptedToken
|
data: {
|
||||||
? Constants.CassandraBackend.guestQueryApi
|
accountName:
|
||||||
: Constants.CassandraBackend.queryApi;
|
collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
||||||
$.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
cassandraEndpoint: this.trimCassandraEndpoint(
|
||||||
type: "POST",
|
collection.container.databaseAccount().properties.cassandraEndpoint
|
||||||
data: {
|
),
|
||||||
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
resourceId: collection.container.databaseAccount().id,
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(
|
keyspaceId: collection.databaseId,
|
||||||
collection.container.databaseAccount().properties.cassandraEndpoint
|
tableId: collection.id(),
|
||||||
),
|
query,
|
||||||
resourceId: collection.container.databaseAccount().id,
|
paginationToken
|
||||||
keyspaceId: collection.databaseId,
|
|
||||||
tableId: collection.id(),
|
|
||||||
query: query,
|
|
||||||
paginationToken: paginationToken
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader,
|
|
||||||
error: this.handleAjaxError,
|
|
||||||
cache: false
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(data: any) => {
|
|
||||||
if (shouldNotify) {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
deferred.resolve({
|
|
||||||
Results: data.result,
|
|
||||||
ContinuationToken: data.paginationToken
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
beforeSend: this.setAuthorizationHeader,
|
||||||
if (shouldNotify) {
|
error: this.handleAjaxError,
|
||||||
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
cache: false
|
||||||
}
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.done(() => {
|
|
||||||
if (shouldNotify) {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return deferred.promise;
|
shouldNotify &&
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(
|
||||||
|
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
Results: data.result,
|
||||||
|
ContinuationToken: data.paginationToken
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
shouldNotify &&
|
||||||
|
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
|
public async deleteDocuments(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
|
): Promise<any> {
|
||||||
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
|
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
const partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
|
||||||
let partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
|
|
||||||
for (let i = 0, len = entitiesToDelete.length; i < len; i++) {
|
await Promise.all(
|
||||||
let currEntityToDelete: Entities.ITableEntity = entitiesToDelete[i];
|
entitiesToDelete.map(async (currEntityToDelete: Entities.ITableEntity) => {
|
||||||
let currQuery = query;
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting row ${currEntityToDelete.RowKey._}`);
|
||||||
let partitionKeyValue = currEntityToDelete[partitionKeyProperty];
|
const partitionKeyValue = currEntityToDelete[partitionKeyProperty];
|
||||||
if (partitionKeyValue._ != null && this.isStringType(partitionKeyValue.$)) {
|
const currQuery =
|
||||||
currQuery = `${currQuery}${partitionKeyProperty} = '${partitionKeyValue._}' AND `;
|
query + this.isStringType(partitionKeyValue.$)
|
||||||
} else {
|
? `${partitionKeyProperty} = '${partitionKeyValue._}'`
|
||||||
currQuery = `${currQuery}${partitionKeyProperty} = ${partitionKeyValue._} AND `;
|
: `${partitionKeyProperty} = ${partitionKeyValue._}`;
|
||||||
}
|
|
||||||
currQuery = currQuery.slice(0, currQuery.length - 5);
|
try {
|
||||||
const notificationId = NotificationConsoleUtils.logConsoleMessage(
|
await this.queryDocuments(collection, currQuery);
|
||||||
ConsoleDataType.InProgress,
|
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted row ${currEntityToDelete.RowKey._}`);
|
||||||
`Deleting row ${currEntityToDelete.RowKey._}`
|
} catch (error) {
|
||||||
);
|
handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
|
||||||
promiseArray.push(
|
throw error;
|
||||||
this.queryDocuments(collection, currQuery)
|
} finally {
|
||||||
.then(
|
clearMessage();
|
||||||
() => {
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
})
|
||||||
ConsoleDataType.Info,
|
);
|
||||||
`Successfully deleted row ${currEntityToDelete.RowKey._}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Q.all(promiseArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public createKeyspace(
|
public createKeyspace(
|
||||||
|
|||||||
@@ -16,18 +16,16 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
|
import { QueryIterator, Resource, ConflictDefinition, FeedOptions } from "@azure/cosmos";
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import {
|
|
||||||
queryConflicts,
|
|
||||||
deleteConflict,
|
|
||||||
deleteDocument,
|
|
||||||
createDocument,
|
|
||||||
updateDocument
|
|
||||||
} from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
import { deleteConflict } from "../../Common/dataAccess/deleteConflict";
|
||||||
|
import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
|
||||||
|
|
||||||
export default class ConflictsTab extends TabsBase {
|
export default class ConflictsTab extends TabsBase {
|
||||||
public selectedConflictId: ko.Observable<ConflictId>;
|
public selectedConflictId: ko.Observable<ConflictId>;
|
||||||
@@ -225,25 +223,15 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
public async refreshDocumentsGrid(): Promise<void> {
|
||||||
// clear documents grid
|
try {
|
||||||
this.conflictIds([]);
|
// clear documents grid
|
||||||
return this.createIterator()
|
this.conflictIds([]);
|
||||||
.then(
|
this._documentsIterator = this.createIterator();
|
||||||
// reset iterator
|
await this.loadNextPage();
|
||||||
iterator => {
|
} catch (error) {
|
||||||
this._documentsIterator = iterator;
|
window.alert(getErrorMessage(error));
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.then(
|
|
||||||
// load documents
|
|
||||||
() => {
|
|
||||||
return this.loadNextPage();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch(error => {
|
|
||||||
window.alert(getErrorMessage(error));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@@ -265,9 +253,9 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAcceptChangesClick = (): Q.Promise<any> => {
|
public onAcceptChangesClick = async (): Promise<void> => {
|
||||||
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
||||||
return Q();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
@@ -285,81 +273,79 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
conflictResourceId: selectedConflict.resourceId
|
conflictResourceId: selectedConflict.resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
let operationPromise: Q.Promise<any> = Q();
|
try {
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
|
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = updateDocument(
|
await updateDocument(
|
||||||
this.collection,
|
this.collection,
|
||||||
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty]),
|
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty]),
|
||||||
documentContent
|
documentContent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Create) {
|
if (selectedConflict.operationType === Constants.ConflictOperationType.Create) {
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = createDocument(this.collection, documentContent);
|
await createDocument(this.collection, documentContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Delete && !!this.selectedConflictContent()) {
|
if (
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
selectedConflict.operationType === Constants.ConflictOperationType.Delete &&
|
||||||
|
!!this.selectedConflictContent()
|
||||||
|
) {
|
||||||
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = deleteDocument(
|
await deleteDocument(
|
||||||
this.collection,
|
this.collection,
|
||||||
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty])
|
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return operationPromise
|
await deleteConflict(this.collection, selectedConflict);
|
||||||
.then(
|
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
||||||
() => {
|
this.selectedConflictContent("");
|
||||||
return deleteConflict(this.collection, selectedConflict).then(() => {
|
this.selectedConflictCurrent("");
|
||||||
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
this.selectedConflictId(null);
|
||||||
this.selectedConflictContent("");
|
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||||
this.selectedConflictCurrent("");
|
TelemetryProcessor.traceSuccess(
|
||||||
this.selectedConflictId(null);
|
Action.ResolveConflict,
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
{
|
||||||
TelemetryProcessor.traceSuccess(
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
Action.ResolveConflict,
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
{
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
tabTitle: this.tabTitle(),
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
tabTitle: this.tabTitle(),
|
conflictResourceId: selectedConflict.resourceId
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
|
||||||
conflictOperationType: selectedConflict.operationType,
|
|
||||||
conflictResourceId: selectedConflict.resourceId
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
error => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
window.alert(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.ResolveConflict,
|
window.alert(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.ResolveConflict,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
tabTitle: this.tabTitle(),
|
||||||
conflictResourceId: selectedConflict.resourceId,
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
error: errorMessage,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
errorStack: getErrorStack(error)
|
conflictResourceId: selectedConflict.resourceId,
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
}
|
startKey
|
||||||
)
|
);
|
||||||
.finally(() => this.isExecuting(false));
|
} finally {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDeleteClick = (): Q.Promise<any> => {
|
public onDeleteClick = async (): Promise<void> => {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
@@ -375,50 +361,48 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
conflictResourceId: selectedConflict.resourceId
|
conflictResourceId: selectedConflict.resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return deleteConflict(this.collection, selectedConflict)
|
try {
|
||||||
.then(
|
await deleteConflict(this.collection, selectedConflict);
|
||||||
() => {
|
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
||||||
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
this.selectedConflictContent("");
|
||||||
this.selectedConflictContent("");
|
this.selectedConflictCurrent("");
|
||||||
this.selectedConflictCurrent("");
|
this.selectedConflictId(null);
|
||||||
this.selectedConflictId(null);
|
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
TelemetryProcessor.traceSuccess(
|
||||||
TelemetryProcessor.traceSuccess(
|
Action.DeleteConflict,
|
||||||
Action.DeleteConflict,
|
{
|
||||||
{
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
conflictResourceId: selectedConflict.resourceId
|
||||||
conflictResourceId: selectedConflict.resourceId
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
error => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
window.alert(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.DeleteConflict,
|
window.alert(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.DeleteConflict,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
tabTitle: this.tabTitle(),
|
||||||
conflictResourceId: selectedConflict.resourceId,
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
error: errorMessage,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
errorStack: getErrorStack(error)
|
conflictResourceId: selectedConflict.resourceId,
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
}
|
startKey
|
||||||
)
|
);
|
||||||
.finally(() => this.isExecuting(false));
|
} finally {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDiscardClick = (): Q.Promise<any> => {
|
public onDiscardClick = (): Q.Promise<any> => {
|
||||||
@@ -445,60 +429,47 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
if (this._documentsIterator) {
|
|
||||||
return Q.resolve(this._documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createIterator().then(
|
if (!this._documentsIterator) {
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
try {
|
||||||
this._documentsIterator = iterator;
|
this._documentsIterator = await this.createIterator();
|
||||||
return this.loadNextPage();
|
await this.loadNextPage();
|
||||||
},
|
} catch (error) {
|
||||||
error => {
|
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
TelemetryProcessor.traceFailure(
|
||||||
TelemetryProcessor.traceFailure(
|
Action.Tab,
|
||||||
Action.Tab,
|
{
|
||||||
{
|
databaseAccountName: this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection.container.databaseAccount().name,
|
databaseName: this.collection.databaseId,
|
||||||
databaseName: this.collection.databaseId,
|
collectionName: this.collection.id(),
|
||||||
collectionName: this.collection.id(),
|
defaultExperience: this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
error: getErrorMessage(error),
|
||||||
error: getErrorMessage(error),
|
errorStack: getErrorStack(error)
|
||||||
errorStack: getErrorStack(error)
|
},
|
||||||
},
|
this.onLoadStartKey
|
||||||
this.onLoadStartKey
|
);
|
||||||
);
|
this.onLoadStartKey = null;
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshClick(): Q.Promise<any> {
|
public createIterator(): QueryIterator<ConflictDefinition & Resource> {
|
||||||
return this.refreshDocumentsGrid().then(() => {
|
|
||||||
this.selectedConflictContent("");
|
|
||||||
this.selectedConflictId(null);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public createIterator(): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
// TODO: Conflict Feed does not allow filtering atm
|
// TODO: Conflict Feed does not allow filtering atm
|
||||||
const query: string = undefined;
|
const query: string = undefined;
|
||||||
let options: any = {};
|
const options = {
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey()
|
||||||
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options);
|
};
|
||||||
|
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options as FeedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
|
|||||||
@@ -23,6 +23,19 @@
|
|||||||
<div class="scaleDivison" aria-label="Scale" aria-controls="scaleRegion">
|
<div class="scaleDivison" aria-label="Scale" aria-controls="scaleRegion">
|
||||||
<span class="scaleSettingTitle">Scale</span>
|
<span class="scaleSettingTitle">Scale</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="freeTierInfoBanner" data-bind="visible: isFreeTierAccount">
|
||||||
|
<span class="freeTierInfoIcon"><img src="/info_color.svg" alt="Info"/></span>
|
||||||
|
<span class="freeTierInfoMessage"
|
||||||
|
>With free tier, you'll 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.
|
||||||
|
<a
|
||||||
|
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Learn more.</a
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="ssTextAllignment" id="scaleRegion">
|
<div class="ssTextAllignment" id="scaleRegion">
|
||||||
<throughput-input-autopilot-v3
|
<throughput-input-autopilot-v3
|
||||||
params="{
|
params="{
|
||||||
@@ -46,7 +59,8 @@
|
|||||||
autoPilotUsageCost: autoPilotUsageCost,
|
autoPilotUsageCost: autoPilotUsageCost,
|
||||||
canExceedMaximumValue: canExceedMaximumValue,
|
canExceedMaximumValue: canExceedMaximumValue,
|
||||||
overrideWithAutoPilotSettings: overrideWithAutoPilotSettings,
|
overrideWithAutoPilotSettings: overrideWithAutoPilotSettings,
|
||||||
overrideWithProvisionedThroughputSettings: overrideWithProvisionedThroughputSettings
|
overrideWithProvisionedThroughputSettings: overrideWithProvisionedThroughputSettings,
|
||||||
|
freeTierExceedThroughputWarning: freeTierExceedThroughputWarning
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
</throughput-input-autopilot-v3>
|
</throughput-input-autopilot-v3>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
public canThroughputExceedMaximumValue: ko.Computed<boolean>;
|
public canThroughputExceedMaximumValue: ko.Computed<boolean>;
|
||||||
public costsVisible: ko.Computed<boolean>;
|
public costsVisible: ko.Computed<boolean>;
|
||||||
public displayedError: ko.Observable<string>;
|
public displayedError: ko.Observable<string>;
|
||||||
|
public isFreeTierAccount: ko.Computed<boolean>;
|
||||||
public isTemplateReady: ko.Observable<boolean>;
|
public isTemplateReady: ko.Observable<boolean>;
|
||||||
public minRUAnotationVisible: ko.Computed<boolean>;
|
public minRUAnotationVisible: ko.Computed<boolean>;
|
||||||
public minRUs: ko.Observable<number>;
|
public minRUs: ko.Observable<number>;
|
||||||
@@ -82,6 +83,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
public throughputAutoPilotRadioId: string;
|
public throughputAutoPilotRadioId: string;
|
||||||
public throughputProvisionedRadioId: string;
|
public throughputProvisionedRadioId: string;
|
||||||
public throughputModeRadioName: string;
|
public throughputModeRadioName: string;
|
||||||
|
public freeTierExceedThroughputWarning: ko.Computed<string>;
|
||||||
|
|
||||||
private _hasProvisioningTypeChanged: ko.Computed<boolean>;
|
private _hasProvisioningTypeChanged: ko.Computed<boolean>;
|
||||||
private _wasAutopilotOriginallySet: ko.Observable<boolean>;
|
private _wasAutopilotOriginallySet: ko.Observable<boolean>;
|
||||||
@@ -359,6 +361,17 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
|
|
||||||
this.isTemplateReady = ko.observable<boolean>(false);
|
this.isTemplateReady = ko.observable<boolean>(false);
|
||||||
|
|
||||||
|
this.isFreeTierAccount = ko.computed<boolean>(() => {
|
||||||
|
const databaseAccount = this.container?.databaseAccount();
|
||||||
|
return databaseAccount?.properties?.enableFreeTier;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.freeTierExceedThroughputWarning = ko.computed<string>(() =>
|
||||||
|
this.isFreeTierAccount()
|
||||||
|
? "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."
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
this._buildCommandBarOptions();
|
this._buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,11 +442,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(async () => {
|
super.onActivate();
|
||||||
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||||
await this.database.loadOffer();
|
await this.database.loadOffer();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setBaseline() {
|
private _setBaseline() {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<button
|
<button
|
||||||
class="filterbtnstyle queryButton"
|
class="filterbtnstyle queryButton"
|
||||||
data-bind="
|
data-bind="
|
||||||
click: onApplyFilterClick,
|
click: refreshDocumentsGrid,
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -19,19 +19,24 @@ import SaveIcon from "../../../images/save-cosmos.svg";
|
|||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
||||||
import UploadIcon from "../../../images/Upload_16x16.svg";
|
import UploadIcon from "../../../images/Upload_16x16.svg";
|
||||||
import { extractPartitionKey, PartitionKeyDefinition, QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import {
|
||||||
|
extractPartitionKey,
|
||||||
|
PartitionKeyDefinition,
|
||||||
|
QueryIterator,
|
||||||
|
ItemDefinition,
|
||||||
|
Resource,
|
||||||
|
Item
|
||||||
|
} from "@azure/cosmos";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import {
|
|
||||||
readDocument,
|
|
||||||
queryDocuments,
|
|
||||||
deleteDocument,
|
|
||||||
updateDocument,
|
|
||||||
createDocument
|
|
||||||
} from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { readDocument } from "../../Common/dataAccess/readDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
|
||||||
export default class DocumentsTab extends TabsBase {
|
export default class DocumentsTab extends TabsBase {
|
||||||
public selectedDocumentId: ko.Observable<DocumentId>;
|
public selectedDocumentId: ko.Observable<DocumentId>;
|
||||||
@@ -369,36 +374,22 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onApplyFilterClick(): Q.Promise<any> {
|
public async refreshDocumentsGrid(): Promise<void> {
|
||||||
// clear documents grid
|
// clear documents grid
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
return this.createIterator()
|
|
||||||
.then(
|
|
||||||
// reset iterator
|
|
||||||
iterator => {
|
|
||||||
this._documentsIterator = iterator;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
// load documents
|
|
||||||
() => {
|
|
||||||
return this.loadNextPage();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
// collapse filter
|
|
||||||
this.appliedFilter(this.filterContent());
|
|
||||||
this.isFilterExpanded(false);
|
|
||||||
const focusElement = document.getElementById("errorStatusIcon");
|
|
||||||
focusElement && focusElement.focus();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
window.alert(getErrorMessage(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
try {
|
||||||
return this.onApplyFilterClick();
|
// reset iterator
|
||||||
|
this._documentsIterator = this.createIterator();
|
||||||
|
// load documents
|
||||||
|
await this.loadNextPage();
|
||||||
|
// collapse filter
|
||||||
|
this.appliedFilter(this.filterContent());
|
||||||
|
this.isFilterExpanded(false);
|
||||||
|
document.getElementById("errorStatusIcon")?.focus();
|
||||||
|
} catch (error) {
|
||||||
|
window.alert(getErrorMessage(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@@ -434,7 +425,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveNewDocumentClick = (): Q.Promise<any> => {
|
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
@@ -502,7 +493,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveExisitingDocumentClick = (): Q.Promise<any> => {
|
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||||
|
|
||||||
@@ -571,17 +562,15 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDeleteExisitingDocumentClick = (): Q.Promise<any> => {
|
public onDeleteExisitingDocumentClick = async (): Promise<void> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const msg = !this.isPreferredApiMongoDB
|
const msg = !this.isPreferredApiMongoDB
|
||||||
? "Are you sure you want to delete the selected item ?"
|
? "Are you sure you want to delete the selected item ?"
|
||||||
: "Are you sure you want to delete the selected document ?";
|
: "Are you sure you want to delete the selected document ?";
|
||||||
|
|
||||||
if (window.confirm(msg)) {
|
if (window.confirm(msg)) {
|
||||||
return this._deleteDocument(selectedDocumentId);
|
await this._deleteDocument(selectedDocumentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onValidDocumentEdit(): Q.Promise<any> {
|
public onValidDocumentEdit(): Q.Promise<any> {
|
||||||
@@ -617,63 +606,50 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
if (this._documentsIterator) {
|
|
||||||
return Q.resolve(this._documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createIterator().then(
|
if (!this._documentsIterator) {
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
try {
|
||||||
this._documentsIterator = iterator;
|
this._documentsIterator = this.createIterator();
|
||||||
return this.loadNextPage();
|
await this.loadNextPage();
|
||||||
},
|
} catch (error) {
|
||||||
error => {
|
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
TelemetryProcessor.traceFailure(
|
||||||
TelemetryProcessor.traceFailure(
|
Action.Tab,
|
||||||
Action.Tab,
|
{
|
||||||
{
|
databaseAccountName: this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection.container.databaseAccount().name,
|
databaseName: this.collection.databaseId,
|
||||||
databaseName: this.collection.databaseId,
|
collectionName: this.collection.id(),
|
||||||
collectionName: this.collection.id(),
|
defaultExperience: this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
error: getErrorMessage(error),
|
||||||
error: getErrorMessage(error),
|
errorStack: getErrorStack(error)
|
||||||
errorStack: getErrorStack(error)
|
},
|
||||||
},
|
this.onLoadStartKey
|
||||||
this.onLoadStartKey
|
);
|
||||||
);
|
this.onLoadStartKey = null;
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshClick(): Q.Promise<any> {
|
|
||||||
return this.refreshDocumentsGrid().then(() => {
|
|
||||||
this.selectedDocumentContent("");
|
|
||||||
this.selectedDocumentId(null);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private _isIgnoreDirtyEditor = (): boolean => {
|
private _isIgnoreDirtyEditor = (): boolean => {
|
||||||
var msg: string = "Changes will be lost. Do you want to continue?";
|
var msg: string = "Changes will be lost. Do you want to continue?";
|
||||||
return window.confirm(msg);
|
return window.confirm(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> {
|
protected __deleteDocument(documentId: DocumentId): Promise<void> {
|
||||||
return deleteDocument(this.collection, documentId);
|
return deleteDocument(this.collection, documentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deleteDocument(selectedDocumentId: DocumentId): Q.Promise<any> {
|
private _deleteDocument(selectedDocumentId: DocumentId): Promise<void> {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
@@ -684,7 +660,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return this.__deleteDocument(selectedDocumentId)
|
return this.__deleteDocument(selectedDocumentId)
|
||||||
.then(
|
.then(
|
||||||
(result: any) => {
|
() => {
|
||||||
this.documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
|
this.documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
|
||||||
this.selectedDocumentContent("");
|
this.selectedDocumentContent("");
|
||||||
this.selectedDocumentId(null);
|
this.selectedDocumentId(null);
|
||||||
@@ -720,7 +696,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
.finally(() => this.isExecuting(false));
|
.finally(() => this.isExecuting(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public createIterator(): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
public createIterator(): QueryIterator<ItemDefinition & Resource> {
|
||||||
let filters = this.lastFilterContents();
|
let filters = this.lastFilterContents();
|
||||||
const filter: string = this.filterContent().trim();
|
const filter: string = this.filterContent().trim();
|
||||||
const query: string = this.buildQuery(filter);
|
const query: string = this.buildQuery(filter);
|
||||||
@@ -734,11 +710,10 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
|
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectDocument(documentId: DocumentId): Q.Promise<any> {
|
public async selectDocument(documentId: DocumentId): Promise<void> {
|
||||||
this.selectedDocumentId(documentId);
|
this.selectedDocumentId(documentId);
|
||||||
return readDocument(this.collection, documentId).then((content: any) => {
|
const content = await readDocument(this.collection, documentId);
|
||||||
this.initDocumentEditor(documentId, content);
|
this.initDocumentEditor(documentId, content);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
|
|||||||
@@ -114,10 +114,9 @@ export default class GraphTab extends TabsBase {
|
|||||||
: `${account.name}.graphs.azure.com:443/`;
|
: `${account.name}.graphs.azure.com:443/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -289,7 +289,7 @@
|
|||||||
<button
|
<button
|
||||||
class="filterbtnstyle queryButton"
|
class="filterbtnstyle queryButton"
|
||||||
data-bind="
|
data-bind="
|
||||||
click: onApplyFilterClick,
|
click: refreshDocumentsGrid,
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
>
|
>
|
||||||
Apply Filter
|
Apply Filter
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
super.buildCommandBarOptions();
|
super.buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSaveNewDocumentClick = (): Q.Promise<any> => {
|
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||||
this.displayedError("");
|
this.displayedError("");
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||||
@@ -78,12 +78,12 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab");
|
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab");
|
||||||
return Q.reject("Document without shard key");
|
throw new Error("Document without shard key");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return Q(createDocument(this.collection.databaseId, this.collection, this.partitionKeyProperty, documentContent))
|
return createDocument(this.collection.databaseId, this.collection, this.partitionKeyProperty, documentContent)
|
||||||
.then(
|
.then(
|
||||||
(savedDocument: any) => {
|
(savedDocument: any) => {
|
||||||
let partitionKeyArray = extractPartitionKey(
|
let partitionKeyArray = extractPartitionKey(
|
||||||
@@ -136,7 +136,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
.finally(() => this.isExecuting(false));
|
.finally(() => this.isExecuting(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveExisitingDocumentClick = (): Q.Promise<any> => {
|
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const documentContent = this.selectedDocumentContent();
|
const documentContent = this.selectedDocumentContent();
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
@@ -148,7 +148,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
tabTitle: this.tabTitle()
|
tabTitle: this.tabTitle()
|
||||||
});
|
});
|
||||||
|
|
||||||
return Q(updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent))
|
return updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent)
|
||||||
.then(
|
.then(
|
||||||
(updatedDocument: any) => {
|
(updatedDocument: any) => {
|
||||||
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
||||||
@@ -204,13 +204,10 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
return filter || "{}";
|
return filter || "{}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectDocument(documentId: DocumentId): Q.Promise<any> {
|
public async selectDocument(documentId: DocumentId): Promise<void> {
|
||||||
this.selectedDocumentId(documentId);
|
this.selectedDocumentId(documentId);
|
||||||
return Q(
|
const content = await readDocument(this.collection.databaseId, this.collection, documentId);
|
||||||
readDocument(this.collection.databaseId, this.collection, documentId).then((content: any) => {
|
this.initDocumentEditor(documentId, content);
|
||||||
this.initDocumentEditor(documentId, content);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
@@ -330,7 +327,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
return partitionKey;
|
return partitionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> {
|
protected __deleteDocument(documentId: DocumentId): Promise<void> {
|
||||||
return Q(deleteDocument(this.collection.databaseId, this.collection, documentId));
|
return deleteDocument(this.collection.databaseId, this.collection, documentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,9 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleMessage(event: MessageEvent) {
|
public handleMessage(event: MessageEvent) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<json-editor
|
<json-editor
|
||||||
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
||||||
data-bind="visible: queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
||||||
>
|
>
|
||||||
</json-editor>
|
</json-editor>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import { QueryUtils } from "../../Utils/QueryUtils";
|
|||||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||||
|
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
Result,
|
Result,
|
||||||
@@ -163,20 +164,19 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this._buildCommandBarOptions();
|
this._buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteQueryClick = (): Q.Promise<any> => {
|
public onExecuteQueryClick = async (): Promise<void> => {
|
||||||
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
||||||
this.sqlStatementToExecute(sqlStatement);
|
this.sqlStatementToExecute(sqlStatement);
|
||||||
this.allResultsMetadata([]);
|
this.allResultsMetadata([]);
|
||||||
this.queryResults("");
|
this.queryResults("");
|
||||||
this._iterator = null;
|
this._iterator = undefined;
|
||||||
|
|
||||||
return this._executeQueryDocumentsPage(0);
|
await this._executeQueryDocumentsPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
public onLoadQueryClick = (): void => {
|
public onLoadQueryClick = (): void => {
|
||||||
@@ -191,13 +191,13 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onFetchNextPageClick(): Q.Promise<any> {
|
public async onFetchNextPageClick(): Promise<void> {
|
||||||
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
||||||
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
||||||
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
||||||
const itemCount: number = (metadata && Number(metadata.itemCount)) || 0;
|
const itemCount: number = (metadata && Number(metadata.itemCount)) || 0;
|
||||||
|
|
||||||
return this._executeQueryDocumentsPage(firstResultIndex + itemCount - 1);
|
await this._executeQueryDocumentsPage(firstResultIndex + itemCount - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
||||||
@@ -265,19 +265,18 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _executeQueryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
private async _executeQueryDocumentsPage(firstItemIndex: number): Promise<any> {
|
||||||
this.error("");
|
this.error("");
|
||||||
this.roundTrips(undefined);
|
this.roundTrips(undefined);
|
||||||
if (this._iterator == null) {
|
if (this._iterator === undefined) {
|
||||||
const queryIteratorPromise = this._initIterator();
|
this._initIterator();
|
||||||
return queryIteratorPromise.finally(() => this._queryDocumentsPage(firstItemIndex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._queryDocumentsPage(firstItemIndex);
|
await this._queryDocumentsPage(firstItemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Position and enable spinner when request is in progress
|
// TODO: Position and enable spinner when request is in progress
|
||||||
private _queryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
private async _queryDocumentsPage(firstItemIndex: number): Promise<void> {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this._resetAggregateQueryMetrics();
|
this._resetAggregateQueryMetrics();
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
||||||
@@ -289,90 +288,75 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
let options: any = {};
|
let options: any = {};
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
||||||
|
|
||||||
const queryDocuments = (firstItemIndex: number) =>
|
const queryDocuments = async (firstItemIndex: number) =>
|
||||||
queryDocumentsPage(this.collection && this.collection.id(), this._iterator, firstItemIndex, options);
|
await queryDocumentsPage(this.collection && this.collection.id(), this._iterator, firstItemIndex);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return QueryUtils.queryPagesUntilContentPresent(firstItemIndex, queryDocuments)
|
|
||||||
.then(
|
|
||||||
(queryResults: ViewModels.QueryResults) => {
|
|
||||||
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
|
||||||
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
|
||||||
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
|
||||||
hasMoreResults: queryResults.hasMoreResults,
|
|
||||||
itemCount: queryResults.itemCount,
|
|
||||||
firstItemIndex: queryResults.firstItemIndex,
|
|
||||||
lastItemIndex: queryResults.lastItemIndex
|
|
||||||
};
|
|
||||||
this.allResultsMetadata.push(resultsMetadata);
|
|
||||||
this.activityId(queryResults.activityId);
|
|
||||||
this.roundTrips(queryResults.roundTrips);
|
|
||||||
|
|
||||||
this._updateQueryMetricsMap(queryResults.headers[Constants.HttpHeaders.queryMetrics]);
|
try {
|
||||||
|
const queryResults: ViewModels.QueryResults = await QueryUtils.queryPagesUntilContentPresent(
|
||||||
|
firstItemIndex,
|
||||||
|
queryDocuments
|
||||||
|
);
|
||||||
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
||||||
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
||||||
|
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
||||||
|
hasMoreResults: queryResults.hasMoreResults,
|
||||||
|
itemCount: queryResults.itemCount,
|
||||||
|
firstItemIndex: queryResults.firstItemIndex,
|
||||||
|
lastItemIndex: queryResults.lastItemIndex
|
||||||
|
};
|
||||||
|
this.allResultsMetadata.push(resultsMetadata);
|
||||||
|
this.activityId(queryResults.activityId);
|
||||||
|
this.roundTrips(queryResults.roundTrips);
|
||||||
|
|
||||||
if (queryResults.itemCount == 0 && metadata != null && metadata.itemCount >= 0) {
|
this._updateQueryMetricsMap(queryResults.headers[Constants.HttpHeaders.queryMetrics]);
|
||||||
// we let users query for the next page because the SDK sometimes specifies there are more elements
|
|
||||||
// even though there aren't any so we should not update the prior query results.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const documents: any[] = queryResults.documents;
|
if (queryResults.itemCount == 0 && metadata != null && metadata.itemCount >= 0) {
|
||||||
const results = this.renderObjectForEditor(documents, null, 4);
|
// we let users query for the next page because the SDK sometimes specifies there are more elements
|
||||||
|
// even though there aren't any so we should not update the prior query results.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const resultsDisplay: string =
|
const documents: any[] = queryResults.documents;
|
||||||
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
|
const results = this.renderObjectForEditor(documents, null, 4);
|
||||||
this.showingDocumentsDisplayText(resultsDisplay);
|
|
||||||
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
|
|
||||||
|
|
||||||
if (!this.queryResults() && !results) {
|
const resultsDisplay: string =
|
||||||
const errorMessage: string = JSON.stringify({
|
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
|
||||||
error: `Returned no results after query execution`,
|
this.showingDocumentsDisplayText(resultsDisplay);
|
||||||
accountName: this.collection && this.collection.container.databaseAccount(),
|
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
this.queryResults(results);
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
sqlQuery: this.sqlStatementToExecute(),
|
|
||||||
hasMoreResults: resultsMetadata.hasMoreResults,
|
|
||||||
itemCount: resultsMetadata.itemCount,
|
|
||||||
responseHeaders: queryResults && queryResults.headers
|
|
||||||
});
|
|
||||||
Logger.logError(errorMessage, "QueryTab");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queryResults(results);
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.ExecuteQuery,
|
||||||
TelemetryProcessor.traceSuccess(
|
{
|
||||||
Action.ExecuteQuery,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
{
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
tabTitle: this.tabTitle()
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle()
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
this.error(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.ExecuteQuery,
|
this.error(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.ExecuteQuery,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
error: errorMessage,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
errorStack: getErrorStack(error)
|
tabTitle: this.tabTitle(),
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
document.getElementById("error-display").focus();
|
startKey
|
||||||
}
|
);
|
||||||
)
|
document.getElementById("error-display").focus();
|
||||||
.finally(() => {
|
} finally {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.togglesOnFocus();
|
this.togglesOnFocus();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateQueryMetricsMap(metricsMap: { [partitionKeyRange: string]: DataModels.QueryMetrics }): void {
|
private _updateQueryMetricsMap(metricsMap: { [partitionKeyRange: string]: DataModels.QueryMetrics }): void {
|
||||||
@@ -477,16 +461,17 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _initIterator(): Q.Promise<MinimalQueryIterator> {
|
protected _initIterator(): void {
|
||||||
const options: any = QueryTab.getIteratorOptions(this.collection);
|
const options: any = QueryTab.getIteratorOptions(this.collection);
|
||||||
if (this._resourceTokenPartitionKey) {
|
if (this._resourceTokenPartitionKey) {
|
||||||
options.partitionKey = this._resourceTokenPartitionKey;
|
options.partitionKey = this._resourceTokenPartitionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q(
|
this._iterator = queryDocuments(
|
||||||
queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then(
|
this.collection.databaseId,
|
||||||
iterator => (this._iterator = iterator)
|
this.collection.id(),
|
||||||
)
|
this.sqlStatementToExecute(),
|
||||||
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,17 +161,16 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public onActivate(): void {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
const columns =
|
const columns =
|
||||||
!!this.tableEntityListViewModel() &&
|
!!this.tableEntityListViewModel() &&
|
||||||
!!this.tableEntityListViewModel().table &&
|
!!this.tableEntityListViewModel().table &&
|
||||||
this.tableEntityListViewModel().table.columns;
|
this.tableEntityListViewModel().table.columns;
|
||||||
if (!!columns) {
|
if (!!columns) {
|
||||||
columns.adjust();
|
columns.adjust();
|
||||||
$(window).resize();
|
$(window).resize();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
|
|||||||
@@ -186,12 +186,11 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
this._setBaselines();
|
this._setBaselines();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
if (this.isNew()) {
|
if (this.isNew()) {
|
||||||
this.collection.selectedSubnodeKind(this.tabKind);
|
this.collection.selectedSubnodeKind(this.tabKind);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract onSaveClick: () => Promise<any>;
|
public abstract onSaveClick: () => Promise<any>;
|
||||||
|
|||||||
@@ -42,54 +42,49 @@ export default class SettingsTabV2 extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<unknown> {
|
public async onActivate(): Promise<void> {
|
||||||
this.isExecuting(true);
|
try {
|
||||||
this.currentCollection.loadOffer().then(
|
this.isExecuting(true);
|
||||||
() => {
|
await this.currentCollection.loadOffer();
|
||||||
// passed in options and set by parent as "Settings" by default
|
// passed in options and set by parent as "Settings" by default
|
||||||
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
||||||
this.offerRead(true);
|
|
||||||
this.options.getPendingNotification.then(
|
|
||||||
(data: DataModels.Notification) => {
|
|
||||||
this.notification = data;
|
|
||||||
this.notificationRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.notification = undefined;
|
|
||||||
this.notificationRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
traceFailure(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
|
||||||
databaseName: this.options.collection.databaseId,
|
|
||||||
collectionName: this.options.collection.id(),
|
|
||||||
defaultExperience: this.options.collection.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle,
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error)
|
|
||||||
},
|
|
||||||
this.options.onLoadStartKey
|
|
||||||
);
|
|
||||||
logConsoleError(
|
|
||||||
`Error while fetching container settings for container ${this.options.collection.id()}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.offerRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.onActivate().then(() => {
|
this.options.getPendingNotification.then(
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
(data: DataModels.Notification) => {
|
||||||
});
|
this.notification = data;
|
||||||
|
this.notificationRead(true);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
this.notification = undefined;
|
||||||
|
this.notificationRead(true);
|
||||||
|
traceFailure(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
||||||
|
databaseName: this.options.collection.databaseId,
|
||||||
|
collectionName: this.options.collection.id(),
|
||||||
|
defaultExperience: this.options.collection.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
|
},
|
||||||
|
this.options.onLoadStartKey
|
||||||
|
);
|
||||||
|
logConsoleError(
|
||||||
|
`Error while fetching container settings for container ${this.options.collection.id()}: ${errorMessage}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.offerRead(true);
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivate();
|
||||||
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSettingsTabContainer(): Explorer {
|
public getSettingsTabContainer(): Explorer {
|
||||||
|
|||||||
@@ -94,9 +94,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
this.getContainer().tabsManager.activateTab(this);
|
this.getContainer().tabsManager.activateTab(this);
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedNode(): void {
|
protected updateSelectedNode(): void {
|
||||||
@@ -128,7 +127,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public onActivate(): void {
|
||||||
this.updateSelectedNode();
|
this.updateSelectedNode();
|
||||||
if (!!this.collection) {
|
if (!!this.collection) {
|
||||||
this.collection.selectedSubnodeKind(this.tabKind);
|
this.collection.selectedSubnodeKind(this.tabKind);
|
||||||
@@ -151,7 +150,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
tabTitle: this.tabTitle(),
|
tabTitle: this.tabTitle(),
|
||||||
tabId: this.tabId
|
tabId: this.tabId
|
||||||
});
|
});
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
<!-- ko if: $data.tabKind === 19 -->
|
<!-- ko if: $data.tabKind === 20 -->
|
||||||
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
||||||
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
||||||
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
||||||
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
@@ -39,6 +38,7 @@ import Explorer from "../Explorer";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
|
||||||
export default class Collection implements ViewModels.Collection {
|
export default class Collection implements ViewModels.Collection {
|
||||||
public nodeKind: string;
|
public nodeKind: string;
|
||||||
@@ -551,7 +551,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, tab => {
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1091,8 +1091,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createDocumentsFromFile(fileName: string, documentContent: string): Q.Promise<UploadDetailsRecord> {
|
private async _createDocumentsFromFile(fileName: string, documentContent: string): Promise<UploadDetailsRecord> {
|
||||||
const deferred: Q.Deferred<UploadDetailsRecord> = Q.defer();
|
|
||||||
const record: UploadDetailsRecord = {
|
const record: UploadDetailsRecord = {
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
numSucceeded: 0,
|
numSucceeded: 0,
|
||||||
@@ -1102,39 +1101,25 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const content = JSON.parse(documentContent);
|
const content = JSON.parse(documentContent);
|
||||||
const promises: Array<Q.Promise<any>> = [];
|
|
||||||
|
|
||||||
const triggerCreateDocument: (documentContent: any) => Q.Promise<any> = (documentContent: any) => {
|
|
||||||
return createDocument(this, documentContent).then(
|
|
||||||
doc => {
|
|
||||||
record.numSucceeded++;
|
|
||||||
return Q.resolve();
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
record.numFailed++;
|
|
||||||
record.errors = [...record.errors, getErrorMessage(error)];
|
|
||||||
return Q.resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
for (let i = 0; i < content.length; i++) {
|
await Promise.all(
|
||||||
promises.push(triggerCreateDocument(content[i]));
|
content.map(async documentContent => {
|
||||||
}
|
await createDocument(this, documentContent);
|
||||||
|
record.numSucceeded++;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
promises.push(triggerCreateDocument(content));
|
await createDocument(this, documentContent);
|
||||||
|
record.numSucceeded++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q.all(promises).then(() => {
|
return record;
|
||||||
deferred.resolve(record);
|
} catch (error) {
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
record.numFailed++;
|
record.numFailed++;
|
||||||
record.errors = [...record.errors, e.message];
|
record.errors = [...record.errors, error.message];
|
||||||
deferred.resolve(record);
|
return record;
|
||||||
}
|
}
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { extractPartitionKey } from "@azure/cosmos";
|
import { extractPartitionKey } from "@azure/cosmos";
|
||||||
import ConflictsTab from "../Tabs/ConflictsTab";
|
import ConflictsTab from "../Tabs/ConflictsTab";
|
||||||
import { readDocument } from "../../Common/DocumentClientUtilityBase";
|
import { readDocument } from "../../Common/dataAccess/readDocument";
|
||||||
|
|
||||||
export default class ConflictId {
|
export default class ConflictId {
|
||||||
public container: ConflictsTab;
|
public container: ConflictsTab;
|
||||||
@@ -59,41 +59,42 @@ export default class ConflictId {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadConflict(): Q.Promise<any> {
|
public async loadConflict(): Promise<void> {
|
||||||
const conflictsTab = this.container;
|
|
||||||
this.container.selectedConflictId(this);
|
this.container.selectedConflictId(this);
|
||||||
|
|
||||||
if (this.operationType === Constants.ConflictOperationType.Create) {
|
if (this.operationType === Constants.ConflictOperationType.Create) {
|
||||||
this.container.initDocumentEditorForCreate(this, this.content);
|
this.container.initDocumentEditorForCreate(this, this.content);
|
||||||
return Q();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.loadingConflictData(true);
|
this.container.loadingConflictData(true);
|
||||||
return readDocument(this.container.collection, this.buildDocumentIdFromConflict(this.partitionKeyValue)).then(
|
|
||||||
(currentDocumentContent: any) => {
|
|
||||||
this.container.loadingConflictData(false);
|
|
||||||
if (this.operationType === Constants.ConflictOperationType.Replace) {
|
|
||||||
this.container.initDocumentEditorForReplace(this, this.content, currentDocumentContent);
|
|
||||||
} else {
|
|
||||||
this.container.initDocumentEditorForDelete(this, currentDocumentContent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
this.container.loadingConflictData(false);
|
|
||||||
|
|
||||||
// Document could be deleted
|
try {
|
||||||
if (
|
const currentDocumentContent = await readDocument(
|
||||||
reason &&
|
this.container.collection,
|
||||||
reason.code === Constants.HttpStatusCodes.NotFound &&
|
this.buildDocumentIdFromConflict(this.partitionKeyValue)
|
||||||
this.operationType === Constants.ConflictOperationType.Delete
|
);
|
||||||
) {
|
|
||||||
this.container.initDocumentEditorForNoOp(this);
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Q.reject(reason);
|
if (this.operationType === Constants.ConflictOperationType.Replace) {
|
||||||
|
this.container.initDocumentEditorForReplace(this, this.content, currentDocumentContent);
|
||||||
|
} else {
|
||||||
|
this.container.initDocumentEditorForDelete(this, currentDocumentContent);
|
||||||
}
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
// Document could be deleted
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
error.code === Constants.HttpStatusCodes.NotFound &&
|
||||||
|
this.operationType === Constants.ConflictOperationType.Delete
|
||||||
|
) {
|
||||||
|
this.container.initDocumentEditorForNoOp(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.container.loadingConflictData(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPartitionKeyValueAsString(): string {
|
public getPartitionKeyValueAsString(): string {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default class DocumentId {
|
|||||||
return JSON.stringify(partitionKeyValue);
|
return JSON.stringify(partitionKeyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadDocument(): Q.Promise<any> {
|
public async loadDocument(): Promise<void> {
|
||||||
return this.container.selectDocument(this);
|
await this.container.selectDocument(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||||
import { executeStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
|||||||
export class NotebookWorkspaceManager {
|
export class NotebookWorkspaceManager {
|
||||||
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
||||||
|
|
||||||
constructor(private _armEndpoint: string) {
|
constructor() {
|
||||||
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this._armEndpoint);
|
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export function initializeExplorer(): Explorer {
|
|||||||
cassandraEndpoint: ""
|
cassandraEndpoint: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
explorer.isAccountReady(true);
|
explorer.isAccountReady(true);
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,42 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
|
||||||
export class ConnectionStringParser {
|
export class ConnectionStringParser {
|
||||||
public static parseConnectionString(connectionString: string): DataModels.AccessInputMetadata {
|
public static parseConnectionString(connectionString: string): DataModels.AccessInputMetadata | undefined {
|
||||||
if (!!connectionString) {
|
if (!!connectionString) {
|
||||||
try {
|
try {
|
||||||
const accessInput: DataModels.AccessInputMetadata = {} as DataModels.AccessInputMetadata;
|
const accessInput: DataModels.AccessInputMetadata = {} as DataModels.AccessInputMetadata;
|
||||||
const connectionStringParts = connectionString.split(";");
|
const connectionStringParts = connectionString.split(";");
|
||||||
|
|
||||||
connectionStringParts.forEach((connectionStringPart: string) => {
|
connectionStringParts.forEach((connectionStringPart: string) => {
|
||||||
if (RegExp(Constants.EndpointsRegex.sql).test(connectionStringPart)) {
|
const sqlMatchResult = connectionStringPart.match(Constants.EndpointsRegex.sql);
|
||||||
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.sql)[1];
|
const mongoMatchResult = connectionStringPart.match(Constants.EndpointsRegex.mongo);
|
||||||
|
const mongoComputeMatchResult = connectionStringPart.match(Constants.EndpointsRegex.mongoCompute);
|
||||||
|
const tableMatchResult = connectionStringPart.match(Constants.EndpointsRegex.table);
|
||||||
|
|
||||||
|
if (sqlMatchResult && sqlMatchResult.length > 1) {
|
||||||
|
accessInput.accountName = sqlMatchResult[1];
|
||||||
accessInput.apiKind = DataModels.ApiKind.SQL;
|
accessInput.apiKind = DataModels.ApiKind.SQL;
|
||||||
} else if (RegExp(Constants.EndpointsRegex.mongo).test(connectionStringPart)) {
|
} else if (mongoMatchResult && mongoMatchResult.length > 2) {
|
||||||
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongo);
|
accessInput.accountName = mongoMatchResult[2];
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
|
||||||
accessInput.apiKind = DataModels.ApiKind.MongoDB;
|
accessInput.apiKind = DataModels.ApiKind.MongoDB;
|
||||||
} else if (RegExp(Constants.EndpointsRegex.mongoCompute).test(connectionStringPart)) {
|
} else if (mongoComputeMatchResult && mongoComputeMatchResult.length > 2) {
|
||||||
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongoCompute);
|
accessInput.accountName = mongoComputeMatchResult[2];
|
||||||
accessInput.accountName = matches && matches.length > 1 && matches[2];
|
|
||||||
accessInput.apiKind = DataModels.ApiKind.MongoDBCompute;
|
accessInput.apiKind = DataModels.ApiKind.MongoDBCompute;
|
||||||
} else if (Constants.EndpointsRegex.cassandra.some(regex => RegExp(regex).test(connectionStringPart))) {
|
} else if (
|
||||||
|
Constants.EndpointsRegex.cassandra &&
|
||||||
|
Constants.EndpointsRegex.cassandra.some(regex => RegExp(regex).test(connectionStringPart))
|
||||||
|
) {
|
||||||
Constants.EndpointsRegex.cassandra.forEach(regex => {
|
Constants.EndpointsRegex.cassandra.forEach(regex => {
|
||||||
if (RegExp(regex).test(connectionStringPart)) {
|
if (RegExp(regex).test(connectionStringPart)) {
|
||||||
accessInput.accountName = connectionStringPart.match(regex)[1];
|
const connectionMatch = connectionStringPart.match(regex);
|
||||||
accessInput.apiKind = DataModels.ApiKind.Cassandra;
|
if (connectionMatch && connectionMatch.length > 1) {
|
||||||
|
accessInput.accountName = connectionMatch[1];
|
||||||
|
accessInput.apiKind = DataModels.ApiKind.Cassandra;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (RegExp(Constants.EndpointsRegex.table).test(connectionStringPart)) {
|
} else if (tableMatchResult && tableMatchResult.length > 1) {
|
||||||
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.table)[1];
|
accessInput.accountName = tableMatchResult[1];
|
||||||
accessInput.apiKind = DataModels.ApiKind.Table;
|
accessInput.apiKind = DataModels.ApiKind.Table;
|
||||||
} else if (connectionStringPart.indexOf("ApiKind=Gremlin") >= 0) {
|
} else if (connectionStringPart.indexOf("ApiKind=Gremlin") >= 0) {
|
||||||
accessInput.apiKind = DataModels.ApiKind.Graph;
|
accessInput.apiKind = DataModels.ApiKind.Graph;
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ export default class Main {
|
|||||||
masterKey?: string /* master key extracted from connection string if available */,
|
masterKey?: string /* master key extracted from connection string if available */,
|
||||||
account?: DatabaseAccount,
|
account?: DatabaseAccount,
|
||||||
authorizationToken?: string /* access key */
|
authorizationToken?: string /* access key */
|
||||||
): Q.Promise<void> {
|
): void {
|
||||||
const serverId: string = AuthHeadersUtil.serverId;
|
const serverId: string = AuthHeadersUtil.serverId;
|
||||||
const authType: string = (<any>window).authType;
|
const authType: string = (<any>window).authType;
|
||||||
const accountResourceId =
|
const accountResourceId =
|
||||||
@@ -373,7 +373,7 @@ export default class Main {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q.reject(`Unsupported AuthType ${authType}`);
|
throw new Error(`Unsupported AuthType ${authType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _instantiateExplorer(): Explorer {
|
private static _instantiateExplorer(): Explorer {
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||||
import Explorer from "../../Explorer/Explorer";
|
import Explorer from "../../Explorer/Explorer";
|
||||||
|
import { handleMessage } from "../../Controls/Heatmap/Heatmap";
|
||||||
|
|
||||||
export function initializeExplorer(): Explorer {
|
export function initializeExplorer(): Explorer {
|
||||||
const explorer = new Explorer();
|
const explorer = new Explorer();
|
||||||
|
|
||||||
|
// In development mode, try to load the iframe message from session storage.
|
||||||
|
// This allows webpack hot reload to funciton properly
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
|
||||||
|
if (initMessage) {
|
||||||
|
const message = JSON.parse(initMessage);
|
||||||
|
console.warn("Loaded cached portal iframe message from session storage");
|
||||||
|
console.dir(message);
|
||||||
|
explorer.initDataExplorerWithFrameInputs(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
||||||
|
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { configContext } from "../ConfigContext";
|
||||||
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
|
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
|
||||||
import { ResourceProviderClient } from "./ResourceProviderClient";
|
import { ResourceProviderClient } from "./ResourceProviderClient";
|
||||||
|
|
||||||
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
|
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
|
||||||
|
private armEndpoint: string;
|
||||||
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
|
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
|
||||||
|
|
||||||
constructor(private armEndpoint: string) {}
|
constructor() {
|
||||||
|
this.armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
public getOrCreate(url: string): IResourceProviderClient<any> {
|
public getOrCreate(url: string): IResourceProviderClient<any> {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ export class TabRouteHandler {
|
|||||||
): void {
|
): void {
|
||||||
this._initRouter();
|
this._initRouter();
|
||||||
const parseHash = (newHash: string, oldHash: string) => this._tabRouter.parse(newHash);
|
const parseHash = (newHash: string, oldHash: string) => this._tabRouter.parse(newHash);
|
||||||
const defaultRoutedCallback = (request: string, data: { route: any; params: string[]; isFirst: boolean }) => {
|
const defaultRoutedCallback = (request: string, data: { route: any; params: string[]; isFirst: boolean }) => {};
|
||||||
console.log(request);
|
|
||||||
};
|
|
||||||
this._tabRouter.routed.add(onMatch || defaultRoutedCallback);
|
this._tabRouter.routed.add(onMatch || defaultRoutedCallback);
|
||||||
hasher.initialized.add(parseHash);
|
hasher.initialized.add(parseHash);
|
||||||
hasher.changed.add(parseHash);
|
hasher.changed.add(parseHash);
|
||||||
|
|||||||
@@ -144,10 +144,6 @@ export class OfferPricing {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GeneralResources {
|
|
||||||
public static loadingText: string = "Loading...";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CollectionCreation {
|
export class CollectionCreation {
|
||||||
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
|
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
|
||||||
public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
|
public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
|
||||||
@@ -228,32 +224,6 @@ export class IndexingPolicies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SubscriptionUtilMappings {
|
export class SubscriptionUtilMappings {
|
||||||
// TODO: Expose this through a web API from the portal
|
|
||||||
public static SubscriptionTypeMap: { [key: string]: SubscriptionType } = {
|
|
||||||
"AAD_2015-09-01": SubscriptionType.Free,
|
|
||||||
"AzureDynamics_2014-09-01": SubscriptionType.Free,
|
|
||||||
"AzureInOpen_2014-09-01": SubscriptionType.EA,
|
|
||||||
"AzurePass_2014-09-01": SubscriptionType.Free,
|
|
||||||
"BackupStorage_2014-09-01": SubscriptionType.PAYG,
|
|
||||||
"BizSpark_2014-09-01": SubscriptionType.Benefits,
|
|
||||||
"BizSparkPlus_2014-09-01": SubscriptionType.Benefits,
|
|
||||||
"CSP_2015-05-01": SubscriptionType.EA,
|
|
||||||
"Default_2014-09-01": SubscriptionType.PAYG,
|
|
||||||
"DevEssentials_2016-01-01": SubscriptionType.Benefits,
|
|
||||||
"DreamSpark_2015-02-01": SubscriptionType.Benefits,
|
|
||||||
"EnterpriseAgreement_2014-09-01": SubscriptionType.EA,
|
|
||||||
"FreeTrial_2014-09-01": SubscriptionType.Free,
|
|
||||||
"Internal_2014-09-01": SubscriptionType.Internal,
|
|
||||||
"LegacyMonetaryCommitment_2014-09-01": SubscriptionType.EA,
|
|
||||||
"LightweightTrial_2016-09-01": SubscriptionType.Free,
|
|
||||||
"MonetaryCommitment_2015-05-01": SubscriptionType.EA,
|
|
||||||
"MPN_2014-09-01": SubscriptionType.Benefits,
|
|
||||||
"MSDN_2014-09-01": SubscriptionType.Benefits,
|
|
||||||
"MSDNDevTest_2014-09-01": SubscriptionType.Benefits,
|
|
||||||
"PayAsYouGo_2014-09-01": SubscriptionType.PAYG,
|
|
||||||
"Sponsored_2016-01-01": SubscriptionType.Benefits
|
|
||||||
};
|
|
||||||
|
|
||||||
public static FreeTierSubscriptionIds: string[] = [
|
public static FreeTierSubscriptionIds: string[] = [
|
||||||
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
|
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
|
||||||
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
|
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
|
||||||
@@ -264,57 +234,6 @@ export class SubscriptionUtilMappings {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Offers {
|
|
||||||
public static offerTypeS1: string = "S1";
|
|
||||||
public static offerTypeS2: string = "S2";
|
|
||||||
public static offerTypeS3: string = "S3";
|
|
||||||
public static offerTypeStandard: string = "Standard";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OfferThoughput {
|
|
||||||
public static offerS1Throughput: number = 250;
|
|
||||||
public static offerS2Throughput: number = 1000;
|
|
||||||
public static offerS3Throughput: number = 2500;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OfferVersions {
|
|
||||||
public static offerV1: string = "V1";
|
|
||||||
public static offerV2: string = "V2";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InvalidOffers {
|
|
||||||
public static offerTypeInvalid: string = "Invalid";
|
|
||||||
public static offerTypeError: string = "Loading Error";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SpecTypes {
|
|
||||||
public static collection: string = "DocumentDbCollection";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CurrencyCodes {
|
|
||||||
public static usd: string = "USD";
|
|
||||||
public static rmb: string = "RMB";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ColorSchemes {
|
|
||||||
public static standard: string = "mediumBlue";
|
|
||||||
public static legacy: string = "yellowGreen";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FeatureIds {
|
|
||||||
public static storage: string = "storage";
|
|
||||||
public static sla: string = "sla";
|
|
||||||
public static partitioned: string = "partitioned";
|
|
||||||
public static singlePartitioned: string = "singlePartition";
|
|
||||||
public static legacySinglePartitioned: string = "legacySinglePartition";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FeatureIconNames {
|
|
||||||
public static storage: string = "SSD";
|
|
||||||
public static sla: string = "Monitoring";
|
|
||||||
public static productionReady: string = "ProductionReadyDb";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AutopilotDocumentation {
|
export class AutopilotDocumentation {
|
||||||
public static Url: string = "https://aka.ms/cosmos-autoscale-info";
|
public static Url: string = "https://aka.ms/cosmos-autoscale-info";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as DataModels from "../Contracts/DataModels";
|
|||||||
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
||||||
|
|
||||||
export class DefaultExperienceUtility {
|
export class DefaultExperienceUtility {
|
||||||
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string {
|
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string | null {
|
||||||
if (!databaseAccount) {
|
if (!databaseAccount) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -81,11 +81,9 @@ export class DefaultExperienceUtility {
|
|||||||
|
|
||||||
private static _getDefaultExperience(kind: string, capabilities: DataModels.Capability[]): string {
|
private static _getDefaultExperience(kind: string, capabilities: DataModels.Capability[]): string {
|
||||||
const defaultDefaultExperience: string = Constants.DefaultAccountExperience.DocumentDB;
|
const defaultDefaultExperience: string = Constants.DefaultAccountExperience.DocumentDB;
|
||||||
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind);
|
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind) || "";
|
||||||
const defaultExperienceFromCapabilities: string = DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(
|
const defaultExperienceFromCapabilities: string =
|
||||||
capabilities
|
DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(capabilities) || "";
|
||||||
);
|
|
||||||
|
|
||||||
if (!!defaultExperienceFromKind) {
|
if (!!defaultExperienceFromKind) {
|
||||||
return defaultExperienceFromKind;
|
return defaultExperienceFromKind;
|
||||||
}
|
}
|
||||||
@@ -97,7 +95,7 @@ export class DefaultExperienceUtility {
|
|||||||
return defaultDefaultExperience;
|
return defaultDefaultExperience;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _getDefaultExperienceFromAccountKind(kind: string): string {
|
private static _getDefaultExperienceFromAccountKind(kind: string): string | null {
|
||||||
if (!kind) {
|
if (!kind) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -113,7 +111,7 @@ export class DefaultExperienceUtility {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string {
|
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string | null {
|
||||||
if (!capabilities) {
|
if (!capabilities) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ import { ArmApiVersions, ArmResourceTypes } from "../Common/Constants";
|
|||||||
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
|
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
|
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
|
||||||
import { configContext } from "../ConfigContext";
|
|
||||||
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export class ArcadiaResourceManager {
|
export class ArcadiaResourceManager {
|
||||||
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
||||||
|
|
||||||
constructor(private armEndpoint = configContext.ARM_ENDPOINT) {
|
constructor() {
|
||||||
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this.armEndpoint);
|
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> {
|
public async getWorkspacesAsync(arcadiaResourceId: string): Promise<ArcadiaWorkspace[]> {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface UserContext {
|
|||||||
defaultExperience?: DefaultAccountExperienceType;
|
defaultExperience?: DefaultAccountExperienceType;
|
||||||
useSDKOperations?: boolean;
|
useSDKOperations?: boolean;
|
||||||
subscriptionType?: SubscriptionType;
|
subscriptionType?: SubscriptionType;
|
||||||
|
quotaId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userContext: Readonly<UserContext> = {} as const;
|
const userContext: Readonly<UserContext> = {} as const;
|
||||||
|
|||||||
@@ -25,39 +25,151 @@ describe("PricingUtils Tests", () => {
|
|||||||
|
|
||||||
describe("computeRUUsagePriceHourly()", () => {
|
describe("computeRUUsagePriceHourly()", () => {
|
||||||
it("should return 0 for NaN regions default cloud", () => {
|
it("should return 0 for NaN regions default cloud", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: null,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
|
expect(value).toBe(0);
|
||||||
|
});
|
||||||
|
it("should return 0 for NaN regions default cloud, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: null,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0 for -1 regions", () => {
|
it("should return 0 for -1 regions", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: -1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
|
expect(value).toBe(0);
|
||||||
|
});
|
||||||
|
it("should return 0 for -1 regions, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: -1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
expect(value).toBe(0);
|
expect(value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => {
|
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
expect(value).toBe(0.00008);
|
expect(value).toBe(0.00008);
|
||||||
});
|
});
|
||||||
|
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
|
expect(value).toBe(0.00012);
|
||||||
|
});
|
||||||
|
|
||||||
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => {
|
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "mooncake",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
expect(value).toBe(0.00051);
|
expect(value).toBe(0.00051);
|
||||||
});
|
});
|
||||||
|
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "mooncake",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
|
expect(value).toBe(0.00076);
|
||||||
|
});
|
||||||
|
|
||||||
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => {
|
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 2,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
expect(value).toBe(0.00016);
|
expect(value).toBe(0.00016);
|
||||||
});
|
});
|
||||||
|
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 2,
|
||||||
|
multimasterEnabled: false,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
|
expect(value).toBe(0.00024);
|
||||||
|
});
|
||||||
|
|
||||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => {
|
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: true,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
expect(value).toBe(0.00008);
|
expect(value).toBe(0.00008);
|
||||||
});
|
});
|
||||||
|
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 1,
|
||||||
|
multimasterEnabled: true,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
|
expect(value).toBe(0.00012);
|
||||||
|
});
|
||||||
|
|
||||||
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
||||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true);
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 2,
|
||||||
|
multimasterEnabled: true,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
expect(value).toBe(0.00048);
|
expect(value).toBe(0.00048);
|
||||||
});
|
});
|
||||||
|
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => {
|
||||||
|
const value = PricingUtils.computeRUUsagePriceHourly({
|
||||||
|
serverId: "default",
|
||||||
|
requestUnits: 1,
|
||||||
|
numberOfRegions: 2,
|
||||||
|
multimasterEnabled: true,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
|
expect(value).toBe(0.00096);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getPriceCurrency()", () => {
|
describe("getPriceCurrency()", () => {
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
|
||||||
import * as Constants from "../Shared/Constants";
|
import * as Constants from "../Shared/Constants";
|
||||||
|
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
||||||
|
|
||||||
|
interface ComputeRUUsagePriceHourlyArgs {
|
||||||
|
serverId: string;
|
||||||
|
requestUnits: number;
|
||||||
|
numberOfRegions: number;
|
||||||
|
multimasterEnabled: boolean;
|
||||||
|
isAutoscale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const estimatedCostDisclaimer =
|
||||||
|
"*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anything that is not a number should return 0
|
* Anything that is not a number should return 0
|
||||||
@@ -47,15 +59,16 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
|
|||||||
return multimasterMultiplier;
|
return multimasterMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeRUUsagePriceHourly(
|
export function computeRUUsagePriceHourly({
|
||||||
serverId: string,
|
serverId,
|
||||||
requestUnits: number,
|
requestUnits,
|
||||||
numberOfRegions: number,
|
numberOfRegions,
|
||||||
multimasterEnabled: boolean
|
multimasterEnabled,
|
||||||
): number {
|
isAutoscale
|
||||||
|
}: ComputeRUUsagePriceHourlyArgs): number {
|
||||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||||
const pricePerRu = getPricePerRu(serverId);
|
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId);
|
||||||
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
||||||
|
|
||||||
return Number(ruCharge.toFixed(5));
|
return Number(ruCharge.toFixed(5));
|
||||||
@@ -159,28 +172,19 @@ export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDat
|
|||||||
}' target='_blank' aria-label='Learn more about autoscale throughput'>Learn more</a>.`;
|
}' target='_blank' aria-label='Learn more about autoscale throughput'>Learn more</a>.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeAutoscaleUsagePriceHourly(
|
|
||||||
serverId: string,
|
|
||||||
requestUnits: number,
|
|
||||||
numberOfRegions: number,
|
|
||||||
multimasterEnabled: boolean
|
|
||||||
): number {
|
|
||||||
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
|
|
||||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
|
||||||
|
|
||||||
const pricePerRu = getAutoscalePricePerRu(serverId, multimasterMultiplier);
|
|
||||||
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
|
|
||||||
|
|
||||||
return Number(ruCharge.toFixed(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEstimatedAutoscaleSpendHtml(
|
export function getEstimatedAutoscaleSpendHtml(
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
regions: number,
|
||||||
multimaster: boolean
|
multimaster: boolean
|
||||||
): string {
|
): string {
|
||||||
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
|
serverId: serverId,
|
||||||
|
requestUnits: throughput,
|
||||||
|
numberOfRegions: regions,
|
||||||
|
multimasterEnabled: multimaster,
|
||||||
|
isAutoscale: true
|
||||||
|
});
|
||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
@@ -203,7 +207,13 @@ export function getEstimatedSpendHtml(
|
|||||||
regions: number,
|
regions: number,
|
||||||
multimaster: boolean
|
multimaster: boolean
|
||||||
): string {
|
): string {
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
|
serverId: serverId,
|
||||||
|
requestUnits: throughput,
|
||||||
|
numberOfRegions: regions,
|
||||||
|
multimasterEnabled: multimaster,
|
||||||
|
isAutoscale: false
|
||||||
|
});
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currency: string = getPriceCurrency(serverId);
|
const currency: string = getPriceCurrency(serverId);
|
||||||
@@ -217,7 +227,7 @@ export function getEstimatedSpendHtml(
|
|||||||
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
|
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly </b> ` +
|
||||||
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
|
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
|
||||||
`<p style='padding: 10px 0px 0px 0px;'>` +
|
`<p style='padding: 10px 0px 0px 0px;'>` +
|
||||||
`<em>*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account</em></p>`
|
`<em>${estimatedCostDisclaimer}</em></p>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,9 +238,13 @@ export function getEstimatedSpendAcknowledgeString(
|
|||||||
multimaster: boolean,
|
multimaster: boolean,
|
||||||
isAutoscale: boolean
|
isAutoscale: boolean
|
||||||
): string {
|
): string {
|
||||||
const hourlyPrice: number = isAutoscale
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
|
serverId: serverId,
|
||||||
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
requestUnits: throughput,
|
||||||
|
numberOfRegions: regions,
|
||||||
|
multimasterEnabled: multimaster,
|
||||||
|
isAutoscale: isAutoscale
|
||||||
|
});
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
@@ -243,9 +257,19 @@ export function getEstimatedSpendAcknowledgeString(
|
|||||||
)} - ${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly cost for the throughput above.`;
|
)} - ${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly cost for the throughput above.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUpsellMessage(serverId = "default", isFreeTier = false): string {
|
export function getUpsellMessage(
|
||||||
|
serverId = "default",
|
||||||
|
isFreeTier = false,
|
||||||
|
isFirstResourceCreated = false,
|
||||||
|
defaultExperience: string,
|
||||||
|
isCollection: boolean
|
||||||
|
): string {
|
||||||
if (isFreeTier) {
|
if (isFreeTier) {
|
||||||
return "With free tier discount, you'll get the first 400 RU/s and 5 GB of storage in this account for free. Charges will apply if your resource throughput exceeds 400 RU/s.";
|
const collectionName = getCollectionName(defaultExperience);
|
||||||
|
const resourceType = isCollection ? collectionName : "database";
|
||||||
|
return isFirstResourceCreated
|
||||||
|
? `The free tier discount of 400 RU/s has already been applied to a database or ${collectionName} in this account. Billing will apply to this ${resourceType} after it is created.`
|
||||||
|
: `With free tier, you'll get the first 400 RU/s and 5 GB of storage in this account for free. Billing will apply if you provision more than 400 RU/s of manual throughput, or if the ${resourceType} scales beyond 400 RU/s with autoscale.`;
|
||||||
} else {
|
} else {
|
||||||
let price: number = Constants.OfferPricing.MonthlyPricing.default.Standard.StartingPrice;
|
let price: number = Constants.OfferPricing.MonthlyPricing.default.Standard.StartingPrice;
|
||||||
|
|
||||||
@@ -256,3 +280,19 @@ export function getUpsellMessage(serverId = "default", isFreeTier = false): stri
|
|||||||
return `Start at ${getCurrencySign(serverId)}${price}/mo per database, multiple containers included`;
|
return `Start at ${getCurrencySign(serverId)}${price}/mo per database, multiple containers included`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCollectionName(defaultExperience: string): string {
|
||||||
|
switch (defaultExperience) {
|
||||||
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
|
return "container";
|
||||||
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
|
return "collection";
|
||||||
|
case DefaultAccountExperienceType.Table:
|
||||||
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
|
return "table";
|
||||||
|
case DefaultAccountExperienceType.Graph:
|
||||||
|
return "graph";
|
||||||
|
default:
|
||||||
|
throw Error("unknown API type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,41 +58,36 @@ export class QueryUtils {
|
|||||||
return projections.join(",");
|
return projections.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static queryPagesUntilContentPresent(
|
public static async queryPagesUntilContentPresent(
|
||||||
firstItemIndex: number,
|
firstItemIndex: number,
|
||||||
queryItems: (itemIndex: number) => Q.Promise<ViewModels.QueryResults>
|
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||||
): Q.Promise<ViewModels.QueryResults> {
|
): Promise<ViewModels.QueryResults> {
|
||||||
let roundTrips: number = 0;
|
let roundTrips: number = 0;
|
||||||
let netRequestCharge: number = 0;
|
let netRequestCharge: number = 0;
|
||||||
const doRequest = (itemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||||
queryItems(itemIndex).then(
|
const results: ViewModels.QueryResults = await queryItems(itemIndex);
|
||||||
(results: ViewModels.QueryResults) => {
|
roundTrips = roundTrips + 1;
|
||||||
roundTrips = roundTrips + 1;
|
results.roundTrips = roundTrips;
|
||||||
results.roundTrips = roundTrips;
|
results.requestCharge = Number(results.requestCharge) + netRequestCharge;
|
||||||
results.requestCharge = Number(results.requestCharge) + netRequestCharge;
|
netRequestCharge = Number(results.requestCharge);
|
||||||
netRequestCharge = Number(results.requestCharge);
|
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
||||||
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
hasMoreResults: results.hasMoreResults,
|
||||||
hasMoreResults: results.hasMoreResults,
|
itemCount: results.itemCount,
|
||||||
itemCount: results.itemCount,
|
firstItemIndex: results.firstItemIndex,
|
||||||
firstItemIndex: results.firstItemIndex,
|
lastItemIndex: results.lastItemIndex
|
||||||
lastItemIndex: results.lastItemIndex
|
};
|
||||||
};
|
if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) {
|
||||||
if (resultsMetadata.itemCount === 0 && resultsMetadata.hasMoreResults) {
|
return await doRequest(resultsMetadata.lastItemIndex);
|
||||||
return doRequest(resultsMetadata.lastItemIndex);
|
}
|
||||||
}
|
return results;
|
||||||
return Q.resolve(results);
|
};
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
return Q.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return doRequest(firstItemIndex);
|
return await doRequest(firstItemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static queryAllPages(
|
public static async queryAllPages(
|
||||||
queryItems: (itemIndex: number) => Q.Promise<ViewModels.QueryResults>
|
queryItems: (itemIndex: number) => Promise<ViewModels.QueryResults>
|
||||||
): Q.Promise<ViewModels.QueryResults> {
|
): Promise<ViewModels.QueryResults> {
|
||||||
const queryResults: ViewModels.QueryResults = {
|
const queryResults: ViewModels.QueryResults = {
|
||||||
documents: [],
|
documents: [],
|
||||||
activityId: undefined,
|
activityId: undefined,
|
||||||
@@ -103,25 +98,20 @@ export class QueryUtils {
|
|||||||
requestCharge: 0,
|
requestCharge: 0,
|
||||||
roundTrips: 0
|
roundTrips: 0
|
||||||
};
|
};
|
||||||
const doRequest = (itemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
const doRequest = async (itemIndex: number): Promise<ViewModels.QueryResults> => {
|
||||||
queryItems(itemIndex).then(
|
const results: ViewModels.QueryResults = await queryItems(itemIndex);
|
||||||
(results: ViewModels.QueryResults) => {
|
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
|
||||||
const { requestCharge, hasMoreResults, itemCount, lastItemIndex, documents } = results;
|
queryResults.roundTrips = queryResults.roundTrips + 1;
|
||||||
queryResults.roundTrips = queryResults.roundTrips + 1;
|
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
|
||||||
queryResults.requestCharge = Number(queryResults.requestCharge) + Number(requestCharge);
|
queryResults.hasMoreResults = hasMoreResults;
|
||||||
queryResults.hasMoreResults = hasMoreResults;
|
queryResults.itemCount = queryResults.itemCount + itemCount;
|
||||||
queryResults.itemCount = queryResults.itemCount + itemCount;
|
queryResults.lastItemIndex = lastItemIndex;
|
||||||
queryResults.lastItemIndex = lastItemIndex;
|
queryResults.documents = queryResults.documents.concat(documents);
|
||||||
queryResults.documents = queryResults.documents.concat(documents);
|
if (queryResults.hasMoreResults) {
|
||||||
if (queryResults.hasMoreResults) {
|
return doRequest(queryResults.lastItemIndex + 1);
|
||||||
return doRequest(queryResults.lastItemIndex + 1);
|
}
|
||||||
}
|
return queryResults;
|
||||||
return Q.resolve(queryResults);
|
};
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
return Q.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return doRequest(0);
|
return doRequest(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ describe("Collection Add and Delete SQL spec", () => {
|
|||||||
// validate created
|
// validate created
|
||||||
// open database menu
|
// open database menu
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
await frame.waitFor(LOADING_STATE_DELAY);
|
await frame.waitFor(CREATE_DELAY);
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
|
||||||
const selectedDbId = await frame.evaluate(element => {
|
const selectedDbId = await frame.evaluate(element => {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"./src/Bindings/ReactBindingHandler.ts",
|
"./src/Bindings/ReactBindingHandler.ts",
|
||||||
"./src/Common/ArrayHashMap.ts",
|
"./src/Common/ArrayHashMap.ts",
|
||||||
"./src/Common/Constants.ts",
|
"./src/Common/Constants.ts",
|
||||||
"./src/Common/DeleteFeedback.ts",
|
"./src/Common/DeleteFeedback.ts",
|
||||||
|
"./src/Common/DocumentUtility.ts",
|
||||||
"./src/Common/EnvironmentUtility.ts",
|
"./src/Common/EnvironmentUtility.ts",
|
||||||
"./src/Common/HashMap.ts",
|
"./src/Common/HashMap.ts",
|
||||||
"./src/Common/HeadersUtility.ts",
|
"./src/Common/HeadersUtility.ts",
|
||||||
@@ -21,7 +22,8 @@
|
|||||||
"./src/Common/MongoUtility.ts",
|
"./src/Common/MongoUtility.ts",
|
||||||
"./src/Common/ObjectCache.ts",
|
"./src/Common/ObjectCache.ts",
|
||||||
"./src/Common/ThemeUtility.ts",
|
"./src/Common/ThemeUtility.ts",
|
||||||
"./src/Common/UrlUtility.ts",
|
"./src/Common/UrlUtility.ts",
|
||||||
|
"./src/Common/Splitter.ts",
|
||||||
"./src/ConfigContext.ts",
|
"./src/ConfigContext.ts",
|
||||||
"./src/Contracts/ActionContracts.ts",
|
"./src/Contracts/ActionContracts.ts",
|
||||||
"./src/Contracts/DataModels.ts",
|
"./src/Contracts/DataModels.ts",
|
||||||
@@ -60,6 +62,8 @@
|
|||||||
"./src/GitHub/GitHubConnector.ts",
|
"./src/GitHub/GitHubConnector.ts",
|
||||||
"./src/Index.ts",
|
"./src/Index.ts",
|
||||||
"./src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts",
|
"./src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts",
|
||||||
|
"./src/Platform/Hosted/Helpers/ConnectionStringParser.ts",
|
||||||
|
"./src/Platform/Hosted/HostedUtils.ts",
|
||||||
"./src/ReactDevTools.ts",
|
"./src/ReactDevTools.ts",
|
||||||
"./src/ResourceProvider/IResourceProviderClient.ts",
|
"./src/ResourceProvider/IResourceProviderClient.ts",
|
||||||
"./src/Shared/Constants.ts",
|
"./src/Shared/Constants.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user