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 (
+