Reporting: Fix missing time range in the PDF page header (#47916)
* layout selector refactoring
* remove some any types
* Expose the time duration for sharing apps
* switch Reporting to use timeRange.duration
* fix mock
* up the logs for platform bug
* add jest snapshot test
* add functional ui test
* Revert "up the logs for platform bug"
This reverts commit b128c35e15
.
* fix snapshot test
This commit is contained in:
parent
2335902ffa
commit
1939b8c55c
|
@ -47,9 +47,14 @@ startMock.uiSettings.get.mockImplementation((key: string) => {
|
|||
},
|
||||
];
|
||||
case 'dateFormat':
|
||||
return 'YY';
|
||||
return 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
case 'history:limit':
|
||||
return 10;
|
||||
case 'timepicker:timeDefaults':
|
||||
return {
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unexpected config key: ${key}`);
|
||||
}
|
||||
|
@ -122,6 +127,7 @@ function wrapQueryBarTopRowInContext(testProps: any) {
|
|||
describe('QueryBarTopRowTopRow', () => {
|
||||
const QUERY_INPUT_SELECTOR = 'QueryBarInputUI';
|
||||
const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker';
|
||||
const TIMEPICKER_DURATION = '[data-shared-timefilter-duration]';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -198,6 +204,24 @@ describe('QueryBarTopRowTopRow', () => {
|
|||
expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1);
|
||||
});
|
||||
|
||||
it('Should render the timefilter duration container for sharing', () => {
|
||||
const component = mount(
|
||||
wrapQueryBarTopRowInContext({
|
||||
isDirty: false,
|
||||
screenTitle: 'Another Screen',
|
||||
showDatePicker: true,
|
||||
dateRangeFrom: 'now-7d',
|
||||
dateRangeTo: 'now',
|
||||
timeHistory: timefilterSetupMock.history,
|
||||
})
|
||||
);
|
||||
|
||||
// match the data attribute rendered in the in the ReactHTML object
|
||||
expect(component.find(TIMEPICKER_DURATION)).toMatchObject(
|
||||
/<div\b.*\bdata-shared-timefilter-duration\b/
|
||||
);
|
||||
});
|
||||
|
||||
it('Should render only query input bar', () => {
|
||||
const component = mount(
|
||||
wrapQueryBarTopRowInContext({
|
||||
|
|
|
@ -17,12 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSuperDatePicker } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiSuperDatePicker,
|
||||
prettyDuration,
|
||||
} from '@elastic/eui';
|
||||
// @ts-ignore
|
||||
import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
|
@ -156,6 +164,14 @@ function QueryBarTopRowUI(props: Props) {
|
|||
});
|
||||
}
|
||||
|
||||
function toAbsoluteString(value: string, roundUp = false) {
|
||||
const valueAsMoment = dateMath.parse(value, { roundUp });
|
||||
if (!valueAsMoment) {
|
||||
return value;
|
||||
}
|
||||
return valueAsMoment.toISOString();
|
||||
}
|
||||
|
||||
function renderQueryInput() {
|
||||
if (!shouldRenderQueryInput()) return;
|
||||
return (
|
||||
|
@ -174,6 +190,22 @@ function QueryBarTopRowUI(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
function renderSharingMetaFields() {
|
||||
const { from, to } = getDateRange();
|
||||
const dateRangePretty = prettyDuration(
|
||||
toAbsoluteString(from),
|
||||
toAbsoluteString(to),
|
||||
[],
|
||||
uiSettings.get('dateFormat')
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-shared-timefilter-duration={dateRangePretty}
|
||||
data-test-subj="dataSharedTimefilterDuration"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function shouldRenderDatePicker(): boolean {
|
||||
return Boolean(props.showDatePicker || props.showAutoRefreshOnly);
|
||||
}
|
||||
|
@ -322,6 +354,7 @@ function QueryBarTopRowUI(props: Props) {
|
|||
justifyContent="flexEnd"
|
||||
>
|
||||
{renderQueryInput()}
|
||||
{renderSharingMetaFields()}
|
||||
<EuiFlexItem grow={false}>{renderUpdateButton()}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -22,7 +22,7 @@ import expect from '@kbn/expect';
|
|||
export default function ({ getService, getPageObjects }) {
|
||||
const retry = getService('retry');
|
||||
const dashboardPanelActions = getService('dashboardPanelActions');
|
||||
const PageObjects = getPageObjects(['dashboard']);
|
||||
const PageObjects = getPageObjects(['dashboard', 'timePicker']);
|
||||
|
||||
describe('dashboard data-shared attributes', function describeIndexTests() {
|
||||
let originalPanelTitles;
|
||||
|
@ -32,6 +32,13 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
});
|
||||
|
||||
it('should have time picker with data-shared-timefilter-duration', async () => {
|
||||
await retry.try(async () => {
|
||||
const sharedData = await PageObjects.timePicker.getTimeDurationForSharing();
|
||||
expect(sharedData).to.not.be(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have data-shared-items-count set to the number of embeddables on the dashboard', async () => {
|
||||
await retry.try(async () => {
|
||||
const sharedItemsCount = await PageObjects.dashboard.getSharedItemsCount();
|
||||
|
|
|
@ -184,6 +184,14 @@ export function TimePickerPageProvider({ getService, getPageObjects }) {
|
|||
};
|
||||
}
|
||||
|
||||
async getTimeDurationForSharing() {
|
||||
return await retry.try(async () => {
|
||||
const element = await testSubjects.find('dataSharedTimefilterDuration');
|
||||
const data = await element.getAttribute('data-shared-timefilter-duration');
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
async getTimeConfigAsAbsoluteTimes() {
|
||||
await this.showStartEndTimes();
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@ export interface LayoutSelectorDictionary {
|
|||
screenshot: string;
|
||||
renderComplete: string;
|
||||
itemsCountAttribute: string;
|
||||
timefilterFromAttribute: string;
|
||||
timefilterToAttribute: string;
|
||||
timefilterDurationAttribute: string;
|
||||
toastHeader: string;
|
||||
}
|
||||
|
||||
|
@ -36,6 +35,14 @@ export interface PdfImageSize {
|
|||
height?: number;
|
||||
}
|
||||
|
||||
export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({
|
||||
screenshot: '[data-shared-items-container]',
|
||||
renderComplete: '[data-shared-item]',
|
||||
itemsCountAttribute: 'data-shared-items-count',
|
||||
timefilterDurationAttribute: 'data-shared-timefilter-duration',
|
||||
toastHeader: '[data-test-subj="euiToastHeader"]',
|
||||
});
|
||||
|
||||
export abstract class Layout {
|
||||
public id: string = '';
|
||||
|
||||
|
|
|
@ -5,21 +5,19 @@
|
|||
*/
|
||||
import path from 'path';
|
||||
import { LayoutTypes } from '../constants';
|
||||
import { Layout, LayoutSelectorDictionary, PageSizeParams, Size } from './layout';
|
||||
import {
|
||||
getDefaultLayoutSelectors,
|
||||
Layout,
|
||||
LayoutSelectorDictionary,
|
||||
PageSizeParams,
|
||||
Size,
|
||||
} from './layout';
|
||||
|
||||
// We use a zoom of two to bump up the resolution of the screenshot a bit.
|
||||
const ZOOM: number = 2;
|
||||
|
||||
export class PreserveLayout extends Layout {
|
||||
public readonly selectors: LayoutSelectorDictionary = {
|
||||
screenshot: '[data-shared-items-container]',
|
||||
renderComplete: '[data-shared-item]',
|
||||
itemsCountAttribute: 'data-shared-items-count',
|
||||
timefilterFromAttribute: 'data-shared-timefilter-from',
|
||||
timefilterToAttribute: 'data-shared-timefilter-to',
|
||||
toastHeader: '[data-test-subj="euiToastHeader"]',
|
||||
};
|
||||
|
||||
public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors();
|
||||
public readonly groupCount = 1;
|
||||
private readonly height: number;
|
||||
private readonly width: number;
|
||||
|
|
|
@ -9,21 +9,15 @@ import { LevelLogger } from '../../../server/lib';
|
|||
import { HeadlessChromiumDriver } from '../../../server/browsers/chromium/driver';
|
||||
import { KbnServer } from '../../../types';
|
||||
import { LayoutTypes } from '../constants';
|
||||
import { Layout, LayoutSelectorDictionary, Size } from './layout';
|
||||
import { getDefaultLayoutSelectors, Layout, LayoutSelectorDictionary, Size } from './layout';
|
||||
import { CaptureConfig } from './types';
|
||||
|
||||
export class PrintLayout extends Layout {
|
||||
public readonly selectors: LayoutSelectorDictionary = {
|
||||
...getDefaultLayoutSelectors(),
|
||||
screenshot: '[data-shared-item]',
|
||||
renderComplete: '[data-shared-item]',
|
||||
itemsCountAttribute: 'data-shared-items-count',
|
||||
timefilterFromAttribute: 'data-shared-timefilter-from',
|
||||
timefilterToAttribute: 'data-shared-timefilter-to',
|
||||
toastHeader: '[data-test-subj="euiToastHeader"]',
|
||||
};
|
||||
|
||||
public readonly groupCount = 2;
|
||||
|
||||
private captureConfig: CaptureConfig;
|
||||
|
||||
constructor(server: KbnServer) {
|
||||
|
|
|
@ -17,27 +17,25 @@ export const getTimeRange = async (
|
|||
logger.debug('getting timeRange');
|
||||
|
||||
const timeRange: TimeRange | null = await browser.evaluate({
|
||||
fn: (fromAttribute, toAttribute) => {
|
||||
const fromElement = document.querySelector(`[${fromAttribute}]`);
|
||||
const toElement = document.querySelector(`[${toAttribute}]`);
|
||||
fn: durationAttribute => {
|
||||
const durationElement = document.querySelector(`[${durationAttribute}]`);
|
||||
|
||||
if (!fromElement || !toElement) {
|
||||
if (!durationElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const from = fromElement.getAttribute(fromAttribute);
|
||||
const to = toElement.getAttribute(toAttribute);
|
||||
if (!to || !from) {
|
||||
const duration = durationElement.getAttribute(durationAttribute);
|
||||
if (!duration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { from, to };
|
||||
return { duration };
|
||||
},
|
||||
args: [layout.selectors.timefilterFromAttribute, layout.selectors.timefilterToAttribute],
|
||||
args: [layout.selectors.timefilterDurationAttribute],
|
||||
});
|
||||
|
||||
if (timeRange) {
|
||||
logger.debug(`timeRange from ${timeRange.from} to ${timeRange.to}`);
|
||||
logger.info(`timeRange: ${timeRange.duration}`);
|
||||
} else {
|
||||
logger.debug('no timeRange');
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ export interface ScreenshotObservableOpts {
|
|||
}
|
||||
|
||||
export interface TimeRange {
|
||||
from: any;
|
||||
to: any;
|
||||
duration: string;
|
||||
}
|
||||
|
||||
export interface AttributesMap {
|
||||
|
@ -31,7 +30,7 @@ export interface ElementsPositionAndAttribute {
|
|||
}
|
||||
|
||||
export interface Screenshot {
|
||||
base64EncodedData: any;
|
||||
title: any;
|
||||
description: any;
|
||||
base64EncodedData: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ function generatePngObservableFn(server: KbnServer) {
|
|||
const captureConcurrency = 1;
|
||||
|
||||
// prettier-ignore
|
||||
const createPngWithScreenshots = async ({ urlScreenshots }: { urlScreenshots: UrlScreenshot[] }) => {
|
||||
const createPngWithScreenshots = async ({ urlScreenshots }: { urlScreenshots: UrlScreenshot[] }): Promise<string> => {
|
||||
if (urlScreenshots.length !== 1) {
|
||||
throw new Error(
|
||||
`Expected there to be 1 URL screenshot, but there are ${urlScreenshots.length}`
|
||||
|
@ -47,7 +47,7 @@ function generatePngObservableFn(server: KbnServer) {
|
|||
browserTimezone: string,
|
||||
conditionalHeaders: ConditionalHeaders,
|
||||
layoutParams: LayoutParams
|
||||
) {
|
||||
): Rx.Observable<string> {
|
||||
if (!layoutParams || !layoutParams.dimensions) {
|
||||
throw new Error(`LayoutParams.Dimensions is undefined.`);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import * as Rx from 'rxjs';
|
||||
import { toArray, mergeMap } from 'rxjs/operators';
|
||||
import moment from 'moment-timezone';
|
||||
import { groupBy } from 'lodash';
|
||||
import { LevelLogger } from '../../../../server/lib';
|
||||
import { KbnServer, ConditionalHeaders } from '../../../../types';
|
||||
|
@ -39,10 +38,6 @@ const getTimeRange = (urlScreenshots: UrlScreenshot[]) => {
|
|||
return null;
|
||||
};
|
||||
|
||||
const formatDate = (date: Date, timezone: string) => {
|
||||
return moment.tz(date, timezone).format('llll');
|
||||
};
|
||||
|
||||
function generatePdfObservableFn(server: KbnServer) {
|
||||
const screenshotsObservable = screenshotsObservableFactory(server);
|
||||
const captureConcurrency = 1;
|
||||
|
@ -72,12 +67,7 @@ function generatePdfObservableFn(server: KbnServer) {
|
|||
|
||||
if (title) {
|
||||
const timeRange = getTimeRange(urlScreenshots);
|
||||
title += timeRange
|
||||
? ` — ${formatDate(timeRange.from, browserTimezone)} to ${formatDate(
|
||||
timeRange.to,
|
||||
browserTimezone
|
||||
)}`
|
||||
: '';
|
||||
title += timeRange ? ` - ${timeRange.duration}` : '';
|
||||
pdfOutput.setTitle(title);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue