Skip to content

Commit

Permalink
[Reporting/Screenshots] Handle page setup errors and capture the page…
Browse files Browse the repository at this point in the history
…, don't fail the job (elastic#58683)

* [Reporting] Handle error if intercepted request could not be continued

* [Reporting/Screenshots] Handle page setup errors and capture the page with errors shown

* show warnings in UI

* i18n todos

* Cleanup an old troubleshooting task

* set the default for all new timeout settings to 30 seconds

* fix some tests

* update error strings

* Cleanup 2

* fix tests 2

* polish the job info map status items

* More error message updating

* Log the error that was caught

* Oops fix ts

* add documentation

* fix i18n

* fix mocha test

* use the openUrl timeout as the default for navigation

* fix comment

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
tsullivan and elasticmachine committed Mar 6, 2020
1 parent 69b5f80 commit 8782f90
Show file tree
Hide file tree
Showing 40 changed files with 724 additions and 503 deletions.
22 changes: 22 additions & 0 deletions docs/settings/reporting-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ index for any pending Reporting jobs. Defaults to `3000` (3 seconds).
[[xpack-reporting-q-timeout]]`xpack.reporting.queue.timeout`::
How long each worker has to produce a report. If your machine is slow or under
heavy load, you might need to increase this timeout. Specified in milliseconds.
If a Reporting job execution time goes over this time limit, the job will be
marked as a failure and there will not be a download available.
Defaults to `120000` (two minutes).

[float]
Expand All @@ -104,6 +106,26 @@ Defaults to `120000` (two minutes).
Reporting works by capturing screenshots from Kibana. The following settings
control the capturing process.

`xpack.reporting.capture.timeouts.openUrl`::
How long to allow the Reporting browser to wait for the initial data of the
Kibana page to load. Defaults to `30000` (30 seconds).

`xpack.reporting.capture.timeouts.waitForElements`::
How long to allow the Reporting browser to wait for the visualization panels to
load on the Kibana page. Defaults to `30000` (30 seconds).

`xpack.reporting.capture.timeouts.renderComplete`::
How long to allow the Reporting brwoser to wait for each visualization to
signal that it is done renderings. Defaults to `30000` (30 seconds).

[NOTE]
============
If any timeouts from `xpack.reporting.capture.timeouts.*` settings occur when
running a report job, Reporting will log the error and try to continue
capturing the page with a screenshot. As a result, a download will be
available, but there will likely be errors in the visualizations in the report.
============

`xpack.reporting.capture.maxAttempts`::
If capturing a report fails for any reason, Kibana will re-attempt othe reporting
job, as many times as this setting. Defaults to `3`.
Expand Down
20 changes: 20 additions & 0 deletions x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap

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

