Compare commits

..

9 Commits

Author SHA1 Message Date
Steve Faulkner
4be1389705 Upload on failure 2020-12-17 17:01:28 -06:00
Steve Faulkner
65844414dd Test 2020-12-16 20:18:59 -06:00
Steve Faulkner
b9461de695 Test 2020-12-16 20:18:53 -06:00
Steve Faulkner
0473c49cc6 Merge branch 'e2e-test-debugging' of https://github.com/Azure/cosmos-explorer into e2e-test-debugging 2020-12-16 20:18:39 -06:00
Steve Faulkner
53ea8dc528 Test 2020-12-16 20:18:31 -06:00
Steve Faulkner
8b7d43823a Merge branch 'master' into e2e-test-debugging 2020-12-16 20:01:37 -06:00
Steve Faulkner
3ab7e93bab Test 2020-12-16 19:56:40 -06:00
Steve Faulkner
9e2efa01e5 Test 2020-12-16 19:37:55 -06:00
Steve Faulkner
ba5ab37bac Debugging failed tests 2020-12-16 19:22:47 -06:00
24 changed files with 283 additions and 1051 deletions

View File

@@ -167,7 +167,7 @@ jobs:
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -191,7 +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, accessibility] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -213,28 +213,3 @@ 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"

Binary file not shown.

47
package-lock.json generated
View File

@@ -9231,53 +9231,6 @@
"through": "~2.3.6" "through": "~2.3.6"
} }
}, },
"esbuild-loader": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.7.0.tgz",
"integrity": "sha512-1v7PVHZ+GvHPlTYVKjNDblUZbzx58Iwt0LJrBI1INzi4/UHz9D3IJ/skNOimaypHDTohj6HtS3ExWJX2xCRzWg==",
"dev": true,
"requires": {
"esbuild": "^0.8.17",
"loader-utils": "^2.0.0",
"type-fest": "^0.20.2",
"webpack-sources": "^2.2.0"
},
"dependencies": {
"esbuild": {
"version": "0.8.26",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.26.tgz",
"integrity": "sha512-u3MMHOOumdWoAKF+073GHPpzvVB2cM+y9VD4ZwYs1FAQ6atRPISya35dbrbOu/mM68mQ42P+nwPzQVBTfQhkvQ==",
"dev": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
"webpack-sources": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz",
"integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==",
"dev": true,
"requires": {
"source-list-map": "^2.0.1",
"source-map": "^0.6.1"
}
}
}
},
"escalade": { "escalade": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",

View File

@@ -6,8 +6,8 @@
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.1.0", "@azure/identity": "1.1.0",
"@azure/cosmos-language-service": "0.0.5",
"@jupyterlab/services": "6.0.0-rc.2", "@jupyterlab/services": "6.0.0-rc.2",
"@jupyterlab/terminal": "3.0.0-rc.2", "@jupyterlab/terminal": "3.0.0-rc.2",
"@microsoft/applicationinsights-web": "2.5.9", "@microsoft/applicationinsights-web": "2.5.9",
@@ -139,7 +139,6 @@
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5", "enzyme-adapter-react-16": "1.15.5",
"enzyme-to-json": "3.6.1", "enzyme-to-json": "3.6.1",
"esbuild-loader": "2.7.0",
"eslint": "7.8.1", "eslint": "7.8.1",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",

View File

@@ -362,7 +362,7 @@ export enum CollectionTabKind {
Gallery = 17, Gallery = 17,
NotebookViewer = 18, NotebookViewer = 18,
Schema = 19, Schema = 19,
SettingsV2 = 20 SettingsV2 = 19
} }
export enum TerminalKind { export enum TerminalKind {

View File

@@ -1,9 +1,9 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { IColumn, Text } from "office-ui-fabric-react";
import { import {
getAutoPilotV3SpendElement, getAutoPilotV3SpendElement,
getEstimatedSpendingElement, getEstimatedSpendElement,
getEstimatedAutoscaleSpendElement,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement,
ttlWarning, ttlWarning,
indexingPolicynUnsavedWarningMessage, indexingPolicynUnsavedWarningMessage,
@@ -19,37 +19,11 @@ 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)}
@@ -57,7 +31,9 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{getAutoPilotV3SpendElement(1000, true)} {getAutoPilotV3SpendElement(1000, true)}
{getAutoPilotV3SpendElement(undefined, true)} {getAutoPilotV3SpendElement(undefined, true)}
{getEstimatedSpendingElement(estimatedSpendingColumns, estimatedSpendingItems, 1000, 2, priceBreakdown, false)} {getEstimatedSpendElement(1000, "mooncake", 2, false)}
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
{manualToAutoscaleDisclaimerElement} {manualToAutoscaleDisclaimerElement}
{ttlWarning} {ttlWarning}
@@ -93,14 +69,4 @@ describe("SettingsUtils functions", () => {
const wrapper = shallow(<SettingsRenderUtilsTestComponent />); const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("should return correct price breakdown for a manual RU setting of 500, 1 region, multimaster disabled", () => {
const prices = getRuPriceBreakdown(500, "", 1, false, false);
expect(prices.hourlyPrice).toBe(0.04);
expect(prices.dailyPrice).toBe(0.96);
expect(prices.monthlyPrice).toBe(29.2);
expect(prices.pricePerRu).toBe(0.00008);
expect(prices.currency).toBe("USD");
expect(prices.currencySign).toBe("$");
});
}); });

