[Dashboard/Reporting] Removal of Reporting injected CSS and JS (#111958) (#119847)

* checkpoint 1: dashboard reading screenshot mode values is screenshot mode and screenshot layout

* checkpoint 2: dashboard handling preserve layout

* temp setting up print viewport

* slight clean up, detect a new view mode "print"

* fix types

* adde todo comment

* added "print" route to dashboard that does not rely on screenshotMode service

* updated jest tests and added screenshot mode public mock

* try to respect embed settings

* fix lint

* remove print mode from share data

* re-add ViewMode.VIEW to share data

* updated TODO comment

* remove injected print css

* remove double declaration of ScreenshotModePluginStart

* re-add missing import 🤦

* fix types issues

* changed css injection removal to use only viewMode.PRINT rather than a new route

* turn off defer below fold when in print mode

* elastic@ email address

* address some CI checks that were failing

* use .includes instead of || to check view mode

Co-authored-by: Michael Dokolin <dokmic@gmail.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Devon A Thomson <devon.thomson@hotmail.com>
Co-authored-by: Michael Dokolin <dokmic@gmail.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Devon A Thomson <devon.thomson@hotmail.com>
Co-authored-by: Michael Dokolin <dokmic@gmail.com>
This commit is contained in:
Jean-Louis Leysens 2021-11-29 16:55:04 +01:00 committed by GitHub
parent 9322069da7
commit 12d4882904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 197 additions and 199 deletions

View file

@ -13,6 +13,7 @@
"navigation",
"savedObjects",
"share",
"screenshotMode",
"uiActions",
"urlForwarding",
"presentationUtil",

View file

@ -15,6 +15,7 @@ import { CoreStart } from 'kibana/public';
import { coreMock, uiSettingsServiceMock } from '../../../../../core/public/mocks';
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
import {
EmbeddableInput,
@ -65,6 +66,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
container = new DashboardContainer(getSampleDashboardInput(), containerOptions);

View file

@ -23,6 +23,7 @@ import {
} from '../../services/embeddable_test_samples';
import { ErrorEmbeddable, IContainer, isErrorEmbeddable } from '../../services/embeddable';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@ -56,6 +57,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
const input = getSampleDashboardInput({
panels: {

View file

@ -13,6 +13,7 @@ import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helper
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
import { isErrorEmbeddable } from '../../services/embeddable';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
import {
CONTACT_CARD_EMBEDDABLE,
@ -48,6 +49,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
const input = getSampleDashboardInput({
panels: {

View file

@ -25,6 +25,7 @@ import { DataPublicPluginStart } from '../../../../data/public/types';
import { dataPluginMock } from '../../../../data/public/mocks';
import { LINE_FEED_CHARACTER } from 'src/plugins/data/common/exports/export_csv';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
describe('Export CSV action', () => {
const { setup, doStart } = embeddablePluginMock.createInstance();
@ -61,6 +62,7 @@ describe('Export CSV action', () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
const input = getSampleDashboardInput({
panels: {

View file

@ -28,6 +28,7 @@ import {
CONTACT_CARD_EMBEDDABLE,
} from '../../services/embeddable_test_samples';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@ -62,6 +63,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
container = new DashboardContainer(getSampleDashboardInput(), containerOptions);

View file

@ -29,6 +29,7 @@ import {
ContactCardEmbeddable,
} from '../../services/embeddable_test_samples';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
describe('LibraryNotificationPopover', () => {
const { setup, doStart } = embeddablePluginMock.createInstance();
@ -58,6 +59,7 @@ describe('LibraryNotificationPopover', () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
container = new DashboardContainer(getSampleDashboardInput(), containerOptions);

View file

@ -22,6 +22,7 @@ import {
ContactCardEmbeddableOutput,
} from '../../services/embeddable_test_samples';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@ -48,6 +49,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
const input = getSampleDashboardInput({
panels: {

View file

@ -30,6 +30,7 @@ import {
CONTACT_CARD_EMBEDDABLE,
} from '../../services/embeddable_test_samples';
import { getStubPluginServices } from '../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
const { setup, doStart } = embeddablePluginMock.createInstance();
setup.registerEmbeddableFactory(
@ -57,6 +58,7 @@ beforeEach(async () => {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreStart.http,
presentationUtil: getStubPluginServices(),
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
container = new DashboardContainer(getSampleDashboardInput(), containerOptions);

View file

@ -17,11 +17,11 @@ import {
getDashboardTitle,
leaveConfirmStrings,
} from '../dashboard_strings';
import { EmbeddableRenderer } from '../services/embeddable';
import { createDashboardEditUrl } from '../dashboard_constants';
import { EmbeddableRenderer, ViewMode } from '../services/embeddable';
import { DashboardTopNav, isCompleteDashboardAppState } from './top_nav/dashboard_top_nav';
import { DashboardAppServices, DashboardEmbedSettings, DashboardRedirect } from '../types';
import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils';
import { createDashboardEditUrl } from '../dashboard_constants';
export interface DashboardAppProps {
history: History;
savedDashboardId?: string;
@ -51,7 +51,6 @@ export function DashboardApp({
const dashboardState = useDashboardSelector((state) => state.dashboardStateReducer);
const dashboardAppState = useDashboardAppState({
history,
redirectTo,
savedDashboardId,
kbnUrlStateStorage,
isEmbeddedExternally: Boolean(embedSettings),
@ -101,15 +100,26 @@ export function DashboardApp({
};
}, [data.search.session]);
const printMode = useMemo(
() => dashboardAppState.getLatestDashboardState?.().viewMode === ViewMode.PRINT,
[dashboardAppState]
);
useEffect(() => {
if (!embedSettings) chrome.setIsVisible(!printMode);
}, [chrome, printMode, embedSettings]);
return (
<>
{isCompleteDashboardAppState(dashboardAppState) && (
<>
<DashboardTopNav
redirectTo={redirectTo}
embedSettings={embedSettings}
dashboardAppState={dashboardAppState}
/>
{!printMode && (
<DashboardTopNav
redirectTo={redirectTo}
embedSettings={embedSettings}
dashboardAppState={dashboardAppState}
/>
)}
{dashboardAppState.savedDashboard.outcome === 'conflict' &&
dashboardAppState.savedDashboard.id &&

View file

@ -109,6 +109,7 @@ export async function mountApp({
embeddable: embeddableStart,
uiSettings: coreStart.uiSettings,
scopedHistory: () => scopedHistory,
screenshotModeService: screenshotMode,
indexPatterns: dataStart.indexPatterns,
savedQueryService: dataStart.query.savedQueries,
savedObjectsClient: coreStart.savedObjects.client,
@ -131,7 +132,6 @@ export async function mountApp({
activeSpaceId || 'default'
),
spacesService: spacesApi,
screenshotModeService: screenshotMode,
};
const getUrlStateStorage = (history: RouteComponentProps['history']) =>

View file

@ -43,11 +43,13 @@ import { getStubPluginServices } from '../../../../presentation_util/public';
const presentationUtil = getStubPluginServices();
const options: DashboardContainerServices = {
// TODO: clean up use of any
application: {} as any,
embeddable: {} as any,
notifications: {} as any,
overlays: {} as any,
inspector: {} as any,
screenshotMode: {} as any,
SavedObjectFinder: () => null,
ExitFullScreenButton: () => null,
uiActions: {} as any,

View file

@ -40,6 +40,7 @@ import {
import { PLACEHOLDER_EMBEDDABLE } from './placeholder';
import { DashboardAppCapabilities, DashboardContainerInput } from '../../types';
import { PresentationUtilPluginStart } from '../../services/presentation_util';
import type { ScreenshotModePluginStart } from '../../services/screenshot_mode';
import { PanelPlacementMethod, IPanelPlacementArgs } from './panel/dashboard_panel_placement';
import {
combineDashboardFiltersWithControlGroupFilters,
@ -55,6 +56,7 @@ export interface DashboardContainerServices {
application: CoreStart['application'];
inspector: InspectorStartContract;
overlays: CoreStart['overlays'];
screenshotMode: ScreenshotModePluginStart;
uiSettings: IUiSettingsClient;
embeddable: EmbeddableStart;
uiActions: UiActionsStart;

View file

@ -23,6 +23,7 @@ import {
} from '../../../services/embeddable_test_samples';
import { coreMock, uiSettingsServiceMock } from '../../../../../../core/public/mocks';
import { getStubPluginServices } from '../../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../../screenshot_mode/public/mocks';
let dashboardContainer: DashboardContainer | undefined;
const presentationUtil = getStubPluginServices();
@ -71,6 +72,7 @@ function prepare(props?: Partial<DashboardGridProps>) {
uiSettings: uiSettingsServiceMock.createStartContract(),
http: coreMock.createStart().http,
presentationUtil,
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
dashboardContainer = new DashboardContainer(initialInput, options);
const defaultTestProps: DashboardGridProps = {

View file

@ -154,7 +154,7 @@ class DashboardGridUi extends React.Component<DashboardGridProps, State> {
id: 'dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage',
defaultMessage: 'Unable to load dashboard.',
}),
body: error.message,
body: (error as { message: string }).message,
toastLifeTimeMs: 5000,
});
}
@ -254,6 +254,11 @@ class DashboardGridUi extends React.Component<DashboardGridProps, State> {
/>
));
// in print mode, dashboard layout is not controlled by React Grid Layout
if (viewMode === ViewMode.PRINT) {
return <>{dashboardPanels}</>;
}
return (
<ResponsiveSizedGrid
isViewMode={isViewMode}

View file

@ -10,9 +10,10 @@ import React, { useState, useRef, useEffect, FC } from 'react';
import { EuiLoadingChart } from '@elastic/eui';
import classNames from 'classnames';
import { EmbeddableChildPanel } from '../../../services/embeddable';
import { EmbeddableChildPanel, ViewMode } from '../../../services/embeddable';
import { useLabs } from '../../../services/presentation_util';
import { DashboardPanelState } from '../types';
import { DashboardContainer } from '..';
type PanelProps = Pick<EmbeddableChildPanel, 'container' | 'PanelComponent'>;
type DivProps = Pick<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'children'>;
@ -20,6 +21,7 @@ type DivProps = Pick<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style'
interface Props extends PanelProps, DivProps {
id: DashboardPanelState['explicitInput']['id'];
type: DashboardPanelState['type'];
container: DashboardContainer;
focusedPanelId?: string;
expandedPanelId?: string;
key: string;
@ -52,6 +54,8 @@ const Item = React.forwardRef<HTMLDivElement, Props>(
'dshDashboardGrid__item--expanded': expandPanel,
// eslint-disable-next-line @typescript-eslint/naming-convention
'dshDashboardGrid__item--hidden': hidePanel,
// eslint-disable-next-line @typescript-eslint/naming-convention
printViewport__vis: container.getInput().viewMode === ViewMode.PRINT,
});
return (
@ -116,7 +120,8 @@ export const ObservedItem: FC<Props> = (props: Props) => {
export const DashboardGridItem: FC<Props> = (props: Props) => {
const { isProjectEnabled } = useLabs();
const isEnabled = isProjectEnabled('labs:dashboard:deferBelowFold');
const isPrintMode = props.container.getInput().viewMode === ViewMode.PRINT;
const isEnabled = !isPrintMode && isProjectEnabled('labs:dashboard:deferBelowFold');
return isEnabled ? <ObservedItem {...props} /> : <Item {...props} />;
};

View file

@ -1 +1,2 @@
@import './dashboard_viewport';
@import './print_viewport';

View file

@ -0,0 +1,9 @@
.printViewport {
&__vis {
height: 600px; // These values might need to be passed in as dimensions for the report. I.e., print should use layout dimensions.
width: 975px;
// Some vertical space between vis, but center horizontally
margin: 10px auto;
}
}

View file

@ -27,6 +27,7 @@ import {
CONTACT_CARD_EMBEDDABLE,
} from '../../../../../embeddable/public/lib/test_samples';
import { getStubPluginServices } from '../../../../../presentation_util/public';
import { screenshotModePluginMock } from '../../../../../screenshot_mode/public/mocks';
let dashboardContainer: DashboardContainer | undefined;
const presentationUtil = getStubPluginServices();
@ -65,6 +66,7 @@ function getProps(props?: Partial<DashboardViewportProps>): {
getTriggerCompatibleActions: (() => []) as any,
} as any,
presentationUtil,
screenshotMode: screenshotModePluginMock.createSetupContract(),
};
const input = getSampleDashboardInput({

View file

@ -52,7 +52,6 @@ const createDashboardAppStateProps = (): UseDashboardStateProps => ({
savedDashboardId: 'testDashboardId',
history: createBrowserHistory(),
isEmbeddedExternally: false,
redirectTo: jest.fn(),
});
const createDashboardAppStateServices = () => {

View file

@ -22,7 +22,6 @@ import {
DashboardBuildContext,
DashboardAppServices,
DashboardAppState,
DashboardRedirect,
DashboardState,
} from '../../types';
import { DashboardAppLocatorParams } from '../../locator';
@ -44,14 +43,12 @@ import {
export interface UseDashboardStateProps {
history: History;
savedDashboardId?: string;
redirectTo: DashboardRedirect;
isEmbeddedExternally: boolean;
kbnUrlStateStorage: IKbnUrlStateStorage;
}
export const useDashboardAppState = ({
history,
redirectTo,
savedDashboardId,
kbnUrlStateStorage,
isEmbeddedExternally,
@ -184,12 +181,20 @@ export const useDashboardAppState = ({
savedDashboard,
});
// Backwards compatible way of detecting that we are taking a screenshot
const legacyPrintLayoutDetected =
screenshotModeService?.isScreenshotMode() &&
screenshotModeService.getScreenshotLayout() === 'print';
const initialDashboardState = {
...savedDashboardState,
...dashboardSessionStorageState,
...initialDashboardStateFromUrl,
...forwardedAppState,
// if we are in legacy print mode, dashboard needs to be in print viewMode
...(legacyPrintLayoutDetected ? { viewMode: ViewMode.PRINT } : {}),
// if there is an incoming embeddable, dashboard always needs to be in edit mode to receive it.
...(incomingEmbeddable ? { viewMode: ViewMode.EDIT } : {}),
};

View file

@ -15,6 +15,7 @@ import { DashboardAppServices, DashboardAppCapabilities } from '../../types';
import { embeddablePluginMock } from '../../../../embeddable/public/mocks';
import { IndexPatternsContract, SavedQueryService } from '../../services/data';
import { savedObjectsPluginMock } from '../../../../saved_objects/public/mocks';
import { screenshotModePluginMock } from '../../../../screenshot_mode/public/mocks';
import { visualizationsPluginMock } from '../../../../visualizations/public/mocks';
import { PluginInitializerContext, ScopedHistory } from '../../../../../core/public';
import { SavedObjectLoader, SavedObjectLoaderFindOptions } from '../../services/saved_objects';
@ -72,6 +73,7 @@ export function makeDefaultServices(): DashboardAppServices {
} as PluginInitializerContext;
return {
screenshotModeService: screenshotModePluginMock.createSetupContract(),
visualizations: visualizationsPluginMock.createStartContract(),
savedObjects: savedObjectsPluginMock.createStartContract(),
embeddable: embeddablePluginMock.createInstance().doStart(),

View file

@ -14,6 +14,7 @@ export const DashboardConstants = {
LANDING_PAGE_PATH: '/list',
CREATE_NEW_DASHBOARD_URL: '/create',
VIEW_DASHBOARD_URL: '/view',
PRINT_DASHBOARD_URL: '/print',
ADD_EMBEDDABLE_ID: 'addEmbeddableId',
ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
DASHBOARDS_ID: 'dashboards',

View file

@ -128,6 +128,7 @@ export class DashboardAppLocatorDefinition implements LocatorDefinition<Dashboar
...restParams
} = params;
const useHash = paramsUseHash ?? this.deps.useHashedUrl;
const hash = dashboardId ? `view/${dashboardId}` : `create`;
const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise<Filter[]> => {

View file

@ -12,7 +12,6 @@ import { filter, map } from 'rxjs/operators';
import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public';
import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public';
import { APP_WRAPPER_CLASS } from '../../../core/public';
import {
App,
@ -37,6 +36,10 @@ import { NavigationPublicPluginStart as NavigationStart } from './services/navig
import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from './services/data';
import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from './services/share';
import type { SavedObjectTaggingOssPluginStart } from './services/saved_objects_tagging_oss';
import type {
ScreenshotModePluginSetup,
ScreenshotModePluginStart,
} from './services/screenshot_mode';
import {
getSavedObjectFinder,
SavedObjectLoader,
@ -102,6 +105,7 @@ export interface DashboardSetupDependencies {
share?: SharePluginSetup;
uiActions: UiActionsSetup;
usageCollection?: UsageCollectionSetup;
screenshotMode: ScreenshotModePluginSetup;
}
export interface DashboardStartDependencies {
@ -116,9 +120,9 @@ export interface DashboardStartDependencies {
savedObjects: SavedObjectsStart;
presentationUtil: PresentationUtilPluginStart;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
screenshotMode?: ScreenshotModePluginStart;
spaces?: SpacesPluginStart;
visualizations: VisualizationsStart;
screenshotMode: ScreenshotModePluginStart;
}
export interface DashboardSetup {
@ -162,7 +166,15 @@ export class DashboardPlugin
public setup(
core: CoreSetup<DashboardStartDependencies, DashboardStart>,
{ share, embeddable, home, urlForwarding, data, usageCollection }: DashboardSetupDependencies
{
share,
embeddable,
home,
urlForwarding,
data,
usageCollection,
screenshotMode,
}: DashboardSetupDependencies
): DashboardSetup {
this.dashboardFeatureFlagConfig =
this.initializerContext.config.get<DashboardFeatureFlagConfig>();
@ -197,6 +209,7 @@ export class DashboardPlugin
embeddable: deps.embeddable,
uiActions: deps.uiActions,
inspector: deps.inspector,
screenshotMode: deps.screenshotMode,
http: coreStart.http,
ExitFullScreenButton,
presentationUtil: deps.presentationUtil,

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type {
ScreenshotModePluginStart,
ScreenshotModePluginSetup,
} from '../../../screenshot_mode/public';
export type { Layout } from '../../../screenshot_mode/common';

View file

@ -19,7 +19,6 @@ import type {
import { History } from 'history';
import { AnyAction, Dispatch } from 'redux';
import { BehaviorSubject, Subject } from 'rxjs';
import { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public';
import { Query, Filter, IndexPattern, RefreshInterval, TimeRange } from './services/data';
import { ContainerInput, EmbeddableInput, ViewMode } from './services/embeddable';
import { SharePluginStart } from './services/share';
@ -33,6 +32,7 @@ import { SavedObjectsTaggingApi } from './services/saved_objects_tagging_oss';
import { DataPublicPluginStart, IndexPatternsContract } from './services/data';
import { SavedObjectLoader, SavedObjectsStart } from './services/saved_objects';
import { IKbnUrlStateStorage } from './services/kibana_utils';
import type { ScreenshotModePluginStart } from './services/screenshot_mode';
import type { DashboardContainer, DashboardSavedObject } from '.';
import { VisualizationsStart } from '../../visualizations/public';
import { DashboardAppLocatorParams } from './locator';
@ -206,9 +206,9 @@ export interface DashboardAppServices {
onAppLeave: AppMountParameters['onAppLeave'];
savedObjectsTagging?: SavedObjectsTaggingApi;
savedObjectsClient: SavedObjectsClientContract;
screenshotModeService: ScreenshotModePluginStart;
dashboardSessionStorage: DashboardSessionStorage;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
savedQueryService: DataPublicPluginStart['query']['savedQueries'];
spacesService?: SpacesPluginStart;
screenshotModeService?: ScreenshotModePluginStart;
}

View file

@ -13,6 +13,7 @@ import { PersistableStateService, PersistableState } from '../../kibana_utils/co
export enum ViewMode {
EDIT = 'edit',
PREVIEW = 'preview',
PRINT = 'print',
VIEW = 'view',
}

View file

@ -251,7 +251,7 @@ export class EmbeddablePanel extends React.Component<Props, State> {
};
public render() {
const viewOnlyMode = this.state.viewMode === ViewMode.VIEW;
const viewOnlyMode = [ViewMode.VIEW, ViewMode.PRINT].includes(this.state.viewMode);
const classes = classNames('embPanel', {
'embPanel--editing': !viewOnlyMode,
'embPanel--loading': this.state.loading,

View file

@ -10,6 +10,7 @@ import { take } from 'rxjs/operators';
import { coreMock } from '../../../core/public/mocks';
import { NewsfeedPublicPlugin } from './plugin';
import { NewsfeedApiEndpoint } from './lib/api';
import { screenshotModePluginMock } from '../../screenshot_mode/public/mocks';
describe('Newsfeed plugin', () => {
let plugin: NewsfeedPublicPlugin;
@ -46,7 +47,7 @@ describe('Newsfeed plugin', () => {
describe('base case', () => {
it('makes fetch requests', () => {
const startContract = plugin.start(coreMock.createStart(), {
screenshotMode: { isScreenshotMode: () => false },
screenshotMode: screenshotModePluginMock.createSetupContract(),
});
const sub = startContract
.createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do
@ -60,8 +61,10 @@ describe('Newsfeed plugin', () => {
describe('when in screenshot mode', () => {
it('makes no fetch requests in screenshot mode', () => {
const screenshotMode = screenshotModePluginMock.createSetupContract();
screenshotMode.isScreenshotMode.mockReturnValue(true);
const startContract = plugin.start(coreMock.createStart(), {
screenshotMode: { isScreenshotMode: () => true },
screenshotMode,
});
const sub = startContract
.createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do

View file

@ -7,7 +7,7 @@
*/
// **PLEASE NOTE**
// The functionality in this file targets a browser environment and is intended to be used both in public and server.
// The functionality in this file targets a browser environment AND is intended to be used both in public and server.
// For instance, reporting uses these functions when starting puppeteer to set the current browser into "screenshot" mode.
export const KBN_SCREENSHOT_MODE_ENABLED_KEY = '__KBN_SCREENSHOT_MODE_ENABLED_KEY__';
@ -61,3 +61,31 @@ export const setScreenshotModeDisabled = () => {
}
);
};
/** @deprecated */
export const KBN_SCREENSHOT_MODE_LAYOUT_KEY = '__KBN_SCREENSHOT_MODE_LAYOUT_KEY__';
/** @deprecated */
export type Layout = 'canvas' | 'preserve_layout' | 'print';
/** @deprecated */
export const setScreenshotLayout = (value: Layout) => {
Object.defineProperty(
window,
'__KBN_SCREENSHOT_MODE_LAYOUT_KEY__', // Literal value to prevent adding an external reference
{
enumerable: true,
writable: true,
configurable: false,
value,
}
);
};
/** @deprecated */
export const getScreenshotLayout = (): undefined | Layout => {
return (
(window as unknown as Record<string, Layout>)[KBN_SCREENSHOT_MODE_LAYOUT_KEY] ||
(window.localStorage.getItem(KBN_SCREENSHOT_MODE_LAYOUT_KEY) as Layout)
);
};

View file

@ -11,6 +11,11 @@ export {
setScreenshotModeEnabled,
setScreenshotModeDisabled,
KBN_SCREENSHOT_MODE_ENABLED_KEY,
KBN_SCREENSHOT_MODE_LAYOUT_KEY,
setScreenshotLayout,
getScreenshotLayout,
} from './get_set_browser_screenshot_mode';
export type { Layout } from './get_set_browser_screenshot_mode';
export { KBN_SCREENSHOT_MODE_HEADER } from './constants';

View file

@ -11,9 +11,11 @@ import type { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './typ
export const screenshotModePluginMock = {
createSetupContract: (): DeeplyMockedKeys<ScreenshotModePluginSetup> => ({
getScreenshotLayout: jest.fn(),
isScreenshotMode: jest.fn(() => false),
}),
createStartContract: (): DeeplyMockedKeys<ScreenshotModePluginStart> => ({
getScreenshotLayout: jest.fn(),
isScreenshotMode: jest.fn(() => false),
}),
};

View file

@ -10,11 +10,12 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types';
import { getScreenshotMode } from '../common';
import { getScreenshotMode, getScreenshotLayout } from '../common';
export class ScreenshotModePlugin implements Plugin<ScreenshotModePluginSetup> {
private publicContract = Object.freeze({
isScreenshotMode: () => getScreenshotMode() === true,
getScreenshotLayout,
});
public setup(core: CoreSetup): ScreenshotModePluginSetup {

View file

@ -6,12 +6,17 @@
* Side Public License, v 1.
*/
import type { Layout } from '../common';
export interface IScreenshotModeService {
/**
* Returns a boolean indicating whether the current user agent (browser) would like to view UI optimized for
* screenshots or printing.
*/
isScreenshotMode: () => boolean;
/** @deprecated */
getScreenshotLayout: () => undefined | Layout;
}
export type ScreenshotModePluginSetup = IScreenshotModeService;

View file

@ -30,10 +30,11 @@ export class ScreenshotModePlugin
// We use "require" here to ensure the import does not have external references due to code bundling that
// commonly happens during transpiling. External references would be missing in the environment puppeteer creates.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { setScreenshotModeEnabled } = require('../common');
const { setScreenshotModeEnabled, setScreenshotLayout } = require('../common');
return {
setScreenshotModeEnabled,
setScreenshotLayout,
isScreenshotMode,
};
}

View file

@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
import { RequestHandlerContext, KibanaRequest } from 'src/core/server';
import type { RequestHandlerContext, KibanaRequest } from 'src/core/server';
import type { Layout } from '../common';
/**
* Any context that requires access to the screenshot mode flag but does not have access
@ -23,6 +24,9 @@ export interface ScreenshotModePluginSetup {
* on the page have run to ensure that screenshot mode is detected as early as possible.
*/
setScreenshotModeEnabled: () => void;
/** @deprecated */
setScreenshotLayout: (value: Layout) => void;
}
export interface ScreenshotModePluginStart {

View file

@ -17,7 +17,7 @@ import { ReportingCore } from '../../..';
import { KBN_SCREENSHOT_MODE_HEADER } from '../../../../../../../src/plugins/screenshot_mode/server';
import { ConditionalHeaders, ConditionalHeadersConditions } from '../../../export_types/common';
import { LevelLogger } from '../../../lib';
import { ViewZoomWidthHeight } from '../../../lib/layouts/layout';
import { Layout, ViewZoomWidthHeight } from '../../../lib/layouts/layout';
import { ElementPosition } from '../../../lib/screenshots';
import { allowRequest, NetworkPolicy } from '../../network_policy';
@ -97,11 +97,13 @@ export class HeadlessChromiumDriver {
waitForSelector: pageLoadSelector,
timeout,
locator,
layout,
}: {
conditionalHeaders: ConditionalHeaders;
waitForSelector: string;
timeout: number;
locator?: LocatorParams;
layout?: Layout;
},
logger: LevelLogger
): Promise<void> {
@ -116,6 +118,10 @@ export class HeadlessChromiumDriver {
*/
await this.page.evaluateOnNewDocument(this.core.getEnableScreenshotMode());
if (layout) {
await this.page.evaluateOnNewDocument(this.core.getSetScreenshotLayout(), layout.id);
}
if (locator) {
await this.page.evaluateOnNewDocument(
(key: string, value: unknown) => {

View file

@ -253,9 +253,16 @@ export class ReportingCore {
.toPromise();
}
private getScreenshotModeDep() {
return this.getPluginSetupDeps().screenshotMode;
}
public getEnableScreenshotMode() {
const { screenshotMode } = this.getPluginSetupDeps();
return screenshotMode.setScreenshotModeEnabled;
return this.getScreenshotModeDep().setScreenshotModeEnabled;
}
public getSetScreenshotLayout() {
return this.getScreenshotModeDep().setScreenshotLayout;
}
/*

View file

@ -5,7 +5,7 @@
******
*/
/**
/**
* global
*/
@ -27,7 +27,6 @@ filter-bar,
* Discover Tweaks
*/
/* hide unusable controls */
discover-app .dscTimechart,
discover-app .dscSidebar__container,
@ -36,7 +35,6 @@ discover-app .discover-table-footer {
display: none;
}
/**
* The global banner (e.g. "Help us improve Elastic...") should not print.
*/
@ -73,7 +71,8 @@ discover-app .discover-table-footer {
position: fixed;
width: 100%;
height: 100%;
top: 0; left: 0;
top: 0;
left: 0;
}
/**

View file

@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import path from 'path';
import { CustomPageSize } from 'pdfmake/interfaces';
import { LAYOUT_TYPES } from '../../../common/constants';
@ -37,6 +36,7 @@ export class PreserveLayout extends Layout implements LayoutInstance {
}
public getCssOverridesPath() {
// TODO: Remove this path once we have migrated all plugins away from depending on this hiding page elements.
return path.join(__dirname, 'preserve_layout.css');
}

View file

@ -1,122 +0,0 @@
/*
******
****** This is a collection of CSS overrides that make Kibana look better for
****** generating PDF reports with headless browser
******
*/
/**
* global
*/
/* elements can hide themselves when shared */
.hide-for-sharing {
display: none !important;
}
/* hide unusable controls */
kbn-top-nav,
filter-bar,
.kbnTopNavMenu__wrapper,
::-webkit-scrollbar,
.euiNavDrawer {
display: none !important;
}
/**
* Discover Tweaks
*/
/* hide unusable controls */
discover-app .dscTimechart,
discover-app .dscSidebar__container,
discover-app .dscCollapsibleSidebar__collapseButton,
discover-app .discover-table-footer {
display: none;
}
/**
* The global banner (e.g. "Help us improve Elastic...") should not print.
*/
#globalBannerList {
display: none;
}
/**
* Visualize Editor Tweaks
*/
/* hide unusable controls
* !important is required to override resizable panel inline display */
.visEditor__content .visEditor--default > :not(.visEditor__visualization__wrapper) {
display: none !important;
}
/** THIS IS FOR TSVB UNTIL REFACTOR **/
.tvbEditorVisualization {
position: static !important;
}
.visualize .tvbVisTimeSeries__legendToggle,
.tvbEditor--hideForReporting {
/* all non-content rows in interface */
display: none;
}
/** END TSVB BAD BAD HACKS **/
/* remove left padding from visualizations so that map lines up with .leaflet-container and
* setting the position to be fixed and to take up the entire screen, because some zoom levels/viewports
* are triggering the media breakpoints that cause the .visEditor__canvas to take up more room than the viewport */
.visEditor .visEditor__canvas {
padding-left: 0px;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
/**
* Visualization tweaks
*/
/* hide unusable controls */
.visualize .visLegend__toggle,
.visualize .kbnAggTable__controls/* export raw, export formatted, etc. */,
.visualize .leaflet-container .leaflet-top.leaflet-left/* tilemap controls */,
.visualize paginate-controls {
display: none;
}
/* Ensure the min-height of the small breakpoint isn't used */
.vis-editor visualization {
min-height: 0 !important;
}
/**
* Dashboard tweaks
*/
.dashboardViewport .embPanel__header {
/* hide the panel heading with the controls and title */
display: none !important;
}
.dashboardViewport .euiPanel {
/* Remove the border from the eui panel */
border: none !important;
}
/**
* 1. Reporting manually makes each visualization it wants to screenshot larger, so we need to hide
* the visualizations in the other panels. We can only use properties that will be manually set in
* reporting/export_types/printable_pdf/lib/screenshot.js or this will also hide the visualization
* we want to capture.
* 2. React grid item's transform affects the visualizations, even when they are using fixed positioning. Chrome seems
* to handle this fine, but firefox moves the visualizations around.
*/
.dashboardViewport .react-grid-item {
height: 0 !important; /* 1. */
width: 0 !important; /* 1. */
transform: none !important; /* 2. */
-webkit-transform: none !important; /* 2. */
}

View file

@ -5,13 +5,8 @@
* 2.0.
*/
import path from 'path';
import { PageOrientation, PredefinedPageSize } from 'pdfmake/interfaces';
import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer';
import { LevelLogger } from '../';
import { DEFAULT_VIEWPORT, LAYOUT_TYPES } from '../../../common/constants';
import { Size } from '../../../common/types';
import { HeadlessChromiumDriver } from '../../browsers';
import { CaptureConfig } from '../../types';
import { getDefaultLayoutSelectors, LayoutInstance, LayoutSelectorDictionary } from './';
import { Layout } from './layout';
@ -31,7 +26,7 @@ export class PrintLayout extends Layout implements LayoutInstance {
}
public getCssOverridesPath() {
return path.join(__dirname, 'print.css');
return undefined;
}
public getBrowserViewport() {
@ -49,40 +44,6 @@ export class PrintLayout extends Layout implements LayoutInstance {
height: this.viewport.height * itemsCount,
};
}
public async positionElements(
browser: HeadlessChromiumDriver,
logger: LevelLogger
): Promise<void> {
logger.debug('positioning elements');
const elementSize: Size = {
width: this.viewport.width / this.captureConfig.zoom,
height: this.viewport.height / this.captureConfig.zoom,
};
const evalOptions: { fn: EvaluateFn; args: SerializableOrJSHandle[] } = {
fn: (selector: string, height: number, width: number) => {
const visualizations = document.querySelectorAll(selector) as NodeListOf<HTMLElement>;
const visualizationsLength = visualizations.length;
for (let i = 0; i < visualizationsLength; i++) {
const visualization = visualizations[i];
const style = visualization.style;
style.position = 'fixed';
style.top = `${height * i}px`;
style.left = '0';
style.width = `${width}px`;
style.height = `${height}px`;
style.zIndex = '1';
style.backgroundColor = 'inherit';
}
},
args: [this.selectors.screenshot, elementSize.height, elementSize.width],
};
await browser.evaluate(evalOptions, { context: 'PositionElements' }, logger);
}
public getPdfImageSize() {
return {
width: 500,

View file

@ -76,6 +76,7 @@ export class ScreenshotObservableHandler {
index,
urlOrUrlLocatorTuple,
this.conditionalHeaders,
this.layout,
this.logger
)
).pipe(this.waitUntil(this.timeouts.openUrl));

View file

@ -10,6 +10,7 @@ import { LevelLogger, startTrace } from '../';
import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types';
import { HeadlessChromiumDriver } from '../../browsers';
import { ConditionalHeaders } from '../../export_types/common';
import { Layout } from '../layouts';
import { DEFAULT_PAGELOAD_SELECTOR } from './constants';
export const openUrl = async (
@ -18,6 +19,7 @@ export const openUrl = async (
index: number,
urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple,
conditionalHeaders: ConditionalHeaders,
layout: undefined | Layout,
logger: LevelLogger
): Promise<void> => {
// If we're moving to another page in the app, we'll want to wait for the app to tell us
@ -36,7 +38,11 @@ export const openUrl = async (
}
try {
await browser.open(url, { conditionalHeaders, waitForSelector, timeout, locator }, logger);
await browser.open(
url,
{ conditionalHeaders, waitForSelector, timeout, locator, layout },
logger
);
} catch (err) {
logger.error(err);
throw new Error(