From d45af21996a5f713eab0f91e7aff12e1b7ecefb4 Mon Sep 17 00:00:00 2001 From: vchske Date: Thu, 6 Aug 2020 10:56:40 -0700 Subject: [PATCH] Updating error message on mongo collection create (#118) * 1) Updated mongo collection create pane to display a better error when a shard key is ill formed. 2) Updated an error message to use double quotes since no string interpolation is used. I thought it was included in a previoue PR but I guess the change didn't get staged. --- .eslintignore | 1 - src/Common/ErrorParserUtility.ts | 136 ++++++++++++++++--------------- src/Utils/PricingUtils.test.ts | 4 +- src/Utils/PricingUtils.ts | 23 +++--- 4 files changed, 83 insertions(+), 81 deletions(-) diff --git a/.eslintignore b/.eslintignore index 5154698a5..94dd6b723 100644 --- a/.eslintignore +++ b/.eslintignore @@ -301,7 +301,6 @@ src/Utils/NotebookConfigurationUtils.ts src/Utils/OfferUtils.test.ts src/Utils/OfferUtils.ts src/Utils/PricingUtils.test.ts -src/Utils/PricingUtils.ts src/Utils/QueryUtils.test.ts src/Utils/QueryUtils.ts src/Utils/StringUtils.test.ts diff --git a/src/Common/ErrorParserUtility.ts b/src/Common/ErrorParserUtility.ts index 9c61e2b93..04af62f5a 100644 --- a/src/Common/ErrorParserUtility.ts +++ b/src/Common/ErrorParserUtility.ts @@ -1,67 +1,69 @@ -import * as DataModels from "../Contracts/DataModels"; -import * as ViewModels from "../Contracts/ViewModels"; - -export function replaceKnownError(err: string): string { - if ( - window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal && - err.indexOf("SharedOffer is Disabled for your account") >= 0 - ) { - return "Database throughput is not supported for internal subscriptions."; - } - - return err; -} - -export function parse(err: any): DataModels.ErrorDataModel[] { - try { - return _parse(err); - } catch (e) { - return [{ message: JSON.stringify(err) }]; - } -} - -function _parse(err: any): DataModels.ErrorDataModel[] { - var normalizedErrors: DataModels.ErrorDataModel[] = []; - if (err.message && !err.code) { - normalizedErrors.push(err); - } else { - const innerErrors: any[] = _getInnerErrors(err.message); - normalizedErrors = innerErrors.map(innerError => - typeof innerError === "string" ? { message: innerError } : innerError - ); - } - - return normalizedErrors; -} - -function _getInnerErrors(message: string): any[] { - /* - The backend error message has an inner-message which is a stringified object. - - For SQL errors, the "errors" property is an array of SqlErrorDataModel. - Example: - "Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p" - For non-SQL errors the "Errors" propery is an array of string. - Example: - "Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s" - */ - - let innerMessage: any = null; - - const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, ""); - try { - // Multi-Partition error flavor - const regExp = /^(.*)ActivityId: (.*)/g; - const regString = regExp.exec(singleLineMessage); - const innerMessageString = regString[1]; - innerMessage = JSON.parse(innerMessageString); - } catch (e) { - // Single-partition error flavor - const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g; - const regString = regExp.exec(singleLineMessage); - const innerMessageString = regString[1]; - innerMessage = JSON.parse(innerMessageString); - } - - return innerMessage.errors ? innerMessage.errors : innerMessage.Errors; -} +import * as DataModels from "../Contracts/DataModels"; +import * as ViewModels from "../Contracts/ViewModels"; + +export function replaceKnownError(err: string): string { + if ( + window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal && + err.indexOf("SharedOffer is Disabled for your account") >= 0 + ) { + return "Database throughput is not supported for internal subscriptions."; + } else if (err.indexOf("Partition key paths must contain only valid") >= 0) { + return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character."; + } + + return err; +} + +export function parse(err: any): DataModels.ErrorDataModel[] { + try { + return _parse(err); + } catch (e) { + return [{ message: JSON.stringify(err) }]; + } +} + +function _parse(err: any): DataModels.ErrorDataModel[] { + var normalizedErrors: DataModels.ErrorDataModel[] = []; + if (err.message && !err.code) { + normalizedErrors.push(err); + } else { + const innerErrors: any[] = _getInnerErrors(err.message); + normalizedErrors = innerErrors.map(innerError => + typeof innerError === "string" ? { message: innerError } : innerError + ); + } + + return normalizedErrors; +} + +function _getInnerErrors(message: string): any[] { + /* + The backend error message has an inner-message which is a stringified object. + + For SQL errors, the "errors" property is an array of SqlErrorDataModel. + Example: + "Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p" + For non-SQL errors the "Errors" propery is an array of string. + Example: + "Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s" + */ + + let innerMessage: any = null; + + const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, ""); + try { + // Multi-Partition error flavor + const regExp = /^(.*)ActivityId: (.*)/g; + const regString = regExp.exec(singleLineMessage); + const innerMessageString = regString[1]; + innerMessage = JSON.parse(innerMessageString); + } catch (e) { + // Single-partition error flavor + const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g; + const regString = regExp.exec(singleLineMessage); + const innerMessageString = regString[1]; + innerMessage = JSON.parse(innerMessageString); + } + + return innerMessage.errors ? innerMessage.errors : innerMessage.Errors; +} diff --git a/src/Utils/PricingUtils.test.ts b/src/Utils/PricingUtils.test.ts index eff453aa9..af1cedf66 100644 --- a/src/Utils/PricingUtils.test.ts +++ b/src/Utils/PricingUtils.test.ts @@ -373,12 +373,12 @@ describe("PricingUtils Tests", () => { expect(value).toBe(1); }); - it("should return 1 for -1", () => { + it("should return -1 for -1", () => { const value = PricingUtils.normalizeNumber(-1); expect(value).toBe(-1); }); - it("should return 1 for 0.1", () => { + it("should return 0 for 0.1", () => { const value = PricingUtils.normalizeNumber(0.1); expect(value).toBe(0); }); diff --git a/src/Utils/PricingUtils.ts b/src/Utils/PricingUtils.ts index 35cddd422..878ec92fb 100644 --- a/src/Utils/PricingUtils.ts +++ b/src/Utils/PricingUtils.ts @@ -7,10 +7,11 @@ import { AutopilotTier } from "../Contracts/DataModels"; * Otherwise, return numberOfRegions * @param number */ -export function normalizeNumber(number: any): number { - const normalizedNumber: number = number === null ? 0 : isNaN(number) ? 0 : parseInt(number); - - return normalizedNumber; +export function normalizeNumber(number: null | undefined | string | number): number { + if (!number) { + return 0; + } + return Math.floor(Number(number)); } export function getRuToolTipText(isV2AutoPilot: boolean): string { @@ -79,16 +80,16 @@ export function getPriceCurrency(serverId: string): string { export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string { if (serverId === "mooncake") { - let storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerGB; + const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerGB; return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; } - let storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerGB; + const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerGB; return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; } export function computeDisplayUsageString(usageInKB: number): string { - let usageInMB = usageInKB / 1024, + const usageInMB = usageInKB / 1024, usageInGB = usageInMB / 1024, displayUsageString = usageInGB > 0.1 @@ -100,7 +101,7 @@ export function computeDisplayUsageString(usageInKB: number): string { } export function usageInGB(usageInKB: number): number { - let usageInMB = usageInKB / 1024, + const usageInMB = usageInKB / 1024, usageInGB = usageInMB / 1024; return Math.ceil(usageInGB); } @@ -109,7 +110,7 @@ export function calculateEstimateNumber(n: number): string { return n >= 1 ? n.toFixed(2) : n.toPrecision(2); } -export function numberWithCommasFormatter(n: number) { +export function numberWithCommasFormatter(n: number): string { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } @@ -279,9 +280,9 @@ export function getEstimatedSpendAcknowledgeString( )} - ${currencySign}${calculateEstimateNumber(monthlyPrice)} monthly cost for the throughput above.`; } -export function getUpsellMessage(serverId: string = "default", isFreeTier: boolean = false): string { +export function getUpsellMessage(serverId = "default", isFreeTier = false): 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.`; + 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."; } else { let price: number = Constants.OfferPricing.MonthlyPricing.default.Standard.StartingPrice;