View File

@@ -3,13 +3,14 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants"; import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
import { Urls, StyleConstants } from "../../../Common/Constants"; import { Urls, StyleConstants } from "../../../Common/Constants";
import { import {
computeAutoscaleUsagePriceHourly,
getPriceCurrency, getPriceCurrency,
getCurrencySign, getCurrencySign,
getAutoscalePricePerRu, getAutoscalePricePerRu,
getMultimasterMultiplier, getMultimasterMultiplier,
computeRUUsagePriceHourly, computeRUUsagePriceHourly,
getPricePerRu, getPricePerRu,
estimatedCostDisclaimer calculateEstimateNumber
} from "../../../Utils/PricingUtils"; } from "../../../Utils/PricingUtils";
import { import {
ITextFieldStyles, ITextFieldStyles,
@@ -31,41 +32,10 @@ import {
MessageBarType, MessageBarType,
Stack, Stack,
Spinner, Spinner,
SpinnerSize, SpinnerSize
DetailsList,
IColumn,
SelectionMode,
DetailsListLayoutMode,
IDetailsRowProps,
DetailsRow,
IDetailsColumnStyles
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { isDirtyTypes, isDirty } from "./SettingsUtils"; import { isDirtyTypes, isDirty } from "./SettingsUtils";
export interface EstimatedSpendingDisplayProps {
costType: JSX.Element;
}
export interface ManualEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
hourly: JSX.Element;
daily: JSX.Element;
monthly: JSX.Element;
}
export interface AutoscaleEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
minPerMonth: JSX.Element;
maxPerMonth: JSX.Element;
}
export interface PriceBreakdown {
hourlyPrice: number;
dailyPrice: number;
monthlyPrice: number;
pricePerRu: number;
currency: string;
currencySign: string;
}
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
@@ -134,16 +104,6 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
} }
}; };
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
root: {
selectors: {
":hover": {
background: "transparent"
}
}
}
};
export const customDetailsListStyles: Partial<IDetailsListStyles> = { export const customDetailsListStyles: Partial<IDetailsListStyles> = {
root: { root: {
selectors: { selectors: {
@@ -170,10 +130,6 @@ export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop:
export const throughputUnit = "RU/s"; export const throughputUnit = "RU/s";
export function onRenderRow(props: IDetailsRowProps): JSX.Element {
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
}
export const getAutoPilotV3SpendElement = ( export const getAutoPilotV3SpendElement = (
maxAutoPilotThroughputSet: number, maxAutoPilotThroughputSet: number,
isDatabaseThroughput: boolean, isDatabaseThroughput: boolean,
@@ -209,61 +165,63 @@ export const getAutoPilotV3SpendElement = (
); );
}; };
export const getRuPriceBreakdown = ( export const getEstimatedAutoscaleSpendElement = (
throughput: number, throughput: number,
serverId: string, serverId: string,
numberOfRegions: number, regions: number,
isMultimaster: boolean, multimaster: boolean
isAutoscale: boolean ): JSX.Element => {
): PriceBreakdown => { const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
const hourlyPrice: number = computeRUUsagePriceHourly({ const monthlyPrice: number = hourlyPrice * hoursInAMonth;
serverId: serverId, const currency: string = getPriceCurrency(serverId);
requestUnits: throughput, const currencySign: string = getCurrencySign(serverId);
numberOfRegions: numberOfRegions, const pricePerRu =
multimasterEnabled: isMultimaster, getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
isAutoscale: isAutoscale getMultimasterMultiplier(regions, multimaster);
});
const basePricePerRu: number = isAutoscale return (
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster)) <Text id="autoscaleSpendElement">
: getPricePerRu(serverId); Estimated monthly cost ({currency}) is{" "}
return { <b>
hourlyPrice: hourlyPrice, {currencySign}
dailyPrice: hourlyPrice * 24, {calculateEstimateNumber(monthlyPrice / 10)}
monthlyPrice: hourlyPrice * hoursInAMonth, {` - `}
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster), {currencySign}
currency: getPriceCurrency(serverId), {calculateEstimateNumber(monthlyPrice)}{" "}
currencySign: getCurrencySign(serverId) </b>
}; ({"regions: "} {regions}, {throughput / 10} - {throughput} RU/s, {currencySign}
{pricePerRu}/RU)
</Text>
);
}; };
export const getEstimatedSpendingElement = ( export const getEstimatedSpendElement = (
estimatedSpendingColumns: IColumn[],
estimatedSpendingItems: EstimatedSpendingDisplayProps[],
throughput: number, throughput: number,
numberOfRegions: number, serverId: string,
priceBreakdown: PriceBreakdown, regions: number,
isAutoscale: boolean multimaster: boolean
): JSX.Element => { ): JSX.Element => {
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : ""; const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
const currency: string = getPriceCurrency(serverId);
const currencySign: string = getCurrencySign(serverId);
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
return ( return (
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}> <Text id="throughputSpendElement">
<DetailsList Estimated cost ({currency}):{" "}
disableSelectionZone <b>
items={estimatedSpendingItems} {currencySign}
columns={estimatedSpendingColumns} {calculateEstimateNumber(hourlyPrice)} hourly {` / `}
selectionMode={SelectionMode.none} {currencySign}
layoutMode={DetailsListLayoutMode.justified} {calculateEstimateNumber(dailyPrice)} daily {` / `}
onRenderRow={onRenderRow} {currencySign}
/> {calculateEstimateNumber(monthlyPrice)} monthly{" "}
<Text id="throughputSpendElement"> </b>
({"regions: "} {numberOfRegions}, {ruRange} ({"regions: "} {regions}, {throughput}RU/s, {currencySign}
{throughput} RU/s, {priceBreakdown.currencySign} {pricePerRu}/RU)
{priceBreakdown.pricePerRu}/RU) </Text>
</Text>
<Text>
<em>{estimatedCostDisclaimer}</em>
</Text>
</Stack>
); );
}; };
@@ -307,13 +265,6 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
</Text> </Text>
); );
export const saveThroughputWarningMessage: JSX.Element = (
<Text styles={infoAndToolTipTextStyle}>
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
before saving your changes
</Text>
);
const getCurrentThroughput = ( const getCurrentThroughput = (
isAutoscale: boolean, isAutoscale: boolean,
throughput: number, throughput: number,

View File

@@ -6,6 +6,8 @@ import {
IconButton, IconButton,
Text, Text,
SelectionMode, SelectionMode,
IDetailsRowProps,
DetailsRow,
IColumn, IColumn,
MessageBar, MessageBar,
MessageBarType, MessageBarType,
@@ -19,11 +21,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 {
@@ -138,6 +140,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return undefined; return undefined;
}; };
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
};
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => { private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
return isCurrentIndex ? ( return isCurrentIndex ? (
<IconButton <IconButton
@@ -247,7 +253,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
items={initialIndexes} items={initialIndexes}
columns={this.initialIndexesColumns} columns={this.initialIndexesColumns}
selectionMode={SelectionMode.none} selectionMode={SelectionMode.none}
onRenderRow={onRenderRow} onRenderRow={this.onRenderRow}
layoutMode={DetailsListLayoutMode.justified} layoutMode={DetailsListLayoutMode.justified}
/> />
{this.renderIndexesToBeAdded()} {this.renderIndexesToBeAdded()}
@@ -273,7 +279,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
items={indexesToBeDropped} items={indexesToBeDropped}
columns={this.indexesToBeDroppedColumns} columns={this.indexesToBeDroppedColumns}
selectionMode={SelectionMode.none} selectionMode={SelectionMode.none}
onRenderRow={onRenderRow} onRenderRow={this.onRenderRow}
layoutMode={DetailsListLayoutMode.justified} layoutMode={DetailsListLayoutMode.justified}
/> />
)} )}

