Skip to content

Commit 45e07af

Browse files
[Reporting] Add new data-render-error attribute (#114472)
* added new data-render-error attribute, read it and store it on job object * added data-render-error to the example app * added jest test * address pr feedback - make renderErrors optional in interfaces - create separate selectors for data render error selector/attr - Tidy up mergeMap behaviour * fix observable.test.ts snapshots and browser driver mock * updated jest snapshots Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 2afb43b commit 45e07af

File tree

14 files changed

+191
-4
lines changed

14 files changed

+191
-4
lines changed

x-pack/examples/reporting_example/public/components/app.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,12 @@ export const ReportingExampleApp = ({
244244
)}
245245
</EuiFlexItem>
246246
{logos.map((item, index) => (
247-
<EuiFlexItem key={index} data-shared-item>
247+
<EuiFlexItem
248+
key={index}
249+
data-shared-item
250+
data-shared-render-error
251+
data-render-error="This is an example error"
252+
>
248253
<EuiCard
249254
icon={<EuiIcon size="xxl" type={`logo${item}`} />}
250255
title={`Elastic ${item}`}

x-pack/plugins/reporting/server/export_types/common/generate_png.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export async function generatePngObservableFactory(reporting: ReportingCore) {
5454
if (current.error) {
5555
found.push(current.error.message);
5656
}
57+
if (current.renderErrors) {
58+
found.push(...current.renderErrors);
59+
}
5760
return found;
5861
}, [] as string[]),
5962
})),

x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) {
108108
if (current.error) {
109109
found.push(current.error.message);
110110
}
111+
if (current.renderErrors) {
112+
found.push(...current.renderErrors);
113+
}
111114
return found;
112115
}, [] as string[]),
113116
};

x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ export async function generatePdfObservableFactory(reporting: ReportingCore) {
120120
if (current.error) {
121121
found.push(current.error.message);
122122
}
123+
if (current.renderErrors) {
124+
found.push(...current.renderErrors);
125+
}
123126
return found;
124127
}, [] as string[]),
125128
};

