diff --git a/superset-frontend/packages/superset-ui-core/src/utils/index.ts b/superset-frontend/packages/superset-ui-core/src/utils/index.ts index 426af7e9f3fb..4d6e869cd0ca 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/index.ts @@ -25,6 +25,7 @@ export { default as isEqualArray } from './isEqualArray'; export { default as makeSingleton } from './makeSingleton'; export { default as promiseTimeout } from './promiseTimeout'; export { default as removeDuplicates } from './removeDuplicates'; +export { default as withLabel } from './withLabel'; export { lruCache } from './lruCache'; export { getSelectedText } from './getSelectedText'; export * from './featureFlags'; diff --git a/superset-frontend/packages/superset-ui-core/src/utils/withLabel.ts b/superset-frontend/packages/superset-ui-core/src/utils/withLabel.ts new file mode 100644 index 000000000000..59da9612b3d4 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/utils/withLabel.ts @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { ValidatorFunction } from '../validator'; + +/** + * Wraps a validator function to prepend a label to its error message. + * + * @param validator - The validator function to wrap + * @param label - The label to prepend to error messages + * @returns A new validator function that includes the label in error messages + * + * @example + * validators: [ + * withLabel(validateInteger, t('Row limit')), + * ] + * // Returns: "Row limit is expected to be an integer" + */ +export default function withLabel( + validator: ValidatorFunction, + label: string, +): ValidatorFunction { + return (value: V, state?: S): string | false => { + const error = validator(value, state); + return error ? `${label} ${error}` : false; + }; +} diff --git a/superset-frontend/packages/superset-ui-core/src/validator/index.ts b/superset-frontend/packages/superset-ui-core/src/validator/index.ts index df8dc10a70f9..1bc68d2929e9 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export * from './types'; export { default as legacyValidateInteger } from './legacyValidateInteger'; export { default as legacyValidateNumber } from './legacyValidateNumber'; export { default as validateInteger } from './validateInteger'; diff --git a/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateInteger.ts b/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateInteger.ts index 972fdf855dac..e2b3303506b3 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateInteger.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateInteger.ts @@ -23,7 +23,7 @@ import { t } from '@apache-superset/core'; * formerly called integer() * @param v */ -export default function legacyValidateInteger(v: unknown) { +export default function legacyValidateInteger(v: unknown): string | false { if ( v && (Number.isNaN(Number(v)) || parseInt(v as string, 10) !== Number(v)) diff --git a/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateNumber.ts b/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateNumber.ts index d6a7b337f0d0..e075500c76d6 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateNumber.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/legacyValidateNumber.ts @@ -23,7 +23,7 @@ import { t } from '@apache-superset/core'; * formerly called numeric() * @param v */ -export default function numeric(v: unknown) { +export default function numeric(v: unknown): string | false { if (v && Number.isNaN(Number(v))) { return t('is expected to be a number'); } diff --git a/superset-frontend/packages/superset-ui-core/src/validator/types.ts b/superset-frontend/packages/superset-ui-core/src/validator/types.ts new file mode 100644 index 000000000000..3313af30fbe8 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/validator/types.ts @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Type definition for a validator function. + * Returns an error message string if validation fails, or false if validation passes. + */ +export type ValidatorFunction = ( + value: V, + state?: S, +) => string | false; diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateInteger.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateInteger.ts index bea18dca9d78..4aa4af1d21bd 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateInteger.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateInteger.ts @@ -19,7 +19,7 @@ import { t } from '@apache-superset/core'; -export default function validateInteger(v: unknown) { +export default function validateInteger(v: unknown): string | false { if ( (typeof v === 'string' && v.trim().length > 0 && diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts index facbc149aefc..66c474d5ad49 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts @@ -25,7 +25,7 @@ const VALIDE_OSM_URLS = ['https://tile.osm', 'https://tile.openstreetmap']; * Validate a [Mapbox styles URL](https://docs.mapbox.com/help/glossary/style-url/) * @param v */ -export default function validateMapboxStylesUrl(v: unknown) { +export default function validateMapboxStylesUrl(v: unknown): string | false { if (typeof v === 'string') { const trimmed_v = v.trim(); if ( diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateMaxValue.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateMaxValue.ts index bb7e6c052b3a..353e14315c64 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateMaxValue.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateMaxValue.ts @@ -18,7 +18,10 @@ */ import { t } from '@apache-superset/core'; -export default function validateMaxValue(v: unknown, max: number) { +export default function validateMaxValue( + v: unknown, + max: number, +): string | false { if (Number(v) > +max) { return t('Value cannot exceed %s', max); } diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateNonEmpty.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateNonEmpty.ts index 835c433fe2e6..1d8a525c631f 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateNonEmpty.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateNonEmpty.ts @@ -19,7 +19,7 @@ import { t } from '@apache-superset/core'; -export default function validateNonEmpty(v: unknown) { +export default function validateNonEmpty(v: unknown): string | false { if ( v === null || typeof v === 'undefined' || diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts index ce8db32cd28b..524d27d4b581 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts @@ -19,7 +19,7 @@ import { t } from '@apache-superset/core'; -export default function validateInteger(v: any) { +export default function validateNumber(v: unknown): string | false { if ( (typeof v === 'string' && v.trim().length > 0 && diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateServerPagination.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateServerPagination.ts index 1907a8198c5f..4fde0b12aa93 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateServerPagination.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateServerPagination.ts @@ -23,7 +23,7 @@ export default function validateServerPagination( serverPagination: boolean, maxValueWithoutServerPagination: number, maxServer: number, -) { +): string | false { if ( Number(v) > +maxValueWithoutServerPagination && Number(v) <= maxServer && diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateTimeComparisonRangeValues.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateTimeComparisonRangeValues.ts index b362757db12b..2c8ccc5cb980 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateTimeComparisonRangeValues.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateTimeComparisonRangeValues.ts @@ -22,13 +22,13 @@ import { t } from '@apache-superset/core'; import { ensureIsArray } from '../utils'; export const validateTimeComparisonRangeValues = ( - timeRangeValue?: any, - controlValue?: any, -) => { + timeRangeValue?: unknown, + controlValue?: unknown, +): string[] => { const isCustomTimeRange = timeRangeValue === ComparisonTimeRangeType.Custom; - const isCustomControlEmpty = controlValue?.every( - (val: any) => ensureIsArray(val).length === 0, - ); + const isCustomControlEmpty = + Array.isArray(controlValue) && + controlValue.every((val: unknown) => ensureIsArray(val).length === 0); return isCustomTimeRange && isCustomControlEmpty ? [t('Filters for comparison must have a value')] : []; diff --git a/superset-frontend/packages/superset-ui-core/test/validator/validateMaxValue.test.ts b/superset-frontend/packages/superset-ui-core/test/validator/validateMaxValue.test.ts index 6a8ed1642e7b..3a6698f06a82 100644 --- a/superset-frontend/packages/superset-ui-core/test/validator/validateMaxValue.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/validator/validateMaxValue.test.ts @@ -20,13 +20,13 @@ import { validateMaxValue } from '@superset-ui/core'; import './setup'; -test('validateInteger returns the warning message if invalid', () => { +test('validateMaxValue returns the warning message if invalid', () => { expect(validateMaxValue(10.1, 10)).toBeTruthy(); expect(validateMaxValue(1, 0)).toBeTruthy(); expect(validateMaxValue('2', 1)).toBeTruthy(); }); -test('validateInteger returns false if the input is valid', () => { +test('validateMaxValue returns false if the input is valid', () => { expect(validateMaxValue(0, 1)).toBeFalsy(); expect(validateMaxValue(10, 10)).toBeFalsy(); expect(validateMaxValue(undefined, 1)).toBeFalsy(); diff --git a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx index 4ab4d8139ba0..51fec319f83f 100644 --- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx @@ -51,6 +51,7 @@ import { SMART_DATE_ID, validateMaxValue, validateServerPagination, + withLabel, } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core'; import { isEmpty, last } from 'lodash'; @@ -384,7 +385,7 @@ const config: ControlPanelConfig = { description: t('Rows per page, 0 means no pagination'), visibility: ({ controls }: ControlPanelsContainerProps) => Boolean(controls?.server_pagination?.value), - validators: [validateInteger], + validators: [withLabel(validateInteger, t('Server Page Length'))], }, }, ], @@ -403,7 +404,7 @@ const config: ControlPanelConfig = { state?.common?.conf?.SQL_MAX_ROW, }), validators: [ - validateInteger, + withLabel(validateInteger, t('Row limit')), (v, state) => validateMaxValue( v, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx index 978b1427d6d9..839a2af354f7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Histogram/controlPanel.tsx @@ -17,7 +17,11 @@ * under the License. */ import { t } from '@apache-superset/core'; -import { validateInteger, validateNonEmpty } from '@superset-ui/core'; +import { + validateInteger, + validateNonEmpty, + withLabel, +} from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core'; import { ControlPanelConfig, @@ -66,7 +70,7 @@ const config: ControlPanelConfig = { default: 5, choices: formatSelectOptionsForRange(5, 20, 5), description: t('The number of bins for the histogram'), - validators: [validateInteger], + validators: [withLabel(validateInteger, t('Bins'))], }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index cdff93eb8e70..e4219d8f38d1 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -52,6 +52,7 @@ import { SMART_DATE_ID, validateMaxValue, validateServerPagination, + withLabel, } from '@superset-ui/core'; import { GenericDataType } from '@apache-superset/core/api/core'; import { isEmpty, last } from 'lodash'; @@ -407,7 +408,7 @@ const config: ControlPanelConfig = { description: t('Rows per page, 0 means no pagination'), visibility: ({ controls }: ControlPanelsContainerProps) => Boolean(controls?.server_pagination?.value), - validators: [validateInteger], + validators: [withLabel(validateInteger, t('Server Page Length'))], }, }, ], @@ -426,7 +427,7 @@ const config: ControlPanelConfig = { state?.common?.conf?.SQL_MAX_ROW, }), validators: [ - validateInteger, + withLabel(validateInteger, t('Row limit')), (v, state) => validateMaxValue( v, @@ -448,9 +449,6 @@ const config: ControlPanelConfig = { 'Limits the number of the rows that are computed in the query that is the source of the data used for this chart.', ), }, - override: { - default: 1000, - }, }, ], [ diff --git a/superset-frontend/src/explore/controlUtils/getControlState.ts b/superset-frontend/src/explore/controlUtils/getControlState.ts index 4a9e139ec4d6..a50d39313c89 100644 --- a/superset-frontend/src/explore/controlUtils/getControlState.ts +++ b/superset-frontend/src/explore/controlUtils/getControlState.ts @@ -122,7 +122,8 @@ export function applyMapStateToPropsToControl( } } // If no current value, set it as default - if (state.default && value === undefined) { + // Use loose equality to catch both null and undefined + if (state.default != null && value == null) { value = state.default; } // If a choice control went from multi=false to true, wrap value in array