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

Lazy load metric & mardown visualizations #78391

Merged
merged 9 commits into from
Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/plugins/vis_type_markdown/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"ui": true,
"server": true,
"requiredPlugins": ["expressions", "visualizations"],
"requiredBundles": ["kibanaUtils", "kibanaReact", "charts", "visualizations", "expressions", "visDefaultEditor"]
"requiredBundles": ["kibanaReact", "charts", "visualizations", "expressions", "visDefaultEditor"]
}

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

8 changes: 0 additions & 8 deletions src/plugins/vis_type_markdown/public/index.scss

This file was deleted.

8 changes: 4 additions & 4 deletions src/plugins/vis_type_markdown/public/markdown_fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Render } from '../../expressions/public';
import { Arguments, MarkdownVisParams } from './types';

interface RenderValue {
export interface MarkdownVisRenderValue {
visType: 'markdown';
visConfig: MarkdownVisParams;
visParams: MarkdownVisParams;
}

export type MarkdownVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
'markdownVis',
unknown,
Arguments,
Render<RenderValue>
Render<MarkdownVisRenderValue>
>;

export const createMarkdownVisFn = (): MarkdownVisExpressionFunctionDefinition => ({
Expand Down Expand Up @@ -70,7 +70,7 @@ export const createMarkdownVisFn = (): MarkdownVisExpressionFunctionDefinition =
as: 'markdown_vis',
value: {
visType: 'markdown',
visConfig: {
visParams: {
markdown: args.markdown,
openLinksInNewTab: args.openLinksInNewTab,
fontSize: parseInt(args.font.spec.fontSize || '12', 10),
Expand Down
50 changes: 19 additions & 31 deletions src/plugins/vis_type_markdown/public/markdown_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,29 @@
* under the License.
*/

import React from 'react';
import React, { lazy } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { VisualizationContainer } from '../../visualizations/public';
import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers';
import { MarkdownVisWrapper } from './markdown_vis_controller';
import { StartServicesGetter } from '../../kibana_utils/public';
import { MarkdownVisRenderValue } from './markdown_fn';

export const getMarkdownRenderer = (start: StartServicesGetter) => {
const markdownVisRenderer: () => ExpressionRenderDefinition = () => ({
name: 'markdown_vis',
displayName: 'markdown visualization',
reuseDomNode: true,
render: async (domNode: HTMLElement, config: any, handlers: any) => {
const { visConfig } = config;
// @ts-ignore
const MarkdownVisComponent = lazy(() => import('./markdown_vis_controller'));

const I18nContext = await start().core.i18n.Context;
export const markdownVisRenderer: ExpressionRenderDefinition<MarkdownVisRenderValue> = {
name: 'markdown_vis',
displayName: 'markdown visualization',
reuseDomNode: true,
render: async (domNode, { visParams }, handlers) => {
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});

handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});

render(
<I18nContext>
<VisualizationContainer className="markdownVis">
<MarkdownVisWrapper
visParams={visConfig}
renderComplete={handlers.done}
fireEvent={handlers.event}
/>
</VisualizationContainer>
</I18nContext>,
domNode
);
},
});

return markdownVisRenderer;
render(
<VisualizationContainer className="markdownVis">
<MarkdownVisComponent {...visParams} renderComplete={handlers.done} />
</VisualizationContainer>,
domNode
);
},
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Prefix all styles with "mkd" to avoid conflicts.
// Examples
// mkdChart
// mkdChart__legend
// mkdChart__legend--small
// mkdChart__legend-isLoading

.mkdVis {
padding: $euiSizeS;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import React from 'react';
import { wait } from '@testing-library/dom';
import { render, cleanup } from '@testing-library/react/pure';
import { MarkdownVisWrapper } from './markdown_vis_controller';
import MarkdownVisComponent from './markdown_vis_controller';

afterEach(cleanup);

Expand All @@ -36,7 +36,7 @@ describe('markdown vis controller', () => {
};

const { getByTestId, getByText } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
<MarkdownVisComponent {...vis.params} renderComplete={jest.fn()} />
);

await wait(() => getByTestId('markdownBody'));
Expand All @@ -60,7 +60,7 @@ describe('markdown vis controller', () => {
};

const { getByTestId, getByText } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
<MarkdownVisComponent {...vis.params} renderComplete={jest.fn()} />
);

await wait(() => getByTestId('markdownBody'));
Expand All @@ -82,17 +82,15 @@ describe('markdown vis controller', () => {
};

const { getByTestId, getByText, rerender } = render(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
<MarkdownVisComponent {...vis.params} renderComplete={jest.fn()} />
);

await wait(() => getByTestId('markdownBody'));

expect(getByText(/initial/i)).toBeInTheDocument();

vis.params.markdown = 'Updated';
rerender(
<MarkdownVisWrapper visParams={vis.params} renderComplete={jest.fn()} fireEvent={jest.fn()} />
);
rerender(<MarkdownVisComponent {...vis.params} renderComplete={jest.fn()} />);

expect(getByText(/Updated/i)).toBeInTheDocument();
});
Expand All @@ -114,11 +112,7 @@ describe('markdown vis controller', () => {

it('should be called on initial rendering', async () => {
const { getByTestId } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
<MarkdownVisComponent {...vis.params} renderComplete={renderComplete} />
);

await wait(() => getByTestId('markdownBody'));
Expand All @@ -128,11 +122,7 @@ describe('markdown vis controller', () => {

it('should be called on successive render when params change', async () => {
const { getByTestId, rerender } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
<MarkdownVisComponent {...vis.params} renderComplete={renderComplete} />
);

await wait(() => getByTestId('markdownBody'));
Expand All @@ -142,24 +132,14 @@ describe('markdown vis controller', () => {
renderComplete.mockClear();
vis.params.markdown = 'changed';

rerender(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
rerender(<MarkdownVisComponent {...vis.params} renderComplete={renderComplete} />);

expect(renderComplete).toHaveBeenCalledTimes(1);
});

it('should be called on successive render even without data change', async () => {
const { getByTestId, rerender } = render(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
<MarkdownVisComponent {...vis.params} renderComplete={renderComplete} />
);

await wait(() => getByTestId('markdownBody'));
Expand All @@ -168,13 +148,7 @@ describe('markdown vis controller', () => {

renderComplete.mockClear();

rerender(
<MarkdownVisWrapper
visParams={vis.params}
renderComplete={renderComplete}
fireEvent={jest.fn()}
/>
);
rerender(<MarkdownVisComponent {...vis.params} renderComplete={renderComplete} />);

expect(renderComplete).toHaveBeenCalledTimes(1);
});
Expand Down
92 changes: 22 additions & 70 deletions src/plugins/vis_type_markdown/public/markdown_vis_controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,83 +17,35 @@
* under the License.
*/

import React from 'react';
import React, { useEffect } from 'react';
import { Markdown } from '../../kibana_react/public';
import { MarkdownVisParams } from './types';

import './markdown_vis.scss';

interface MarkdownVisComponentProps extends MarkdownVisParams {
renderComplete: () => void;
}

/**
* The MarkdownVisComponent renders markdown to HTML and presents it.
*/
class MarkdownVisComponent extends React.Component<MarkdownVisComponentProps> {
/**
* Will be called after the first render when the component is present in the DOM.
*
* We call renderComplete here, to signal, that we are done with rendering.
*/
componentDidMount() {
this.props.renderComplete();
}

/**
* Will be called after the component has been updated and the changes has been
* flushed into the DOM.
*
* We will use this to signal that we are done rendering by calling the
* renderComplete property.
*/
componentDidUpdate() {
this.props.renderComplete();
}
const MarkdownVisComponent = ({
fontSize,
markdown,
openLinksInNewTab,
renderComplete,
}: MarkdownVisComponentProps) => {
useEffect(renderComplete); // renderComplete will be called after each render to signal, that we are done with rendering.

/**
* Render the actual HTML.
* Note: if only fontSize parameter has changed, this method will be called
* and return the appropriate JSX, but React will detect, that only the
* style argument has been updated, and thus only set this attribute to the DOM.
*/
render() {
return (
<div className="mkdVis" style={{ fontSize: `${this.props.fontSize}pt` }}>
<Markdown
data-test-subj="markdownBody"
markdown={this.props.markdown}
openLinksInNewTab={this.props.openLinksInNewTab}
/>
</div>
);
}
}

/**
* This is a wrapper component, that is actually used as the visualization.
* The sole purpose of this component is to extract all required parameters from
* the properties and pass them down as separate properties to the actual component.
* That way the actual (MarkdownVisComponent) will properly trigger it's prop update
* callback (componentWillReceiveProps) if one of these params change. It wouldn't
* trigger otherwise (e.g. it doesn't for this wrapper), since it only triggers
* if the reference to the prop changes (in this case the reference to vis).
*
* The way React works, this wrapper nearly brings no overhead, but allows us
* to use proper lifecycle methods in the actual component.
*/

export interface MarkdownVisWrapperProps {
visParams: MarkdownVisParams;
fireEvent: (event: any) => void;
renderComplete: () => void;
}

export function MarkdownVisWrapper(props: MarkdownVisWrapperProps) {
return (
<MarkdownVisComponent
fontSize={props.visParams.fontSize}
markdown={props.visParams.markdown}
openLinksInNewTab={props.visParams.openLinksInNewTab}
renderComplete={props.renderComplete}
/>
<div className="mkdVis" style={{ fontSize: `${fontSize}pt` }}>
<Markdown
data-test-subj="markdownBody"
markdown={markdown}
openLinksInNewTab={openLinksInNewTab}
/>
</div>
);
}
};

// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { MarkdownVisComponent as default };
8 changes: 2 additions & 6 deletions src/plugins/vis_type_markdown/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ import { VisualizationsSetup } from '../../visualizations/public';
import { markdownVisDefinition } from './markdown_vis';
import { createMarkdownVisFn } from './markdown_fn';
import { ConfigSchema } from '../config';

import './index.scss';
import { getMarkdownRenderer } from './markdown_renderer';
import { createStartServicesGetter } from '../../kibana_utils/public';
import { markdownVisRenderer } from './markdown_renderer';

/** @internal */
export interface MarkdownPluginSetupDependencies {
Expand All @@ -44,9 +41,8 @@ export class MarkdownPlugin implements Plugin<void, void> {
}

public setup(core: CoreSetup, { expressions, visualizations }: MarkdownPluginSetupDependencies) {
const start = createStartServicesGetter(core.getStartServices);
visualizations.createBaseVisualization(markdownVisDefinition);
expressions.registerRenderer(getMarkdownRenderer(start));
expressions.registerRenderer(markdownVisRenderer);
expressions.registerFunction(createMarkdownVisFn);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Prefix all styles with "mtr" to avoid conflicts.
// Examples
// mtrChart
// mtrChart__legend
// mtrChart__legend--small
// mtrChart__legend-isLoading

.mtrVis {
width: 100%;
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import React from 'react';
import { shallow } from 'enzyme';

import { MetricVisComponent, MetricVisComponentProps } from './metric_vis_component';
import MetricVisComponent, { MetricVisComponentProps } from './metric_vis_component';

jest.mock('../services', () => ({
getFormatService: () => ({
Expand Down