mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 07:26:20 +00:00
fix: correctly set and unset the stackbarchart value across panel types (#9158)
This commit is contained in:
parent
c68096152d
commit
9114b44c0e
@ -300,6 +300,7 @@ function RightContainer({
|
||||
style={{ width: '100%' }}
|
||||
className="panel-type-select"
|
||||
data-testid="panel-change-select"
|
||||
data-stacking-state={stackedBarChart ? 'true' : 'false'}
|
||||
>
|
||||
{graphTypes.map((item) => (
|
||||
<Option key={item.name} value={item.name}>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
// This test suite covers several important scenarios:
|
||||
// - Empty layout - widget should be placed at origin (0,0)
|
||||
// - Empty layout with custom dimensions
|
||||
@ -6,13 +7,20 @@
|
||||
// - Handling multiple rows correctly
|
||||
// - Handling widgets with different heights
|
||||
|
||||
import { screen } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import i18n from 'ReactI18';
|
||||
import { render } from 'tests/test-utils';
|
||||
import {
|
||||
fireEvent,
|
||||
getByText as getByTextUtil,
|
||||
render,
|
||||
userEvent,
|
||||
within,
|
||||
} from 'tests/test-utils';
|
||||
|
||||
import NewWidget from '..';
|
||||
import {
|
||||
@ -21,6 +29,28 @@ import {
|
||||
placeWidgetBetweenRows,
|
||||
} from '../utils';
|
||||
|
||||
// Helper function to check stack series state
|
||||
const checkStackSeriesState = (
|
||||
container: HTMLElement,
|
||||
expectedChecked: boolean,
|
||||
): HTMLElement => {
|
||||
expect(getByTextUtil(container, 'Stack series')).toBeInTheDocument();
|
||||
|
||||
const stackSeriesSection = container.querySelector(
|
||||
'section > .stack-chart',
|
||||
) as HTMLElement;
|
||||
expect(stackSeriesSection).toBeInTheDocument();
|
||||
|
||||
const switchElement = within(stackSeriesSection).getByRole('switch');
|
||||
if (expectedChecked) {
|
||||
expect(switchElement).toBeChecked();
|
||||
} else {
|
||||
expect(switchElement).not.toBeChecked();
|
||||
}
|
||||
|
||||
return switchElement;
|
||||
};
|
||||
|
||||
const MOCK_SEARCH_PARAMS =
|
||||
'?graphType=bar&widgetId=b473eef0-8eb5-4dd3-8089-c1817734084f&compositeQuery=%7B"id"%3A"f026c678-9abf-42af-a3dc-f73dc8cbb810"%2C"builder"%3A%7B"queryData"%3A%5B%7B"dataSource"%3A"metrics"%2C"queryName"%3A"A"%2C"aggregateOperator"%3A"count"%2C"aggregateAttribute"%3A%7B"id"%3A"----"%2C"dataType"%3A""%2C"key"%3A""%2C"type"%3A""%7D%2C"timeAggregation"%3A"rate"%2C"spaceAggregation"%3A"sum"%2C"filter"%3A%7B"expression"%3A""%7D%2C"aggregations"%3A%5B%7B"metricName"%3A""%2C"temporality"%3A""%2C"timeAggregation"%3A"count"%2C"spaceAggregation"%3A"sum"%2C"reduceTo"%3A"avg"%7D%5D%2C"functions"%3A%5B%5D%2C"filters"%3A%7B"items"%3A%5B%5D%2C"op"%3A"AND"%7D%2C"expression"%3A"A"%2C"disabled"%3Afalse%2C"stepInterval"%3Anull%2C"having"%3A%5B%5D%2C"limit"%3Anull%2C"orderBy"%3A%5B%5D%2C"groupBy"%3A%5B%5D%2C"legend"%3A""%2C"reduceTo"%3A"avg"%2C"source"%3A""%7D%5D%2C"queryFormulas"%3A%5B%5D%2C"queryTraceOperator"%3A%5B%5D%7D%2C"clickhouse_sql"%3A%5B%7B"name"%3A"A"%2C"legend"%3A""%2C"disabled"%3Afalse%2C"query"%3A""%7D%5D%2C"promql"%3A%5B%7B"name"%3A"A"%2C"query"%3A""%2C"legend"%3A""%2C"disabled"%3Afalse%7D%5D%2C"queryType"%3A"builder"%7D&relativeTime=30m';
|
||||
// Mocks
|
||||
@ -279,7 +309,7 @@ describe('Stacking bar in new panel', () => {
|
||||
jest.fn(),
|
||||
]);
|
||||
|
||||
const { container, getByText, getByRole } = render(
|
||||
const { container, getByText } = render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<DashboardProvider>
|
||||
<PreferenceContextProvider>
|
||||
@ -305,7 +335,83 @@ describe('Stacking bar in new panel', () => {
|
||||
expect(switchBtn).toBeInTheDocument();
|
||||
expect(switchBtn).toHaveClass('ant-switch-checked');
|
||||
|
||||
// (Optional) More semantic: verify by role
|
||||
expect(getByRole('switch')).toBeChecked();
|
||||
// Check that stack series is present and checked
|
||||
checkStackSeriesState(container, true);
|
||||
});
|
||||
});
|
||||
|
||||
const STACKING_STATE_ATTR = 'data-stacking-state';
|
||||
|
||||
describe('when switching to BAR panel type', () => {
|
||||
jest.setTimeout(10000);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Mock useSearchParams to return the expected values
|
||||
(useSearchParams as jest.Mock).mockReturnValue([
|
||||
new URLSearchParams(MOCK_SEARCH_PARAMS),
|
||||
jest.fn(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve saved stacking value of true', async () => {
|
||||
const { getByTestId, getByText, container } = render(
|
||||
<DashboardProvider>
|
||||
<NewWidget
|
||||
selectedGraph={PANEL_TYPES.BAR}
|
||||
fillSpans={undefined}
|
||||
yAxisUnit={undefined}
|
||||
/>
|
||||
</DashboardProvider>,
|
||||
);
|
||||
|
||||
expect(getByTestId('panel-change-select')).toHaveAttribute(
|
||||
STACKING_STATE_ATTR,
|
||||
'true',
|
||||
);
|
||||
|
||||
await userEvent.click(getByText('Bar')); // Panel Type Selected
|
||||
|
||||
// find dropdown with - .ant-select-dropdown
|
||||
const panelDropdown = document.querySelector(
|
||||
'.ant-select-dropdown',
|
||||
) as HTMLElement;
|
||||
expect(panelDropdown).toBeInTheDocument();
|
||||
|
||||
// Select TimeSeries from dropdown
|
||||
const option = within(panelDropdown).getByText('Time Series');
|
||||
fireEvent.click(option);
|
||||
|
||||
expect(getByTestId('panel-change-select')).toHaveAttribute(
|
||||
STACKING_STATE_ATTR,
|
||||
'false',
|
||||
);
|
||||
|
||||
// Since we are on timeseries panel, stack series should be false
|
||||
expect(screen.queryByText('Stack series')).not.toBeInTheDocument();
|
||||
|
||||
// switch back to Bar panel
|
||||
const panelTypeDropdown2 = getByTestId('panel-change-select') as HTMLElement;
|
||||
expect(panelTypeDropdown2).toBeInTheDocument();
|
||||
|
||||
expect(getByTextUtil(panelTypeDropdown2, 'Time Series')).toBeInTheDocument();
|
||||
fireEvent.click(getByTextUtil(panelTypeDropdown2, 'Time Series'));
|
||||
|
||||
// find dropdown with - .ant-select-dropdown
|
||||
const panelDropdown2 = document.querySelector(
|
||||
'.ant-select-dropdown',
|
||||
) as HTMLElement;
|
||||
// // Select BAR from dropdown
|
||||
const BarOption = within(panelDropdown2).getByText('Bar');
|
||||
fireEvent.click(BarOption);
|
||||
|
||||
// Stack series should be true
|
||||
checkStackSeriesState(container, true);
|
||||
|
||||
expect(getByTestId('panel-change-select')).toHaveAttribute(
|
||||
STACKING_STATE_ATTR,
|
||||
'true',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
export const BarNonStackedChartData = {
|
||||
apiResponse: {
|
||||
data: {
|
||||
result: [
|
||||
{
|
||||
metric: {
|
||||
'service.name': 'recommendationservice',
|
||||
},
|
||||
values: [
|
||||
[1758713940, '33.933'],
|
||||
[1758715020, '31.767'],
|
||||
],
|
||||
queryName: 'A',
|
||||
metaData: {
|
||||
alias: '__result_0',
|
||||
index: 0,
|
||||
queryName: 'A',
|
||||
},
|
||||
legend: '',
|
||||
},
|
||||
{
|
||||
metric: {
|
||||
'service.name': 'frontend',
|
||||
},
|
||||
values: [
|
||||
[1758713940, '20.0'],
|
||||
[1758715020, '25.0'],
|
||||
],
|
||||
queryName: 'B',
|
||||
metaData: {
|
||||
alias: '__result_1',
|
||||
index: 1,
|
||||
queryName: 'B',
|
||||
},
|
||||
legend: '',
|
||||
},
|
||||
],
|
||||
resultType: 'time_series',
|
||||
newResult: {
|
||||
data: {
|
||||
resultType: 'time_series',
|
||||
result: [
|
||||
{
|
||||
queryName: 'A',
|
||||
legend: '',
|
||||
series: [
|
||||
{
|
||||
labels: {
|
||||
'service.name': 'recommendationservice',
|
||||
},
|
||||
labelsArray: [
|
||||
{
|
||||
'service.name': 'recommendationservice',
|
||||
},
|
||||
],
|
||||
values: [
|
||||
{
|
||||
timestamp: 1758713940000,
|
||||
value: '33.933',
|
||||
},
|
||||
{
|
||||
timestamp: 1758715020000,
|
||||
value: '31.767',
|
||||
},
|
||||
],
|
||||
metaData: {
|
||||
alias: '__result_0',
|
||||
index: 0,
|
||||
queryName: 'A',
|
||||
},
|
||||
},
|
||||
],
|
||||
predictedSeries: [],
|
||||
upperBoundSeries: [],
|
||||
lowerBoundSeries: [],
|
||||
anomalyScores: [],
|
||||
list: null,
|
||||
},
|
||||
{
|
||||
queryName: 'B',
|
||||
legend: '',
|
||||
series: [
|
||||
{
|
||||
labels: {
|
||||
'service.name': 'frontend',
|
||||
},
|
||||
labelsArray: [
|
||||
{
|
||||
'service.name': 'frontend',
|
||||
},
|
||||
],
|
||||
values: [
|
||||
{
|
||||
timestamp: 1758713940000,
|
||||
value: '20.0',
|
||||
},
|
||||
{
|
||||
timestamp: 1758715020000,
|
||||
value: '25.0',
|
||||
},
|
||||
],
|
||||
metaData: {
|
||||
alias: '__result_1',
|
||||
index: 1,
|
||||
queryName: 'B',
|
||||
},
|
||||
},
|
||||
],
|
||||
predictedSeries: [],
|
||||
upperBoundSeries: [],
|
||||
lowerBoundSeries: [],
|
||||
anomalyScores: [],
|
||||
list: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as MetricRangePayloadProps,
|
||||
fillSpans: false,
|
||||
stackedBarChart: false,
|
||||
};
|
||||
|
||||
export const BarStackedChartData = {
|
||||
...BarNonStackedChartData,
|
||||
stackedBarChart: true,
|
||||
};
|
||||
|
||||
export const TimeSeriesChartData = {
|
||||
...BarNonStackedChartData,
|
||||
stackedBarChart: false,
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { getUPlotChartData } from '../../../lib/uPlotLib/utils/getUplotChartData';
|
||||
import {
|
||||
BarNonStackedChartData,
|
||||
BarStackedChartData,
|
||||
TimeSeriesChartData,
|
||||
} from './__mocks__/uplotChartData';
|
||||
|
||||
describe('getUplotChartData', () => {
|
||||
it('should return the correct chart data for non-stacked bar chart', () => {
|
||||
const result = getUPlotChartData(
|
||||
BarNonStackedChartData.apiResponse,
|
||||
BarNonStackedChartData.fillSpans,
|
||||
BarNonStackedChartData.stackedBarChart,
|
||||
);
|
||||
expect(result).toEqual([
|
||||
[1758713940, 1758715020],
|
||||
[33.933, 31.767],
|
||||
[20.0, 25.0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the correct chart data for stacked bar chart', () => {
|
||||
const result = getUPlotChartData(
|
||||
BarStackedChartData.apiResponse,
|
||||
BarStackedChartData.fillSpans,
|
||||
BarStackedChartData.stackedBarChart,
|
||||
);
|
||||
// For stacked charts, the values should be cumulative
|
||||
// First series: [33.933, 31.767] + [20.0, 25.0] = [53.933, 56.767]
|
||||
// Second series: [20.0, 25.0] (unchanged)
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toEqual([1758713940, 1758715020]);
|
||||
expect(result[1][0]).toBeCloseTo(53.933, 3);
|
||||
expect(result[1][1]).toBeCloseTo(56.767, 3);
|
||||
expect(result[2]).toEqual([20.0, 25.0]);
|
||||
});
|
||||
|
||||
it('should return the correct chart data for time series chart', () => {
|
||||
const result = getUPlotChartData(
|
||||
TimeSeriesChartData.apiResponse,
|
||||
TimeSeriesChartData.fillSpans,
|
||||
TimeSeriesChartData.stackedBarChart,
|
||||
);
|
||||
expect(result).toEqual([
|
||||
[1758713940, 1758715020],
|
||||
[33.933, 31.767],
|
||||
[20.0, 25.0],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -595,6 +595,13 @@ function NewWidget({
|
||||
selectedGraph,
|
||||
);
|
||||
setGraphType(type);
|
||||
|
||||
// with a single source of truth for stacking, we can use the saved stacking value as a default value
|
||||
const savedStackingValue = getWidget()?.stackedBarChart;
|
||||
setStackedBarChart(
|
||||
type === PANEL_TYPES.BAR ? savedStackingValue || false : false,
|
||||
);
|
||||
|
||||
redirectWithQueryBuilderData(
|
||||
updatedQuery,
|
||||
{ [QueryParams.graphType]: type },
|
||||
|
||||
@ -553,7 +553,7 @@ export const getDefaultWidgetData = (
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
stackedBarChart: true,
|
||||
stackedBarChart: name === PANEL_TYPES.BAR,
|
||||
selectedLogFields: defaultLogsSelectedColumns.map((field) => ({
|
||||
...field,
|
||||
type: field.fieldContext ?? '',
|
||||
|
||||
@ -124,15 +124,23 @@ function UplotPanelWrapper({
|
||||
queryResponse.data.payload.data.result = sortedSeriesData;
|
||||
}
|
||||
|
||||
const stackedBarChart = useMemo(
|
||||
() =>
|
||||
(selectedGraph
|
||||
? selectedGraph === PANEL_TYPES.BAR
|
||||
: widget?.panelTypes === PANEL_TYPES.BAR) && widget?.stackedBarChart,
|
||||
[selectedGraph, widget?.panelTypes, widget?.stackedBarChart],
|
||||
);
|
||||
|
||||
const chartData = getUPlotChartData(
|
||||
queryResponse?.data?.payload,
|
||||
widget.fillSpans,
|
||||
widget?.stackedBarChart,
|
||||
stackedBarChart,
|
||||
hiddenGraph,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (widget.panelTypes === PANEL_TYPES.BAR && widget?.stackedBarChart) {
|
||||
if (widget.panelTypes === PANEL_TYPES.BAR && stackedBarChart) {
|
||||
const graphV = cloneDeep(graphVisibility)?.slice(1);
|
||||
const isSomeSelectedLegend = graphV?.some((v) => v === false);
|
||||
if (isSomeSelectedLegend) {
|
||||
@ -145,7 +153,7 @@ function UplotPanelWrapper({
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [graphVisibility, hiddenGraph, widget.panelTypes, widget?.stackedBarChart]);
|
||||
}, [graphVisibility, hiddenGraph, widget.panelTypes, stackedBarChart]);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
@ -221,7 +229,7 @@ function UplotPanelWrapper({
|
||||
setGraphsVisibilityStates: setGraphVisibility,
|
||||
panelType: selectedGraph || widget.panelTypes,
|
||||
currentQuery,
|
||||
stackBarChart: widget?.stackedBarChart,
|
||||
stackBarChart: stackedBarChart,
|
||||
hiddenGraph,
|
||||
setHiddenGraph,
|
||||
customTooltipElement,
|
||||
@ -261,6 +269,7 @@ function UplotPanelWrapper({
|
||||
enableDrillDown,
|
||||
onClickHandler,
|
||||
widget,
|
||||
stackedBarChart,
|
||||
],
|
||||
);
|
||||
|
||||
@ -274,14 +283,14 @@ function UplotPanelWrapper({
|
||||
items={menuItemsConfig.items}
|
||||
onClose={onClose}
|
||||
/>
|
||||
{widget?.stackedBarChart && isFullViewMode && (
|
||||
{stackedBarChart && isFullViewMode && (
|
||||
<Alert
|
||||
message="Selecting multiple legends is currently not supported in case of stacked bar charts"
|
||||
type="info"
|
||||
className="info-text"
|
||||
/>
|
||||
)}
|
||||
{isFullViewMode && setGraphVisibility && !widget?.stackedBarChart && (
|
||||
{isFullViewMode && setGraphVisibility && !stackedBarChart && (
|
||||
<GraphManager
|
||||
data={getUPlotChartData(queryResponse?.data?.payload, widget.fillSpans)}
|
||||
name={widget.id}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user