import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
import * as Constants from "../Shared/Constants";
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
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
* Otherwise, return numberOfRegions
* @param number
*/
export function normalizeNumber(number: null | undefined | string | number): number {
if (!number) {
return 0;
}
return Math.floor(Number(number));
}
export function getRuToolTipText(): string {
return `Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.`;
}
/**
* For anything other than a numbers or numbers <= 0, should return 0
* Otherwise, return numberOfRegions
* @param numberOfRegions
*/
export function getRegionMultiplier(numberOfRegions: number, multimasterEnabled: boolean): 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 multimasterMultiplier: number = !multimasterEnabled ? 1 : regionMultiplier > 1 ? 2 : 1;
return multimasterMultiplier;
}
export function computeRUUsagePriceHourly({
serverId,
requestUnits,
numberOfRegions,
multimasterEnabled,
isAutoscale,
}: ComputeRUUsagePriceHourlyArgs): number {
const regionMultiplier: number = getRegionMultiplier(numberOfRegions, multimasterEnabled);
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multimasterMultiplier) : getPricePerRu(serverId);
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
return Number(ruCharge.toFixed(5));
}
export function getPriceCurrency(serverId: string): string {
if (serverId === "mooncake") {
return Constants.OfferPricing.HourlyPricing.mooncake.Currency;
}
return Constants.OfferPricing.HourlyPricing.default.Currency;
}
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {
if (serverId === "mooncake") {
const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerGB;
return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
}
const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerGB;
return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
}
export function computeDisplayUsageString(usageInKB: number): string {
const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024,
displayUsageString =
usageInGB > 0.1
? usageInGB.toFixed(2) + " GB"
: usageInMB > 0.1
? usageInMB.toFixed(2) + " MB"
: usageInKB.toFixed(2) + " KB";
return displayUsageString;
}
export function usageInGB(usageInKB: number): number {
const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024;
return Math.ceil(usageInGB);
}
export function calculateEstimateNumber(n: number): string {
return n >= 1 ? n.toFixed(2) : n.toPrecision(2);
}
export function numberWithCommasFormatter(n: number): string {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
export function isLargerThanDefaultMinRU(ru: number): boolean {
if (typeof ru === "number" && ru > Constants.CollectionCreation.DefaultCollectionRUs400) {
return true;
}
return false;
}
export function getCurrencySign(serverId: string): string {
if (serverId === "mooncake") {
return Constants.OfferPricing.HourlyPricing.mooncake.CurrencySign;
}
return Constants.OfferPricing.HourlyPricing.default.CurrencySign;
}
export function getAutoscalePricePerRu(serverId: string, mmMultiplier: number): number {
if (serverId === "mooncake") {
if (mmMultiplier > 1) {
return Constants.AutoscalePricing.HourlyPricing.mooncake.multiMaster.Standard.PricePerRU;
} else {
return Constants.AutoscalePricing.HourlyPricing.mooncake.singleMaster.Standard.PricePerRU;
}
}
if (mmMultiplier > 1) {
return Constants.AutoscalePricing.HourlyPricing.default.multiMaster.Standard.PricePerRU;
} else {
return Constants.AutoscalePricing.HourlyPricing.default.singleMaster.Standard.PricePerRU;
}
}
export function getPricePerRu(serverId: string): number {
if (serverId === "mooncake") {
return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
}
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
}
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
if (!maxAutoPilotThroughputSet) {
return "";
}
const resource: string = isDatabaseThroughput ? "database" : "container";
return `Your ${resource} throughput will automatically scale from ${AutoPilotUtils.getMinRUsBasedOnUserInput(
maxAutoPilotThroughputSet
)} RU/s (10% of max RU/s) - ${maxAutoPilotThroughputSet} RU/s based on usage.
After the first ${AutoPilotUtils.getStorageBasedOnUserInput(
maxAutoPilotThroughputSet
)} GB of data stored, the max RU/s will be automatically upgraded based on the new storage value. Learn more.`;
}
export function getEstimatedAutoscaleSpendHtml(
throughput: number,
serverId: string,
regions: number,
multimaster: boolean
): string {
const hourlyPrice: number = computeRUUsagePriceHourly({
serverId: serverId,
requestUnits: throughput,
numberOfRegions: regions,
multimasterEnabled: multimaster,
isAutoscale: true,
});
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);
return (
`Estimated monthly cost (${currency}): ` +
`${currencySign}${calculateEstimateNumber(monthlyPrice / 10)} - ` +
`${currencySign}${calculateEstimateNumber(monthlyPrice)} ` +
`(${regions} ${regions === 1 ? "region" : "regions"}, ${
throughput / 10
} - ${throughput} RU/s, ${currencySign}${pricePerRu}/RU)`
);
}
export function getEstimatedSpendHtml(
throughput: number,
serverId: string,
regions: number,
multimaster: boolean
): string {
const hourlyPrice: number = computeRUUsagePriceHourly({
serverId: serverId,
requestUnits: throughput,
numberOfRegions: regions,
multimasterEnabled: multimaster,
isAutoscale: false,
});
const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
const currency: string = getPriceCurrency(serverId);
const currencySign: string = getCurrencySign(serverId);
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
return (
`Cost (${currency}): ` +
`${currencySign}${calculateEstimateNumber(hourlyPrice)} hourly / ` +
`${currencySign}${calculateEstimateNumber(dailyPrice)} daily / ` +
`${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly ` +
`(${regions} ${regions === 1 ? "region" : "regions"}, ${throughput}RU/s, ${currencySign}${pricePerRu}/RU)` +
`
` + `${estimatedCostDisclaimer}
` ); } export function getEstimatedSpendAcknowledgeString( throughput: number, serverId: string, regions: number, multimaster: boolean, isAutoscale: boolean ): string { const hourlyPrice: number = computeRUUsagePriceHourly({ serverId: serverId, requestUnits: throughput, numberOfRegions: regions, multimasterEnabled: multimaster, isAutoscale: isAutoscale, }); const dailyPrice: number = hourlyPrice * 24; const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const currencySign: string = getCurrencySign(serverId); return !isAutoscale ? `I acknowledge the estimated ${currencySign}${calculateEstimateNumber( dailyPrice )} daily cost for the throughput above.` : `I acknowledge the estimated ${currencySign}${calculateEstimateNumber( monthlyPrice / 10 )} - ${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly cost for the throughput above.`; } export function getUpsellMessage( serverId = "default", isFreeTier = false, isFirstResourceCreated = false, defaultExperience: string, isCollection: boolean ): string { if (isFreeTier) { 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; if (serverId === "mooncake") { price = Constants.OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice; } return `Start at ${getCurrencySign(serverId)}${price}/mo per database, multiple containers included`; } } export 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"); } }