feat: conditional formatting improvements in tables (#34330)

This commit is contained in:
SBIN2010
2025-08-19 01:13:16 +03:00
committed by GitHub
parent 1f482b42eb
commit 852adaa6cc
12 changed files with 571 additions and 76 deletions

View File

@@ -458,6 +458,10 @@ export enum Comparator {
BetweenOrEqual = '≤ x ≤',
BetweenOrLeftEqual = '≤ x <',
BetweenOrRightEqual = '< x ≤',
BeginsWith = 'begins with',
EndsWith = 'ends with',
Containing = 'containing',
NotContaining = 'not containing',
}
export const MultipleValueComparators = [
@@ -469,7 +473,7 @@ export const MultipleValueComparators = [
export type ConditionalFormattingConfig = {
operator?: Comparator;
targetValue?: number;
targetValue?: number | string;
targetValueLeft?: number;
targetValueRight?: number;
column?: string;
@@ -478,7 +482,7 @@ export type ConditionalFormattingConfig = {
export type ColorFormatters = {
column: string;
getColorFromValue: (value: number) => string | undefined;
getColorFromValue: (value: number | string) => string | undefined;
}[];
export default {};

View File

@@ -32,13 +32,18 @@ const MIN_OPACITY_BOUNDED = 0.05;
const MIN_OPACITY_UNBOUNDED = 0;
const MAX_OPACITY = 1;
export const getOpacity = (
value: number,
cutoffPoint: number,
extremeValue: number,
value: number | string,
cutoffPoint: number | string,
extremeValue: number | string,
minOpacity = MIN_OPACITY_BOUNDED,
maxOpacity = MAX_OPACITY,
) => {
if (extremeValue === cutoffPoint) {
if (
extremeValue === cutoffPoint ||
typeof cutoffPoint !== 'number' ||
typeof extremeValue !== 'number' ||
typeof value !== 'number'
) {
return maxOpacity;
}
return Math.min(
@@ -61,16 +66,16 @@ export const getColorFunction = (
targetValueRight,
colorScheme,
}: ConditionalFormattingConfig,
columnValues: number[],
columnValues: number[] | string[],
alpha?: boolean,
) => {
let minOpacity = MIN_OPACITY_BOUNDED;
const maxOpacity = MAX_OPACITY;
let comparatorFunction: (
value: number,
allValues: number[],
) => false | { cutoffValue: number; extremeValue: number };
value: number | string,
allValues: number[] | string[],
) => false | { cutoffValue: number | string; extremeValue: number | string };
if (operator === undefined || colorScheme === undefined) {
return () => undefined;
}
@@ -90,7 +95,10 @@ export const getColorFunction = (
switch (operator) {
case Comparator.None:
minOpacity = MIN_OPACITY_UNBOUNDED;
comparatorFunction = (value: number, allValues: number[]) => {
comparatorFunction = (value: number | string, allValues: number[]) => {
if (typeof value !== 'number') {
return { cutoffValue: value!, extremeValue: value! };
}
const cutoffValue = Math.min(...allValues);
const extremeValue = Math.max(...allValues);
return value >= cutoffValue && value <= extremeValue
@@ -100,49 +108,65 @@ export const getColorFunction = (
break;
case Comparator.GreaterThan:
comparatorFunction = (value: number, allValues: number[]) =>
value > targetValue!
? { cutoffValue: targetValue!, extremeValue: Math.max(...allValues) }
typeof targetValue === 'number' && value > targetValue!
? {
cutoffValue: targetValue!,
extremeValue: Math.max(...allValues),
}
: false;
break;
case Comparator.LessThan:
comparatorFunction = (value: number, allValues: number[]) =>
value < targetValue!
? { cutoffValue: targetValue!, extremeValue: Math.min(...allValues) }
typeof targetValue === 'number' && value < targetValue!
? {
cutoffValue: targetValue!,
extremeValue: Math.min(...allValues),
}
: false;
break;
case Comparator.GreaterOrEqual:
comparatorFunction = (value: number, allValues: number[]) =>
value >= targetValue!
? { cutoffValue: targetValue!, extremeValue: Math.max(...allValues) }
typeof targetValue === 'number' && value >= targetValue!
? {
cutoffValue: targetValue!,
extremeValue: Math.max(...allValues),
}
: false;
break;
case Comparator.LessOrEqual:
comparatorFunction = (value: number, allValues: number[]) =>
value <= targetValue!
? { cutoffValue: targetValue!, extremeValue: Math.min(...allValues) }
typeof targetValue === 'number' && value <= targetValue!
? {
cutoffValue: targetValue!,
extremeValue: Math.min(...allValues),
}
: false;
break;
case Comparator.Equal:
comparatorFunction = (value: number) =>
comparatorFunction = (value: number | string) =>
value === targetValue!
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
case Comparator.NotEqual:
comparatorFunction = (value: number, allValues: number[]) => {
if (value === targetValue!) {
return false;
if (typeof targetValue === 'number') {
if (value === targetValue!) {
return false;
}
const max = Math.max(...allValues);
const min = Math.min(...allValues);
return {
cutoffValue: targetValue!,
extremeValue:
Math.abs(targetValue! - min) > Math.abs(max - targetValue!)
? min
: max,
};
}
const max = Math.max(...allValues);
const min = Math.min(...allValues);
return {
cutoffValue: targetValue!,
extremeValue:
Math.abs(targetValue! - min) > Math.abs(max - targetValue!)
? min
: max,
};
return false;
};
break;
case Comparator.Between:
comparatorFunction = (value: number) =>
@@ -168,12 +192,38 @@ export const getColorFunction = (
? { cutoffValue: targetValueLeft!, extremeValue: targetValueRight! }
: false;
break;
case Comparator.BeginsWith:
comparatorFunction = (value: string) =>
isString(value) && value?.startsWith(targetValue as string)
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
case Comparator.EndsWith:
comparatorFunction = (value: string) =>
isString(value) && value?.endsWith(targetValue as string)
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
case Comparator.Containing:
comparatorFunction = (value: string) =>
isString(value) &&
value?.toLowerCase().includes((targetValue as string).toLowerCase())
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
case Comparator.NotContaining:
comparatorFunction = (value: string) =>
isString(value) &&
!value?.toLowerCase().includes((targetValue as string).toLowerCase())
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
default:
comparatorFunction = () => false;
break;
}
return (value: number) => {
return (value: number | string) => {
const compareResult = comparatorFunction(value, columnValues);
if (compareResult === false) return undefined;
const { cutoffValue, extremeValue } = compareResult;
@@ -218,3 +268,7 @@ export const getColorFormatters = memoizeOne(
[],
) ?? [],
);
function isString(value: unknown) {
return typeof value === 'string';
}

View File

@@ -32,6 +32,9 @@ const mockData = [
];
const countValues = mockData.map(row => row.count);
const strData = [{ name: 'Brian' }, { name: 'Carlos' }, { name: 'Diana' }];
const strValues = strData.map(row => row.name);
describe('round', () => {
it('round', () => {
expect(round(1)).toEqual(1);
@@ -339,6 +342,90 @@ describe('getColorFunction()', () => {
expect(colorFunction(50)).toBeUndefined();
expect(colorFunction(100)).toBeUndefined();
});
it('getColorFunction BeginsWith', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.BeginsWith,
targetValue: 'C',
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Brian')).toBeUndefined();
expect(colorFunction('Carlos')).toEqual('#FF0000FF');
});
it('getColorFunction EndsWith', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.EndsWith,
targetValue: 'n',
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Carlos')).toBeUndefined();
expect(colorFunction('Brian')).toEqual('#FF0000FF');
});
it('getColorFunction Containing', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.Containing,
targetValue: 'o',
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Diana')).toBeUndefined();
expect(colorFunction('Carlos')).toEqual('#FF0000FF');
});
it('getColorFunction NotContaining', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.NotContaining,
targetValue: 'i',
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Diana')).toBeUndefined();
expect(colorFunction('Carlos')).toEqual('#FF0000FF');
});
it('getColorFunction Equal', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.Equal,
targetValue: 'Diana',
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Carlos')).toBeUndefined();
expect(colorFunction('Diana')).toEqual('#FF0000FF');
});
it('getColorFunction None', () => {
const colorFunction = getColorFunction(
{
operator: Comparator.None,
colorScheme: '#FF0000',
column: 'name',
},
strValues,
);
expect(colorFunction('Diana')).toEqual('#FF0000FF');
expect(colorFunction('Carlos')).toEqual('#FF0000FF');
expect(colorFunction('Brian')).toEqual('#FF0000FF');
});
});
describe('getColorFormatters()', () => {
@@ -388,4 +475,47 @@ describe('getColorFormatters()', () => {
const colorFormatters = getColorFormatters(undefined, mockData);
expect(colorFormatters.length).toEqual(0);
});
it('correct column string config', () => {
const columnConfigString = [
{
operator: Comparator.BeginsWith,
targetValue: 'D',
colorScheme: '#FF0000',
column: 'name',
},
{
operator: Comparator.EndsWith,
targetValue: 'n',
colorScheme: '#FF0000',
column: 'name',
},
{
operator: Comparator.Containing,
targetValue: 'o',
colorScheme: '#FF0000',
column: 'name',
},
{
operator: Comparator.NotContaining,
targetValue: 'i',
colorScheme: '#FF0000',
column: 'name',
},
];
const colorFormatters = getColorFormatters(columnConfigString, strData);
expect(colorFormatters.length).toEqual(4);
expect(colorFormatters[0].column).toEqual('name');
expect(colorFormatters[0].getColorFromValue('Diana')).toEqual('#FF0000FF');
expect(colorFormatters[1].column).toEqual('name');
expect(colorFormatters[1].getColorFromValue('Brian')).toEqual('#FF0000FF');
expect(colorFormatters[2].column).toEqual('name');
expect(colorFormatters[2].getColorFromValue('Carlos')).toEqual('#FF0000FF');
expect(colorFormatters[3].column).toEqual('name');
expect(colorFormatters[3].getColorFromValue('Carlos')).toEqual('#FF0000FF');
});
});

View File

@@ -722,6 +722,8 @@ const config: ControlPanelConfig = {
label: Array.isArray(verboseMap)
? colname
: (verboseMap[colname] ?? colname),
dataType:
colnames && coltypes[colnames?.indexOf(colname)],
}))
: [];
const columnOptions = explore?.controls?.time_compare?.value

View File

@@ -110,6 +110,8 @@ export default {
(Array.isArray(verboseMap)
? verboseMap[colname as number]
: verboseMap[colname as string]) ?? colname,
dataType:
colnames && coltypes[colnames?.indexOf(colname)],
}))
: [];
return {

View File

@@ -413,6 +413,8 @@ const config: ControlPanelConfig = {
? (explore?.datasource as Dataset)?.verbose_map
: (explore?.datasource?.columns ?? {});
const chartStatus = chart?.chartStatus;
const { colnames, coltypes } =
chart?.queriesResponse?.[0] ?? {};
const metricColumn = values.map(value => {
if (typeof value === 'string') {
return {
@@ -420,9 +422,15 @@ const config: ControlPanelConfig = {
label: Array.isArray(verboseMap)
? value
: verboseMap[value],
dataType: colnames && coltypes[colnames?.indexOf(value)],
};
}
return { value: value.label, label: value.label };
return {
value: value.label,
label: value.label,
dataType:
colnames && coltypes[colnames?.indexOf(value.label)],
};
});
return {
removeIrrelevantConditions: chartStatus === 'success',

View File

@@ -726,7 +726,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
const {
key,
label: originalLabel,
isNumeric,
dataType,
isMetric,
isPercentMetric,
@@ -771,7 +770,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
const { truncateLongCells } = config;
const hasColumnColorFormatters =
isNumeric &&
Array.isArray(columnColorFormatters) &&
columnColorFormatters.length > 0;

View File

@@ -772,17 +772,22 @@ const config: ControlPanelConfig = {
chart?.queriesResponse?.[0] ?? {};
const numericColumns =
Array.isArray(colnames) && Array.isArray(coltypes)
? colnames
.filter(
(colname: string, index: number) =>
coltypes[index] === GenericDataType.Numeric,
)
.map((colname: string) => ({
value: colname,
label: Array.isArray(verboseMap)
? colname
: (verboseMap[colname] ?? colname),
}))
? colnames.reduce((acc, colname, index) => {
if (
coltypes[index] === GenericDataType.Numeric ||
(!explore?.controls?.time_compare?.value &&
coltypes[index] === GenericDataType.String)
) {
acc.push({
value: colname,
label: Array.isArray(verboseMap)
? colname
: (verboseMap[colname] ?? colname),
dataType: coltypes[index],
});
}
return acc;
}, [])
: [];
const columnOptions = explore?.controls?.time_compare?.value
? processComparisonColumns(

View File

@@ -571,6 +571,191 @@ describe('plugin-chart-table', () => {
);
cells = document.querySelectorAll('td');
});
it('render color with string column color formatter(operator begins with)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: 'begins with',
targetValue: 'J',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
);
});
it('render color with string column color formatter (operator ends with)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: 'ends with',
targetValue: 'ia',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Maria')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe('');
});
it('render color with string column color formatter (operator containing)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: 'containing',
targetValue: 'c',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe('');
});
it('render color with string column color formatter (operator not containing)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: 'not containing',
targetValue: 'i',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
);
});
it('render color with string column color formatter (operator =)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: '=',
targetValue: 'Joe',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
);
});
it('render color with string column color formatter (operator None)', () => {
render(
ProviderWrapper({
children: (
<TableChart
{...transformProps({
...testData.advanced,
rawFormData: {
...testData.advanced.rawFormData,
conditional_formatting: [
{
colorScheme: '#ACE1C4',
column: 'name',
operator: 'None',
},
],
},
})}
/>
),
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'rgba(172, 225, 196, 1)',
);
expect(getComputedStyle(screen.getByText('Maria')).background).toBe(
'rgba(172, 225, 196, 1)',
);
});
});
});
});

View File

@@ -24,13 +24,19 @@ import {
} from 'spec/helpers/testing-library';
import { Comparator } from '@superset-ui/chart-controls';
import { ColorSchemeEnum } from '@superset-ui/plugin-chart-table';
import { GenericDataType } from '@superset-ui/core';
import { FormattingPopoverContent } from './FormattingPopoverContent';
const mockOnChange = jest.fn();
const columns = [
{ label: 'Column 1', value: 'column1' },
{ label: 'Column 2', value: 'column2' },
{ label: 'Column 1', value: 'column1', dataType: GenericDataType.Numeric },
{ label: 'Column 2', value: 'column2', dataType: GenericDataType.Numeric },
];
const columnsStringType = [
{ label: 'Column 1', value: 'column1', dataType: GenericDataType.String },
{ label: 'Column 2', value: 'column2', dataType: GenericDataType.String },
];
const extraColorChoices = [
@@ -119,3 +125,19 @@ test('renders None for operator when Green for increase is selected', async () =
// Assert that the operator is set to 'None'
expect(screen.getByText(/none/i)).toBeInTheDocument();
});
test('displays the correct input fields based on the selected string type operator', async () => {
render(
<FormattingPopoverContent
onChange={mockOnChange}
columns={columnsStringType}
extraColorChoices={extraColorChoices}
/>,
);
fireEvent.change(screen.getAllByLabelText('Operator')[0], {
target: { value: Comparator.BeginsWith },
});
fireEvent.click(await screen.findByTitle('begins with'));
expect(await screen.findByLabelText('Target value')).toBeInTheDocument();
});

View File

@@ -16,8 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useState } from 'react';
import { styled, SupersetTheme, t, useTheme } from '@superset-ui/core';
import { useMemo, useState, useEffect } from 'react';
import {
GenericDataType,
styled,
SupersetTheme,
t,
useTheme,
} from '@superset-ui/core';
import {
Comparator,
MultipleValueComparators,
@@ -28,6 +34,7 @@ import {
Form,
FormItem,
InputNumber,
Input,
Col,
Row,
type FormProps,
@@ -45,6 +52,10 @@ const FullWidthInputNumber = styled(InputNumber)`
width: 100%;
`;
const FullWidthInput = styled(Input)`
width: 100%;
`;
const JustifyEnd = styled.div`
display: flex;
justify-content: flex-end;
@@ -70,6 +81,15 @@ const operatorOptions = [
{ value: Comparator.BetweenOrRightEqual, label: '< x ≤' },
];
const stringOperatorOptions = [
{ value: Comparator.None, label: t('None') },
{ value: Comparator.Equal, label: '=' },
{ value: Comparator.BeginsWith, label: t('begins with') },
{ value: Comparator.EndsWith, label: t('ends with') },
{ value: Comparator.Containing, label: t('containing') },
{ value: Comparator.NotContaining, label: t('not containing') },
];
const targetValueValidator =
(
compare: (targetValue: number, compareValue: number) => boolean,
@@ -132,24 +152,41 @@ const shouldFormItemUpdate = (
isOperatorMultiValue(prevValues.operator) !==
isOperatorMultiValue(currentValues.operator);
const renderOperator = ({ showOnlyNone }: { showOnlyNone?: boolean } = {}) => (
<FormItem
name="operator"
label={t('Operator')}
rules={rulesRequired}
initialValue={operatorOptions[0].value}
>
<Select
ariaLabel={t('Operator')}
options={showOnlyNone ? [operatorOptions[0]] : operatorOptions}
/>
</FormItem>
);
const renderOperator = ({
showOnlyNone,
columnType,
}: { showOnlyNone?: boolean; columnType?: GenericDataType } = {}) => {
const options =
columnType === GenericDataType.String
? stringOperatorOptions
: operatorOptions;
const renderOperatorFields = ({ getFieldValue }: GetFieldValue) =>
isOperatorNone(getFieldValue('operator')) ? (
return (
<FormItem
name="operator"
label={t('Operator')}
rules={rulesRequired}
initialValue={options[0].value}
>
<Select
ariaLabel={t('Operator')}
options={showOnlyNone ? [options[0]] : options}
/>
</FormItem>
);
};
const renderOperatorFields = (
{ getFieldValue }: GetFieldValue,
columnType?: GenericDataType,
) => {
const columnTypeString = columnType === GenericDataType.String;
const operatorColSpan = columnTypeString ? 8 : 6;
const valueColSpan = columnTypeString ? 16 : 18;
return isOperatorNone(getFieldValue('operator')) ? (
<Row gutter={12}>
<Col span={6}>{renderOperator()}</Col>
<Col span={operatorColSpan}>{renderOperator({ columnType })}</Col>
</Row>
) : isOperatorMultiValue(getFieldValue('operator')) ? (
<Row gutter={12}>
@@ -165,7 +202,7 @@ const renderOperatorFields = ({ getFieldValue }: GetFieldValue) =>
<FullWidthInputNumber />
</FormItem>
</Col>
<Col span={6}>{renderOperator()}</Col>
<Col span={6}>{renderOperator({ columnType })}</Col>
<Col span={9}>
<FormItem
name="targetValueRight"
@@ -181,18 +218,19 @@ const renderOperatorFields = ({ getFieldValue }: GetFieldValue) =>
</Row>
) : (
<Row gutter={12}>
<Col span={6}>{renderOperator()}</Col>
<Col span={18}>
<Col span={operatorColSpan}>{renderOperator({ columnType })}</Col>
<Col span={valueColSpan}>
<FormItem
name="targetValue"
label={t('Target value')}
rules={rulesRequired}
>
<FullWidthInputNumber />
{columnTypeString ? <FullWidthInput /> : <FullWidthInputNumber />}
</FormItem>
</Col>
</Row>
);
};
export const FormattingPopoverContent = ({
config,
@@ -202,10 +240,11 @@ export const FormattingPopoverContent = ({
}: {
config?: ConditionalFormattingConfig;
onChange: (config: ConditionalFormattingConfig) => void;
columns: { label: string; value: string }[];
columns: { label: string; value: string; dataType: GenericDataType }[];
extraColorChoices?: { label: string; value: string }[];
}) => {
const theme = useTheme();
const [form] = Form.useForm();
const colorScheme = colorSchemeOptions(theme);
const [showOperatorFields, setShowOperatorFields] = useState(
config === undefined ||
@@ -218,8 +257,45 @@ export const FormattingPopoverContent = ({
);
};
const [column, setColumn] = useState<string>(
config?.column || columns[0]?.value,
);
const [previousColumnType, setPreviousColumnType] = useState<
GenericDataType | undefined
>();
const columnType = useMemo(
() => columns.find(item => item.value === column)?.dataType,
[columns, column],
);
const handleColumnChange = (value: string) => {
const newColumnType = columns.find(item => item.value === value)?.dataType;
if (newColumnType !== previousColumnType) {
const defaultOperator =
newColumnType === GenericDataType.String
? stringOperatorOptions[0].value
: operatorOptions[0].value;
form.setFieldsValue({
operator: defaultOperator,
});
}
setColumn(value);
setPreviousColumnType(newColumnType);
};
useEffect(() => {
if (column && !previousColumnType) {
setPreviousColumnType(
columns.find(item => item.value === column)?.dataType,
);
}
}, [column, columns, previousColumnType]);
return (
<Form
form={form}
onFinish={onChange}
initialValues={config}
requiredMark="optional"
@@ -233,7 +309,13 @@ export const FormattingPopoverContent = ({
rules={rulesRequired}
initialValue={columns[0]?.value}
>
<Select ariaLabel={t('Select column')} options={columns} />
<Select
ariaLabel={t('Select column')}
options={columns}
onChange={value => {
handleColumnChange(value as string);
}}
/>
</FormItem>
</Col>
<Col span={12}>
@@ -253,10 +335,12 @@ export const FormattingPopoverContent = ({
</Row>
<FormItem noStyle shouldUpdate={shouldFormItemUpdate}>
{showOperatorFields ? (
renderOperatorFields
(props: GetFieldValue) => renderOperatorFields(props, columnType)
) : (
<Row gutter={12}>
<Col span={6}>{renderOperator({ showOnlyNone: true })}</Col>
<Col span={6}>
{renderOperator({ showOnlyNone: true, columnType })}
</Col>
</Row>
)}
</FormItem>

View File

@@ -20,6 +20,7 @@
import { ReactNode } from 'react';
import { PopoverProps } from '@superset-ui/core/components/Popover';
import { Comparator, ControlComponentProps } from '@superset-ui/chart-controls';
import { GenericDataType } from '@superset-ui/core';
export type ConditionalFormattingConfig = {
operator?: Comparator;
@@ -33,7 +34,7 @@ export type ConditionalFormattingConfig = {
export type ConditionalFormattingControlProps = ControlComponentProps<
ConditionalFormattingConfig[]
> & {
columnOptions: { label: string; value: string }[];
columnOptions: { label: string; value: string; dataType: GenericDataType }[];
removeIrrelevantConditions: boolean;
verboseMap: Record<string, string>;
label: string;
@@ -42,7 +43,7 @@ export type ConditionalFormattingControlProps = ControlComponentProps<
};
export type FormattingPopoverProps = PopoverProps & {
columns: { label: string; value: string }[];
columns: { label: string; value: string; dataType: GenericDataType }[];
onChange: (value: ConditionalFormattingConfig) => void;
config?: ConditionalFormattingConfig;
title: string;