View File

@@ -54,6 +54,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
expect(wrapper.exists("#throughputInput")).toEqual(true); expect(wrapper.exists("#throughputInput")).toEqual(true);
expect(wrapper.exists("#autopilotInput")).toEqual(false); expect(wrapper.exists("#autopilotInput")).toEqual(false);
expect(wrapper.exists("#throughputSpendElement")).toEqual(true); expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(false);
}); });
it("autopilot input visible", () => { it("autopilot input visible", () => {
@@ -71,7 +72,8 @@ describe("ThroughputInputAutoPilotV3Component", () => {
wrapper.setProps({ wasAutopilotOriginallySet: true }); wrapper.setProps({ wasAutopilotOriginallySet: true });
wrapper.update(); wrapper.update();
expect(wrapper.exists("#throughputSpendElement")).toEqual(true); expect(wrapper.exists("#autoscaleSpendElement")).toEqual(true);
expect(wrapper.exists("#throughputSpendElement")).toEqual(false);
}); });
it("spendAck checkbox visible", () => { it("spendAck checkbox visible", () => {

View File

@@ -8,15 +8,10 @@ import {
checkBoxAndInputStackProps, checkBoxAndInputStackProps,
getChoiceGroupStyles, getChoiceGroupStyles,
messageBarStyles, messageBarStyles,
getEstimatedSpendingElement, getEstimatedSpendElement,
getEstimatedAutoscaleSpendElement,
getAutoPilotV3SpendElement, getAutoPilotV3SpendElement,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement
saveThroughputWarningMessage,
ManualEstimatedSpendingDisplayProps,
AutoscaleEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown,
transparentDetailsHeaderStyle
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { import {
Text, Text,
@@ -28,9 +23,7 @@ import {
Label, Label,
Link, Link,
MessageBar, MessageBar,
MessageBarType, MessageBarType
FontIcon,
IColumn
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils"; import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
@@ -39,7 +32,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, calculateEstimateNumber } from "../../../../../Utils/PricingUtils"; import { usageInGB } from "../../../../../Utils/PricingUtils";
import { Features } from "../../../../../Common/Constants"; import { Features } from "../../../../../Common/Constants";
export interface ThroughputInputAutoPilotV3Props { export interface ThroughputInputAutoPilotV3Props {
@@ -172,243 +165,33 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
return <></>; return <></>;
} }
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
const serverId: string = this.props.serverId; const serverId: string = this.props.serverId;
const offerThroughput: number = this.props.throughput;
const regions = account?.properties?.readLocations?.length || 1; const regions = account?.properties?.readLocations?.length || 1;
const multimaster = account?.properties?.enableMultipleWriteLocations || false; const multimaster = account?.properties?.enableMultipleWriteLocations || false;
let estimatedSpend: JSX.Element; let estimatedSpend: JSX.Element;
if (!this.props.isAutoPilotSelected) { if (!this.props.isAutoPilotSelected) {
estimatedSpend = this.getEstimatedManualSpendElement( estimatedSpend = getEstimatedSpendElement(
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set... // if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline, this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster
isDirty ? this.props.throughput : undefined
); );
} else { } else {
estimatedSpend = this.getEstimatedAutoscaleSpendElement( estimatedSpend = getEstimatedAutoscaleSpendElement(
this.props.maxAutoPilotThroughputBaseline, this.props.maxAutoPilotThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster
isDirty ? this.props.maxAutoPilotThroughput : undefined
); );
} }
return estimatedSpend; return estimatedSpend;
}; };
private getEstimatedAutoscaleSpendElement = (
throughput: number,
serverId: string,
numberOfRegions: number,
isMultimaster: boolean,
newThroughput?: number
): JSX.Element => {
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
const estimatedSpendingColumns: IColumn[] = [
{
key: "costType",
name: "",
fieldName: "costType",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
},
{
key: "minPerMonth",
name: "Min Per Month",
fieldName: "minPerMonth",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
},
{
key: "maxPerMonth",
name: "Max Per Month",
fieldName: "maxPerMonth",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
}
];
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
{
costType: <Text>Current Cost</Text>,
minPerMonth: (
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}
</Text>
),
maxPerMonth: (
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
</Text>
)
}
];
if (newThroughput) {
const newPrices: PriceBreakdown = getRuPriceBreakdown(
newThroughput,
serverId,
numberOfRegions,
isMultimaster,
true
);
estimatedSpendingItems.unshift({
costType: (
<Text>
<b>Updated Cost</b>
</Text>
),
minPerMonth: (
<Text>
<b>
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}
</b>
</Text>
),
maxPerMonth: (
<Text>
<b>
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
</b>
</Text>
)
});
}
return getEstimatedSpendingElement(
estimatedSpendingColumns,
estimatedSpendingItems,
newThroughput ?? throughput,
numberOfRegions,
prices,
true
);
};
private getEstimatedManualSpendElement = (
throughput: number,
serverId: string,
numberOfRegions: number,
isMultimaster: boolean,
newThroughput?: number
): JSX.Element => {
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
const estimatedSpendingColumns: IColumn[] = [
{
key: "costType",
name: "",
fieldName: "costType",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
},
{
key: "hourly",
name: "Hourly",
fieldName: "hourly",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
},
{
key: "daily",
name: "Daily",
fieldName: "daily",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
},
{
key: "monthly",
name: "Monthly",
fieldName: "monthly",
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
}
];
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
{
costType: <Text>Current Cost</Text>,
hourly: (
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.hourlyPrice)}
</Text>
),
daily: (
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.dailyPrice)}
</Text>
),
monthly: (
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
</Text>
)
}
];
if (newThroughput) {
const newPrices: PriceBreakdown = getRuPriceBreakdown(
newThroughput,
serverId,
numberOfRegions,
isMultimaster,
false
);
estimatedSpendingItems.unshift({
costType: (
<Text>
<b>Updated Cost</b>
</Text>
),
hourly: (
<Text>
<b>
{newPrices.currencySign} {calculateEstimateNumber(newPrices.hourlyPrice)}
</b>
</Text>
),
daily: (
<Text>
<b>
{newPrices.currencySign} {calculateEstimateNumber(newPrices.dailyPrice)}
</b>
</Text>
),
monthly: (
<Text>
<b>
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
</b>
</Text>
)
});
}
return getEstimatedSpendingElement(
estimatedSpendingColumns,
estimatedSpendingItems,
newThroughput ?? throughput,
numberOfRegions,
prices,
false
);
};
private getAutoPilotUsageCost = (): JSX.Element => { private getAutoPilotUsageCost = (): JSX.Element => {
if (!this.props.maxAutoPilotThroughput) { if (!this.props.maxAutoPilotThroughput) {
return <></>; return <></>;
@@ -535,12 +318,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
private renderThroughputInput = (): JSX.Element => ( private renderThroughputInput = (): JSX.Element => (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<Text>
Estimate your required throughput with
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
</Link>
</Text>
<TextField <TextField
required required
type="number" type="number"
@@ -572,24 +349,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
onChange={this.onSpendAckChecked} onChange={this.onSpendAckChecked}
/> />
)} )}
<br />
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>} {this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
</Stack> </Stack>
); );
private renderWarningMessage = (): JSX.Element => {
let warningMessage: JSX.Element;
if (this.IsComponentDirty().isDiscardable) {
warningMessage = saveThroughputWarningMessage;
}
return <>{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}</>;
};
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...checkBoxAndInputStackProps}> <Stack {...checkBoxAndInputStackProps}>
{this.renderWarningMessage()}
{this.renderThroughputModeChoices()} {this.renderThroughputModeChoices()}
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()} {this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}

