Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Vega] Fix element sizing issues in fullscreen mode (#194330) #195328

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { useEffect, useRef, useMemo, useCallback } from 'react';
import React, { useEffect, useRef, useCallback } from 'react';
import { EuiResizeObserver, EuiResizeObserverProps, useEuiTheme } from '@elastic/eui';
import { throttle } from 'lodash';

import type { IInterpreterRenderHandlers, RenderMode } from '@kbn/expressions-plugin/common';
import { createVegaVisualization } from '../vega_visualization';
Expand All @@ -28,8 +27,6 @@ interface VegaVisComponentProps {

type VegaVisController = InstanceType<ReturnType<typeof createVegaVisualization>>;

const THROTTLE_INTERVAL = 300;

export const VegaVisComponent = ({
visData,
fireEvent,
Expand Down Expand Up @@ -64,26 +61,11 @@ export const VegaVisComponent = ({
}
}, [renderComplete, visData]);

const resizeChart = useMemo(
() =>
throttle(
(dimensions) => {
visController.current?.resize(dimensions);
},
THROTTLE_INTERVAL,
{ leading: false, trailing: true }
),
[]
);

const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback(
(dimensions) => {
if (renderCompleted.current) {
resizeChart(dimensions);
}
},
[resizeChart]
);
const onContainerResize: EuiResizeObserverProps['onResize'] = useCallback((dimensions) => {
if (renderCompleted.current) {
visController.current?.resize(dimensions);
}
}, []);

const euiTheme = useEuiTheme();

Expand Down
12 changes: 4 additions & 8 deletions src/plugins/vis_types/vega/public/vega_view/vega_base_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ export class VegaBaseView {
}
}

async resize() {
async resize(dimensions) {
if (this._parser.useResize && this._view) {
this.updateVegaSize(this._view);
this.updateVegaSize(this._view, dimensions);
await this._view.runAsync();

// The derived class should create this method
Expand All @@ -293,12 +293,8 @@ export class VegaBaseView {
}

updateVegaSize(view, dimensions) {
const width = Math.floor(
Math.max(0, dimensions?.width ?? this._container.getBoundingClientRect().width)
);
const height = Math.floor(
Math.max(0, dimensions?.height ?? this._container.getBoundingClientRect().height)
);
const width = Math.floor(Math.max(0, dimensions?.width ?? this._container.clientWidth - 1));
const height = Math.floor(Math.max(0, dimensions?.height ?? this._container.clientHeight - 1));

if (view.width() !== width || view.height() !== height) {
view.width(width).height(height);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
*/

import 'jest-canvas-mock';
import { render, screen } from '@testing-library/react';

import { createVegaVisualization } from './vega_visualization';
import { VegaVisType, createVegaVisualization } from './vega_visualization';

import vegaliteGraph from './test_utils/vegalite_graph.json';
import vegaGraph from './test_utils/vega_graph.json';
Expand All @@ -21,37 +22,41 @@ import { setInjectedVars, setData, setNotifications } from './services';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
import { VegaVisualizationDependencies } from './plugin';
import React from 'react';
import { TimeCache } from './data_model/time_cache';

jest.mock('./default_spec', () => ({
getDefaultSpec: () => jest.requireActual('./test_utils/default.spec.json'),
}));

describe('VegaVisualizations', () => {
let domNode;
let VegaVisualization;
let vegaVisualizationDependencies;

let mockGetBoundingClientRect;
let mockedWidthValue;
let mockedHeightValue;
let domNode: HTMLDivElement;
let VegaVisualization: VegaVisType;
let vegaVisualizationDependencies: VegaVisualizationDependencies;
let mockedHeightValue: number;
let mockedWidthValue: number;

const coreStart = coreMock.createStart();
const dataPluginStart = dataPluginMock.createStartContract();
const dataViewsPluginStart = dataViewPluginMocks.createStartContract();

const setupDOM = (width = 512, height = 512) => {
render(<div data-test-subj="vega-vis-text" />);
domNode = screen.getByTestId('vega-vis-text');
domNode.style.height = `${height}px`;
domNode.style.width = `${width}px`;
mockedWidthValue = width;
mockedHeightValue = height;
domNode = document.createElement('div');

mockGetBoundingClientRect = jest
.spyOn(Element.prototype, 'getBoundingClientRect')
.mockImplementation(() => ({ width: mockedWidthValue, height: mockedHeightValue }));
// rtl does not update client dimensions on element, see https://github.com/testing-library/react-testing-library/issues/353
jest
.spyOn(Element.prototype, 'clientHeight', 'get')
.mockImplementation(() => mockedHeightValue);
jest.spyOn(Element.prototype, 'clientWidth', 'get').mockImplementation(() => mockedWidthValue);
};

const mockGetServiceSettings = () => {
return {};
};
const mockGetServiceSettings = jest.fn() as any;

beforeEach(() => {
setInjectedVars({
Expand All @@ -68,23 +73,19 @@ describe('VegaVisualizations', () => {
getServiceSettings: mockGetServiceSettings,
};

VegaVisualization = createVegaVisualization(vegaVisualizationDependencies);
VegaVisualization = createVegaVisualization(vegaVisualizationDependencies, 'view');
});

describe('VegaVisualization - basics', () => {
beforeEach(async () => {
setupDOM();
});

afterEach(() => {
mockGetBoundingClientRect.mockRestore();
});

test('should show vegalite graph and update on resize (may fail in dev env)', async () => {
const mockedConsoleLog = jest.spyOn(console, 'log'); // mocked console.log to avoid messages in the console when running tests
mockedConsoleLog.mockImplementation(() => {}); // comment this line when console logging for debugging comment this line

let vegaVis;
let vegaVis: InstanceType<VegaVisType>;
try {
vegaVis = new VegaVisualization(domNode, jest.fn());

Expand All @@ -95,8 +96,8 @@ describe('VegaVisualizations', () => {
indexPatterns: dataViewsPluginStart,
uiSettings: coreStart.uiSettings,
}),
0,
0,
new TimeCache(dataPluginStart.query.timefilter.timefilter, 0),
{},
mockGetServiceSettings
);
await vegaParser.parseAsync();
Expand All @@ -106,12 +107,14 @@ describe('VegaVisualizations', () => {
mockedWidthValue = 256;
mockedHeightValue = 250;

// @ts-expect-error - accessing private member
await vegaVis.vegaView.resize();

expect(domNode.innerHTML).toMatchSnapshot();
} finally {
vegaVis.destroy();
}
// eslint-disable-next-line no-console
expect(console.log).toBeCalledTimes(2);
mockedConsoleLog.mockRestore();
});
Expand All @@ -127,8 +130,8 @@ describe('VegaVisualizations', () => {
indexPatterns: dataViewsPluginStart,
uiSettings: coreStart.uiSettings,
}),
0,
0,
new TimeCache(dataPluginStart.query.timefilter.timefilter, 0),
{},
mockGetServiceSettings
);
await vegaParser.parseAsync();
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/vis_types/vega/public/vega_visualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import { getNotifications, getData } from './services';
import type { VegaView } from './vega_view/vega_view';
import { createVegaStateRestorer } from './lib/vega_state_restorer';

type VegaVisType = new (el: HTMLDivElement, fireEvent: IInterpreterRenderHandlers['event']) => {
export type VegaVisType = new (
el: HTMLDivElement,
fireEvent: IInterpreterRenderHandlers['event']
) => {
render(visData: VegaParser): Promise<void>;
resize(dimensions?: { height: number; width: number }): Promise<void>;
destroy(): void;
Expand Down