Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -106,8 +106,8 @@ import getControlItemsMap from './getControlItemsMap';
import RemovedFilter from './RemovedFilter';
import { useBackendFormUpdate, useDefaultValue } from './state';
import {
hasTemporalColumns,
getTimeGrainOptions,
hasTemporalColumns,
isValidFilterValue,
mostUsedDataset,
setNativeFilterFieldValues,
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -454,7 +448,7 @@ const FiltersConfigForm = (
formFilter?.filterType,
);

const canDependOnOtherFilters = TYPES_SUPPORT_DEPENDENCIES.includes(
const canDependOnOtherFilters = filterSupportsDependencies(
formFilter?.filterType,
);

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -956,16 +950,6 @@ const FiltersConfigForm = (
label: name || pluginKey,
};
})}
optionRender={option => (
<span
data-test={getOptionDataTest(
'customization-type-option',
option.value,
)}
>
{option.label || option.value}
</span>
)}
onChange={value => {
setNativeFilterFieldValues(form, filterId, {
filterType: value,
Expand Down Expand Up @@ -1024,16 +1008,6 @@ const FiltersConfigForm = (
disabled: isDisabled,
};
})}
optionRender={option => (
<span
data-test={getOptionDataTest(
'filter-type-option',
option.value,
)}
>
{option.label || option.value}
</span>
)}
onChange={value => {
setNativeFilterFieldValues(form, filterId, {
filterType: value,
Expand Down Expand Up @@ -1463,9 +1437,10 @@ const FiltersConfigForm = (
)}
onChange={value => {
const previous =
form.getFieldValue('filters')?.[
filterId
].controlValues || {};
form.getFieldValue(
'filters',
)?.[filterId]?.controlValues ||
{};
setNativeFilterFieldValues(
form,
filterId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 (
<StyledLabel>
{resolvedLabel}
{resolvedDescription != null && (
<>
&nbsp;
<InfoTooltip placement="top" tooltip={resolvedDescription} />
</>
)}
</StyledLabel>
);
}

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 (
<Select
allowClear
ariaLabel={t('Column')}
loading={loading}
value={value ?? undefined}
onChange={val => onChange?.(typeof val === 'string' ? val : null)}
options={options.map(col => ({ label: col, value: col }))}
placeholder={t('None — show filter values as labels')}
showSearch
/>
);
}

export default function getControlItemsMap({
expanded,
datasetId,
Expand All @@ -81,6 +184,10 @@ export default function getControlItemsMap({
const controlPanelRegistry = getChartControlPanelRegistry();
const controlItems =
getControlItems(controlPanelRegistry.get(filterType)) ?? [];
const notifyChange = () => {
forceUpdate();
formChanged();
};
const mapControlItems: Record<
string,
{ element: ReactNode; checked: boolean }
Expand All @@ -96,10 +203,11 @@ export default function getControlItemsMap({
mainControlItem?.name === 'groupby',
)
.forEach(mainControlItem => {
const initialValue =
filterToEdit?.controlValues?.[mainControlItem.name] ??
customizationToEdit?.controlValues?.[mainControlItem.name] ??
mainControlItem?.config?.default;
const initialValue = resolveInitialValue(
mainControlItem,
filterToEdit,
customizationToEdit,
);
const initColumn =
customizationToEdit?.targets?.[0]?.column?.name ??
filterToEdit?.targets?.[0]?.column?.name;
Expand All @@ -120,11 +228,9 @@ export default function getControlItemsMap({
name={['filters', filterId, 'column']}
initialValue={initColumn}
label={
<StyledLabel>
{typeof mainControlItem.config?.label === 'function'
? (mainControlItem.config.label as Function)()
: mainControlItem.config?.label || t('Column')}
</StyledLabel>
<ControlLabel
label={mainControlItem.config?.label || t('Column')}
/>
}
rules={[
{
Expand All @@ -150,8 +256,7 @@ export default function getControlItemsMap({
setNativeFilterFieldValues(form, filterId, {
defaultDataMask: null,
});
forceUpdate();
formChanged();
notifyChange();
}}
/>
</StyledFormItem>
Expand All @@ -171,10 +276,11 @@ export default function getControlItemsMap({
controlItem.name !== 'operatorType',
)
.forEach(controlItem => {
const initialValue =
filterToEdit?.controlValues?.[controlItem.name] ??
customizationToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default;
const initialValue = resolveInitialValue(
controlItem,
filterToEdit,
customizationToEdit,
);
const element = (
<>
<CleanFormItem
Expand Down Expand Up @@ -217,33 +323,50 @@ export default function getControlItemsMap({
defaultDataMask: null,
});
}
formChanged();
forceUpdate();
notifyChange();
}}
>
<>
{typeof controlItem.config.label === 'function'
? (controlItem.config.label as Function)()
: controlItem.config.label}
&nbsp;
{controlItem.config.description && (
<InfoTooltip
placement="top"
tooltip={
typeof controlItem.config.description === 'function'
? (controlItem.config.description as Function)()
: (controlItem.config.description as React.ReactNode)
}
/>
)}
</>
<ControlLabel
label={controlItem.config.label}
description={controlItem.config.description}
/>
</Checkbox>
</StyledRowFormItem>
</Tooltip>
</>
);
mapControlItems[controlItem.name] = { element, checked: initialValue };
});

// Render plugin-declared column-picker controls using config hooks
controlItems
.filter((item: CustomControlItem) => item?.config?.isColumnSelect === true)
.forEach(controlItem => {
const initialValue = resolveInitialValue(
controlItem,
filterToEdit,
customizationToEdit,
);
const element = (
<StyledFormItem
expanded={expanded}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={initialValue}
label={
<ControlLabel
label={controlItem.config?.label}
description={controlItem.config?.description}
/>
}
>
<DatasetColumnSelect datasetId={datasetId} onChange={notifyChange} />
Comment thread
ashah65 marked this conversation as resolved.
</StyledFormItem>
);
mapMainControlItems[controlItem.name] = {
element,
checked: initialValue,
};
});
return {
controlItems: mapControlItems,
mainControlItems: mapMainControlItems,
Expand Down
Loading