diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 1160c2296..4d86f5ff7 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -145,6 +145,9 @@ export class Queries { public static QueryEditorMinHeightRatio: number = 0.1; public static QueryEditorMaxHeightRatio: number = 0.4; public static readonly DefaultMaxDegreeOfParallelism = 6; + public static readonly DefaultRetryAttempts = 9; + public static readonly DefaultRetryIntervalInMs = 0; + public static readonly DefaultMaxWaitTimeInSeconds = 30; } export class SavedQueries { diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 57d250531..34cba6a28 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -3,6 +3,7 @@ import { sendCachedDataMessage } from "Common/MessageHandler"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; +import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { AuthType } from "../AuthType"; import { PriorityLevel } from "../Common/Constants"; import { Platform, configContext } from "../ConfigContext"; @@ -150,6 +151,13 @@ export function client(): Cosmos.CosmosClient { tokenProvider, userAgentSuffix: "Azure Portal", defaultHeaders: _defaultHeaders, + connectionPolicy: { + retryOptions: { + maxRetryAttemptCount: LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts), + fixedRetryIntervalInMilliseconds: LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval), + maxWaitTimeInSeconds: LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds), + }, + }, }; if (configContext.PROXY_PATH !== undefined) { diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts index 0fd79a902..a4834b128 100644 --- a/src/Common/QueriesClient.ts +++ b/src/Common/QueriesClient.ts @@ -1,3 +1,4 @@ +import { isServerlessAccount } from "Utils/CapabilityUtils"; import * as _ from "underscore"; import * as DataModels from "../Contracts/DataModels"; import * as ViewModels from "../Contracts/ViewModels"; @@ -8,12 +9,11 @@ import { useDatabases } from "../Explorer/useDatabases"; import { userContext } from "../UserContext"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; +import { handleError } from "./ErrorHandlingUtils"; import { createCollection } from "./dataAccess/createCollection"; import { createDocument } from "./dataAccess/createDocument"; import { deleteDocument } from "./dataAccess/deleteDocument"; import { queryDocuments } from "./dataAccess/queryDocuments"; -import { handleError } from "./ErrorHandlingUtils"; -import { isServerlessAccount } from "Utils/CapabilityUtils"; export class QueriesClient { private static readonly PartitionKey: DataModels.PartitionKey = { diff --git a/src/Common/dataAccess/queryDocuments.test.ts b/src/Common/dataAccess/queryDocuments.test.ts index 38565a3df..a8978d9ff 100644 --- a/src/Common/dataAccess/queryDocuments.test.ts +++ b/src/Common/dataAccess/queryDocuments.test.ts @@ -1,5 +1,5 @@ -import { getCommonQueryOptions } from "./queryDocuments"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; +import { getCommonQueryOptions } from "./queryDocuments"; describe("getCommonQueryOptions", () => { it("builds the correct default options objects", () => { diff --git a/src/Common/dataAccess/queryDocuments.ts b/src/Common/dataAccess/queryDocuments.ts index 21f303efa..0b8ebd29d 100644 --- a/src/Common/dataAccess/queryDocuments.ts +++ b/src/Common/dataAccess/queryDocuments.ts @@ -26,6 +26,5 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => { (storedItemPerPageSetting !== undefined && storedItemPerPageSetting) || Queries.itemsPerPage; options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism); - return options; }; diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 14a9fd5d7..4f7373fbb 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -53,6 +53,21 @@ export const SettingsPane: FunctionComponent = () => { ? LocalStorageUtility.getEntryString(StorageKey.IsGraphAutoVizDisabled) : "false", ); + const [retryAttempts, setRetryAttempts] = useState( + LocalStorageUtility.hasItem(StorageKey.RetryAttempts) + ? LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts) + : Constants.Queries.DefaultRetryAttempts, + ); + const [retryInterval, setRetryInterval] = useState( + LocalStorageUtility.hasItem(StorageKey.RetryInterval) + ? LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval) + : Constants.Queries.DefaultRetryIntervalInMs, + ); + const [MaxWaitTimeInSeconds, setMaxWaitTimeInSeconds] = useState( + LocalStorageUtility.hasItem(StorageKey.MaxWaitTimeInSeconds) + ? LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds) + : Constants.Queries.DefaultMaxWaitTimeInSeconds, + ); const [maxDegreeOfParallelism, setMaxDegreeOfParallelism] = useState( LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) ? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism) @@ -78,6 +93,9 @@ export const SettingsPane: FunctionComponent = () => { ); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); + LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); + LocalStorageUtility.setEntryNumber(StorageKey.RetryInterval, retryInterval); + LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds); LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); @@ -179,6 +197,27 @@ export const SettingsPane: FunctionComponent = () => { } }; + const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { + const retryAttempts = Number(newValue); + if (!isNaN(retryAttempts)) { + setRetryAttempts(retryAttempts); + } + }; + + const handleOnRetryIntervalSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { + const retryInterval = Number(newValue); + if (!isNaN(retryInterval)) { + setRetryInterval(retryInterval); + } + }; + + const handleOnMaxWaitTimeSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { + const MaxWaitTimeInSeconds = Number(newValue); + if (!isNaN(MaxWaitTimeInSeconds)) { + setMaxWaitTimeInSeconds(MaxWaitTimeInSeconds); + } + }; + const choiceButtonStyles = { root: { clear: "both", @@ -323,6 +362,77 @@ export const SettingsPane: FunctionComponent = () => { )} +
+
+
+ Retry Settings + Retry policy associated with throttled requests during CosmosDB queries. +
+
+ + Max retry attempts + + Max number of retries to be performed for a request. Default value 9. +
+ setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)} + onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)} + onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)} + styles={queryTimeoutSpinButtonStyles} + /> +
+ + Fixed retry interval (ms) + + + Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part + of the response. Default value is 0 milliseconds. + +
+ setRetryInterval(parseInt(newValue) + 1000 || retryInterval)} + onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)} + onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)} + styles={queryTimeoutSpinButtonStyles} + /> +
+ + Max wait time (s) + + + Max wait time in seconds to wait for a request while the retries are happening. Default value 30 + seconds. + +
+ setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)} + onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)} + onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)} + styles={queryTimeoutSpinButtonStyles} + /> +
+
diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 372e7e179..8898bae23 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -137,6 +137,139 @@ exports[`Settings Pane should render Default properly 1`] = `
+
+
+
+ Retry Settings + + Retry policy associated with throttled requests during CosmosDB queries. + +
+
+ + Max retry attempts + + + Max number of retries to be performed for a request. Default value 9. + +
+ +
+ + Fixed retry interval (ms) + + + Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response. Default value is 0 milliseconds. + +
+ +
+ + Max wait time (s) + + + Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds. + +
+ +
+
@@ -251,6 +384,139 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
+
+
+
+ Retry Settings + + Retry policy associated with throttled requests during CosmosDB queries. + +
+
+ + Max retry attempts + + + Max number of retries to be performed for a request. Default value 9. + +
+ +
+ + Fixed retry interval (ms) + + + Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response. Default value is 0 milliseconds. + +
+ +
+ + Max wait time (s) + + + Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds. + +
+ +
+
diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index d54e985de..596974d4d 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -3,12 +3,12 @@ import * as ko from "knockout"; import Q from "q"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; +import { handleError } from "../../Common/ErrorHandlingUtils"; +import * as HeadersUtility from "../../Common/HeadersUtility"; import { createDocument } from "../../Common/dataAccess/createDocument"; import { deleteDocument } from "../../Common/dataAccess/deleteDocument"; import { queryDocuments } from "../../Common/dataAccess/queryDocuments"; import { updateDocument } from "../../Common/dataAccess/updateDocument"; -import { handleError } from "../../Common/ErrorHandlingUtils"; -import * as HeadersUtility from "../../Common/HeadersUtility"; import { configContext } from "../../ConfigContext"; import * as ViewModels from "../../Contracts/ViewModels"; import { userContext } from "../../UserContext"; diff --git a/src/Shared/ExplorerSettings.ts b/src/Shared/ExplorerSettings.ts index 2c5691b8c..983391c03 100644 --- a/src/Shared/ExplorerSettings.ts +++ b/src/Shared/ExplorerSettings.ts @@ -6,6 +6,9 @@ export const createDefaultSettings = () => { LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true"); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, Constants.Queries.DefaultMaxDegreeOfParallelism); + LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, Constants.Queries.DefaultRetryAttempts); + LocalStorageUtility.setEntryNumber(StorageKey.RetryInterval, Constants.Queries.DefaultRetryIntervalInMs); + LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, Constants.Queries.DefaultMaxWaitTimeInSeconds); LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, Constants.PriorityLevel.Default); }; @@ -13,7 +16,10 @@ export const hasSettingsDefined = (): boolean => { return ( LocalStorageUtility.hasItem(StorageKey.ActualItemPerPage) && LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) && - LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) + LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) && + LocalStorageUtility.hasItem(StorageKey.RetryAttempts) && + LocalStorageUtility.hasItem(StorageKey.RetryInterval) && + LocalStorageUtility.hasItem(StorageKey.MaxWaitTimeInSeconds) ); }; diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts index dd142c8f3..d3aaa6f13 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -6,6 +6,9 @@ export enum StorageKey { ActualItemPerPage, QueryTimeoutEnabled, QueryTimeout, + RetryAttempts, + RetryInterval, + MaxWaitTimeInSeconds, AutomaticallyCancelQueryAfterTimeout, ContainerPaginationEnabled, CustomItemPerPage,