mirror of
https://github.com/apache/superset.git
synced 2026-04-19 16:14:52 +00:00
feat(table): Gradient Toggle (#36280)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
e1a8886d32
commit
4479614754
@@ -484,6 +484,7 @@ export type ConditionalFormattingConfig = {
|
||||
colorScheme?: string;
|
||||
toAllRow?: boolean;
|
||||
toTextColor?: boolean;
|
||||
useGradient?: boolean;
|
||||
};
|
||||
|
||||
export type ColorFormatters = {
|
||||
|
||||
@@ -69,6 +69,7 @@ export const getColorFunction = (
|
||||
targetValueLeft,
|
||||
targetValueRight,
|
||||
colorScheme,
|
||||
useGradient,
|
||||
}: ConditionalFormattingConfig,
|
||||
columnValues: number[] | string[] | (boolean | null)[],
|
||||
alpha?: boolean,
|
||||
@@ -256,6 +257,13 @@ export const getColorFunction = (
|
||||
const compareResult = comparatorFunction(value, columnValues);
|
||||
if (compareResult === false) return undefined;
|
||||
const { cutoffValue, extremeValue } = compareResult;
|
||||
|
||||
// If useGradient is explicitly false, return solid color
|
||||
if (useGradient === false) {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
// Otherwise apply gradient (default behavior for backward compatibility)
|
||||
if (alpha === undefined || alpha) {
|
||||
return addAlpha(
|
||||
colorScheme,
|
||||
|
||||
@@ -596,6 +596,104 @@ test('correct column string config', () => {
|
||||
expect(colorFormatters[3].getColorFromValue('Carlos')).toEqual('#FF0000FF');
|
||||
});
|
||||
|
||||
test('getColorFunction with useGradient false returns solid color', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
operator: Comparator.GreaterOrEqual,
|
||||
targetValue: 50,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
useGradient: false,
|
||||
},
|
||||
countValues,
|
||||
);
|
||||
// When useGradient is false, should return solid color without opacity
|
||||
expect(colorFunction(50)).toEqual('#FF0000');
|
||||
expect(colorFunction(100)).toEqual('#FF0000');
|
||||
expect(colorFunction(0)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getColorFunction with useGradient true returns gradient color', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
operator: Comparator.GreaterOrEqual,
|
||||
targetValue: 50,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
useGradient: true,
|
||||
},
|
||||
countValues,
|
||||
);
|
||||
// When useGradient is true, should return gradient color with opacity
|
||||
expect(colorFunction(50)).toEqual('#FF00000D');
|
||||
expect(colorFunction(100)).toEqual('#FF0000FF');
|
||||
expect(colorFunction(0)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getColorFunction with useGradient undefined defaults to gradient (backward compatibility)', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
operator: Comparator.GreaterOrEqual,
|
||||
targetValue: 50,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
// useGradient is undefined
|
||||
},
|
||||
countValues,
|
||||
);
|
||||
// When useGradient is undefined, should default to gradient for backward compatibility
|
||||
expect(colorFunction(50)).toEqual('#FF00000D');
|
||||
expect(colorFunction(100)).toEqual('#FF0000FF');
|
||||
expect(colorFunction(0)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getColorFunction with useGradient false and None operator returns solid color', () => {
|
||||
const colorFunction = getColorFunction(
|
||||
{
|
||||
operator: Comparator.None,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
useGradient: false,
|
||||
},
|
||||
countValues,
|
||||
);
|
||||
// When useGradient is false, all matching values should return solid color
|
||||
expect(colorFunction(20)).toBeUndefined();
|
||||
expect(colorFunction(50)).toEqual('#FF0000');
|
||||
expect(colorFunction(75)).toEqual('#FF0000');
|
||||
expect(colorFunction(100)).toEqual('#FF0000');
|
||||
expect(colorFunction(120)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getColorFormatters with useGradient flag', () => {
|
||||
const columnConfig = [
|
||||
{
|
||||
operator: Comparator.GreaterThan,
|
||||
targetValue: 50,
|
||||
colorScheme: '#FF0000',
|
||||
column: 'count',
|
||||
useGradient: false,
|
||||
},
|
||||
{
|
||||
operator: Comparator.GreaterThan,
|
||||
targetValue: 50,
|
||||
colorScheme: '#00FF00',
|
||||
column: 'count',
|
||||
useGradient: true,
|
||||
},
|
||||
];
|
||||
const colorFormatters = getColorFormatters(columnConfig, mockData);
|
||||
expect(colorFormatters.length).toEqual(2);
|
||||
|
||||
// First formatter with useGradient: false should return solid color
|
||||
expect(colorFormatters[0].column).toEqual('count');
|
||||
expect(colorFormatters[0].getColorFromValue(100)).toEqual('#FF0000');
|
||||
|
||||
// Second formatter with useGradient: true should return gradient color
|
||||
expect(colorFormatters[1].column).toEqual('count');
|
||||
expect(colorFormatters[1].getColorFromValue(100)).toEqual('#00FF00FF');
|
||||
});
|
||||
|
||||
test('correct column boolean config', () => {
|
||||
const columnConfigBoolean = [
|
||||
{
|
||||
|
||||
@@ -1216,6 +1216,136 @@ describe('plugin-chart-table', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('render color with useGradient false returns solid color', () => {
|
||||
render(
|
||||
ProviderWrapper({
|
||||
children: (
|
||||
<TableChart
|
||||
{...transformProps({
|
||||
...testData.advanced,
|
||||
rawFormData: {
|
||||
...testData.advanced.rawFormData,
|
||||
conditional_formatting: [
|
||||
{
|
||||
colorScheme: '#ACE1C4',
|
||||
column: 'sum__num',
|
||||
operator: '>',
|
||||
targetValue: 2467,
|
||||
useGradient: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// When useGradient is false, should return solid color (no opacity variation)
|
||||
// The color should be the same for all matching values
|
||||
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
|
||||
'rgb(172, 225, 196)',
|
||||
);
|
||||
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
|
||||
});
|
||||
|
||||
test('render color with useGradient true returns gradient color', () => {
|
||||
render(
|
||||
ProviderWrapper({
|
||||
children: (
|
||||
<TableChart
|
||||
{...transformProps({
|
||||
...testData.advanced,
|
||||
rawFormData: {
|
||||
...testData.advanced.rawFormData,
|
||||
conditional_formatting: [
|
||||
{
|
||||
colorScheme: '#ACE1C4',
|
||||
column: 'sum__num',
|
||||
operator: '>',
|
||||
targetValue: 2467,
|
||||
useGradient: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// When useGradient is true, should return gradient color with opacity
|
||||
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
|
||||
'rgba(172, 225, 196, 1)',
|
||||
);
|
||||
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
|
||||
});
|
||||
|
||||
test('render color with useGradient undefined defaults to gradient (backward compatibility)', () => {
|
||||
render(
|
||||
ProviderWrapper({
|
||||
children: (
|
||||
<TableChart
|
||||
{...transformProps({
|
||||
...testData.advanced,
|
||||
rawFormData: {
|
||||
...testData.advanced.rawFormData,
|
||||
conditional_formatting: [
|
||||
{
|
||||
colorScheme: '#ACE1C4',
|
||||
column: 'sum__num',
|
||||
operator: '>',
|
||||
targetValue: 2467,
|
||||
// useGradient is undefined
|
||||
},
|
||||
],
|
||||
},
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// When useGradient is undefined, should default to gradient for backward compatibility
|
||||
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
|
||||
'rgba(172, 225, 196, 1)',
|
||||
);
|
||||
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
|
||||
});
|
||||
|
||||
test('render color with useGradient false and None operator returns solid color', () => {
|
||||
render(
|
||||
ProviderWrapper({
|
||||
children: (
|
||||
<TableChart
|
||||
{...transformProps({
|
||||
...testData.advanced,
|
||||
rawFormData: {
|
||||
...testData.advanced.rawFormData,
|
||||
conditional_formatting: [
|
||||
{
|
||||
colorScheme: '#ACE1C4',
|
||||
column: 'sum__num',
|
||||
operator: 'None',
|
||||
useGradient: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// When useGradient is false with None operator, all values should have solid color
|
||||
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
|
||||
'rgb(172, 225, 196)',
|
||||
);
|
||||
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
|
||||
'rgb(172, 225, 196)',
|
||||
);
|
||||
});
|
||||
|
||||
it('recalculates totals when user filters data', async () => {
|
||||
const formDataWithTotals = {
|
||||
...testData.basic.formData,
|
||||
|
||||
@@ -203,3 +203,53 @@ test('Not displays the toAllRow and toTextColor flags', () => {
|
||||
expect(screen.queryByText('To entire row')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('To text color')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('displays Use gradient checkbox', () => {
|
||||
render(
|
||||
<FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Use gradient')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Helper function to find the "Use gradient" checkbox
|
||||
// The checkbox and text are in sibling columns within the same row
|
||||
const findUseGradientCheckbox = (): HTMLInputElement => {
|
||||
const useGradientText = screen.getByText('Use gradient');
|
||||
// Find the common parent row that contains both the text and checkbox
|
||||
let rowElement: HTMLElement | null = useGradientText.parentElement;
|
||||
while (rowElement) {
|
||||
const checkbox = rowElement.querySelector('input[type="checkbox"]');
|
||||
if (checkbox && rowElement.textContent?.includes('Use gradient')) {
|
||||
return checkbox as HTMLInputElement;
|
||||
}
|
||||
rowElement = rowElement.parentElement;
|
||||
}
|
||||
throw new Error('Could not find Use gradient checkbox');
|
||||
};
|
||||
|
||||
test('Use gradient checkbox defaults to checked', () => {
|
||||
render(
|
||||
<FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
|
||||
);
|
||||
|
||||
const checkbox = findUseGradientCheckbox();
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
test('Use gradient checkbox can be toggled', async () => {
|
||||
render(
|
||||
<FormattingPopoverContent onChange={mockOnChange} columns={columns} />,
|
||||
);
|
||||
|
||||
const checkbox = findUseGradientCheckbox();
|
||||
expect(checkbox).toBeChecked();
|
||||
|
||||
// Uncheck the checkbox
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
// Check the checkbox again
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
@@ -292,6 +292,9 @@ export const FormattingPopoverContent = ({
|
||||
const [toTextColor, setToTextColor] = useState(() =>
|
||||
Boolean(config?.toTextColor),
|
||||
);
|
||||
const [useGradient, setUseGradient] = useState(() =>
|
||||
config?.useGradient !== undefined ? config.useGradient : true,
|
||||
);
|
||||
|
||||
const useConditionalFormattingFlag = (
|
||||
flagKey: 'toAllRowCheck' | 'toColorTextCheck',
|
||||
@@ -406,6 +409,23 @@ export const FormattingPopoverContent = ({
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={20}>
|
||||
<Col span={1}>
|
||||
<FormItem
|
||||
name="useGradient"
|
||||
valuePropName="checked"
|
||||
initialValue={useGradient}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={event => setUseGradient(event.target.checked)}
|
||||
checked={useGradient}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col>
|
||||
<FormItem required>{t('Use gradient')}</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormItem noStyle shouldUpdate={shouldFormItemUpdate}>
|
||||
{showOperatorFields ? (
|
||||
(props: GetFieldValue) => renderOperatorFields(props, columnType)
|
||||
|
||||
@@ -31,6 +31,7 @@ export type ConditionalFormattingConfig = {
|
||||
colorScheme?: string;
|
||||
toAllRow?: boolean;
|
||||
toTextColor?: boolean;
|
||||
useGradient?: boolean;
|
||||
};
|
||||
|
||||
export type ConditionalFormattingControlProps = ControlComponentProps<
|
||||
|
||||
Reference in New Issue
Block a user