mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Screenshot mode] Create plugin to provide "screenshot mode" awareness (#99627)
* initial version of the screenshot mode service * First iteration of client side of screenshot mode plugin Also hooked it up to the chromium browser imitating the preload functionality of electron to set up the environment before code runs. * First implementation of server-side logic for detecting screenshot mode * fix some type issues and do a small refactor * fix size limits, docs and ts issues * fixed types issues and made sure screenshot mode is correctly detected on the client * Moved the screenshot mode header definition to common Added a server-side example for screenshot mode Export the screenshot mode header in both public and server * move require() to screenshotMode plugin * Update chromium_driver.ts * cleaned up some comments, minor refactor in ReportingCore and changed the screenshotmode detection function to check for a specific value. * fix export * Expanded server-side screenshot mode contract with function that checks a kibana request to determine whether we in screenshot mode * added comments to explain use of literal value rather than external reference * updated comment * update reporting example Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Timothy Sullivan <tsullivan@elastic.co> Co-authored-by: Tim Sullivan <tsullivan@users.noreply.github.com>
This commit is contained in:
parent
8f1bf66a7b
commit
f97aad30f4
52 changed files with 907 additions and 49 deletions
|
@ -181,6 +181,10 @@ Content is fetched from the remote (https://feeds.elastic.co and https://feeds-s
|
|||
oss plugins.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/screenshot_mode/README.md[screenshotMode]
|
||||
|The service exposed by this plugin informs consumers whether they should optimize for non-interactivity. In this way plugins can avoid loading unnecessary code, data or other services.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/security_oss/README.md[securityOss]
|
||||
|securityOss is responsible for educating users about Elastic's free security features,
|
||||
so they can properly protect the data within their clusters.
|
||||
|
|
7
examples/screenshot_mode_example/.i18nrc.json
Normal file
7
examples/screenshot_mode_example/.i18nrc.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"prefix": "screenshotModeExample",
|
||||
"paths": {
|
||||
"screenshotModeExample": "."
|
||||
},
|
||||
"translations": ["translations/ja-JP.json"]
|
||||
}
|
9
examples/screenshot_mode_example/README.md
Executable file
9
examples/screenshot_mode_example/README.md
Executable file
|
@ -0,0 +1,9 @@
|
|||
# screenshotModeExample
|
||||
|
||||
A Kibana plugin
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.
|
11
examples/screenshot_mode_example/common/index.ts
Normal file
11
examples/screenshot_mode_example/common/index.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 const PLUGIN_NAME = 'Screenshot mode example app';
|
||||
|
||||
export const BASE_API_ROUTE = '/api/screenshot_mode_example';
|
9
examples/screenshot_mode_example/kibana.json
Normal file
9
examples/screenshot_mode_example/kibana.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "screenshotModeExample",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["navigation", "screenshotMode", "usageCollection"],
|
||||
"optionalPlugins": []
|
||||
}
|
33
examples/screenshot_mode_example/public/application.tsx
Normal file
33
examples/screenshot_mode_example/public/application.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters, CoreStart } from '../../../src/core/public';
|
||||
import { AppPluginSetupDependencies, AppPluginStartDependencies } from './types';
|
||||
import { ScreenshotModeExampleApp } from './components/app';
|
||||
|
||||
export const renderApp = (
|
||||
{ notifications, http }: CoreStart,
|
||||
{ screenshotMode }: AppPluginSetupDependencies,
|
||||
{ navigation }: AppPluginStartDependencies,
|
||||
{ appBasePath, element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<ScreenshotModeExampleApp
|
||||
basename={appBasePath}
|
||||
notifications={notifications}
|
||||
http={http}
|
||||
navigation={navigation}
|
||||
screenshotMode={screenshotMode}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
122
examples/screenshot_mode_example/public/components/app.tsx
Normal file
122
examples/screenshot_mode_example/public/components/app.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiPageContentHeader,
|
||||
EuiPageHeader,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { CoreStart } from '../../../../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
|
||||
import {
|
||||
ScreenshotModePluginSetup,
|
||||
KBN_SCREENSHOT_MODE_HEADER,
|
||||
} from '../../../../src/plugins/screenshot_mode/public';
|
||||
|
||||
import { PLUGIN_NAME, BASE_API_ROUTE } from '../../common';
|
||||
|
||||
interface ScreenshotModeExampleAppDeps {
|
||||
basename: string;
|
||||
notifications: CoreStart['notifications'];
|
||||
http: CoreStart['http'];
|
||||
navigation: NavigationPublicPluginStart;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
|
||||
export const ScreenshotModeExampleApp = ({
|
||||
basename,
|
||||
notifications,
|
||||
http,
|
||||
navigation,
|
||||
screenshotMode,
|
||||
}: ScreenshotModeExampleAppDeps) => {
|
||||
const isScreenshotMode = screenshotMode.isScreenshotMode();
|
||||
|
||||
useEffect(() => {
|
||||
// fire and forget
|
||||
http.get(`${BASE_API_ROUTE}/check_is_screenshot`, {
|
||||
headers: isScreenshotMode ? { [KBN_SCREENSHOT_MODE_HEADER]: 'true' } : undefined,
|
||||
});
|
||||
notifications.toasts.addInfo({
|
||||
title: 'Welcome to the screenshot example app!',
|
||||
text: isScreenshotMode
|
||||
? 'In screenshot mode we want this to remain visible'
|
||||
: 'In normal mode this toast will disappear eventually',
|
||||
toastLifeTimeMs: isScreenshotMode ? 360000 : 3000,
|
||||
});
|
||||
}, [isScreenshotMode, notifications, http]);
|
||||
return (
|
||||
<Router basename={basename}>
|
||||
<I18nProvider>
|
||||
<>
|
||||
<navigation.ui.TopNavMenu
|
||||
appName={PLUGIN_NAME}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
/>
|
||||
<EuiPage restrictWidth="1000px">
|
||||
<EuiPageBody>
|
||||
<EuiPageHeader>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="screenshotModeExample.helloWorldText"
|
||||
defaultMessage="{name}"
|
||||
values={{ name: PLUGIN_NAME }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentHeader>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{isScreenshotMode ? (
|
||||
<FormattedMessage
|
||||
id="screenshotModeExample.screenshotModeTitle"
|
||||
defaultMessage="We are in screenshot mode!"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="screenshotModeExample.normalModeTitle"
|
||||
defaultMessage="We are not in screenshot mode!"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiPageContentHeader>
|
||||
<EuiPageContentBody>
|
||||
<EuiText>
|
||||
{isScreenshotMode ? (
|
||||
<p>We detected screenshot mode. The chrome navbar should be hidden.</p>
|
||||
) : (
|
||||
<p>
|
||||
This is how the app looks in normal mode. The chrome navbar should be
|
||||
visible.
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
</>
|
||||
</I18nProvider>
|
||||
</Router>
|
||||
);
|
||||
};
|
0
examples/screenshot_mode_example/public/index.scss
Normal file
0
examples/screenshot_mode_example/public/index.scss
Normal file
17
examples/screenshot_mode_example/public/index.ts
Normal file
17
examples/screenshot_mode_example/public/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import './index.scss';
|
||||
|
||||
import { ScreenshotModeExamplePlugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new ScreenshotModeExamplePlugin();
|
||||
}
|
48
examples/screenshot_mode_example/public/plugin.ts
Normal file
48
examples/screenshot_mode_example/public/plugin.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '../../../src/core/public';
|
||||
import { AppPluginSetupDependencies, AppPluginStartDependencies } from './types';
|
||||
import { MetricsTracking } from './services';
|
||||
import { PLUGIN_NAME } from '../common';
|
||||
|
||||
export class ScreenshotModeExamplePlugin implements Plugin<void, void> {
|
||||
uiTracking = new MetricsTracking();
|
||||
|
||||
public setup(core: CoreSetup, depsSetup: AppPluginSetupDependencies): void {
|
||||
const { screenshotMode, usageCollection } = depsSetup;
|
||||
const isScreenshotMode = screenshotMode.isScreenshotMode();
|
||||
|
||||
this.uiTracking.setup({
|
||||
disableTracking: isScreenshotMode, // In screenshot mode there will be no user interactions to track
|
||||
usageCollection,
|
||||
});
|
||||
|
||||
// Register an application into the side navigation menu
|
||||
core.application.register({
|
||||
id: 'screenshotModeExample',
|
||||
title: PLUGIN_NAME,
|
||||
async mount(params: AppMountParameters) {
|
||||
// Load application bundle
|
||||
const { renderApp } = await import('./application');
|
||||
// Get start services as specified in kibana.json
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
|
||||
// For screenshots we don't need to have the top bar visible
|
||||
coreStart.chrome.setIsVisible(!isScreenshotMode);
|
||||
|
||||
// Render the application
|
||||
return renderApp(coreStart, depsSetup, depsStart as AppPluginStartDependencies, params);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart): void {}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { MetricsTracking } from './metrics_tracking';
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { UiCounterMetricType, METRIC_TYPE } from '@kbn/analytics';
|
||||
import { PLUGIN_NAME } from '../../common';
|
||||
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
|
||||
|
||||
export class MetricsTracking {
|
||||
private trackingDisabled = false;
|
||||
private usageCollection?: UsageCollectionSetup;
|
||||
|
||||
private track(eventName: string, type: UiCounterMetricType) {
|
||||
if (this.trackingDisabled) return;
|
||||
|
||||
this.usageCollection?.reportUiCounter(PLUGIN_NAME, type, eventName);
|
||||
}
|
||||
|
||||
public setup({
|
||||
disableTracking,
|
||||
usageCollection,
|
||||
}: {
|
||||
disableTracking?: boolean;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
}) {
|
||||
this.usageCollection = usageCollection;
|
||||
if (disableTracking) this.trackingDisabled = true;
|
||||
}
|
||||
|
||||
public trackInit() {
|
||||
this.track('init', METRIC_TYPE.LOADED);
|
||||
}
|
||||
}
|
20
examples/screenshot_mode_example/public/types.ts
Normal file
20
examples/screenshot_mode_example/public/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
|
||||
import { ScreenshotModePluginSetup } from '../../../src/plugins/screenshot_mode/public';
|
||||
import { UsageCollectionSetup } from '../../../src/plugins/usage_collection/public';
|
||||
|
||||
export interface AppPluginSetupDependencies {
|
||||
usageCollection: UsageCollectionSetup;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
|
||||
export interface AppPluginStartDependencies {
|
||||
navigation: NavigationPublicPluginStart;
|
||||
}
|
12
examples/screenshot_mode_example/server/index.ts
Normal file
12
examples/screenshot_mode_example/server/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'kibana/server';
|
||||
import { ScreenshotModeExamplePlugin } from './plugin';
|
||||
|
||||
export const plugin = (ctx: PluginInitializerContext) => new ScreenshotModeExamplePlugin(ctx);
|
31
examples/screenshot_mode_example/server/plugin.ts
Normal file
31
examples/screenshot_mode_example/server/plugin.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Plugin, PluginInitializerContext, CoreSetup, Logger } from 'kibana/server';
|
||||
import { ScreenshotModePluginSetup } from '../../../src/plugins/screenshot_mode/server';
|
||||
import { RouteDependencies } from './types';
|
||||
import { registerRoutes } from './routes';
|
||||
|
||||
export class ScreenshotModeExamplePlugin implements Plugin<void, void> {
|
||||
log: Logger;
|
||||
constructor(ctx: PluginInitializerContext) {
|
||||
this.log = ctx.logger.get();
|
||||
}
|
||||
setup(core: CoreSetup, { screenshotMode }: { screenshotMode: ScreenshotModePluginSetup }): void {
|
||||
const deps: RouteDependencies = {
|
||||
screenshotMode,
|
||||
router: core.http.createRouter(),
|
||||
log: this.log,
|
||||
};
|
||||
|
||||
registerRoutes(deps);
|
||||
}
|
||||
|
||||
start() {}
|
||||
stop() {}
|
||||
}
|
21
examples/screenshot_mode_example/server/routes.ts
Normal file
21
examples/screenshot_mode_example/server/routes.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { RouteDependencies } from './types';
|
||||
import { BASE_API_ROUTE } from '../common';
|
||||
|
||||
export const registerRoutes = ({ router, log, screenshotMode }: RouteDependencies) => {
|
||||
router.get(
|
||||
{ path: `${BASE_API_ROUTE}/check_is_screenshot`, validate: false },
|
||||
async (ctx, req, res) => {
|
||||
log.info(`Reading screenshot mode from a request: ${screenshotMode.isScreenshotMode(req)}`);
|
||||
log.info(`Reading is screenshot mode from ctx: ${ctx.screenshotMode.isScreenshot}`);
|
||||
return res.ok();
|
||||
}
|
||||
);
|
||||
};
|
19
examples/screenshot_mode_example/server/types.ts
Normal file
19
examples/screenshot_mode_example/server/types.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { IRouter, Logger } from 'kibana/server';
|
||||
import { ScreenshotModeRequestHandlerContext } from '../../../src/plugins/screenshot_mode/server';
|
||||
import { ScreenshotModePluginSetup } from '../../../src/plugins/screenshot_mode/server';
|
||||
|
||||
export type ScreenshotModeExampleRouter = IRouter<ScreenshotModeRequestHandlerContext>;
|
||||
|
||||
export interface RouteDependencies {
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
router: ScreenshotModeExampleRouter;
|
||||
log: Logger;
|
||||
}
|
17
examples/screenshot_mode_example/tsconfig.json
Normal file
17
examples/screenshot_mode_example/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"common/**/*.ts",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*"
|
||||
],
|
||||
"exclude": [],
|
||||
"references": [{ "path": "../../src/core/tsconfig.json" }]
|
||||
}
|
|
@ -110,3 +110,4 @@ pageLoadAssetSize:
|
|||
mapsEms: 26072
|
||||
timelines: 28613
|
||||
cases: 162385
|
||||
screenshotMode: 17856
|
||||
|
|
7
src/plugins/screenshot_mode/.i18nrc.json
Normal file
7
src/plugins/screenshot_mode/.i18nrc.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"prefix": "screenshotMode",
|
||||
"paths": {
|
||||
"screenshotMode": "."
|
||||
},
|
||||
"translations": ["translations/ja-JP.json"]
|
||||
}
|
27
src/plugins/screenshot_mode/README.md
Executable file
27
src/plugins/screenshot_mode/README.md
Executable file
|
@ -0,0 +1,27 @@
|
|||
# Screenshot Mode
|
||||
|
||||
The service exposed by this plugin informs consumers whether they should optimize for non-interactivity. In this way plugins can avoid loading unnecessary code, data or other services.
|
||||
|
||||
The primary intention is to inform other lower-level plugins (plugins that don't depend on other plugins) that we do not expect an actual user to interact with browser. In this way we can avoid loading unnecessary resources (code and data).
|
||||
|
||||
**NB** This plugin should have no other dependencies to avoid any possibility of circular dependencies.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### How to test in screenshot mode
|
||||
|
||||
Please note: the following information is subject to change over time.
|
||||
|
||||
In order to test whether we are correctly detecting screenshot mode, developers can run the following JS snippet:
|
||||
|
||||
```js
|
||||
window.localStorage.setItem('__KBN_SCREENSHOT_MODE_ENABLED_KEY__', true);
|
||||
```
|
||||
|
||||
To get out of screenshot mode, run the following snippet:
|
||||
|
||||
```js
|
||||
window.localStorage.removeItem('__KBN_SCREENSHOT_MODE_ENABLED_KEY__');
|
||||
```
|
9
src/plugins/screenshot_mode/common/constants.ts
Normal file
9
src/plugins/screenshot_mode/common/constants.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 const KBN_SCREENSHOT_MODE_HEADER = 'x-kbn-screenshot-mode'.toLowerCase();
|
|
@ -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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// **PLEASE NOTE**
|
||||
// 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__';
|
||||
|
||||
/**
|
||||
* This function is responsible for detecting whether we are currently in screenshot mode.
|
||||
*
|
||||
* We check in the current window context whether screenshot mode is enabled, otherwise we check
|
||||
* localStorage. The ability to set a value in localStorage enables more convenient development and testing
|
||||
* in functionality that needs to detect screenshot mode.
|
||||
*/
|
||||
export const getScreenshotMode = (): boolean => {
|
||||
return (
|
||||
((window as unknown) as Record<string, unknown>)[KBN_SCREENSHOT_MODE_ENABLED_KEY] === true ||
|
||||
window.localStorage.getItem(KBN_SCREENSHOT_MODE_ENABLED_KEY) === 'true'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this function to set the current browser to screenshot mode.
|
||||
*
|
||||
* This function should be called as early as possible to ensure that screenshot mode is
|
||||
* correctly detected for the first page load. It is not suitable for use inside any plugin
|
||||
* code unless the plugin code is guaranteed to, somehow, load before any other code.
|
||||
*
|
||||
* Additionally, we don't know what environment this code will run in so we remove as many external
|
||||
* references as possible to make it portable. For instance, running inside puppeteer.
|
||||
*/
|
||||
export const setScreenshotModeEnabled = () => {
|
||||
Object.defineProperty(
|
||||
window,
|
||||
'__KBN_SCREENSHOT_MODE_ENABLED_KEY__', // Literal value to prevent adding an external reference
|
||||
{
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
configurable: false,
|
||||
value: true,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const setScreenshotModeDisabled = () => {
|
||||
Object.defineProperty(
|
||||
window,
|
||||
'__KBN_SCREENSHOT_MODE_ENABLED_KEY__', // Literal value to prevent adding an external reference
|
||||
{
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
configurable: false,
|
||||
value: undefined,
|
||||
}
|
||||
);
|
||||
};
|
15
src/plugins/screenshot_mode/common/index.ts
Normal file
15
src/plugins/screenshot_mode/common/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 {
|
||||
getScreenshotMode,
|
||||
setScreenshotModeEnabled,
|
||||
setScreenshotModeDisabled,
|
||||
} from './get_set_browser_screenshot_mode';
|
||||
|
||||
export { KBN_SCREENSHOT_MODE_HEADER } from './constants';
|
13
src/plugins/screenshot_mode/jest.config.js
Normal file
13
src/plugins/screenshot_mode/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/src/plugins/screenshot_mode'],
|
||||
};
|
9
src/plugins/screenshot_mode/kibana.json
Normal file
9
src/plugins/screenshot_mode/kibana.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "screenshotMode",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"ui": true,
|
||||
"server": true,
|
||||
"requiredPlugins": [],
|
||||
"optionalPlugins": []
|
||||
}
|
17
src/plugins/screenshot_mode/public/index.ts
Normal file
17
src/plugins/screenshot_mode/public/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { ScreenshotModePlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ScreenshotModePlugin();
|
||||
}
|
||||
|
||||
export { KBN_SCREENSHOT_MODE_HEADER, setScreenshotModeEnabled } from '../common';
|
||||
|
||||
export { ScreenshotModePluginSetup } from './types';
|
43
src/plugins/screenshot_mode/public/plugin.test.ts
Normal file
43
src/plugins/screenshot_mode/public/plugin.test.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { coreMock } from '../../../../src/core/public/mocks';
|
||||
import { ScreenshotModePlugin } from './plugin';
|
||||
import { setScreenshotModeEnabled, setScreenshotModeDisabled } from '../common';
|
||||
|
||||
describe('Screenshot mode public', () => {
|
||||
let plugin: ScreenshotModePlugin;
|
||||
|
||||
beforeEach(() => {
|
||||
plugin = new ScreenshotModePlugin();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
setScreenshotModeDisabled();
|
||||
});
|
||||
|
||||
describe('setup contract', () => {
|
||||
it('detects screenshot mode "true"', () => {
|
||||
setScreenshotModeEnabled();
|
||||
const screenshotMode = plugin.setup(coreMock.createSetup());
|
||||
expect(screenshotMode.isScreenshotMode()).toBe(true);
|
||||
});
|
||||
|
||||
it('detects screenshot mode "false"', () => {
|
||||
setScreenshotModeDisabled();
|
||||
const screenshotMode = plugin.setup(coreMock.createSetup());
|
||||
expect(screenshotMode.isScreenshotMode()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('start contract', () => {
|
||||
it('returns nothing', () => {
|
||||
expect(plugin.start(coreMock.createStart())).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
25
src/plugins/screenshot_mode/public/plugin.ts
Normal file
25
src/plugins/screenshot_mode/public/plugin.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
|
||||
import { ScreenshotModePluginSetup } from './types';
|
||||
|
||||
import { getScreenshotMode } from '../common';
|
||||
|
||||
export class ScreenshotModePlugin implements Plugin<ScreenshotModePluginSetup> {
|
||||
public setup(core: CoreSetup): ScreenshotModePluginSetup {
|
||||
return {
|
||||
isScreenshotMode: () => getScreenshotMode() === true,
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {}
|
||||
|
||||
public stop() {}
|
||||
}
|
17
src/plugins/screenshot_mode/public/types.ts
Normal file
17
src/plugins/screenshot_mode/public/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 interface IScreenshotModeService {
|
||||
/**
|
||||
* Returns a boolean indicating whether the current user agent (browser) would like to view UI optimized for
|
||||
* screenshots or printing.
|
||||
*/
|
||||
isScreenshotMode: () => boolean;
|
||||
}
|
||||
|
||||
export type ScreenshotModePluginSetup = IScreenshotModeService;
|
21
src/plugins/screenshot_mode/server/index.ts
Normal file
21
src/plugins/screenshot_mode/server/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { ScreenshotModePlugin } from './plugin';
|
||||
|
||||
export { setScreenshotModeEnabled, KBN_SCREENSHOT_MODE_HEADER } from '../common';
|
||||
|
||||
export {
|
||||
ScreenshotModeRequestHandlerContext,
|
||||
ScreenshotModePluginSetup,
|
||||
ScreenshotModePluginStart,
|
||||
} from './types';
|
||||
|
||||
export function plugin() {
|
||||
return new ScreenshotModePlugin();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { httpServerMock } from 'src/core/server/mocks';
|
||||
import { KBN_SCREENSHOT_MODE_HEADER } from '../common';
|
||||
import { isScreenshotMode } from './is_screenshot_mode';
|
||||
|
||||
const { createKibanaRequest } = httpServerMock;
|
||||
|
||||
describe('isScreenshotMode', () => {
|
||||
test('screenshot headers are present', () => {
|
||||
expect(
|
||||
isScreenshotMode(createKibanaRequest({ headers: { [KBN_SCREENSHOT_MODE_HEADER]: 'true' } }))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('screenshot headers are not present', () => {
|
||||
expect(isScreenshotMode(createKibanaRequest())).toBe(false);
|
||||
});
|
||||
});
|
16
src/plugins/screenshot_mode/server/is_screenshot_mode.ts
Normal file
16
src/plugins/screenshot_mode/server/is_screenshot_mode.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
import { KBN_SCREENSHOT_MODE_HEADER } from '../common';
|
||||
|
||||
export const isScreenshotMode = (request: KibanaRequest): boolean => {
|
||||
return Object.keys(request.headers).some((header) => {
|
||||
return header.toLowerCase() === KBN_SCREENSHOT_MODE_HEADER;
|
||||
});
|
||||
};
|
47
src/plugins/screenshot_mode/server/plugin.ts
Normal file
47
src/plugins/screenshot_mode/server/plugin.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup } from '../../../core/server';
|
||||
import {
|
||||
ScreenshotModeRequestHandlerContext,
|
||||
ScreenshotModePluginSetup,
|
||||
ScreenshotModePluginStart,
|
||||
} from './types';
|
||||
import { isScreenshotMode } from './is_screenshot_mode';
|
||||
|
||||
export class ScreenshotModePlugin
|
||||
implements Plugin<ScreenshotModePluginSetup, ScreenshotModePluginStart> {
|
||||
public setup(core: CoreSetup): ScreenshotModePluginSetup {
|
||||
core.http.registerRouteHandlerContext<ScreenshotModeRequestHandlerContext, 'screenshotMode'>(
|
||||
'screenshotMode',
|
||||
(ctx, req) => {
|
||||
return {
|
||||
isScreenshot: isScreenshotMode(req),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 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');
|
||||
|
||||
return {
|
||||
setScreenshotModeEnabled,
|
||||
isScreenshotMode,
|
||||
};
|
||||
}
|
||||
|
||||
public start(): ScreenshotModePluginStart {
|
||||
return {
|
||||
isScreenshotMode,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
35
src/plugins/screenshot_mode/server/types.ts
Normal file
35
src/plugins/screenshot_mode/server/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext, KibanaRequest } from 'src/core/server';
|
||||
|
||||
/**
|
||||
* Any context that requires access to the screenshot mode flag but does not have access
|
||||
* to request context {@link ScreenshotModeRequestHandlerContext}, for instance if they are pre-context,
|
||||
* can use this function to check whether the request originates from a client that is in screenshot mode.
|
||||
*/
|
||||
type IsScreenshotMode = (request: KibanaRequest) => boolean;
|
||||
|
||||
export interface ScreenshotModePluginSetup {
|
||||
isScreenshotMode: IsScreenshotMode;
|
||||
|
||||
/**
|
||||
* Set the current environment to screenshot mode. Intended to run in a browser-environment.
|
||||
*/
|
||||
setScreenshotModeEnabled: () => void;
|
||||
}
|
||||
|
||||
export interface ScreenshotModePluginStart {
|
||||
isScreenshotMode: IsScreenshotMode;
|
||||
}
|
||||
|
||||
export interface ScreenshotModeRequestHandlerContext extends RequestHandlerContext {
|
||||
screenshotMode: {
|
||||
isScreenshot: boolean;
|
||||
};
|
||||
}
|
18
src/plugins/screenshot_mode/tsconfig.json
Normal file
18
src/plugins/screenshot_mode/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "./target/types",
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
"public/**/*",
|
||||
"server/**/*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" },
|
||||
]
|
||||
}
|
|
@ -5,5 +5,5 @@
|
|||
"server": false,
|
||||
"ui": true,
|
||||
"optionalPlugins": [],
|
||||
"requiredPlugins": ["reporting", "developerExamples", "navigation"]
|
||||
"requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode"]
|
||||
}
|
||||
|
|
|
@ -8,18 +8,15 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
|
||||
import { StartDeps } from './types';
|
||||
import { SetupDeps, StartDeps } from './types';
|
||||
import { ReportingExampleApp } from './components/app';
|
||||
|
||||
export const renderApp = (
|
||||
coreStart: CoreStart,
|
||||
startDeps: StartDeps,
|
||||
deps: Omit<StartDeps & SetupDeps, 'developerExamples'>,
|
||||
{ appBasePath, element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<ReportingExampleApp basename={appBasePath} {...coreStart} {...startDeps} />,
|
||||
element
|
||||
);
|
||||
ReactDOM.render(<ReportingExampleApp basename={appBasePath} {...coreStart} {...deps} />, element);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
|
|
|
@ -26,6 +26,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import * as Rx from 'rxjs';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/public';
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
|
||||
import { constants, ReportingStart } from '../../../../../x-pack/plugins/reporting/public';
|
||||
|
@ -37,6 +38,7 @@ interface ReportingExampleAppDeps {
|
|||
http: CoreStart['http'];
|
||||
navigation: NavigationPublicPluginStart;
|
||||
reporting: ReportingStart;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
|
||||
const sourceLogos = ['Beats', 'Cloud', 'Logging', 'Kibana'];
|
||||
|
@ -46,6 +48,7 @@ export const ReportingExampleApp = ({
|
|||
notifications,
|
||||
http,
|
||||
reporting,
|
||||
screenshotMode,
|
||||
}: ReportingExampleAppDeps) => {
|
||||
const { getDefaultLayoutSelectors, ReportingAPIClient } = reporting;
|
||||
const [logos, setLogos] = useState<string[]>([]);
|
||||
|
@ -125,6 +128,8 @@ export const ReportingExampleApp = ({
|
|||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<p>Screenshot Mode is {screenshotMode.isScreenshotMode() ? 'ON' : 'OFF'}!</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</EuiPageContentBody>
|
||||
|
|
|
@ -16,7 +16,7 @@ import { PLUGIN_ID, PLUGIN_NAME } from '../common';
|
|||
import { SetupDeps, StartDeps } from './types';
|
||||
|
||||
export class ReportingExamplePlugin implements Plugin<void, void, {}, {}> {
|
||||
public setup(core: CoreSetup, { developerExamples, ...depsSetup }: SetupDeps): void {
|
||||
public setup(core: CoreSetup, { developerExamples, screenshotMode }: SetupDeps): void {
|
||||
core.application.register({
|
||||
id: PLUGIN_ID,
|
||||
title: PLUGIN_NAME,
|
||||
|
@ -30,7 +30,7 @@ export class ReportingExamplePlugin implements Plugin<void, void, {}, {}> {
|
|||
unknown
|
||||
];
|
||||
// Render the application
|
||||
return renderApp(coreStart, { ...depsSetup, ...depsStart }, params);
|
||||
return renderApp(coreStart, { ...depsStart, screenshotMode }, params);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/public';
|
||||
import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
|
||||
import { ReportingStart } from '../../../plugins/reporting/public';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -16,6 +17,7 @@ export interface PluginStart {}
|
|||
|
||||
export interface SetupDeps {
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
export interface StartDeps {
|
||||
navigation: NavigationPublicPluginStart;
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
"id": "reporting",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"optionalPlugins": [
|
||||
"security",
|
||||
"spaces",
|
||||
"usageCollection"
|
||||
],
|
||||
"optionalPlugins": ["security", "spaces", "usageCollection"],
|
||||
"configPath": ["xpack", "reporting"],
|
||||
"requiredPlugins": [
|
||||
"data",
|
||||
|
@ -16,13 +12,11 @@
|
|||
"uiActions",
|
||||
"taskManager",
|
||||
"embeddable",
|
||||
"screenshotMode",
|
||||
"share",
|
||||
"features"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"discover"
|
||||
]
|
||||
"requiredBundles": ["kibanaReact", "discover"]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import open from 'opn';
|
|||
import puppeteer, { ElementHandle, EvaluateFn, SerializableOrJSHandle } from 'puppeteer';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import { getDisallowedOutgoingUrlError } from '../';
|
||||
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';
|
||||
|
@ -59,8 +61,14 @@ export class HeadlessChromiumDriver {
|
|||
|
||||
private listenersAttached = false;
|
||||
private interceptedCount = 0;
|
||||
private core: ReportingCore;
|
||||
|
||||
constructor(page: puppeteer.Page, { inspect, networkPolicy }: ChromiumDriverOptions) {
|
||||
constructor(
|
||||
core: ReportingCore,
|
||||
page: puppeteer.Page,
|
||||
{ inspect, networkPolicy }: ChromiumDriverOptions
|
||||
) {
|
||||
this.core = core;
|
||||
this.page = page;
|
||||
this.inspect = inspect;
|
||||
this.networkPolicy = networkPolicy;
|
||||
|
@ -98,6 +106,8 @@ export class HeadlessChromiumDriver {
|
|||
// Reset intercepted request count
|
||||
this.interceptedCount = 0;
|
||||
|
||||
const enableScreenshotMode = this.core.getEnableScreenshotMode();
|
||||
await this.page.evaluateOnNewDocument(enableScreenshotMode);
|
||||
await this.page.setRequestInterception(true);
|
||||
|
||||
this.registerListeners(conditionalHeaders, logger);
|
||||
|
@ -261,6 +271,7 @@ export class HeadlessChromiumDriver {
|
|||
{
|
||||
...interceptedRequest.request.headers,
|
||||
...conditionalHeaders.headers,
|
||||
[KBN_SCREENSHOT_MODE_HEADER]: 'true',
|
||||
},
|
||||
(value, name) => ({
|
||||
name,
|
||||
|
|
|
@ -15,6 +15,7 @@ import * as Rx from 'rxjs';
|
|||
import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber';
|
||||
import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators';
|
||||
import { getChromiumDisconnectedError } from '../';
|
||||
import { ReportingCore } from '../../..';
|
||||
import { BROWSER_TYPE } from '../../../../common/constants';
|
||||
import { durationToNumber } from '../../../../common/schema_utils';
|
||||
import { CaptureConfig } from '../../../../server/types';
|
||||
|
@ -32,11 +33,14 @@ export class HeadlessChromiumDriverFactory {
|
|||
private browserConfig: BrowserConfig;
|
||||
private userDataDir: string;
|
||||
private getChromiumArgs: (viewport: ViewportConfig) => string[];
|
||||
private core: ReportingCore;
|
||||
|
||||
constructor(binaryPath: string, captureConfig: CaptureConfig, logger: LevelLogger) {
|
||||
constructor(core: ReportingCore, binaryPath: string, logger: LevelLogger) {
|
||||
this.core = core;
|
||||
this.binaryPath = binaryPath;
|
||||
this.captureConfig = captureConfig;
|
||||
this.browserConfig = captureConfig.browser.chromium;
|
||||
const config = core.getConfig();
|
||||
this.captureConfig = config.get('capture');
|
||||
this.browserConfig = this.captureConfig.browser.chromium;
|
||||
|
||||
if (this.browserConfig.disableSandbox) {
|
||||
logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`);
|
||||
|
@ -138,7 +142,7 @@ export class HeadlessChromiumDriverFactory {
|
|||
this.getProcessLogger(browser, logger).subscribe();
|
||||
|
||||
// HeadlessChromiumDriver: object to "drive" a browser page
|
||||
const driver = new HeadlessChromiumDriver(page, {
|
||||
const driver = new HeadlessChromiumDriver(this.core, page, {
|
||||
inspect: !!this.browserConfig.inspect,
|
||||
networkPolicy: this.captureConfig.networkPolicy,
|
||||
});
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BrowserDownload } from '../';
|
||||
import { CaptureConfig } from '../../../server/types';
|
||||
import { ReportingCore } from '../../../server';
|
||||
import { LevelLogger } from '../../lib';
|
||||
import { HeadlessChromiumDriverFactory } from './driver_factory';
|
||||
import { ChromiumArchivePaths } from './paths';
|
||||
|
||||
export const chromium: BrowserDownload = {
|
||||
paths: new ChromiumArchivePaths(),
|
||||
createDriverFactory: (binaryPath: string, captureConfig: CaptureConfig, logger: LevelLogger) =>
|
||||
new HeadlessChromiumDriverFactory(binaryPath, captureConfig, logger),
|
||||
createDriverFactory: (core: ReportingCore, binaryPath: string, logger: LevelLogger) =>
|
||||
new HeadlessChromiumDriverFactory(core, binaryPath, logger),
|
||||
};
|
||||
|
||||
export const getChromiumDisconnectedError = () =>
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import { first } from 'rxjs/operators';
|
||||
import { ReportingConfig } from '../';
|
||||
import { ReportingCore } from '../';
|
||||
import { LevelLogger } from '../lib';
|
||||
import { CaptureConfig } from '../types';
|
||||
import { chromium, ChromiumArchivePaths } from './chromium';
|
||||
import { HeadlessChromiumDriverFactory } from './chromium/driver_factory';
|
||||
import { installBrowser } from './install';
|
||||
|
@ -18,8 +17,8 @@ export { HeadlessChromiumDriver } from './chromium/driver';
|
|||
export { HeadlessChromiumDriverFactory } from './chromium/driver_factory';
|
||||
|
||||
type CreateDriverFactory = (
|
||||
core: ReportingCore,
|
||||
binaryPath: string,
|
||||
captureConfig: CaptureConfig,
|
||||
logger: LevelLogger
|
||||
) => HeadlessChromiumDriverFactory;
|
||||
|
||||
|
@ -28,12 +27,8 @@ export interface BrowserDownload {
|
|||
paths: ChromiumArchivePaths;
|
||||
}
|
||||
|
||||
export const initializeBrowserDriverFactory = async (
|
||||
config: ReportingConfig,
|
||||
logger: LevelLogger
|
||||
) => {
|
||||
export const initializeBrowserDriverFactory = async (core: ReportingCore, logger: LevelLogger) => {
|
||||
const { binaryPath$ } = installBrowser(logger);
|
||||
const binaryPath = await binaryPath$.pipe(first()).toPromise();
|
||||
const captureConfig = config.get('capture');
|
||||
return chromium.createDriverFactory(binaryPath, captureConfig, logger);
|
||||
return chromium.createDriverFactory(core, binaryPath, logger);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Hapi from '@hapi/hapi';
|
||||
import * as Rx from 'rxjs';
|
||||
import { first, map, take } from 'rxjs/operators';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
|
||||
import {
|
||||
BasePath,
|
||||
IClusterClient,
|
||||
|
@ -41,6 +42,7 @@ export interface ReportingInternalSetup {
|
|||
security?: SecurityPluginSetup;
|
||||
spaces?: SpacesPluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
logger: LevelLogger;
|
||||
}
|
||||
|
||||
|
@ -237,6 +239,11 @@ export class ReportingCore {
|
|||
return screenshotsObservableFactory(config.get('capture'), browserDriverFactory);
|
||||
}
|
||||
|
||||
public getEnableScreenshotMode() {
|
||||
const { screenshotMode } = this.getPluginSetupDeps();
|
||||
return screenshotMode.setScreenshotModeEnabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gives synchronous access to the setupDeps
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ jest.mock('puppeteer', () => ({
|
|||
|
||||
import moment from 'moment';
|
||||
import * as Rx from 'rxjs';
|
||||
import { ReportingCore } from '../..';
|
||||
import { HeadlessChromiumDriver } from '../../browsers';
|
||||
import { ConditionalHeaders } from '../../export_types/common';
|
||||
import {
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
createMockConfigSchema,
|
||||
createMockLayoutInstance,
|
||||
createMockLevelLogger,
|
||||
createMockReportingCore,
|
||||
} from '../../test_helpers';
|
||||
import { ElementsPositionAndAttribute } from './';
|
||||
import * as contexts from './constants';
|
||||
|
@ -37,7 +39,7 @@ import { screenshotsObservableFactory } from './observable';
|
|||
*/
|
||||
const logger = createMockLevelLogger();
|
||||
|
||||
const reportingConfig = {
|
||||
const mockSchema = createMockConfigSchema({
|
||||
capture: {
|
||||
loadDelay: moment.duration(2, 's'),
|
||||
timeouts: {
|
||||
|
@ -46,12 +48,13 @@ const reportingConfig = {
|
|||
renderComplete: moment.duration(10, 's'),
|
||||
},
|
||||
},
|
||||
};
|
||||
const mockSchema = createMockConfigSchema(reportingConfig);
|
||||
});
|
||||
const mockConfig = createMockConfig(mockSchema);
|
||||
const captureConfig = mockConfig.get('capture');
|
||||
const mockLayout = createMockLayoutInstance(captureConfig);
|
||||
|
||||
let core: ReportingCore;
|
||||
|
||||
/*
|
||||
* Tests
|
||||
*/
|
||||
|
@ -59,7 +62,8 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
let mockBrowserDriverFactory: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, {});
|
||||
core = await createMockReportingCore(mockSchema);
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {});
|
||||
});
|
||||
|
||||
it('pipelines a single url into screenshot and timeRange', async () => {
|
||||
|
@ -118,7 +122,7 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
const mockOpen = jest.fn();
|
||||
|
||||
// mocks
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, {
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
|
||||
screenshot: mockScreenshot,
|
||||
open: mockOpen,
|
||||
});
|
||||
|
@ -218,7 +222,7 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
});
|
||||
|
||||
// mocks
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, {
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
|
||||
waitForSelector: mockWaitForSelector,
|
||||
});
|
||||
|
||||
|
@ -312,7 +316,7 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
return Rx.never().toPromise();
|
||||
});
|
||||
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, {
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
|
||||
getCreatePage: mockGetCreatePage,
|
||||
waitForSelector: mockWaitForSelector,
|
||||
});
|
||||
|
@ -345,7 +349,7 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(logger, {
|
||||
mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {
|
||||
evaluate: mockBrowserEvaluate,
|
||||
});
|
||||
mockLayout.getViewport = () => null;
|
||||
|
|
|
@ -48,12 +48,13 @@ export class ReportingPlugin
|
|||
registerUiSettings(core);
|
||||
|
||||
const { http } = core;
|
||||
const { features, licensing, security, spaces, taskManager } = plugins;
|
||||
const { screenshotMode, features, licensing, security, spaces, taskManager } = plugins;
|
||||
|
||||
const router = http.createRouter<ReportingRequestHandlerContext>();
|
||||
const basePath = http.basePath;
|
||||
|
||||
reportingCore.pluginSetup({
|
||||
screenshotMode,
|
||||
features,
|
||||
licensing,
|
||||
basePath,
|
||||
|
@ -91,9 +92,8 @@ export class ReportingPlugin
|
|||
// async background start
|
||||
(async () => {
|
||||
await reportingCore.pluginSetsUp();
|
||||
const config = reportingCore.getConfig();
|
||||
|
||||
const browserDriverFactory = await initializeBrowserDriverFactory(config, this.logger);
|
||||
const browserDriverFactory = await initializeBrowserDriverFactory(reportingCore, this.logger);
|
||||
const store = new ReportingStore(reportingCore, this.logger);
|
||||
|
||||
await reportingCore.pluginStart({
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import moment from 'moment';
|
||||
import { Page } from 'puppeteer';
|
||||
import * as Rx from 'rxjs';
|
||||
import { ReportingCore } from '..';
|
||||
import { chromium, HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers';
|
||||
import { LevelLogger } from '../lib';
|
||||
import { ElementsPositionAndAttribute } from '../lib/screenshots';
|
||||
|
@ -96,6 +97,7 @@ const defaultOpts: CreateMockBrowserDriverFactoryOpts = {
|
|||
};
|
||||
|
||||
export const createMockBrowserDriverFactory = async (
|
||||
core: ReportingCore,
|
||||
logger: LevelLogger,
|
||||
opts: Partial<CreateMockBrowserDriverFactoryOpts> = {}
|
||||
): Promise<HeadlessChromiumDriverFactory> => {
|
||||
|
@ -122,9 +124,9 @@ export const createMockBrowserDriverFactory = async (
|
|||
};
|
||||
|
||||
const binaryPath = '/usr/local/share/common/secure/super_awesome_binary';
|
||||
const mockBrowserDriverFactory = chromium.createDriverFactory(binaryPath, captureConfig, logger);
|
||||
const mockBrowserDriverFactory = chromium.createDriverFactory(core, binaryPath, logger);
|
||||
const mockPage = ({ setViewport: () => {} } as unknown) as Page;
|
||||
const mockBrowserDriver = new HeadlessChromiumDriver(mockPage, {
|
||||
const mockBrowserDriver = new HeadlessChromiumDriver(core, mockPage, {
|
||||
inspect: true,
|
||||
networkPolicy: captureConfig.networkPolicy,
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { IRouter, KibanaRequest, RequestHandlerContext } from 'src/core/server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { DataPluginStart } from 'src/plugins/data/server/plugin';
|
||||
import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
|
||||
import { LicensingPluginSetup } from '../../licensing/server';
|
||||
|
@ -32,6 +33,7 @@ export interface ReportingSetupDeps {
|
|||
spaces?: SpacesPluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
screenshotMode: ScreenshotModePluginSetup;
|
||||
}
|
||||
|
||||
export interface ReportingStartDeps {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
{ "path": "../../../src/plugins/embeddable/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/kibana_react/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/management/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/screenshot_mode/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/share/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/ui_actions/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/usage_collection/tsconfig.json" },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue