[Lens] Make Lens compatible with reporting (#46027)

This commit is contained in:
Chris Davies 2019-09-25 11:11:13 -04:00 committed by GitHub
parent 5777a02501
commit b0c3ea042c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1382 additions and 72 deletions

View file

@ -13,6 +13,7 @@ import { KibanaDatatable } from '../../../../../../src/legacy/core_plugins/inter
import { LensMultiTable } from '../types';
import { IInterpreterRenderFunction } from '../../../../../../src/legacy/core_plugins/expressions/public';
import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities';
import { VisualizationContainer } from '../visualization_container';
export interface DatatableColumns {
columnIds: string[];
@ -132,28 +133,30 @@ function DatatableComponent(props: DatatableProps & { formatFactory: FormatFacto
});
return (
<EuiBasicTable
className="lnsDataTable"
data-test-subj="lnsDataTable"
columns={props.args.columns.columnIds
.map((field, index) => {
return {
field,
name: props.args.columns.labels[index],
};
})
.filter(({ field }) => !!field)}
items={
firstTable
? firstTable.rows.map(row => {
const formattedRow: Record<string, unknown> = {};
Object.entries(formatters).forEach(([columnId, formatter]) => {
formattedRow[columnId] = formatter.convert(row[columnId]);
});
return formattedRow;
})
: []
}
/>
<VisualizationContainer>
<EuiBasicTable
className="lnsDataTable"
data-test-subj="lnsDataTable"
columns={props.args.columns.columnIds
.map((field, index) => {
return {
field,
name: props.args.columns.labels[index],
};
})
.filter(({ field }) => !!field)}
items={
firstTable
? firstTable.rows.map(row => {
const formattedRow: Record<string, unknown> = {};
Object.entries(formatters).forEach(([columnId, formatter]) => {
formattedRow[columnId] = formatter.convert(row[columnId]);
});
return formattedRow;
})
: []
}
/>
</VisualizationContainer>
);
}

View file

@ -52,7 +52,8 @@ describe('metric_expression', () => {
expect(shallow(<MetricChart data={data} args={args} formatFactory={x => x as FieldFormat} />))
.toMatchInlineSnapshot(`
<div
<VisualizationContainer
reportTitle="My fanci metric chart"
style={
Object {
"alignItems": "center",
@ -88,7 +89,7 @@ describe('metric_expression', () => {
My fanci metric chart
</div>
</AutoScale>
</div>
</VisualizationContainer>
`);
});
@ -104,34 +105,35 @@ describe('metric_expression', () => {
/>
)
).toMatchInlineSnapshot(`
<div
style={
Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "column",
"justifyContent": "center",
"maxHeight": "100%",
"maxWidth": "100%",
"textAlign": "center",
}
}
>
<AutoScale>
<div
data-test-subj="lns_metric_value"
style={
Object {
"fontSize": "60pt",
"fontWeight": 600,
}
}
>
10110
</div>
</AutoScale>
</div>
`);
<VisualizationContainer
reportTitle="My fanci metric chart"
style={
Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "column",
"justifyContent": "center",
"maxHeight": "100%",
"maxWidth": "100%",
"textAlign": "center",
}
}
>
<AutoScale>
<div
data-test-subj="lns_metric_value"
style={
Object {
"fontSize": "60pt",
"fontWeight": 600,
}
}
>
10110
</div>
</AutoScale>
</VisualizationContainer>
`);
});
});
});

View file

@ -12,6 +12,7 @@ import { IInterpreterRenderFunction } from '../../../../../../src/legacy/core_pl
import { MetricConfig } from './types';
import { LensMultiTable } from '../types';
import { AutoScale } from './auto_scale';
import { VisualizationContainer } from '../visualization_container';
export interface MetricChartProps {
data: LensMultiTable;
@ -105,7 +106,8 @@ export function MetricChart({
}
return (
<div
<VisualizationContainer
reportTitle={title}
style={{
display: 'flex',
flexDirection: 'column',
@ -126,6 +128,6 @@ export function MetricChart({
</div>
)}
</AutoScale>
</div>
</VisualizationContainer>
);
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { VisualizationContainer } from './visualization_container';
describe('VisualizationContainer', () => {
test('renders reporting data attributes when ready', () => {
const component = mount(<VisualizationContainer isReady={true}>Hello!</VisualizationContainer>);
const reportingEl = component.find('[data-shared-item]').first();
expect(reportingEl.prop('data-render-complete')).toBeTruthy();
expect(reportingEl.prop('data-shared-item')).toBeTruthy();
});
test('does not render data attributes when not ready', () => {
const component = mount(
<VisualizationContainer isReady={false}>Hello!</VisualizationContainer>
);
const reportingEl = component.find('[data-shared-item]').first();
expect(reportingEl.prop('data-render-complete')).toBeFalsy();
expect(reportingEl.prop('data-shared-item')).toBeTruthy();
});
test('renders child content', () => {
const component = mount(
<VisualizationContainer isReady={false}>Hello!</VisualizationContainer>
);
expect(component.text()).toEqual('Hello!');
});
test('defaults to rendered', () => {
const component = mount(<VisualizationContainer>Hello!</VisualizationContainer>);
const reportingEl = component.find('[data-shared-item]').first();
expect(reportingEl.prop('data-render-complete')).toBeTruthy();
expect(reportingEl.prop('data-shared-item')).toBeTruthy();
});
test('renders title for reporting, if provided', () => {
const component = mount(
<VisualizationContainer reportTitle="shazam!">Hello!</VisualizationContainer>
);
const reportingEl = component.find('[data-shared-item]').first();
expect(reportingEl.prop('data-title')).toEqual('shazam!');
});
test('renders style', () => {
const component = mount(
<VisualizationContainer style={{ color: 'blue' }}>Hello!</VisualizationContainer>
);
const reportingEl = component.find('[data-shared-item]').first();
expect(reportingEl.prop('style')).toEqual({ color: 'blue' });
});
});

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
interface Props extends React.HTMLAttributes<HTMLDivElement> {
isReady?: boolean;
reportTitle?: string;
}
/**
* This is a convenience component that wraps rendered Lens visualizations. It adds reporting
* attributes (data-shared-item, data-render-complete, and data-title).
*/
export function VisualizationContainer({ isReady = true, reportTitle, children, ...rest }: Props) {
return (
<div data-shared-item data-render-complete={isReady} data-title={reportTitle} {...rest}>
{children}
</div>
);
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import {
Chart,
@ -26,6 +26,7 @@ import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/
import { IInterpreterRenderFunction } from '../../../../../../src/legacy/core_plugins/expressions/public';
import { LensMultiTable } from '../types';
import { XYArgs, SeriesType, visualizationTypes } from './types';
import { VisualizationContainer } from '../visualization_container';
export interface XYChartProps {
data: LensMultiTable;
@ -38,6 +39,16 @@ export interface XYRender {
value: XYChartProps;
}
export interface XYChartProps {
data: LensMultiTable;
args: XYArgs;
}
type XYChartRenderProps = XYChartProps & {
formatFactory: FormatFactory;
timeZone: string;
};
export const xyChart: ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs, XYRender> = ({
name: 'lens_xy_chart',
type: 'render',
@ -85,11 +96,6 @@ export const xyChart: ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs
// TODO the typings currently don't support custom type args. As soon as they do, this can be removed
} as unknown) as ExpressionFunction<'lens_xy_chart', LensMultiTable, XYArgs, XYRender>;
export interface XYChartProps {
data: LensMultiTable;
args: XYArgs;
}
export const getXyChartRenderer = (dependencies: {
formatFactory: FormatFactory;
timeZone: string;
@ -104,7 +110,7 @@ export const getXyChartRenderer = (dependencies: {
render: async (domNode: Element, config: XYChartProps, _handlers: unknown) => {
ReactDOM.render(
<I18nProvider>
<XYChart {...config} {...dependencies} />
<XYChartReportable {...config} {...dependencies} />
</I18nProvider>,
domNode
);
@ -115,15 +121,25 @@ function getIconForSeriesType(seriesType: SeriesType): IconType {
return visualizationTypes.find(c => c.id === seriesType)!.icon || 'empty';
}
export function XYChart({
data,
args,
formatFactory,
timeZone,
}: XYChartProps & {
formatFactory: FormatFactory;
timeZone: string;
}) {
const MemoizedChart = React.memo(XYChart);
export function XYChartReportable(props: XYChartRenderProps) {
const [isReady, setIsReady] = useState(false);
// It takes a cycle for the XY chart to render. This prevents
// reporting from printing a blank chart placeholder.
useEffect(() => {
setIsReady(true);
}, []);
return (
<VisualizationContainer isReady={isReady}>
<MemoizedChart {...props} />
</VisualizationContainer>
);
}
export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderProps) {
const { legend, layers, isHorizontal } = args;
if (Object.values(data.tables).every(table => table.rows.length === 0)) {

View file

@ -22,13 +22,14 @@ export default function({ getService, loadTestFile }: FtrProviderContext) {
after(async () => {
await esArchiver.unload('logstash_functional');
await esArchiver.unload('visualize/default');
await esArchiver.unload('lens/basic');
});
describe('', function() {
this.tags(['ciGroup4', 'skipFirefox']);
loadTestFile(require.resolve('./smokescreen'));
loadTestFile(require.resolve('./lens_reporting'));
});
});
}

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'reporting']);
const find = getService('find');
const esArchiver = getService('esArchiver');
describe('lens reporting', () => {
before(async () => {
await esArchiver.loadIfNeeded('lens/reporting');
});
after(async () => {
await esArchiver.unload('lens/reporting');
});
it('should not cause PDF reports to fail', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.selectDashboard('Lens reportz');
await PageObjects.reporting.openPdfReportingPanel();
await PageObjects.reporting.clickGenerateReportButton();
expect(await find.byButtonText('Download report', undefined, 60000)).to.be.ok();
});
});
}

File diff suppressed because it is too large Load diff