x-pack/plugins/reporting/server/lib/layouts/create_layout.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ describe('Create Layout', () => {
3232
"selectors": Object {
3333
"itemsCountAttribute": "data-shared-items-count",
3434
"renderComplete": "[data-shared-item]",
35+
"renderError": "[data-render-error]",
36+
"renderErrorAttribute": "data-render-error",
3537
"screenshot": "[data-shared-items-container]",
3638
"timefilterDurationAttribute": "data-shared-timefilter-duration",
3739
},
@@ -63,6 +65,8 @@ describe('Create Layout', () => {
6365
"selectors": Object {
6466
"itemsCountAttribute": "data-shared-items-count",
6567
"renderComplete": "[data-shared-item]",
68+
"renderError": "[data-render-error]",
69+
"renderErrorAttribute": "data-render-error",
6670
"screenshot": "[data-shared-item]",
6771
"timefilterDurationAttribute": "data-shared-timefilter-duration",
6872
},
@@ -91,6 +95,8 @@ describe('Create Layout', () => {
9195
"selectors": Object {
9296
"itemsCountAttribute": "data-shared-items-count",
9397
"renderComplete": "[data-shared-item]",
98+
"renderError": "[data-render-error]",
99+
"renderErrorAttribute": "data-render-error",
94100
"screenshot": "[data-shared-items-container]",
95101
"timefilterDurationAttribute": "data-shared-timefilter-duration",
96102
},

x-pack/plugins/reporting/server/lib/layouts/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type { Layout } from './layout';
1313
export interface LayoutSelectorDictionary {
1414
screenshot: string;
1515
renderComplete: string;
16+
renderError: string;
17+
renderErrorAttribute: string;
1618
itemsCountAttribute: string;
1719
timefilterDurationAttribute: string;
1820
}
@@ -33,6 +35,8 @@ export const LayoutTypes = {
3335
export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({
3436
screenshot: '[data-shared-items-container]',
3537
renderComplete: '[data-shared-item]',
38+
renderError: '[data-render-error]',
39+
renderErrorAttribute: 'data-render-error',
3640
itemsCountAttribute: 'data-shared-items-count',
3741
timefilterDurationAttribute: 'data-shared-timefilter-duration',
3842
});

x-pack/plugins/reporting/server/lib/screenshots/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const DEFAULT_PAGELOAD_SELECTOR = `.${APP_WRAPPER_CLASS}`;
1111
export const CONTEXT_GETNUMBEROFITEMS = 'GetNumberOfItems';
1212
export const CONTEXT_INJECTCSS = 'InjectCss';
1313
export const CONTEXT_WAITFORRENDER = 'WaitForRender';
14+
export const CONTEXT_GETRENDERERRORS = 'GetVisualisationsRenderErrors';
1415
export const CONTEXT_GETTIMERANGE = 'GetTimeRange';
1516
export const CONTEXT_ELEMENTATTRIBUTES = 'ElementPositionAndAttributes';
1617
export const CONTEXT_WAITFORELEMENTSTOBEINDOM = 'WaitForElementsToBeInDOM';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { HeadlessChromiumDriver } from '../../browsers';
9+
import {
10+
createMockBrowserDriverFactory,
11+
createMockConfig,
12+
createMockConfigSchema,
13+
createMockLayoutInstance,
14+
createMockLevelLogger,
15+
createMockReportingCore,
16+
} from '../../test_helpers';
17+
import { CaptureConfig } from '../../types';
18+
import { LayoutInstance } from '../layouts';
19+
import { LevelLogger } from '../level_logger';
20+
import { getRenderErrors } from './get_render_errors';
21+
22+
describe('getRenderErrors', () => {
23+
let captureConfig: CaptureConfig;
24+
let layout: LayoutInstance;
25+
let logger: jest.Mocked<LevelLogger>;
26+
let browser: HeadlessChromiumDriver;
27+
28+
beforeEach(async () => {
29+
const schema = createMockConfigSchema();
30+
const config = createMockConfig(schema);
31+
const core = await createMockReportingCore(schema);
32+
33+
captureConfig = config.get('capture');
34+
layout = createMockLayoutInstance(captureConfig);
35+
logger = createMockLevelLogger();
36+
37+
await createMockBrowserDriverFactory(core, logger, {
38+
evaluate: jest.fn(
39+
async <T extends (...args: unknown[]) => unknown>({
40+
fn,
41+
args,
42+
}: {
43+
fn: T;
44+
args: Parameters<T>;
45+
}) => fn(...args)
46+
),
47+
getCreatePage: (driver) => {
48+
browser = driver;
49+
50+
return jest.fn();
51+
},
52+
});
53+
});
54+
55+
afterEach(() => {
56+
document.body.innerHTML = '';
57+
});
58+
59+
it('should extract the error messages', async () => {
60+
document.body.innerHTML = `
61+
<div dataRenderErrorSelector="a test error" />
62+
<div dataRenderErrorSelector="a test error" />
63+
<div dataRenderErrorSelector="a test error" />
64+
<div dataRenderErrorSelector="a test error" />
65+
`;
66+
67+
await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual([
68+
'a test error',
69+
'a test error',
70+
'a test error',
71+
'a test error',
72+
]);
73+
});
74+
75+
it('should extract the error messages, even when there are none', async () => {
76+
document.body.innerHTML = `
77+
<renderedSelector />
78+
`;
79+
80+
await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual(undefined);
81+
});
82+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { i18n } from '@kbn/i18n';
9+
import type { HeadlessChromiumDriver } from '../../browsers';
10+
import type { LayoutInstance } from '../layouts';
11+
import { LevelLogger, startTrace } from '../';
12+
import { CONTEXT_GETRENDERERRORS } from './constants';
13+
14+
export const getRenderErrors = async (
15+
browser: HeadlessChromiumDriver,
16+
layout: LayoutInstance,
17+
logger: LevelLogger
18+
): Promise<undefined | string[]> => {
19+
const endTrace = startTrace('get_render_errors', 'read');
20+
logger.debug('reading render errors');
21+
const errorsFound: undefined | string[] = await browser.evaluate(
22+
{
23+
fn: (errorSelector, errorAttribute) => {
24+
const visualizations: Element[] = Array.from(document.querySelectorAll(errorSelector));
25+
const errors: string[] = [];
26+
27+
visualizations.forEach((visualization) => {
28+
const errorMessage = visualization.getAttribute(errorAttribute);
29+
if (errorMessage) {
30+
errors.push(errorMessage);
31+
}
32+
});
33+
34+
return errors.length ? errors : undefined;
35+
},
36+
args: [layout.selectors.renderError, layout.selectors.renderErrorAttribute],
37+
},
38+
{ context: CONTEXT_GETRENDERERRORS },
39+
logger
40+
);
41+
endTrace();
42+
43+
if (errorsFound?.length) {
44+
logger.warning(
45+
i18n.translate('xpack.reporting.screencapture.renderErrorsFound', {
46+
defaultMessage: 'Found {count} error messages. See report object for more information.',
47+
values: { count: errorsFound.length },
48+
})
49+
);
50+
}
51+
52+
return errorsFound;
53+
};

x-pack/plugins/reporting/server/lib/screenshots/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,12 @@ export interface ScreenshotResults {
5353
timeRange: string | null;
5454
screenshots: Screenshot[];
5555
error?: Error;
56+
57+
/**
58+
* Individual visualizations might encounter errors at runtime. If there are any they are added to this
59+
* field. Any text captured here is intended to be shown to the user for debugging purposes, reporting
60+
* does no further sanitization on these strings.
61+
*/
62+
renderErrors?: string[];
5663
elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing
5764
}

0 commit comments

Comments
 (0)