11 changes: 11 additions & 0 deletions x-pack/legacy/plugins/reporting/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ export async function config(Joi: any) {
.default(120000),
}).default(),
capture: Joi.object({
timeouts: Joi.object({
openUrl: Joi.number()
.integer()
.default(30000),
waitForElements: Joi.number()
.integer()
.default(30000),
renderComplete: Joi.number()
.integer()
.default(30000),
}).default(),
networkPolicy: Joi.object({
enabled: Joi.boolean().default(true),
rules: Joi.array()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export const LayoutTypes = {
PRINT: 'print',
};

export const WAITFOR_SELECTOR = '.application';
export const PAGELOAD_SELECTOR = '.application';
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface LayoutSelectorDictionary {
renderComplete: string;
itemsCountAttribute: string;
timefilterDurationAttribute: string;
toastHeader: string;
}

export interface PdfImageSize {
Expand All @@ -40,7 +39,6 @@ export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({
renderComplete: '[data-shared-item]',
itemsCountAttribute: 'data-shared-items-count',
timefilterDurationAttribute: 'data-shared-timefilter-duration',
toastHeader: '[data-test-subj="euiToastHeader"]',
});

export abstract class Layout {
Expand Down Expand Up @@ -75,9 +73,11 @@ export interface LayoutParams {
dimensions: Size;
}

export type LayoutInstance = Layout & {
interface LayoutSelectors {
// Fields that are not part of Layout: the instances
// independently implement these fields on their own
selectors: LayoutSelectorDictionary;
positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise<void>;
};
}

export type LayoutInstance = Layout & LayoutSelectors & Size;
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const ZOOM: number = 2;
export class PreserveLayout extends Layout {
public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors();
public readonly groupCount = 1;
private readonly height: number;
private readonly width: number;
public readonly height: number;
public readonly width: number;
private readonly scaledHeight: number;
private readonly scaledWidth: number;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export const CONTEXT_INJECTCSS = 'InjectCss';
export const CONTEXT_WAITFORRENDER = 'WaitForRender';
export const CONTEXT_GETTIMERANGE = 'GetTimeRange';
export const CONTEXT_ELEMENTATTRIBUTES = 'ElementPositionAndAttributes';
export const CONTEXT_CHECKFORTOASTMESSAGE = 'CheckForToastMessage';
export const CONTEXT_WAITFORELEMENTSTOBEINDOM = 'WaitForElementsToBeInDOM';
export const CONTEXT_SKIPTELEMETRY = 'SkipTelemetry';
export const CONTEXT_READMETADATA = 'ReadVisualizationsMetadata';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';
import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers';
import { LayoutInstance } from '../../layouts/layout';
import { AttributesMap, ElementsPositionAndAttribute } from './types';
Expand All @@ -14,50 +15,58 @@ export const getElementPositionAndAttributes = async (
browser: HeadlessBrowser,
layout: LayoutInstance,
logger: Logger
): Promise<ElementsPositionAndAttribute[]> => {
const elementsPositionAndAttributes: ElementsPositionAndAttribute[] = await browser.evaluate(
{
fn: (selector: string, attributes: any) => {
const elements: NodeListOf<Element> = document.querySelectorAll(selector);
): Promise<ElementsPositionAndAttribute[] | null> => {
const { screenshot: screenshotSelector } = layout.selectors; // data-shared-items-container
let elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null;
try {
elementsPositionAndAttributes = await browser.evaluate(
{
fn: (selector, attributes) => {
const elements: NodeListOf<Element> = document.querySelectorAll(selector);

// NodeList isn't an array, just an iterator, unable to use .map/.forEach
const results: ElementsPositionAndAttribute[] = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const boundingClientRect = element.getBoundingClientRect() as DOMRect;
results.push({
position: {
boundingClientRect: {
// modern browsers support x/y, but older ones don't
top: boundingClientRect.y || boundingClientRect.top,
left: boundingClientRect.x || boundingClientRect.left,
width: boundingClientRect.width,
height: boundingClientRect.height,
// NodeList isn't an array, just an iterator, unable to use .map/.forEach
const results: ElementsPositionAndAttribute[] = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const boundingClientRect = element.getBoundingClientRect() as DOMRect;
results.push({
position: {
boundingClientRect: {
// modern browsers support x/y, but older ones don't
top: boundingClientRect.y || boundingClientRect.top,
left: boundingClientRect.x || boundingClientRect.left,
width: boundingClientRect.width,
height: boundingClientRect.height,
},
scroll: {
x: window.scrollX,
y: window.scrollY,
},
},
scroll: {
x: window.scrollX,
y: window.scrollY,
},
},
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
const attribute = attributes[key];
(result as any)[key] = element.getAttribute(attribute);
return result;
}, {} as AttributesMap),
});
}
return results;
attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => {
const attribute = attributes[key];
(result as any)[key] = element.getAttribute(attribute);
return result;
}, {} as AttributesMap),
});
}
return results;
},
args: [screenshotSelector, { title: 'data-title', description: 'data-description' }],
},
args: [layout.selectors.screenshot, { title: 'data-title', description: 'data-description' }],
},
{ context: CONTEXT_ELEMENTATTRIBUTES },
logger
);

if (elementsPositionAndAttributes.length === 0) {
throw new Error(
`No shared items containers were found on the page! Reporting requires a container element with the '${layout.selectors.screenshot}' attribute on the page.`
{ context: CONTEXT_ELEMENTATTRIBUTES },
logger
);

if (!elementsPositionAndAttributes || elementsPositionAndAttributes.length === 0) {
throw new Error(
i18n.translate('xpack.reporting.screencapture.noElements', {
defaultMessage: `An error occurred while reading the page for visualization panels: no panels were found.`,
})
);
}
} catch (err) {
elementsPositionAndAttributes = null;
}

return elementsPositionAndAttributes;
Expand Down
Loading

0 comments on commit 8782f90

Please sign in to comment.