mirror of
https://github.com/apache/superset.git
synced 2026-09-09 00:34:49 +00:00
+27





![dependabot[bot]](/assets/img/avatar_default.png)



Evan Rusackas
Superset Dev
Claude Opus 4.8
dependabot[bot]
Milad Rashidikhah
bucketbase26
Joe Li
Viktor Högberg
joey
Cursor
Chandan P
Mafi
Matt Fitzgerald
Enzo Martellucci
Alexandru Soare
Mehmet Salih Yavuz
Sanmitra Nagaraj
Lalith Kothuru
shaurya
Shaurya
Đỗ Trọng Hải
rlei
Ville Brofeldt
Gabriel Torres Ruiz
hainenber
ʈᵃᵢ
Mike Bridge
Elizabeth Thompson
Francesco.Castaldi
FrancescoCastaldi
Abdul Rehman
Sepuri Sai Krishna
Mallikarjuna Reddy Nimmakayala
PRATHAMESH HUKKERI
Prathamesh Hukkeri
github-actions[bot]
SBIN2010
7d768a938f
Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: hainenber <dotronghai96@gmail.com> Signed-off-by: Gabriel Torres Ruiz <gabo2595@gmail.com> Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Superset Dev <dev@superset.apache.org> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Milad Rashidikhah <mrashidikhah32@gmail.com> Co-authored-by: bucketbase26 <singhayush062006@gmail.com> Co-authored-by: Joe Li <joe@preset.io> Co-authored-by: Viktor Högberg <119532259+vhogberg@users.noreply.github.com> Co-authored-by: joey <97154801+chkang83@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Chandan P <95340276+NoiceHax@users.noreply.github.com> Co-authored-by: Mafi <matt.fitzgerald@gmail.com> Co-authored-by: Matt Fitzgerald <matt.fitzgerald@preset.io> Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Co-authored-by: Alexandru Soare <37236580+alexandrusoare@users.noreply.github.com> Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me> Co-authored-by: Sanmitra Nagaraj <48400413+s1ny1998@users.noreply.github.com> Co-authored-by: Lalith Kothuru <lalith.kothuru@gmail.com> Co-authored-by: shaurya <shauryajaiswal.dev@gmail.com> Co-authored-by: Shaurya <19599684+no-hup@users.noreply.github.com> Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com> Co-authored-by: rlei <242280117+rlei-odes@users.noreply.github.com> Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Co-authored-by: Gabriel Torres Ruiz <gabo2595@gmail.com> Co-authored-by: hainenber <dotronghai96@gmail.com> Co-authored-by: ʈᵃᵢ <tai@apache.org> Co-authored-by: Mike Bridge <michael.bridge@preset.io> Co-authored-by: Elizabeth Thompson <eschutho@gmail.com> Co-authored-by: Francesco.Castaldi <info@francescocastaldi.it> Co-authored-by: FrancescoCastaldi <francesco.castaldi@mapsgroup.it> Co-authored-by: Abdul Rehman <76230556+Abdulrehman-PIAIC80387@users.noreply.github.com> Co-authored-by: Sepuri Sai Krishna <saik20533@gmail.com> Co-authored-by: Mallikarjuna Reddy Nimmakayala <mallikarjunareddy.nimmakayala@gmail.com> Co-authored-by: PRATHAMESH HUKKERI <prathamhukkeri04@gmail.com> Co-authored-by: Prathamesh Hukkeri <prathamesh04@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: SBIN2010 <Sbin2010@mail.ru>
203 lines
6.4 KiB
TypeScript
203 lines
6.4 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { ChartProps } from '@superset-ui/core';
|
|
import { supersetTheme } from '@apache-superset/core/theme';
|
|
import {
|
|
EchartsButterflyChartProps,
|
|
ButterflyTransformedProps,
|
|
} from '../../src/Butterfly/types';
|
|
import transformProps from '../../src/Butterfly/transformProps';
|
|
import { NULL_STRING } from '../../src/constants';
|
|
|
|
type SeriesDataPoint = { value?: number } | number;
|
|
|
|
type ButterflyTestSeries = {
|
|
name?: string;
|
|
data?: SeriesDataPoint[];
|
|
itemStyle?: { color?: string };
|
|
label?: { show?: boolean };
|
|
};
|
|
|
|
type ButterflyTestEchartOptions = {
|
|
series?: ButterflyTestSeries[];
|
|
xAxis?: { name?: string; nameGap?: number };
|
|
yAxis?: {
|
|
name?: string;
|
|
nameGap?: number;
|
|
data?: string[];
|
|
axisLabel?: { rotate?: number };
|
|
};
|
|
legend?: { orient?: string; data?: string[] };
|
|
grid?: { left?: number; top?: number };
|
|
tooltip?: { show?: boolean };
|
|
};
|
|
|
|
const getEchartOptions = (
|
|
props: ButterflyTransformedProps,
|
|
): ButterflyTestEchartOptions =>
|
|
props.echartOptions as ButterflyTestEchartOptions;
|
|
|
|
const extractSeriesValues = (props: ButterflyTransformedProps) => {
|
|
const series = getEchartOptions(props).series ?? [];
|
|
return series.map(item =>
|
|
(item.data ?? []).map(entry =>
|
|
typeof entry === 'object' && entry !== null && 'value' in entry
|
|
? entry.value
|
|
: entry,
|
|
),
|
|
);
|
|
};
|
|
|
|
const extractSeriesNames = (props: ButterflyTransformedProps) => {
|
|
const series = getEchartOptions(props).series ?? [];
|
|
return series.map(item => item.name);
|
|
};
|
|
|
|
const data: Record<string, unknown>[] = [
|
|
{ category: 'A', left_sum: 10, right_sum: 25 },
|
|
{ category: 'B', left_sum: 5, right_sum: 19 },
|
|
];
|
|
|
|
const formData = {
|
|
groupby: ['category'],
|
|
left_metric: 'left_sum',
|
|
right_metric: 'right_sum',
|
|
left_color: { r: 84, g: 112, b: 198 },
|
|
right_color: { r: 145, g: 204, b: 117 },
|
|
showValue: true,
|
|
showLegend: true,
|
|
};
|
|
|
|
const createChartProps = (
|
|
overrides: Record<string, unknown> = {},
|
|
queryData: Record<string, unknown>[] = data,
|
|
) =>
|
|
new ChartProps({
|
|
formData: { ...formData, ...overrides },
|
|
width: 800,
|
|
height: 600,
|
|
queriesData: [{ data: queryData }],
|
|
theme: supersetTheme,
|
|
...((overrides.hooks ? { hooks: overrides.hooks } : {}) as object),
|
|
});
|
|
|
|
test('transforms chart props into diverging bar series', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps() as unknown as EchartsButterflyChartProps,
|
|
);
|
|
|
|
expect(extractSeriesValues(transformedProps)).toEqual([
|
|
[-10, -5],
|
|
[25, 19],
|
|
]);
|
|
});
|
|
|
|
test('uses absolute values for negative right-side metrics', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({}, [
|
|
{ category: 'A', left_sum: -8, right_sum: -15 },
|
|
]) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
|
|
expect(extractSeriesValues(transformedProps)).toEqual([[-8], [15]]);
|
|
});
|
|
|
|
test('formats null categories and missing metric values', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({}, [
|
|
{ category: null, left_sum: undefined, right_sum: 7 },
|
|
]) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
const { yAxis } = getEchartOptions(transformedProps);
|
|
|
|
expect(yAxis?.data).toEqual([NULL_STRING]);
|
|
const [leftValues, rightValues] = extractSeriesValues(transformedProps);
|
|
expect(Math.abs(leftValues[0] as number)).toBe(0);
|
|
expect(rightValues).toEqual([7]);
|
|
});
|
|
|
|
test('applies custom series labels, colors, and axis titles', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({
|
|
left_label: 'Left side',
|
|
right_label: 'Right side',
|
|
left_color: { r: 255, g: 0, b: 0 },
|
|
right_color: { r: 0, g: 255, b: 0 },
|
|
x_axis_label: 'Value axis',
|
|
y_axis_label: 'Category axis',
|
|
}) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
const { series, xAxis, yAxis } = getEchartOptions(transformedProps);
|
|
|
|
expect(extractSeriesNames(transformedProps)).toEqual([
|
|
'Left side',
|
|
'Right side',
|
|
]);
|
|
expect(series?.[0]?.itemStyle?.color).toBe('#ff0000');
|
|
expect(series?.[1]?.itemStyle?.color).toBe('#00ff00');
|
|
expect(xAxis?.name).toBe('Value axis');
|
|
expect(yAxis?.name).toBe('Category axis');
|
|
});
|
|
|
|
test('applies legend orientation, sort, and axis margin settings', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({
|
|
legendOrientation: 'left',
|
|
legendSort: 'desc',
|
|
xAxisLabelRotation: 45,
|
|
x_axis_title_margin: 60,
|
|
y_axis_title_margin: 80,
|
|
}) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
const { legend, xAxis, yAxis, grid } = getEchartOptions(transformedProps);
|
|
|
|
expect(legend?.orient).toBe('vertical');
|
|
expect(legend?.data).toEqual(['right_sum', 'left_sum']);
|
|
expect(xAxis?.nameGap).toBe(60);
|
|
expect(yAxis?.axisLabel?.rotate).toBe(45);
|
|
expect(yAxis?.nameGap).toBe(80);
|
|
expect(grid?.left).toBeGreaterThan(80);
|
|
expect(grid?.top).toBeGreaterThan(60);
|
|
});
|
|
|
|
test('hides value labels when showValue is false', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({
|
|
showValue: false,
|
|
}) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
const { series } = getEchartOptions(transformedProps);
|
|
|
|
expect(series?.[0]?.label?.show).toBe(false);
|
|
expect(series?.[1]?.label?.show).toBe(false);
|
|
});
|
|
|
|
test('hides tooltip while the context menu is open', () => {
|
|
const transformedProps = transformProps(
|
|
createChartProps({}, data) as unknown as EchartsButterflyChartProps,
|
|
);
|
|
const withContextMenu = transformProps({
|
|
...createChartProps(),
|
|
inContextMenu: true,
|
|
} as unknown as EchartsButterflyChartProps);
|
|
|
|
expect(getEchartOptions(transformedProps).tooltip?.show).toBe(true);
|
|
expect(getEchartOptions(withContextMenu).tooltip?.show).toBe(false);
|
|
});
|