View File

@@ -8,21 +8,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
} }
} }
> >
<StyledMessageBarBase
messageBarType={5}
>
<Text
styles={
Object {
"root": Object {
"fontSize": 12,
},
}
}
>
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes
</Text>
</StyledMessageBarBase>
<Stack> <Stack>
<StyledLabelBase <StyledLabelBase
id="settingsV2RadioButtonLabelId" id="settingsV2RadioButtonLabelId"
@@ -229,19 +214,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
} }
} }
> >
<Text>
Estimate your required throughput with
<StyledLinkBase
href="https://cosmos.azure.com/capacitycalculator/"
target="_blank"
>
capacity calculator
<Component
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
</Text>
<StyledTextFieldBase <StyledTextFieldBase
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
@@ -267,142 +239,38 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
type="number" type="number"
value="100" value="100"
/> />
<Stack <Text
styles={ id="throughputSpendElement"
Object {
"root": Object {
"width": 600,
},
}
}
tokens={
Object {
"childrenGap": 10,
}
}
> >
<StyledWithViewportComponent Estimated cost (
columns={ USD
Array [ ):
Object {
"fieldName": "costType",
"isResizable": true,
"key": "costType",
"maxWidth": 200,
"minWidth": 100,
"name": "",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "hourly",
"isResizable": true,
"key": "hourly",
"maxWidth": 200,
"minWidth": 100,
"name": "Hourly",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "daily",
"isResizable": true,
"key": "daily",
"maxWidth": 200,
"minWidth": 100,
"name": "Daily",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "monthly",
"isResizable": true,
"key": "monthly",
"maxWidth": 200,
"minWidth": 100,
"name": "Monthly",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
]
}
disableSelectionZone={true}
items={
Array [
Object {
"costType": <Text>
Current Cost
</Text>,
"daily": <Text>
$
0.19 <b>
</Text>,
"hourly": <Text>
$
0.0080
</Text>,
"monthly": <Text>
$
5.84
</Text>,
},
]
}
layoutMode={1}
onRenderRow={[Function]}
selectionMode={0}
/>
<Text
id="throughputSpendElement"
>
(
regions:
1
,
100
RU/s,
$ $
0.00008 0.0080
/RU) hourly
</Text> /
<Text> $
<em> 0.19
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account daily
</em> /
</Text> $
</Stack> 5.84
monthly
</b>
(
regions:
1
,
100
RU/s,
$
0.00008
/RU)
</Text>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
id="spendAckCheckBox" id="spendAckCheckBox"
@@ -420,7 +288,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
} }
} }
/> />
<br />
</Stack> </Stack>
</Stack> </Stack>
`; `;
@@ -502,19 +369,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
} }
} }
> >
<Text>
Estimate your required throughput with
<StyledLinkBase
href="https://cosmos.azure.com/capacitycalculator/"
target="_blank"
>
capacity calculator
<Component
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
</Text>
<StyledTextFieldBase <StyledTextFieldBase
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
@@ -540,143 +394,38 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
type="number" type="number"
value="100" value="100"
/> />
<Stack <Text
styles={ id="throughputSpendElement"
Object {
"root": Object {
"width": 600,
},
}
}
tokens={
Object {
"childrenGap": 10,
}
}
> >
<StyledWithViewportComponent Estimated cost (
columns={ USD
Array [ ):
Object {
"fieldName": "costType",
"isResizable": true,
"key": "costType",
"maxWidth": 200,
"minWidth": 100,
"name": "",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "hourly",
"isResizable": true,
"key": "hourly",
"maxWidth": 200,
"minWidth": 100,
"name": "Hourly",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "daily",
"isResizable": true,
"key": "daily",
"maxWidth": 200,
"minWidth": 100,
"name": "Daily",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
Object {
"fieldName": "monthly",
"isResizable": true,
"key": "monthly",
"maxWidth": 200,
"minWidth": 100,
"name": "Monthly",
"styles": Object {
"root": Object {
"selectors": Object {
":hover": Object {
"background": "transparent",
},
},
},
},
},
]
}
disableSelectionZone={true}
items={
Array [
Object {
"costType": <Text>
Current Cost
</Text>,
"daily": <Text>
$
0.19 <b>
</Text>,
"hourly": <Text>
$
0.0080
</Text>,
"monthly": <Text>
$
5.84
</Text>,
},
]
}
layoutMode={1}
onRenderRow={[Function]}
selectionMode={0}
/>
<Text
id="throughputSpendElement"
>
(
regions:
1
,
100
RU/s,
$ $
0.00008 0.0080
/RU) hourly
</Text> /
<Text> $
<em> 0.19
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account daily
</em> /
</Text> $
</Stack> 5.84
<br /> monthly
</b>
(
regions:
1
,
100
RU/s,
$
0.00008
/RU)
</Text>
</Stack> </Stack>
</Stack> </Stack>
`; `;

View File

@@ -60,100 +60,66 @@ exports[`SettingsUtils functions render 1`] = `
</StyledLinkBase> </StyledLinkBase>
. .
</Text> </Text>
<Stack <Text
styles={ id="throughputSpendElement"
Object {
"root": Object {
"width": 600,
},
}
}
tokens={
Object {
"childrenGap": 10,
}
}
> >
<StyledWithViewportComponent Estimated cost (
columns={ RMB
Array [ ):
Object {
"fieldName": "costType",
"isResizable": true,
"key": "costType",
"maxWidth": 200,
"minWidth": 100,
"name": "",
},
Object {
"fieldName": "hourly",
"isResizable": true,
"key": "hourly",
"maxWidth": 200,
"minWidth": 100,
"name": "Hourly",
},
Object {
"fieldName": "daily",
"isResizable": true,
"key": "daily",
"maxWidth": 200,
"minWidth": 100,
"name": "Daily",
},
Object {
"fieldName": "monthly",
"isResizable": true,
"key": "monthly",
"maxWidth": 200,
"minWidth": 100,
"name": "Monthly",
},
]
}
disableSelectionZone={true}
items={
Array [
Object {
"costType": <Text>
Current Cost
</Text>,
"daily": <Text>
$ 24.48
</Text>,
"hourly": <Text>
$ 1.02
</Text>,
"monthly": <Text>
$ 744.6
</Text>,
},
]
}
layoutMode={1}
onRenderRow={[Function]}
selectionMode={0}
/>
<Text
id="throughputSpendElement"
>
(
regions:
2 <b>
,
1000
RU/s,
¥ ¥
0.00051 1.02
/RU) hourly
</Text> /
<Text> ¥
<em> 24.48
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account daily
</em> /
</Text> ¥
</Stack> 744.60
monthly
</b>
(
regions:
2
,
1000
RU/s,
¥
0.00051
/RU)
</Text>
<Text
id="autoscaleSpendElement"
>
Estimated monthly cost (
RMB
) is
<b>
¥
111.69
-
¥
1116.90
</b>
(
regions:
2
,
100
-
1000
RU/s,
¥
0.000765
/RU)
</Text>
<Text <Text
id="manualToAutoscaleDisclaimerElement" id="manualToAutoscaleDisclaimerElement"
styles={ styles={

View File

@@ -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() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()" data-bind="visible: queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
> >
</json-editor> </json-editor>
<div <div

View File

@@ -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 === 20 --> <!-- ko if: $data.tabKind === 19 -->
<settings-tab-v2 params="{data: $data}"></settings-tab-v2> <settings-tab-v2 params="{data: $data}"></settings-tab-v2>
<!-- /ko --> <!-- /ko -->
</div> </div>

View File

@@ -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.SettingsV2, tab => { const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, 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();
}); });

View File

@@ -17,7 +17,9 @@ 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);

View File

@@ -25,151 +25,39 @@ 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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false);
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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false);
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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false);
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({ const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false);
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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false);
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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true);
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({ const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true);
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()", () => {

View File

@@ -1,17 +1,6 @@
import * as AutoPilotUtils from "../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
import * as Constants from "../Shared/Constants"; import * as Constants from "../Shared/Constants";
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
* Otherwise, return numberOfRegions * Otherwise, return numberOfRegions
@@ -58,16 +47,15 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
return multimasterMultiplier; return multimasterMultiplier;
} }
export function computeRUUsagePriceHourly({ export function computeRUUsagePriceHourly(
serverId, serverId: string,
requestUnits, requestUnits: number,
numberOfRegions, numberOfRegions: number,
multimasterEnabled, multimasterEnabled: boolean
isAutoscale ): number {
}: 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 = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId); const pricePerRu = getPricePerRu(serverId);
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier; const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
return Number(ruCharge.toFixed(5)); return Number(ruCharge.toFixed(5));
@@ -171,19 +159,28 @@ 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 = computeRUUsagePriceHourly({ const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
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);
@@ -206,13 +203,7 @@ export function getEstimatedSpendHtml(
regions: number, regions: number,
multimaster: boolean multimaster: boolean
): string { ): string {
const hourlyPrice: number = computeRUUsagePriceHourly({ const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
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);
@@ -226,7 +217,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>${estimatedCostDisclaimer}</em></p>` `<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>`
); );
} }
@@ -237,13 +228,9 @@ export function getEstimatedSpendAcknowledgeString(
multimaster: boolean, multimaster: boolean,
isAutoscale: boolean isAutoscale: boolean
): string { ): string {
const hourlyPrice: number = computeRUUsagePriceHourly({ const hourlyPrice: number = isAutoscale
serverId: serverId, ? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
requestUnits: throughput, : computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
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);

View File

@@ -120,7 +120,7 @@ describe("Collection Add and Delete Cassandra spec", () => {
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName; const testName = (expect as any).getState().currentTestName;
await page.screenshot({ path: `failed-${testName}.jpg` }); await page.screenshot({ path: `./failed-${testName}.jpg` });
throw error; throw error;
} }
}); });

