diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index 4d7bd9022..1c49141a0 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -5,7 +5,14 @@ import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { updateUserContext } from "../UserContext"; -import { deleteDocument, getEndpoint, getFeatureEndpointOrDefault, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient"; +import { + deleteDocument, + getEndpoint, + getFeatureEndpointOrDefault, + queryDocuments, + readDocument, + updateDocument, +} from "./MongoProxyClient"; const databaseId = "testDB"; @@ -260,11 +267,10 @@ describe("MongoProxyClient", () => { const features = extractFeatures(params); updateUserContext({ authType: AuthType.AAD, - features: features + features: features, }); }); - it("returns a local endpoint", () => { const endpoint = getFeatureEndpointOrDefault("readDocument"); expect(endpoint).toEqual("https://localhost:12901/api/mongo/explorer"); diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 1869e7061..668a0ab16 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -268,7 +268,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum ? documentId.partitionKeyProperty : "", }; - const endpoint = getFeatureEndpointOrDefault("deleteDocument");; + const endpoint = getFeatureEndpointOrDefault("deleteDocument"); return window .fetch(`${endpoint}?${queryString.stringify(params)}`, { @@ -336,11 +336,13 @@ export function createMongoCollectionWithProxy( } export function getFeatureEndpointOrDefault(feature: string): string { - return (hasFlag(userContext.features.mongoProxyAPIs, feature)) ? getEndpoint(userContext.features.mongoProxyEndpoint) : getEndpoint(); + return hasFlag(userContext.features.mongoProxyAPIs, feature) + ? getEndpoint(userContext.features.mongoProxyEndpoint) + : getEndpoint(); } export function getEndpoint(customEndpoint?: string): string { - let url = customEndpoint ? customEndpoint : (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT); + let url = customEndpoint ? customEndpoint : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; url += "/api/mongo/explorer"; if (userContext.authType === AuthType.EncryptedToken) { diff --git a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx index c0cda4f59..fe29a6d90 100644 --- a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx @@ -1,45 +1,45 @@ -import * as React from "react"; -import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; -import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants"; -import { Urls, StyleConstants } from "../../../Common/Constants"; import { - getPriceCurrency, - getCurrencySign, - getAutoscalePricePerRu, - getMultimasterMultiplier, - computeRUUsagePriceHourly, - getPricePerRu, - estimatedCostDisclaimer, -} from "../../../Utils/PricingUtils"; -import { - ITextFieldStyles, + DetailsList, + DetailsListLayoutMode, + DetailsRow, ICheckboxStyles, - IStackProps, - IStackTokens, IChoiceGroupStyles, - Link, - Text, - IMessageBarStyles, - ITextStyles, - IDetailsRowStyles, - IStackStyles, + IColumn, + IDetailsColumnStyles, IDetailsListStyles, + IDetailsRowProps, + IDetailsRowStyles, IDropdownStyles, + IMessageBarStyles, ISeparatorStyles, + IStackProps, + IStackStyles, + IStackTokens, + ITextFieldStyles, + ITextStyles, + Link, MessageBar, MessageBarType, - Stack, + SelectionMode, Spinner, SpinnerSize, - DetailsList, - IColumn, - SelectionMode, - DetailsListLayoutMode, - IDetailsRowProps, - DetailsRow, - IDetailsColumnStyles, + Stack, + Text, } from "@fluentui/react"; -import { isDirtyTypes, isDirty } from "./SettingsUtils"; +import * as React from "react"; +import { StyleConstants, Urls } from "../../../Common/Constants"; +import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants"; +import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; +import { + computeRUUsagePriceHourly, + estimatedCostDisclaimer, + getAutoscalePricePerRu, + getCurrencySign, + getMultimasterMultiplier, + getPriceCurrency, + getPricePerRu, +} from "../../../Utils/PricingUtils"; +import { isDirty, isDirtyTypes } from "./SettingsUtils"; export interface EstimatedSpendingDisplayProps { costType: JSX.Element; @@ -223,14 +223,15 @@ export const getRuPriceBreakdown = ( multimasterEnabled: isMultimaster, isAutoscale: isAutoscale, }); - const basePricePerRu: number = isAutoscale - ? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster)) - : getPricePerRu(serverId); + const multimasterMultiplier = getMultimasterMultiplier(numberOfRegions, isMultimaster); + const pricePerRu: number = isAutoscale + ? getAutoscalePricePerRu(serverId, multimasterMultiplier) + : getPricePerRu(serverId, multimasterMultiplier); return { - hourlyPrice: hourlyPrice, + hourlyPrice, dailyPrice: hourlyPrice * 24, monthlyPrice: hourlyPrice * hoursInAMonth, - pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster), + pricePerRu, currency: getPriceCurrency(serverId), currencySign: getCurrencySign(serverId), }; diff --git a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx index 51aaae619..fbc469f47 100644 --- a/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx +++ b/src/Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText.tsx @@ -6,6 +6,7 @@ import { userContext } from "../../../../UserContext"; import { calculateEstimateNumber, computeRUUsagePriceHourly, + estimatedCostDisclaimer, getAutoscalePricePerRu, getCurrencySign, getMultimasterMultiplier, @@ -42,11 +43,9 @@ export const CostEstimateText: FunctionComponent = ({ const currency: string = getPriceCurrency(serverId); const currencySign: string = getCurrencySign(serverId); const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); - const pricePerRu = isAutoscale - ? getAutoscalePricePerRu(serverId, multiplier) * multiplier - : getPricePerRu(serverId) * multiplier; + const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier); - const iconWithEstimatedCostDisclaimer: JSX.Element = PricingUtils.estimatedCostDisclaimer; + const iconWithEstimatedCostDisclaimer: JSX.Element = {estimatedCostDisclaimer}; if (isAutoscale) { return ( diff --git a/src/Platform/Hosted/extractFeatures.test.ts b/src/Platform/Hosted/extractFeatures.test.ts index 98fc8ebd7..f99b09d89 100644 --- a/src/Platform/Hosted/extractFeatures.test.ts +++ b/src/Platform/Hosted/extractFeatures.test.ts @@ -31,8 +31,8 @@ describe("hasFlag", () => { expect(hasFlag(singleFlagValue, desiredFlag)).toBe(true); expect(hasFlag(multipleFlagValues, desiredFlag)).toBe(true); expect(hasFlag(differentFlagValue, desiredFlag)).toBe(false); - expect(hasFlag(multipleFlagValues, undefined as unknown as string)).toBe(false); - expect(hasFlag(undefined as unknown as string, desiredFlag)).toBe(false); - expect(hasFlag(undefined as unknown as string, undefined as unknown as string)).toBe(false); + expect(hasFlag(multipleFlagValues, (undefined as unknown) as string)).toBe(false); + expect(hasFlag((undefined as unknown) as string, desiredFlag)).toBe(false); + expect(hasFlag((undefined as unknown) as string, (undefined as unknown) as string)).toBe(false); }); }); diff --git a/src/Shared/Constants.ts b/src/Shared/Constants.ts index 97a9349c5..8594ccc25 100644 --- a/src/Shared/Constants.ts +++ b/src/Shared/Constants.ts @@ -125,7 +125,8 @@ export class OfferPricing { S3Price: 0.1344, Standard: { StartingPrice: 24 / hoursInAMonth, // per hour - PricePerRU: 0.00008, + SingleMasterPricePerRU: 0.00008, + MultiMasterPricePerRU: 0.00016, PricePerGB: 0.25 / hoursInAMonth, }, }, @@ -137,7 +138,8 @@ export class OfferPricing { S3Price: 0.6, Standard: { StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour - PricePerRU: 0.00051, + SingleMasterPricePerRU: 0.00051, + MultiMasterPricePerRU: 0.00102, PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth, }, }, diff --git a/src/Shared/PriceEstimateCalculator.ts b/src/Shared/PriceEstimateCalculator.ts index a7f08d230..e6cb27db6 100644 --- a/src/Shared/PriceEstimateCalculator.ts +++ b/src/Shared/PriceEstimateCalculator.ts @@ -2,11 +2,11 @@ import * as Constants from "./Constants"; export function computeRUUsagePrice(serverId: string, requestUnits: number): string { if (serverId === "mooncake") { - const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU; + const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.SingleMasterPricePerRU; return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; } - const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; + const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.SingleMasterPricePerRU; return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; } diff --git a/src/Utils/PricingUtils.test.ts b/src/Utils/PricingUtils.test.ts index de4efa90c..15a504ce2 100644 --- a/src/Utils/PricingUtils.test.ts +++ b/src/Utils/PricingUtils.test.ts @@ -150,7 +150,7 @@ describe("PricingUtils Tests", () => { expect(value).toBe(0.00012); }); - it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => { + it("should return 0.00032 for default cloud, 1RU, 2 region, multimaster enabled", () => { const value = PricingUtils.computeRUUsagePriceHourly({ serverId: "default", requestUnits: 1, @@ -158,9 +158,9 @@ describe("PricingUtils Tests", () => { multimasterEnabled: true, isAutoscale: false, }); - expect(value).toBe(0.00048); + expect(value).toBe(0.00032); }); - it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => { + it("should return 0.00032 for default cloud, 1RU, 2 region, multimaster enabled, autoscale", () => { const value = PricingUtils.computeRUUsagePriceHourly({ serverId: "default", requestUnits: 1, @@ -168,7 +168,7 @@ describe("PricingUtils Tests", () => { multimasterEnabled: true, isAutoscale: true, }); - expect(value).toBe(0.00096); + expect(value).toBe(0.00032); }); }); @@ -251,70 +251,47 @@ describe("PricingUtils Tests", () => { }); describe("getPricePerRu()", () => { - it("should return 0.00008 for default clouds", () => { - const value = PricingUtils.getPricePerRu("default"); + it("should return 0.00008 for single master default clouds", () => { + const value = PricingUtils.getPricePerRu("default", 1); expect(value).toBe(0.00008); }); - it("should return 0.00051 for mooncake", () => { - const value = PricingUtils.getPricePerRu("mooncake"); + it("should return 0.00016 for multi master default clouds", () => { + const value = PricingUtils.getPricePerRu("default", 2); + expect(value).toBe(0.00016); + }); + + it("should return 0.00051 for single master mooncake", () => { + const value = PricingUtils.getPricePerRu("mooncake", 1); expect(value).toBe(0.00051); }); + + it("should return 0.00102 for multi master mooncake", () => { + const value = PricingUtils.getPricePerRu("mooncake", 2); + expect(value).toBe(0.00102); + }); }); describe("getRegionMultiplier()", () => { - describe("without multimaster", () => { - it("should return 0 for undefined", () => { - const value = PricingUtils.getRegionMultiplier(undefined, false); - expect(value).toBe(0); - }); - - it("should return 0 for -1", () => { - const value = PricingUtils.getRegionMultiplier(-1, false); - expect(value).toBe(0); - }); - - it("should return 0 for 0", () => { - const value = PricingUtils.getRegionMultiplier(0, false); - expect(value).toBe(0); - }); - - it("should return 1 for 1", () => { - const value = PricingUtils.getRegionMultiplier(1, false); - expect(value).toBe(1); - }); - - it("should return 2 for 2", () => { - const value = PricingUtils.getRegionMultiplier(2, false); - expect(value).toBe(2); - }); + it("should return 0 for undefined", () => { + const value = PricingUtils.getRegionMultiplier(undefined); + expect(value).toBe(0); }); - - describe("with multimaster", () => { - it("should return 0 for undefined", () => { - const value = PricingUtils.getRegionMultiplier(undefined, true); - expect(value).toBe(0); - }); - - it("should return 0 for -1", () => { - const value = PricingUtils.getRegionMultiplier(-1, true); - expect(value).toBe(0); - }); - - it("should return 0 for 0", () => { - const value = PricingUtils.getRegionMultiplier(0, true); - expect(value).toBe(0); - }); - - it("should return 1 for 1", () => { - const value = PricingUtils.getRegionMultiplier(1, true); - expect(value).toBe(1); - }); - - it("should return 3 for 2", () => { - const value = PricingUtils.getRegionMultiplier(2, true); - expect(value).toBe(3); - }); + it("should return 0 for -1", () => { + const value = PricingUtils.getRegionMultiplier(-1); + expect(value).toBe(0); + }); + it("should return 0 for 0", () => { + const value = PricingUtils.getRegionMultiplier(0); + expect(value).toBe(0); + }); + it("should return 1 for 1", () => { + const value = PricingUtils.getRegionMultiplier(1); + expect(value).toBe(1); + }); + it("should return 2 for 2", () => { + const value = PricingUtils.getRegionMultiplier(2); + expect(value).toBe(2); }); }); @@ -376,7 +353,7 @@ describe("PricingUtils Tests", () => { true /* multimaster */ ); expect(value).toBe( - "Cost (USD): $0.19 hourly / $4.61 daily / $140.16 monthly (2 regions, 400RU/s, $0.00016/RU)

*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account

" + "Cost (USD): $0.13 hourly / $3.07 daily / $93.44 monthly (2 regions, 400RU/s, $0.00016/RU)

*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account

" ); }); @@ -424,7 +401,7 @@ describe("PricingUtils Tests", () => { true /* multimaster */, false ); - expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above."); + expect(value).toBe("I acknowledge the estimated $3.07 daily cost for the throughput above."); }); it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster", () => { diff --git a/src/Utils/PricingUtils.ts b/src/Utils/PricingUtils.ts index 09b16373e..d4f8f4643 100644 --- a/src/Utils/PricingUtils.ts +++ b/src/Utils/PricingUtils.ts @@ -34,26 +34,18 @@ export function getRuToolTipText(): string { * Otherwise, return numberOfRegions * @param numberOfRegions */ -export function getRegionMultiplier(numberOfRegions: number, multimasterEnabled: boolean): number { +export function getRegionMultiplier(numberOfRegions: number): number { const normalizedNumberOfRegions: number = normalizeNumber(numberOfRegions); if (normalizedNumberOfRegions <= 0) { return 0; } - if (numberOfRegions === 1) { - return numberOfRegions; - } - - if (multimasterEnabled) { - return numberOfRegions + 1; - } - return numberOfRegions; } export function getMultimasterMultiplier(numberOfRegions: number, multimasterEnabled: boolean): number { - const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled); + const regionMultiplier: number = getRegionMultiplier(numberOfRegions); const multimasterMultiplier: number = !multimasterEnabled ? 1 : regionMultiplier > 1 ? 2 : 1; return multimasterMultiplier; @@ -66,10 +58,12 @@ export function computeRUUsagePriceHourly({ multimasterEnabled, isAutoscale, }: ComputeRUUsagePriceHourlyArgs): number { - const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled); + const regionMultiplier: number = getRegionMultiplier(numberOfRegions); const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); - const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId); - const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier; + const pricePerRu = isAutoscale + ? getAutoscalePricePerRu(serverId, multimasterMultiplier) + : getPricePerRu(serverId, multimasterMultiplier); + const ruCharge = requestUnits * pricePerRu * regionMultiplier; return Number(ruCharge.toFixed(5)); } @@ -149,12 +143,16 @@ export function getAutoscalePricePerRu(serverId: string, mmMultiplier: number): } } -export function getPricePerRu(serverId: string): number { +export function getPricePerRu(serverId: string, mmMultiplier: number): number { if (serverId === "mooncake") { - return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU; + return mmMultiplier > 1 + ? Constants.OfferPricing.HourlyPricing.mooncake.Standard.MultiMasterPricePerRU + : Constants.OfferPricing.HourlyPricing.mooncake.Standard.SingleMasterPricePerRU; } - return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; + return mmMultiplier > 1 + ? Constants.OfferPricing.HourlyPricing.default.Standard.MultiMasterPricePerRU + : Constants.OfferPricing.HourlyPricing.default.Standard.SingleMasterPricePerRU; } export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string { @@ -188,9 +186,7 @@ export function getEstimatedAutoscaleSpendHtml( const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const currency: string = getPriceCurrency(serverId); const currencySign: string = getCurrencySign(serverId); - const pricePerRu = - getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) * - getMultimasterMultiplier(regions, multimaster); + const pricePerRu = getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)); return ( `Estimated monthly cost (${currency}): ` + @@ -219,7 +215,7 @@ export function getEstimatedSpendHtml( const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const currency: string = getPriceCurrency(serverId); const currencySign: string = getCurrencySign(serverId); - const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster); + const pricePerRu = getPricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)); return ( `Cost (${currency}): ` +