diff --git a/web-console/e2e-tests/util/playwright.ts b/web-console/e2e-tests/util/playwright.ts index e2c9ed5d1df..6c838ca2a49 100644 --- a/web-console/e2e-tests/util/playwright.ts +++ b/web-console/e2e-tests/util/playwright.ts @@ -27,7 +27,7 @@ export async function createBrowser(): Promise { const headless = process.env['DRUID_E2E_TEST_HEADLESS'] || TRUE; const debug = headless !== TRUE; const launchOptions: any = { - args: [`--window-size=${WIDTH},${HEIGHT + PADDING}`], + args: [`--window-size=${WIDTH},${HEIGHT + PADDING}`, `--disable-local-storage`], }; if (debug) { launchOptions.headless = false; diff --git a/web-console/src/dialogs/numeric-input-dialog/numeric-input-dialog.tsx b/web-console/src/dialogs/numeric-input-dialog/numeric-input-dialog.tsx index b115e035b80..86abce90975 100644 --- a/web-console/src/dialogs/numeric-input-dialog/numeric-input-dialog.tsx +++ b/web-console/src/dialogs/numeric-input-dialog/numeric-input-dialog.tsx @@ -26,6 +26,7 @@ interface NumericInputDialogProps { message?: JSX.Element; minValue?: number; initValue: number; + integer?: boolean; onSubmit(value: number): void; onClose(): void; } @@ -33,9 +34,19 @@ interface NumericInputDialogProps { export const NumericInputDialog = React.memo(function NumericInputDialog( props: NumericInputDialogProps, ) { - const { title, message, minValue, initValue, onSubmit, onClose } = props; + const { title, message, minValue, initValue, integer, onSubmit, onClose } = props; + const effectiveMinValue = minValue ?? DEFAULT_MIN_VALUE; - const [value, setValue] = useState(initValue); + const [valueString, setValueString] = useState(String(initValue)); + + function done() { + let value = Math.max(Number(valueString) || 0, effectiveMinValue); + if (integer) { + value = Math.round(value); + } + onSubmit(value); + onClose(); + } return ( {message} { - if (isNaN(v)) return; - setValue(Math.max(v, DEFAULT_MIN_VALUE)); + value={valueString} + onValueChange={(_, v) => { + // Constrain to only simple numeric characters + v = v.replace(/[^\d\-.]/, ''); + + if (integer) { + // If in integer mode throw away the decimal point + v = v.replace(/\./, ''); + } + + if (effectiveMinValue >= 0) { + // If in non-negative mode throw away the minus + v = v.replace(/-/, ''); + } + + setValueString(v); }} - min={minValue ?? DEFAULT_MIN_VALUE} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key !== 'Enter') return; + done(); + }} + min={effectiveMinValue} stepSize={1} minorStepSize={null} majorStepSize={10} fill autoFocus + selectAllOnFocus + allowNumericCharactersOnly />
diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx index dcc50938872..d218d4b18a7 100644 --- a/web-console/src/utils/local-storage-keys.tsx +++ b/web-console/src/utils/local-storage-keys.tsx @@ -18,6 +18,14 @@ import * as JSONBig from 'json-bigint-native'; +function noLocalStorage(): boolean { + try { + return typeof localStorage === 'undefined'; + } catch { + return true; + } +} + export const LocalStorageKeys = { CAPABILITIES_OVERRIDE: 'capabilities-override' as const, INGESTION_SPEC: 'ingestion-spec' as const, @@ -65,7 +73,7 @@ function prependNamespace(key: string): string { } export function localStorageSet(key: LocalStorageKeys, value: string): void { - if (typeof localStorage === 'undefined') return; + if (noLocalStorage()) return; try { localStorage.setItem(prependNamespace(key), value); } catch (e) { @@ -78,7 +86,7 @@ export function localStorageSetJson(key: LocalStorageKeys, value: any): void { } export function localStorageGet(key: LocalStorageKeys): string | undefined { - if (typeof localStorage === 'undefined') return; + if (noLocalStorage()) return; try { return localStorage.getItem(prependNamespace(key)) || localStorage.getItem(key) || undefined; } catch (e) { @@ -98,7 +106,7 @@ export function localStorageGetJson(key: LocalStorageKeys): any { } export function localStorageRemove(key: LocalStorageKeys): void { - if (typeof localStorage === 'undefined') return; + if (noLocalStorage()) return; try { localStorage.removeItem(prependNamespace(key)); } catch (e) { diff --git a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx index 2830befa152..7391ebaf6e0 100644 --- a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx +++ b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx @@ -103,6 +103,7 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps } minValue={2} + integer initValue={maxNumTasks} onSubmit={p => { changeQueryContext(changeMaxNumTasks(queryContext, p));