diff --git a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx index 2e543ac5f46..999d1fd0d56 100644 --- a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx @@ -7,6 +7,7 @@ import uPlot from 'uplot'; import createMockRaf from 'mock-raf'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; import { preparePlotData } from './utils'; +import { SeriesProps } from './config/UPlotSeriesBuilder'; const mockRaf = createMockRaf(); const setDataMock = jest.fn(); @@ -52,7 +53,9 @@ const mockData = () => { raw: { from: '1602673200000', to: '1602680400000' }, }; - return { data, timeRange, config: new UPlotConfigBuilder() }; + const config = new UPlotConfigBuilder(); + config.addSeries({} as SeriesProps); + return { data, timeRange, config }; }; describe('UPlotChart', () => { @@ -159,14 +162,11 @@ describe('UPlotChart', () => { expect(uPlot).toBeCalledTimes(1); + const nextConfig = new UPlotConfigBuilder(); + nextConfig.addSeries({} as SeriesProps); + rerender( - + ); expect(destroyMock).toBeCalledTimes(1); @@ -190,11 +190,13 @@ describe('UPlotChart', () => { act(() => { mockRaf.step({ count: 1 }); }); + const nextConfig = new UPlotConfigBuilder(); + nextConfig.addSeries({} as SeriesProps); rerender( { expect(uPlot).toBeCalledTimes(1); expect(setSizeMock).toBeCalledTimes(1); }); + + it('does not initialize plot when config and data are not in sync', () => { + const { data, timeRange, config } = mockData(); + + // 1 series in data, 2 series in config + config.addSeries({} as SeriesProps); + + render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + expect(destroyMock).toBeCalledTimes(0); + expect(uPlot).toBeCalledTimes(0); + }); + + it('does not reinitialize plot when config and data are not in sync', () => { + const { data, timeRange, config } = mockData(); + + // 1 series in data, 1 series in config + const { rerender } = render( + + ); + + // we wait 1 frame for plugins initialisation logic to finish + act(() => { + mockRaf.step({ count: 1 }); + }); + + const nextConfig = new UPlotConfigBuilder(); + nextConfig.addSeries({} as SeriesProps); + nextConfig.addSeries({} as SeriesProps); + + // 1 series in data, 2 series in config + rerender( + + ); + + expect(destroyMock).toBeCalledTimes(0); + expect(uPlot).toBeCalledTimes(1); + }); }); }); diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index 6cdf8540505..1fd6da3aac0 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -31,6 +31,14 @@ export const UPlotChart: React.FC = (props) => { return; } + // 0. Exit if the data set length is different than number of series expected to render + // This may happen when GraphNG has not synced config yet with the aligned frame. Alignment happens before the render + // in the getDerivedStateFromProps, while the config creation happens in componentDidUpdate, causing one more render + // of the UPlotChart if the config needs to be updated. + if (currentConfig.current.series.length !== props.data.length) { + return; + } + // 1. When config is ready and there is no uPlot instance, create new uPlot and return if (isConfigReady && !plotInstance.current) { plotInstance.current = initializePlot(props.data, currentConfig.current, canvasRef.current);