Skip to content

Commit

Permalink
[Screenshot mode] Create plugin to provide "screenshot mode" awareness (
Browse files Browse the repository at this point in the history
#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>
  • Loading branch information
4 people authored May 19, 2021
1 parent 8f1bf66 commit f97aad3
Show file tree
Hide file tree
Showing 52 changed files with 907 additions and 49 deletions.
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions examples/screenshot_mode_example/.i18nrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"prefix": "screenshotModeExample",
"paths": {
"screenshotModeExample": "."
},
"translations": ["translations/ja-JP.json"]
}
9 changes: 9 additions & 0 deletions examples/screenshot_mode_example/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions examples/screenshot_mode_example/common/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions examples/screenshot_mode_example/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "screenshotModeExample",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["navigation", "screenshotMode", "usageCollection"],
"optionalPlugins": []
}
33 changes: 33 additions & 0 deletions examples/screenshot_mode_example/public/application.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 122 additions & 0 deletions examples/screenshot_mode_example/public/components/app.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
Empty file.
17 changes: 17 additions & 0 deletions examples/screenshot_mode_example/public/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 48 additions & 0 deletions examples/screenshot_mode_example/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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() {}
}
9 changes: 9 additions & 0 deletions examples/screenshot_mode_example/public/services/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions examples/screenshot_mode_example/public/types.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions examples/screenshot_mode_example/server/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions examples/screenshot_mode_example/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -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() {}
}
Loading

0 comments on commit f97aad3

Please sign in to comment.