View File

@@ -136,7 +136,7 @@ describe("Collection Add and Delete Mongo spec", () => {
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName; const testName = (expect as any).getState().currentTestName;
await page.screenshot({ path: `failed-${testName}.jpg` }); await page.screenshot({ path: `./failed-${testName}.jpg` });
throw error; throw error;
} }
}); });

View File

@@ -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(CREATE_DELAY); await frame.waitFor(LOADING_STATE_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 => {
@@ -139,7 +139,7 @@ describe("Collection Add and Delete SQL spec", () => {
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName; const testName = (expect as any).getState().currentTestName;
await page.screenshot({ path: `failed-${testName}.jpg` }); await page.screenshot({ path: `./failed-${testName}.jpg` });
throw error; throw error;
} }
}); });

View File

@@ -83,7 +83,7 @@ describe("Collection Add and Delete Tables spec", () => {
} catch (error) { } catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName; const testName = (expect as any).getState().currentTestName;
await page.screenshot({ path: `failed-${testName}.jpg` }); await page.screenshot({ path: `./failed-${testName}.jpg` });
throw error; throw error;
} }
}); });

View File

@@ -11,7 +11,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"downlevelIteration": true, "downlevelIteration": true,
"module": "esnext", "module": "esnext",
"target": "es2017", "target": "es5",
"lib": ["es5", "es6", "dom", "webworker.importscripts"], "lib": ["es5", "es6", "dom", "webworker.importscripts"],
"jsx": "react", "jsx": "react",
"moduleResolution": "node", "moduleResolution": "node",

