Compare commits

..

14 Commits

Author SHA1 Message Date
Chris-MS-896
34f53709c8 Merge branch 'v-yiqcao/addHostedFile' of https://github.com/Azure/cosmos-explorer into v-yiqcao/addHostedFile 2021-01-11 11:08:34 -06:00
Chris-MS-896
ed7a8dafcf "minor changess" 2021-01-11 11:08:23 -06:00
Chris-MS-896
2e83adc7db Merge branch 'master' into v-yiqcao/addHostedFile 2021-01-08 10:27:51 -06:00
Laurent Nguyen
6e619175c6 Fix missing scrollbar in left pane when too many collections/notebooks (#375)
Constrain left pane container to height: 100% so that scrollbar show up when content wants to overflow.
The `main` classname seems too generic, but I left it alone (so I don't break anything), since this part will eventually be ported to React.
2021-01-08 14:00:26 +00:00
victor-meng
08e8bf4bcf Fix two settings tab issues (#374) 2021-01-07 15:38:13 -06:00
Chris-MS-896
bbddeddbc7 Merge branch 'master' into v-yiqcao/addHostedFile 2021-01-06 19:49:56 -06:00
Chris-MS-896
89dc0f394b Add Spliter file to Master (#358) 2021-01-06 12:51:42 -06:00
Chris-MS-896
e28b6cd44a no message 2021-01-05 21:26:42 -06:00
Chris-MS-896
30e0001b7f no message (#359) 2021-01-05 16:45:13 -06:00
Steve Faulkner
4a8f408112 Add UX for Mongo indexing experiment (#368)
Co-authored-by: Tim Sander <tisande@microsoft.com>
2021-01-05 16:04:55 -06:00
Armando Trejo Oliver
e801364800 Remove stale .main class from tree.less (#362)
.main CSS class has a naming conflict with Moncao editor CSS classes and this is causing  A11y issues with Moncao editor.

This class should no longer be used since we moved to the new tree component in REACT, so I am removing it. From my testing, this is not affecting anything.

If we find any styling issue later, we should fix without adding back this class.
2021-01-05 10:53:55 -08:00
victor-meng
a55f2d0de9 Free tier improvements in DE (#348)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-01-04 12:56:55 -08:00
Steve Faulkner
d40b1aa9b5 Remove Empty Query Logging (#361) 2021-01-04 13:58:01 -06:00
Steve Faulkner
cc63cdc1fd Remove dependency on canvas (#354) 2020-12-26 21:56:37 -06:00
41 changed files with 2653 additions and 2463 deletions

7
canvas/README.md Normal file
View 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
View File

@@ -0,0 +1 @@
module.exports = {}

11
canvas/package.json Normal file
View 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"
}

View File

@@ -3,8 +3,8 @@
/******************************************************************************/
@font-face {
font-family: wf_segoe-ui_normal;
src: url('../../fonts/segoe-ui/west-european/normal/latest.woff');
font-family: wf_segoe-ui_normal;
src: url("../../fonts/segoe-ui/west-european/normal/latest.woff");
}
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@@ -20,26 +20,26 @@
COLORS
/******************************************************************************/
@AccentMediumHigh: #0058AD;
@AccentMedium: #004E87;
@AccentHigh: #1EBAED;
@AccentExtraHigh: #55B3FF;
@AccentLow: #EDF6FF;
@AccentMediumLow: #DDEEFE;
@AccentLight: #EEF7FF;
@AccentExtra: #DDF0FF;
@AccentMediumHigh: #0058ad;
@AccentMedium: #004e87;
@AccentHigh: #1ebaed;
@AccentExtraHigh: #55b3ff;
@AccentLow: #edf6ff;
@AccentMediumLow: #ddeefe;
@AccentLight: #eef7ff;
@AccentExtra: #ddf0ff;
@SelectionHigh: #B91F26;
@BaseLight: #FFFFFF;
@SelectionHigh: #b91f26;
@BaseLight: #ffffff;
@BaseDark: #000000;
@NotificationLow: #FFF4CE;
@NotificationHigh: #F9E9B0;
@Purple1: #8A2DA5;
@NotificationLow: #fff4ce;
@NotificationHigh: #f9e9b0;
@Purple1: #8a2da5;
@Dirty: #9b4f96;
@BaseLow: #F2F2F2;
@BaseMediumLow: #E6E6E6;
@BaseMedium: #CCCCCC;
@BaseLow: #f2f2f2;
@BaseMediumLow: #e6e6e6;
@BaseMedium: #cccccc;
@BaseMediumHigh: #767676;
@BaseHigh: #393939;
@@ -53,7 +53,7 @@
@ErrorColor: @SelectionHigh;
@SelectionColor: #3074B0;
@SelectionColor: #3074b0;
@FocusColor: #605e5c;
@@ -80,7 +80,7 @@
@ImgWidth: 14px;
@ImgHeight: 14px;
@toggleFontWeight:700;
@toggleFontWeight: 700;
//Resource Tree
@TreeLineHeight: 17px;
@@ -144,16 +144,16 @@
/**********************************************************************************/
.flex-display(@display: flex) {
display: ~"-webkit-@{display}";
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox
display: ~"-ms-@{display}"; // IE11
display: @display;
display: ~"-webkit-@{display}";
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox
display: ~"-ms-@{display}"; // IE11
display: @display;
}
.flex-direction(@direction: column) {
-webkit-flex-direction: @direction;
-ms-flex-direction: @direction;
flex-direction: @direction;
-ms-flex-direction: @direction;
flex-direction: @direction;
}
/*************************************************************************************
@@ -161,32 +161,31 @@
**************************************************************************************/
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.selectedRadio,
.selectedRadio:hover,
.selectedRadio:active,
.selectedRadio.dirty,
.tab [type=radio]:checked ~ label,
.tab [type=radio]:checked ~ label:hover {
-ms-high-contrast-adjust: none;
-webkit-text-fill-color: HighlightText;
color: HighlightText;
border-color: HighlightText;
background-color: Highlight;
}
.queryMetricsSummaryTuple {
th, td {
&:nth-child(2) {
width: @IETableDataWidth;
}
&:nth-child(3) {
width: 50%;
}
}
.selectedRadio,
.selectedRadio:hover,
.selectedRadio:active,
.selectedRadio.dirty,
.tab [type="radio"]:checked ~ label,
.tab [type="radio"]:checked ~ label:hover {
-ms-high-contrast-adjust: none;
-webkit-text-fill-color: HighlightText;
color: HighlightText;
border-color: HighlightText;
background-color: Highlight;
}
.queryMetricsSummaryTuple {
th,
td {
&:nth-child(2) {
width: @IETableDataWidth;
}
&:nth-child(3) {
width: 50%;
}
}
}
}
/********************************************************************************************
@@ -194,15 +193,15 @@
*********************************************************************************************/
.hover() {
background-color: @AccentLight;
background-color: @AccentLight;
}
.active() {
background-color: @AccentExtra;
background-color: @AccentExtra;
}
.focus() {
outline: 1px dashed @FocusColor;
outline: 1px dashed @FocusColor;
}
/************************************************************************************************
@@ -212,63 +211,87 @@
@ToggleWidth: 180px;
.toggleSwitch() {
max-width: 100%;
margin-bottom: @SmallSpace;
padding: @SmallSpace;
cursor: pointer;
color: @BaseHigh;
font-weight: 400;
font-size: @mediumFontSize;
font-family: @DataExplorerFont;
max-width: 100%;
margin-bottom: @SmallSpace;
padding: @SmallSpace;
cursor: pointer;
color: @BaseHigh;
font-weight: 400;
font-size: @mediumFontSize;
font-family: @DataExplorerFont;
}
.selectedToggle() {
border-bottom: 2px solid @BaseHigh;
border-bottom: 2px solid @BaseHigh;
}
.unselectedToggle() {
color: @AccentMediumHigh;
color: @AccentMediumHigh;
}
/********************************************************************************************************
Common Data Explorer Icons
*********************************************************************************************************/
.dataExplorerIcons() {
cursor: pointer;
width: @ImgWidth;
height: @ImgHeight;
cursor: pointer;
width: @ImgWidth;
height: @ImgHeight;
}
/*********************************************************************************************************
Info Tooltip
**********************************************************************************************************/
.infoTooltip() {
position: relative;
display: inline-block;
position: relative;
display: inline-block;
}
.tooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
visibility: hidden;
background-color: @backgroundColor;
color: @textColor;
position: absolute;
z-index: 1;
left: @MediumSpace;
padding: @MediumSpace;
visibility: hidden;
background-color: @backgroundColor;
color: @textColor;
position: absolute;
z-index: 1;
left: @MediumSpace;
padding: @MediumSpace;
}
.tooltipTextAfter(@color: @BaseDark) {
content: "";
position: absolute;
right: 100%;
border-style: solid;
border-color: transparent @color transparent transparent;
left: 0px;
width: 0;
height: 0;
border-color: @InfoPointerColor transparent;
content: "";
position: absolute;
right: 100%;
border-style: solid;
border-color: transparent @color transparent transparent;
left: 0px;
width: 0;
height: 0;
border-color: @InfoPointerColor transparent;
}
.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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,12 @@
@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 {
height: 100%;
flex: 0 0 auto;
.main {
height: 100%;
}
}
.resourceTreeScroll {

220
package-lock.json generated
View File

@@ -5393,11 +5393,6 @@
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -5641,6 +5636,7 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@@ -6883,14 +6879,7 @@
"dev": true
},
"canvas": {
"version": "2.6.1",
"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"
}
"version": "file:canvas"
},
"capture-exit": {
"version": "2.0.0",
@@ -7454,7 +7443,8 @@
"console-control-strings": {
"version": "1.1.0",
"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": {
"version": "1.0.0",
@@ -8435,6 +8425,7 @@
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"optional": true,
"requires": {
"mimic-response": "^2.0.0"
}
@@ -8460,7 +8451,8 @@
"deep-extend": {
"version": "0.6.0",
"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": {
"version": "0.1.3",
@@ -8652,7 +8644,8 @@
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"optional": true
},
"depd": {
"version": "1.1.2",
@@ -8688,7 +8681,8 @@
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"optional": true
},
"detect-newline": {
"version": "2.1.0",
@@ -10674,14 +10668,6 @@
"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": {
"version": "4.1.14",
"resolved": "https://registry.npmjs.org/fs-observable/-/fs-observable-4.1.14.tgz",
@@ -10823,6 +10809,7 @@
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@@ -10837,12 +10824,14 @@
"ansi-regex": {
"version": "2.1.1",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -10851,6 +10840,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -10861,6 +10851,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -11350,7 +11341,8 @@
"has-unicode": {
"version": "2.0.1",
"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": {
"version": "1.0.0",
@@ -11832,14 +11824,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"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": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -15544,7 +15528,8 @@
"mimic-response": {
"version": "2.1.0",
"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": {
"version": "2.19.0",
@@ -15601,15 +15586,6 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"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": {
"version": "1.0.2",
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -15861,7 +15829,8 @@
"nan": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"optional": true
},
"nanomatch": {
"version": "1.2.13",
@@ -15911,26 +15880,6 @@
"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": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
@@ -16099,41 +16048,6 @@
"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": {
"version": "1.1.66",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz",
@@ -16146,15 +16060,6 @@
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=",
"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": {
"version": "2.5.0",
"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",
"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": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -16214,6 +16096,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -16605,7 +16488,8 @@
"os-homedir": {
"version": "1.0.2",
"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": {
"version": "1.4.0",
@@ -16624,20 +16508,6 @@
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@@ -17690,6 +17560,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -19116,12 +18987,14 @@
"simple-concat": {
"version": "1.0.1",
"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": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"optional": true,
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
@@ -20113,30 +19986,6 @@
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
"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": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
@@ -22192,6 +22041,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"optional": true,
"requires": {
"string-width": "^1.0.2 || 2"
},
@@ -22199,12 +22049,14 @@
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"optional": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"optional": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
@@ -22214,6 +22066,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"optional": true,
"requires": {
"ansi-regex": "^3.0.0"
}
@@ -22383,7 +22236,8 @@
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"yargs": {
"version": "13.3.2",

View File

@@ -44,7 +44,7 @@
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.4.1",
"canvas": "2.6.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2",

View File

@@ -132,6 +132,7 @@ export class Features {
export class Flights {
public static readonly SettingsV2 = "settingsv2";
public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing";
}
export class AfecFeatures {

View File

@@ -23,10 +23,10 @@ export class Splitter {
public splitterId: string;
public leftSideId: string;
public splitter: HTMLElement;
public leftSide: HTMLElement;
public lastX: number;
public lastWidth: number;
public splitter!: HTMLElement;
public leftSide!: HTMLElement;
public lastX!: number;
public lastWidth!: number;
private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds;
@@ -42,9 +42,10 @@ export class Splitter {
}
public initialize() {
this.splitter = document.getElementById(this.splitterId);
this.leftSide = document.getElementById(this.leftSideId);
if (document.getElementById(this.splitterId) !== null && document.getElementById(this.leftSideId) != null) {
this.splitter = <HTMLElement>document.getElementById(this.splitterId);
this.leftSide = <HTMLElement>document.getElementById(this.leftSideId);
}
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
const splitterOptions: JQueryUI.ResizableOptions = {
animate: true,

View File

@@ -138,8 +138,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
// Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer =
!this.collection.partitionKey ||
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
this.container.isPreferredApiMongoDB() &&
(!this.collection.partitionKey || this.collection.partitionKey.systemKey);
this.state = {
throughput: undefined,

View File

@@ -66,7 +66,7 @@ export interface PriceBreakdown {
currencySign: string;
}
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: {
@@ -166,7 +166,10 @@ 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";

View File

@@ -8,7 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}

View File

@@ -16,7 +16,7 @@ import {
} from "../SettingsRenderUtils";
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
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";
export interface ScaleComponentProps {
@@ -176,6 +176,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
label={this.getThroughputTitle()}
isEmulator={this.isEmulator}
isFixed={this.props.isFixedContainer}
isFreeTierAccount={this.isFreeTierAccount()}
isAutoPilotSelected={this.props.isAutoPilotSelected}
onAutoPilotSelected={this.props.onAutoPilotSelected}
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 {
return (
<Stack {...subComponentStackProps}>
{this.isFreeTierAccount() && (
<MessageBar
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
styles={{ text: { fontSize: 14 } }}
>
{this.getFreeTierInfoMessage()}
</MessageBar>
)}
{this.getInitialNotificationElement() && (
<MessageBar messageBarType={MessageBarType.warning}>{this.getInitialNotificationElement()}</MessageBar>
)}

View File

@@ -13,16 +13,7 @@ import {
} from "../SettingsUtils";
import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
Label,
Text,
TextField,
Stack,
IChoiceGroupOption,
ChoiceGroup,
MessageBar,
MessageBarType
} from "office-ui-fabric-react";
import { Label, Text, TextField, Stack, IChoiceGroupOption, ChoiceGroup, MessageBar } from "office-ui-fabric-react";
import {
getTextFieldStyles,
changeFeedPolicyToolTip,
@@ -190,7 +181,10 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
/>
{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}
</MessageBar>
)}

View File

@@ -26,6 +26,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
spendAckVisible: false,
showAsMandatory: true,
isFixed: false,
isFreeTierAccount: false,
label: "label",
infoBubbleText: "infoBubbleText",
canExceedMaximumValue: true,

View File

@@ -28,7 +28,6 @@ import {
Label,
Link,
MessageBar,
MessageBarType,
FontIcon,
IColumn
} from "office-ui-fabric-react";
@@ -58,6 +57,7 @@ export interface ThroughputInputAutoPilotV3Props {
spendAckVisible?: boolean;
showAsMandatory?: boolean;
isFixed: boolean;
isFreeTierAccount: boolean;
isEmulator: boolean;
label: string;
infoBubbleText?: string;
@@ -76,6 +76,7 @@ export interface ThroughputInputAutoPilotV3Props {
interface ThroughputInputAutoPilotV3State {
spendAckChecked: boolean;
exceedFreeTierThroughput: boolean;
}
export class ThroughputInputAutoPilotV3Component extends React.Component<
@@ -149,7 +150,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
public constructor(props: ThroughputInputAutoPilotV3Props) {
super(props);
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;
@@ -424,7 +427,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
const newThroughput = getSanitizedInputValue(newValue);
this.props.onMaxAutoPilotThroughputChange(newThroughput);
};
@@ -432,10 +435,11 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
const newThroughput = getSanitizedInputValue(newValue);
if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else {
this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
this.props.onThroughputChange(newThroughput);
}
};
@@ -479,7 +483,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
/>
</Label>
{this.overrideWithProvisionedThroughputSettings() && (
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
<MessageBar
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
styles={messageBarStyles}
>
{manualToAutoscaleDisclaimerElement}
</MessageBar>
)}
@@ -556,8 +563,21 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}
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() && (
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
<MessageBar
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
styles={messageBarStyles}
>
{this.props.getThroughputWarningMessage()}
</MessageBar>
)}
@@ -583,7 +603,15 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
warningMessage = saveThroughputWarningMessage;
}
return <>{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}</>;
return (
<>
{warningMessage && (
<MessageBar messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}>
{warningMessage}
</MessageBar>
)}
</>
);
};
public render(): JSX.Element {

View File

@@ -9,13 +9,18 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
}
>
<StyledMessageBarBase
messageBarType={5}
messageBarIconProps={
Object {
"className": "messageBarWarningIcon",
"iconName": "WarningSolid",
}
}
>
<Text
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -34,7 +39,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -45,12 +50,21 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
/>
</StyledLabelBase>
<StyledMessageBarBase
messageBarType={5}
messageBarIconProps={
Object {
"className": "messageBarInfoIcon",
"iconName": "InfoSolid",
}
}
styles={
Object {
"root": Object {
"backgroundColor": "white",
"marginTop": "5px",
},
"text": Object {
"fontSize": 14,
},
}
}
>
@@ -59,7 +73,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -171,7 +185,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -444,7 +458,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}

View File

@@ -16,7 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}

View File

@@ -136,7 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -412,7 +412,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -952,7 +952,7 @@ exports[`SubSettingsComponent renders 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -1228,7 +1228,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}

View File

@@ -101,13 +101,13 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
return procedureFromBackEnd;
};
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
export const getSanitizedInputValue = (newValueString: string, max?: number): number => {
const newValue = parseInt(newValueString);
if (isNaN(newValue)) {
return zeroValue;
}
// 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 => {

View File

@@ -55,6 +55,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -104,6 +105,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -591,6 +593,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -665,6 +668,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -950,6 +954,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@@ -1175,11 +1180,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@@ -1326,6 +1329,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -1375,6 +1379,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -1862,6 +1867,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -1936,6 +1942,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -2221,6 +2228,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@@ -2446,11 +2454,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@@ -2610,6 +2616,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -2659,6 +2666,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -3146,6 +3154,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -3220,6 +3229,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -3505,6 +3515,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@@ -3730,11 +3741,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@@ -3881,6 +3890,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -3930,6 +3940,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -4417,6 +4428,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function],
"formErrorsDetails": [Function],
"formWarnings": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "addcollectionpane",
"isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function],
@@ -4491,6 +4503,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"freeTierExceedThroughputTooltip": [Function],
"id": "adddatabasepane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
@@ -4776,6 +4789,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@@ -5001,11 +5015,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {

View File

@@ -159,7 +159,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -176,7 +176,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -195,7 +195,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -207,7 +207,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -219,7 +219,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -230,7 +230,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -249,7 +249,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -268,7 +268,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -286,7 +286,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -299,7 +299,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -310,7 +310,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -329,7 +329,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -371,7 +371,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -386,7 +386,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}
@@ -402,7 +402,7 @@ exports[`SettingsUtils functions render 1`] = `
styles={
Object {
"root": Object {
"fontSize": 12,
"fontSize": 14,
},
}
}

View File

@@ -129,6 +129,8 @@ export interface ThroughputInputParams {
showAutoPilot?: ko.Observable<boolean>;
overrideWithAutoPilotSettings: ko.Observable<boolean>;
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
freeTierExceedThroughputTooltip?: ko.Observable<string>;
freeTierExceedThroughputWarning?: ko.Observable<string>;
}
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
@@ -165,6 +167,10 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
public overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
public isManualThroughputInputFieldRequired: 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) {
super();
@@ -219,6 +225,16 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed(
() => 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() {

View File

@@ -132,6 +132,14 @@
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
>
</p>
<div class="inputTooltip">
<span
data-bind="text: freeTierExceedThroughputTooltip, visible: showFreeTierExceedThroughputTooltip"
class="inputTooltipText"
></span>
</div>
<div data-bind="setTemplateReady: true">
<input
data-bind="
@@ -154,6 +162,11 @@
/>
</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">
<span data-bind="html: requestUnitsUsageCost"></span>
</p>

View File

@@ -207,6 +207,7 @@ export default class Explorer {
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>;
public isMongoIndexingEnabled: ko.Observable<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>;
@@ -402,6 +403,7 @@ export default class Explorer {
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
);
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -1896,6 +1898,9 @@ export default class Explorer {
if (!flights) {
return;
}
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
this.isMongoIndexingEnabled(true);
}
}
public findSelectedCollection(): ViewModels.Collection {
@@ -3014,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;
});
}
}

View File

@@ -152,7 +152,8 @@
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFreeTierAccount()
showAutoPilot: !isFreeTierAccount(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}">
</throughput-input-autopilot-v3>
</div>
@@ -333,7 +334,8 @@
maxAutoPilotThroughputSet: autoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFixedStorageSelected()
showAutoPilot: !isFixedStorageSelected(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}">
</throughput-input-autopilot-v3>
</div>

View File

@@ -74,7 +74,7 @@ describe("Add Collection Pane", () => {
explorer.databaseAccount(mockFreeTierDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
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.upsellAnchorText()).toBe("Learn more");
});

View File

@@ -89,9 +89,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
public isSynapseLinkUpdating: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>;
public freeTierExceedThroughputTooltip: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>;
public showUpsellMessage: ko.PureComputed<boolean>;
public shouldCreateMongoWildcardIndex: ko.Observable<boolean>;
public shouldCreateMongoWildcardIndex: ko.Computed<boolean>;
private _isSynapseLinkEnabled: ko.Computed<boolean>;
@@ -99,7 +100,6 @@ export default class AddCollectionPane extends ContextualPaneBase {
super(options);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.formWarnings = ko.observable<string>();
this.collectionId = ko.observable<string>();
this.databaseId = ko.observable<string>();
@@ -481,8 +481,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
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>(() => {
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>(() => {
@@ -534,6 +546,23 @@ export default class AddCollectionPane extends ContextualPaneBase {
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>(() => {
const newDatabaseWithSharedOffer = this.databaseCreateNew() && this.databaseCreateNewShared();
const existingDatabaseWithSharedOffer = !this.databaseCreateNew() && this.databaseHasSharedOffer();
@@ -625,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 {

View File

@@ -114,7 +114,8 @@
maxAutoPilotThroughputSet: maxAutoPilotThroughputSet,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
showAutoPilot: !isFreeTierAccount()
showAutoPilot: !isFreeTierAccount(),
freeTierExceedThroughputTooltip: freeTierExceedThroughputTooltip
}">
</throughput-input-autopilot-v3>
<p data-bind="visible: canRequestSupport">

View File

@@ -77,7 +77,7 @@ describe("Add Database Pane", () => {
explorer.databaseAccount(mockFreeTierDatabaseAccount);
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
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.upsellAnchorText()).toBe("Learn more");
});

View File

@@ -44,6 +44,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
public autoPilotUsageCost: ko.Computed<string>;
public canExceedMaximumValue: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>;
public freeTierExceedThroughputTooltip: ko.Computed<string>;
public isFreeTierAccount: ko.Computed<boolean>;
public canConfigureThroughput: ko.PureComputed<boolean>;
public showUpsellMessage: ko.PureComputed<boolean>;
@@ -54,7 +55,6 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.databaseId = ko.observable<string>();
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
@@ -182,6 +182,18 @@ export default class AddDatabasePane extends ContextualPaneBase {
return isFreeTierAccount;
});
this.showUpsellMessage = ko.pureComputed(() => {
if (this.container.isServerlessEnabled()) {
return false;
}
if (this.isFreeTierAccount()) {
return this.databaseCreateNewShared();
}
return true;
});
this.maxThroughputRUText = ko.pureComputed(() => {
return this.maxThroughputRU().toLocaleString();
});
@@ -219,8 +231,20 @@ export default class AddDatabasePane extends ContextualPaneBase {
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>(() => {
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>(() => {

View File

@@ -265,11 +265,9 @@ export class CassandraAPIDataClient extends TableDataClient {
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestQueryApi
: Constants.CassandraBackend.queryApi;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
method: "POST",
body: JSON.stringify({
const data: any = await $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
type: "POST",
data: {
accountName:
collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
@@ -280,19 +278,11 @@ export class CassandraAPIDataClient extends TableDataClient {
tableId: collection.id(),
query,
paginationToken
}),
headers: {
[authorizationHeader.header]: authorizationHeader.token,
[Constants.HttpHeaders.contentType]: "application/json"
}
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
});
if (!response.ok) {
displayTokenRenewalPromptForStatus(response.status);
throw Error(`Failed to query rows for table ${collection.id()}`);
}
const data = await response.json();
shouldNotify &&
NotificationConsoleUtils.logConsoleInfo(
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
@@ -460,9 +450,9 @@ export class CassandraAPIDataClient extends TableDataClient {
return deferred.promise;
}
public async getTableKeys(collection: ViewModels.Collection): Promise<CassandraTableKeys> {
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!!collection.cassandraKeys) {
return collection.cassandraKeys;
return Q.resolve(collection.cassandraKeys);
}
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
@@ -474,51 +464,45 @@ export class CassandraAPIDataClient extends TableDataClient {
? Constants.CassandraBackend.guestKeysApi
: Constants.CassandraBackend.keysApi;
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const authorizationHeader = getAuthorizationHeader();
try {
const response = await fetch(endpoint, {
method: "POST",
body: JSON.stringify({
accountName:
collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
}),
headers: {
[authorizationHeader.header]: authorizationHeader.token,
[Constants.HttpHeaders.contentType]: "application/json"
const deferred = Q.defer<CassandraTableKeys>();
$.ajax(endpoint, {
type: "POST",
data: {
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
})
.then(
(data: CassandraTableKeys) => {
collection.cassandraKeys = data;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched keys for table ${collection.id()}`
);
deferred.resolve(data);
},
(error: any) => {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(error);
}
)
.done(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
if (!response.ok) {
displayTokenRenewalPromptForStatus(response.status);
throw Error(`Fetching keys for table ${collection.id()} failed`);
}
const data: CassandraTableKeys = await response.json();
collection.cassandraKeys = data;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched keys for table ${collection.id()}`
);
return data;
} catch (error) {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
throw error;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
}
return deferred.promise;
}
public async getTableSchema(collection: ViewModels.Collection): Promise<CassandraTableKey[]> {
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
if (!!collection.cassandraSchema) {
return collection.cassandraSchema;
return Q.resolve(collection.cassandraSchema);
}
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
@@ -530,79 +514,74 @@ export class CassandraAPIDataClient extends TableDataClient {
? Constants.CassandraBackend.guestSchemaApi
: Constants.CassandraBackend.schemaApi;
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const authorizationHeader = getAuthorizationHeader();
try {
const response = await fetch(endpoint, {
method: "POST",
body: JSON.stringify({
accountName:
collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
}),
headers: {
[authorizationHeader.header]: authorizationHeader.token,
[Constants.HttpHeaders.contentType]: "application/json"
const deferred = Q.defer<CassandraTableKey[]>();
$.ajax(endpoint, {
type: "POST",
data: {
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(
collection.container.databaseAccount().properties.cassandraEndpoint
),
resourceId: collection.container.databaseAccount().id,
keyspaceId: collection.databaseId,
tableId: collection.id()
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
})
.then(
(data: any) => {
collection.cassandraSchema = data.columns;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched schema for table ${collection.id()}`
);
deferred.resolve(data.columns);
},
(error: any) => {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(error);
}
)
.done(() => {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
});
if (!response.ok) {
displayTokenRenewalPromptForStatus(response.status);
throw Error(`Failed to fetch schema for table ${collection.id()}`);
}
const data = await response.json();
collection.cassandraSchema = data.columns;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully fetched schema for table ${collection.id()}`
);
return data.columns;
} catch (error) {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
throw error;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
}
return deferred.promise;
}
private async createOrDeleteQuery(
private createOrDeleteQuery(
cassandraEndpoint: string,
resourceId: string,
query: string,
explorer: Explorer
): Promise<void> {
): Q.Promise<any> {
const deferred = Q.defer();
const authType = window.authType;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestCreateOrDeleteApi
: Constants.CassandraBackend.createOrDeleteApi;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
method: "POST",
body: JSON.stringify({
$.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
type: "POST",
data: {
accountName: explorer.databaseAccount() && explorer.databaseAccount().name,
cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint),
resourceId,
query
}),
headers: {
[authorizationHeader.header]: authorizationHeader.token,
[Constants.HttpHeaders.contentType]: "application/json"
resourceId: resourceId,
query: query
},
beforeSend: this.setAuthorizationHeader,
error: this.handleAjaxError,
cache: false
}).then(
(data: any) => {
deferred.resolve();
},
reason => {
deferred.reject(reason);
}
});
if (!response.ok) {
displayTokenRenewalPromptForStatus(response.status);
throw Error(`Failed to create or delete keyspace/table`);
}
);
return deferred.promise;
}
private trimCassandraEndpoint(cassandraEndpoint: string): string {
@@ -621,6 +600,13 @@ export class CassandraAPIDataClient extends TableDataClient {
return cassandraEndpoint;
}
private setAuthorizationHeader: (xhr: XMLHttpRequest) => boolean = (xhr: XMLHttpRequest): boolean => {
const authorizationHeaderMetadata: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
xhr.setRequestHeader(authorizationHeaderMetadata.header, authorizationHeaderMetadata.token);
return true;
};
private isStringType(dataType: string): boolean {
// TODO figure out rest of types that are considered strings by Cassandra (if any have been missed)
return (
@@ -634,4 +620,12 @@ export class CassandraAPIDataClient extends TableDataClient {
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
return collection.cassandraKeys.partitionKeys[0].property;
}
private handleAjaxError = (xhrObj: XMLHttpRequest, textStatus: string, errorThrown: string): void => {
if (!xhrObj) {
return;
}
displayTokenRenewalPromptForStatus(xhrObj.status);
};
}

View File

@@ -23,6 +23,19 @@
<div class="scaleDivison" aria-label="Scale" aria-controls="scaleRegion">
<span class="scaleSettingTitle">Scale</span>
</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">
<throughput-input-autopilot-v3
params="{
@@ -46,7 +59,8 @@
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
overrideWithAutoPilotSettings: overrideWithAutoPilotSettings,
overrideWithProvisionedThroughputSettings: overrideWithProvisionedThroughputSettings
overrideWithProvisionedThroughputSettings: overrideWithProvisionedThroughputSettings,
freeTierExceedThroughputWarning: freeTierExceedThroughputWarning
}"
>
</throughput-input-autopilot-v3>

View File

@@ -57,6 +57,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public canThroughputExceedMaximumValue: ko.Computed<boolean>;
public costsVisible: ko.Computed<boolean>;
public displayedError: ko.Observable<string>;
public isFreeTierAccount: ko.Computed<boolean>;
public isTemplateReady: ko.Observable<boolean>;
public minRUAnotationVisible: ko.Computed<boolean>;
public minRUs: ko.Observable<number>;
@@ -82,6 +83,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public throughputAutoPilotRadioId: string;
public throughputProvisionedRadioId: string;
public throughputModeRadioName: string;
public freeTierExceedThroughputWarning: ko.Computed<string>;
private _hasProvisioningTypeChanged: ko.Computed<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.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();
}

View File

@@ -324,21 +324,6 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
this.showingDocumentsDisplayText(resultsDisplay);
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
if (!this.queryResults() && !results) {
const errorMessage: string = JSON.stringify({
error: `Returned no results after query execution`,
accountName: this.collection && this.collection.container.databaseAccount(),
databaseName: this.collection && this.collection.databaseId,
collectionName: this.collection && this.collection.id(),
sqlQuery: this.sqlStatementToExecute(),
hasMoreResults: resultsMetadata.hasMoreResults,
itemCount: resultsMetadata.itemCount,
responseHeaders: queryResults && queryResults.headers
});
Logger.logError(errorMessage, "QueryTab");
}
this.queryResults(results);
TelemetryProcessor.traceSuccess(

View File

@@ -10,7 +10,6 @@ import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility"
import * as Logger from "../../Common/Logger";
import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export default class AuthHeadersUtil {
public static serverId: string = Constants.ServerIds.productionPortal;
@@ -38,7 +37,8 @@ export default class AuthHeadersUtil {
cacheLocation: window.navigator.userAgent.indexOf("Edge") > -1 ? "localStorage" : undefined
});
public static async getAccessInputMetadata(accessInput: string): Promise<DataModels.AccessInputMetadata> {
public static getAccessInputMetadata(accessInput: string): Q.Promise<DataModels.AccessInputMetadata> {
const deferred: Q.Deferred<DataModels.AccessInputMetadata> = Q.defer<DataModels.AccessInputMetadata>();
const url = `${configContext.BACKEND_ENDPOINT}${Constants.ApiEndpoints.guestRuntimeProxy}/accessinputmetadata`;
const authType: string = (<any>window).authType;
const headers: { [headerName: string]: string } = {};
@@ -49,55 +49,58 @@ export default class AuthHeadersUtil {
headers[Constants.HttpHeaders.connectionString] = accessInput;
}
let responseText: string;
try {
const timeout = setTimeout(() => {
throw Error("Request timed out while fetching access input metadata");
}, Constants.ClientDefaults.requestTimeoutMs);
$.ajax({
url: url,
type: "GET",
headers: headers,
cache: false,
dataType: "text"
}).then(
(data: string, textStatus: string, xhr: JQueryXHR<any>) => {
if (!data) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to get access input metadata`);
deferred.reject(`Failed to get access input metadata`);
}
const response = await fetch(url, {
headers,
method: "GET"
});
clearTimeout(timeout);
responseText = await response.text();
if (!response.ok || !responseText) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to get access input metadata`);
throw Error("Failed to get access input metadata");
try {
const metadata: DataModels.AccessInputMetadata = JSON.parse(JSON.parse(data));
deferred.resolve(metadata); // TODO: update to a single JSON parse once backend response is stringified exactly once
} catch (error) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, "Failed to parse access input metadata");
deferred.reject("Failed to parse access input metadata");
throw error;
}
},
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while fetching access input metadata: ${JSON.stringify(xhr.responseText)}`
);
deferred.reject(xhr.responseText);
}
} catch (error) {
const errorMessage: string = getErrorMessage(error);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error while fetching access input metadata: ${errorMessage}`
);
throw error;
}
);
try {
const metadata: DataModels.AccessInputMetadata = JSON.parse(JSON.parse(responseText));
return metadata; // TODO: update to a single JSON parse once backend response is stringified exactly once
} catch (error) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, "Failed to parse access input metadata");
throw error;
}
return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
}
public static async generateEncryptedToken(): Promise<DataModels.GenerateTokenResponse> {
public static generateEncryptedToken(): Q.Promise<DataModels.GenerateTokenResponse> {
const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + AuthHeadersUtil._generateResourceUrl();
const explorer = window.dataExplorer;
const headers: any = { authorization: userContext.authorizationToken };
headers[Constants.HttpHeaders.getReadOnlyKey] = !explorer.hasWriteAccess();
headers[Constants.HttpHeaders.contentType] = "application/json";
return await AuthHeadersUtil._initiateGenerateTokenRequest(url, "POST", headers);
return AuthHeadersUtil._initiateGenerateTokenRequest({
url: url,
type: "POST",
headers: headers,
contentType: "application/json",
cache: false
});
}
public static async generateUnauthenticatedEncryptedTokenForConnectionString(
public static generateUnauthenticatedEncryptedTokenForConnectionString(
connectionString: string
): Promise<DataModels.GenerateTokenResponse> {
): Q.Promise<DataModels.GenerateTokenResponse> {
if (!connectionString) {
return Q.reject("None or empty connection string specified");
}
@@ -105,9 +108,14 @@ export default class AuthHeadersUtil {
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
const headers: any = {};
headers[Constants.HttpHeaders.connectionString] = connectionString;
headers[Constants.HttpHeaders.contentType] = "application/json";
return await AuthHeadersUtil._initiateGenerateTokenRequest(url, "POST", headers);
return AuthHeadersUtil._initiateGenerateTokenRequest({
url: url,
type: "POST",
headers: headers,
contentType: "application/json",
cache: false
});
}
public static isUserSignedIn(): boolean {
@@ -274,27 +282,24 @@ export default class AuthHeadersUtil {
return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`;
}
private static async _initiateGenerateTokenRequest(
url: string,
method: string,
headers: any
): Promise<DataModels.GenerateTokenResponse> {
const timeout = setTimeout(() => {
throw Error("Request timed out while generating token");
}, Constants.ClientDefaults.requestTimeoutMs);
private static _initiateGenerateTokenRequest(
requestSettings: JQueryAjaxSettings<any>
): Q.Promise<DataModels.GenerateTokenResponse> {
const deferred: Q.Deferred<DataModels.GenerateTokenResponse> = Q.defer<DataModels.GenerateTokenResponse>();
const response = await fetch(url, {
headers,
method
});
$.ajax(requestSettings).then(
(data: string, textStatus: string, xhr: JQueryXHR<any>) => {
if (!data) {
deferred.reject("No token generated");
}
clearTimeout(timeout);
deferred.resolve(JSON.parse(data));
},
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
deferred.reject(xhr.responseText);
}
);
const token: string = await response.json();
if (response.ok && token) {
return JSON.parse(token);
}
throw Error("No token generated");
return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
}
}

View File

@@ -2,33 +2,42 @@ import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
export class ConnectionStringParser {
public static parseConnectionString(connectionString: string): DataModels.AccessInputMetadata {
public static parseConnectionString(connectionString: string): DataModels.AccessInputMetadata | undefined {
if (!!connectionString) {
try {
const accessInput: DataModels.AccessInputMetadata = {} as DataModels.AccessInputMetadata;
const connectionStringParts = connectionString.split(";");
connectionStringParts.forEach((connectionStringPart: string) => {
if (RegExp(Constants.EndpointsRegex.sql).test(connectionStringPart)) {
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.sql)[1];
const sqlMatchResult = connectionStringPart.match(Constants.EndpointsRegex.sql);
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;
} else if (RegExp(Constants.EndpointsRegex.mongo).test(connectionStringPart)) {
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongo);
accessInput.accountName = matches && matches.length > 1 && matches[2];
} else if (mongoMatchResult && mongoMatchResult.length > 2) {
accessInput.accountName = mongoMatchResult[2];
accessInput.apiKind = DataModels.ApiKind.MongoDB;
} else if (RegExp(Constants.EndpointsRegex.mongoCompute).test(connectionStringPart)) {
const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongoCompute);
accessInput.accountName = matches && matches.length > 1 && matches[2];
} else if (mongoComputeMatchResult && mongoComputeMatchResult.length > 2) {
accessInput.accountName = mongoComputeMatchResult[2];
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 => {
if (RegExp(regex).test(connectionStringPart)) {
accessInput.accountName = connectionStringPart.match(regex)[1];
accessInput.apiKind = DataModels.ApiKind.Cassandra;
const connectionMatch = connectionStringPart.match(regex);
if (connectionMatch && connectionMatch.length > 1) {
accessInput.accountName = connectionMatch[1];
accessInput.apiKind = DataModels.ApiKind.Cassandra;
}
}
});
} else if (RegExp(Constants.EndpointsRegex.table).test(connectionStringPart)) {
accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.table)[1];
} else if (tableMatchResult && tableMatchResult.length > 1) {
accessInput.accountName = tableMatchResult[1];
accessInput.apiKind = DataModels.ApiKind.Table;
} else if (connectionStringPart.indexOf("ApiKind=Gremlin") >= 0) {
accessInput.apiKind = DataModels.ApiKind.Graph;

View File

@@ -4,7 +4,7 @@ import * as DataModels from "../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
export class DefaultExperienceUtility {
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string {
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string | null {
if (!databaseAccount) {
return null;
}
@@ -81,11 +81,9 @@ export class DefaultExperienceUtility {
private static _getDefaultExperience(kind: string, capabilities: DataModels.Capability[]): string {
const defaultDefaultExperience: string = Constants.DefaultAccountExperience.DocumentDB;
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind);
const defaultExperienceFromCapabilities: string = DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(
capabilities
);
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind) || "";
const defaultExperienceFromCapabilities: string =
DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(capabilities) || "";
if (!!defaultExperienceFromKind) {
return defaultExperienceFromKind;
}
@@ -97,7 +95,7 @@ export class DefaultExperienceUtility {
return defaultDefaultExperience;
}
private static _getDefaultExperienceFromAccountKind(kind: string): string {
private static _getDefaultExperienceFromAccountKind(kind: string): string | null {
if (!kind) {
return null;
}
@@ -113,7 +111,7 @@ export class DefaultExperienceUtility {
return null;
}
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string {
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string | null {
if (!capabilities) {
return null;
}

View File

@@ -1,5 +1,6 @@
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
import * as Constants from "../Shared/Constants";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
interface ComputeRUUsagePriceHourlyArgs {
serverId: string;
@@ -256,9 +257,19 @@ export function getEstimatedSpendAcknowledgeString(
)} - ${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) {
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 {
let price: number = Constants.OfferPricing.MonthlyPricing.default.Standard.StartingPrice;
@@ -269,3 +280,19 @@ export function getUpsellMessage(serverId = "default", isFreeTier = false): stri
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");
}
}

View File

@@ -12,7 +12,8 @@
"./src/Bindings/ReactBindingHandler.ts",
"./src/Common/ArrayHashMap.ts",
"./src/Common/Constants.ts",
"./src/Common/DeleteFeedback.ts",
"./src/Common/DeleteFeedback.ts",
"./src/Common/DocumentUtility.ts",
"./src/Common/EnvironmentUtility.ts",
"./src/Common/HashMap.ts",
"./src/Common/HeadersUtility.ts",
@@ -21,7 +22,8 @@
"./src/Common/MongoUtility.ts",
"./src/Common/ObjectCache.ts",
"./src/Common/ThemeUtility.ts",
"./src/Common/UrlUtility.ts",
"./src/Common/UrlUtility.ts",
"./src/Common/Splitter.ts",
"./src/ConfigContext.ts",
"./src/Contracts/ActionContracts.ts",
"./src/Contracts/DataModels.ts",
@@ -60,6 +62,8 @@
"./src/GitHub/GitHubConnector.ts",
"./src/Index.ts",
"./src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts",
"./src/Platform/Hosted/Helpers/ConnectionStringParser.ts",
"./src/Platform/Hosted/HostedUtils.ts",
"./src/ReactDevTools.ts",
"./src/ResourceProvider/IResourceProviderClient.ts",
"./src/Shared/Constants.ts",