diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index c59fec2483b5..616a15048a85 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -93,7 +93,7 @@ import { } from 'src/dashboard/components/nativeFilters/utils'; import { DatasetSelectLabel } from 'src/features/datasets/DatasetSelectLabel'; import { - ALLOW_DEPENDENCIES as TYPES_SUPPORT_DEPENDENCIES, + filterSupportsDependencies, getFiltersConfigModalTestId, } from '../FiltersConfigModal'; import { FilterRemoval, NativeFiltersForm } from '../types'; @@ -106,8 +106,8 @@ import getControlItemsMap from './getControlItemsMap'; import RemovedFilter from './RemovedFilter'; import { useBackendFormUpdate, useDefaultValue } from './state'; import { - hasTemporalColumns, getTimeGrainOptions, + hasTemporalColumns, isValidFilterValue, mostUsedDataset, setNativeFilterFieldValues, @@ -264,12 +264,6 @@ export interface FiltersConfigFormProps { const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range']; -const getOptionDataTest = ( - prefix: string, - value: string | number | undefined, -) => - `${prefix}-${String(value ?? 'undefined').replace(/[^a-zA-Z0-9_-]/g, '-')}`; - // TODO: Rename the filter plugins and remove this mapping const FILTER_TYPE_NAME_MAPPING = { [t('Select filter')]: t('Value'), @@ -454,7 +448,7 @@ const FiltersConfigForm = ( formFilter?.filterType, ); - const canDependOnOtherFilters = TYPES_SUPPORT_DEPENDENCIES.includes( + const canDependOnOtherFilters = filterSupportsDependencies( formFilter?.filterType, ); @@ -626,7 +620,7 @@ const FiltersConfigForm = ( !mainControlItems.groupby; const onSortChanged = (value: boolean | undefined) => { - const previous = form.getFieldValue('filters')?.[filterId].controlValues; + const previous = form.getFieldValue('filters')?.[filterId]?.controlValues; setNativeFilterFieldValues(form, filterId, { controlValues: { ...previous, @@ -637,7 +631,7 @@ const FiltersConfigForm = ( }; const onEnableSingleValueChanged = (value: SingleValueType | undefined) => { - const previous = form.getFieldValue('filters')?.[filterId].controlValues; + const previous = form.getFieldValue('filters')?.[filterId]?.controlValues; setNativeFilterFieldValues(form, filterId, { controlValues: { ...previous, @@ -663,7 +657,7 @@ const FiltersConfigForm = ( }, [formFilter?.column, datasetDetails?.columns]); const onOperatorTypeChanged = (value: SelectFilterOperatorType) => { - const previous = form.getFieldValue('filters')?.[filterId].controlValues; + const previous = form.getFieldValue('filters')?.[filterId]?.controlValues; setNativeFilterFieldValues(form, filterId, { controlValues: { ...previous, @@ -956,16 +950,6 @@ const FiltersConfigForm = ( label: name || pluginKey, }; })} - optionRender={option => ( - - {option.label || option.value} - - )} onChange={value => { setNativeFilterFieldValues(form, filterId, { filterType: value, @@ -1024,16 +1008,6 @@ const FiltersConfigForm = ( disabled: isDisabled, }; })} - optionRender={option => ( - - {option.label || option.value} - - )} onChange={value => { setNativeFilterFieldValues(form, filterId, { filterType: value, @@ -1463,9 +1437,10 @@ const FiltersConfigForm = ( )} onChange={value => { const previous = - form.getFieldValue('filters')?.[ - filterId - ].controlValues || {}; + form.getFieldValue( + 'filters', + )?.[filterId]?.controlValues || + {}; setNativeFilterFieldValues( form, filterId, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx index 7b8f95777230..c0de1209d5dd 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/getControlItemsMap.tsx @@ -17,11 +17,14 @@ * under the License. */ import { CustomControlItem } from '@superset-ui/chart-controls'; -import { ReactNode } from 'react'; +import { ReactNode, useState, useEffect } from 'react'; +import rison from 'rison'; +import { cachedSupersetGet } from 'src/utils/cachedSupersetGet'; import { Checkbox, FormItem, InfoTooltip, + Select, Tooltip, type FormInstance, } from '@superset-ui/core/components'; @@ -64,6 +67,106 @@ const CleanFormItem = styled(FormItem)` margin-bottom: 0; `; +/** Resolves the saved or default initial value for a control. */ +function resolveInitialValue( + controlItem: CustomControlItem, + filterToEdit?: ControlItemsProps['filterToEdit'], + customizationToEdit?: ControlItemsProps['customizationToEdit'], +) { + return ( + filterToEdit?.controlValues?.[controlItem.name] ?? + customizationToEdit?.controlValues?.[controlItem.name] ?? + controlItem?.config?.default ?? + null + ); +} + +/** Renders a StyledLabel with an optional description tooltip. */ +function ControlLabel({ + label, + description, +}: { + label?: ReactNode | (() => ReactNode); + description?: ReactNode | (() => ReactNode); +}) { + const resolvedLabel = + typeof label === 'function' ? (label as () => ReactNode)() : label; + const resolvedDescription = + typeof description === 'function' + ? (description as () => ReactNode)() + : description; + return ( + + {resolvedLabel} + {resolvedDescription != null && ( + <> +   + + + )} + + ); +} + +function DatasetColumnSelect({ + datasetId, + value, + onChange, +}: { + datasetId?: number; + value?: string | null; + onChange?: (value: string | null) => void; +}) { + const [{ loadedForId, fetchedColumns }, setFetchState] = useState<{ + loadedForId?: number; + fetchedColumns: string[]; + }>({ fetchedColumns: [] }); + + const loading = !!(datasetId && loadedForId !== datasetId); + const options = loadedForId === datasetId ? fetchedColumns : []; + + useEffect(() => { + if (!datasetId) return; + let cancelled = false; + cachedSupersetGet({ + endpoint: `/api/v1/dataset/${datasetId}?q=${rison.encode({ + columns: ['columns.column_name'], + })}`, + }) + .then(({ json: { result } }) => { + if (!cancelled) { + setFetchState({ + loadedForId: datasetId, + fetchedColumns: result.columns + .map((col: { column_name: string }) => col.column_name) + .filter(Boolean), + }); + } + }) + .catch(() => { + if (!cancelled) { + setFetchState({ loadedForId: datasetId, fetchedColumns: [] }); + } + }); + return () => { + cancelled = true; + }; + }, [datasetId]); + + return ( +