View File

@@ -9,8 +9,8 @@ const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const CreateFileWebpack = require("create-file-webpack"); const CreateFileWebpack = require("create-file-webpack");
const childProcess = require("child_process"); const childProcess = require("child_process");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const isCI = require("is-ci"); const isCI = require("is-ci");
const { ESBuildPlugin, ESBuildMinifyPlugin } = require("esbuild-loader");
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8"); const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
@@ -56,20 +56,37 @@ const htmlRule = {
] ]
}; };
// We compile our own code with ts-loader
const typescriptRule = { const typescriptRule = {
test: /\.tsx?$/, test: /\.tsx?$/,
use: [ use: [
{ {
loader: "esbuild-loader", loader: "ts-loader",
options: { options: {
loader: "tsx", transpileOnly: true
target: "es2017"
} }
} }
], ],
exclude: /node_modules/ exclude: /node_modules/
}; };
// Third party modules are compiled with babel since using ts-loader that much causes webpack to run out of memory
const ModulesRule = {
test: /\.js$/,
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: ".cache/babel",
presets: [["@babel/preset-env", { targets: { ie: "11" }, useBuiltIns: false }]]
}
}
],
include: /node_modules/,
// Exclude large modules we know don't need transpiling
exclude: /vega|monaco|plotly/
};
module.exports = function(env = {}, argv = {}) { module.exports = function(env = {}, argv = {}) {
const mode = argv.mode || "development"; const mode = argv.mode || "development";
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule]; const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
@@ -79,6 +96,7 @@ module.exports = function(env = {}, argv = {}) {
}; };
if (mode === "production") { if (mode === "production") {
rules.push(ModulesRule);
envVars.NODE_ENV = "production"; envVars.NODE_ENV = "production";
} }
@@ -87,7 +105,6 @@ module.exports = function(env = {}, argv = {}) {
} }
const plugins = [ const plugins = [
new ESBuildPlugin(),
new CleanWebpackPlugin(["dist"]), new CleanWebpackPlugin(["dist"]),
new CreateFileWebpack({ new CreateFileWebpack({
path: "./dist", path: "./dist",
@@ -194,8 +211,13 @@ module.exports = function(env = {}, argv = {}) {
optimization: { optimization: {
minimize: mode === "production" ? true : false, minimize: mode === "production" ? true : false,
minimizer: [ minimizer: [
new ESBuildMinifyPlugin({ new TerserPlugin({
target: "es2017" // Syntax to compile to (see options below for possible values) cache: ".cache/terser",
terserOptions: {
// These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory
compress: false,
mangle: true
}
}) })
] ]
}, },