[EuiProvider] Fix AppEx-SharedUX code (#183872)

## Summary

Fixes needed for getting CI to pass when EUI throws an error if
attempting to render a component without the EuiProvider in the render
tree.

## Detailed description
In https://github.com/elastic/kibana/pull/180819, I will deliver a
change that will cause EUI components to throw an error if the
EuiProvider context is missing. This PR comes in as part of the final
work to get all functional tests passing in an environment where EUI
will throw the error. The tied to the ["Fix 'dark mode' inconsistencies
in Kibana" Epic](https://github.com/elastic/kibana-team/issues/805) has
so far been in preparation for this.

**Reviewers: Please interact with critical paths through the UI
components touched in this PR, ESPECIALLY in terms of testing dark mode
and i18n.**

<img width="1107" alt="image"
src="c0d2ce08-ac35-45a7-8192-0b2256fceb0e">

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Tim Sullivan 2024-05-22 09:57:05 -07:00 committed by GitHub
parent c17e957724
commit 7d7f9de609
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 311 additions and 217 deletions

View file

@ -10,6 +10,7 @@ import * as React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { CoreSetup, CoreStart, AppMountParameters } from '@kbn/core/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { BfetchExplorerStartPlugins, ExplorerService } from './plugin';
import { App } from './containers/app';
@ -26,9 +27,11 @@ export const mount =
const [core, plugins] = await coreSetup.getStartServices();
const deps: BfetchDeps = { appBasePath, core, plugins, explorer };
const reactElement = (
<KibanaContextProvider services={deps}>
<App />
</KibanaContextProvider>
<KibanaRenderContextProvider {...core}>
<KibanaContextProvider services={deps}>
<App />
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
render(reactElement, element);
return () => unmountComponentAtNode(element);

View file

@ -19,5 +19,6 @@
"@kbn/bfetch-plugin",
"@kbn/kibana-react-plugin",
"@kbn/shared-ux-router",
"@kbn/react-kibana-context-render",
]
}

View file

@ -9,6 +9,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Redirect } from 'react-router-dom';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
@ -24,65 +25,67 @@ export const renderApp = (
{ element, history }: AppMountParameters
) => {
ReactDOM.render(
<Router history={history}>
<RedirectAppLinks coreStart={core}>
<EuiPageTemplate offset={0}>
<EuiPageTemplate.Sidebar>
<EuiSideNav
items={[
{
id: 'Examples',
name: 'Examples',
items: [
{
id: 'todos',
name: 'Todo app',
'data-test-subj': 'todosExample',
href: '/app/contentManagementExamples/todos',
},
{
id: 'msearch',
name: 'MSearch',
'data-test-subj': 'msearchExample',
href: '/app/contentManagementExamples/msearch',
},
{
id: 'finder',
name: 'Finder',
'data-test-subj': 'finderExample',
href: '/app/contentManagementExamples/finder',
},
],
},
]}
/>
</EuiPageTemplate.Sidebar>
<KibanaRenderContextProvider {...core}>
<Router history={history}>
<RedirectAppLinks coreStart={core}>
<EuiPageTemplate offset={0}>
<EuiPageTemplate.Sidebar>
<EuiSideNav
items={[
{
id: 'Examples',
name: 'Examples',
items: [
{
id: 'todos',
name: 'Todo app',
'data-test-subj': 'todosExample',
href: '/app/contentManagementExamples/todos',
},
{
id: 'msearch',
name: 'MSearch',
'data-test-subj': 'msearchExample',
href: '/app/contentManagementExamples/msearch',
},
{
id: 'finder',
name: 'Finder',
'data-test-subj': 'finderExample',
href: '/app/contentManagementExamples/finder',
},
],
},
]}
/>
</EuiPageTemplate.Sidebar>
<EuiPageTemplate.Section>
<Routes>
<Redirect from="/" to="/todos" exact />
<Route path="/todos">
<TodoApp contentClient={contentManagement.client} />
</Route>
<Route path="/msearch">
<MSearchApp
contentClient={contentManagement.client}
core={core}
savedObjectsTagging={savedObjectsTaggingOss}
/>
</Route>
<Route path="/finder">
<FinderApp
contentClient={contentManagement.client}
core={core}
savedObjectsTagging={savedObjectsTaggingOss}
/>
</Route>
</Routes>
</EuiPageTemplate.Section>
</EuiPageTemplate>
</RedirectAppLinks>
</Router>,
<EuiPageTemplate.Section>
<Routes>
<Redirect from="/" to="/todos" exact />
<Route path="/todos">
<TodoApp contentClient={contentManagement.client} />
</Route>
<Route path="/msearch">
<MSearchApp
contentClient={contentManagement.client}
core={core}
savedObjectsTagging={savedObjectsTaggingOss}
/>
</Route>
<Route path="/finder">
<FinderApp
contentClient={contentManagement.client}
core={core}
savedObjectsTagging={savedObjectsTaggingOss}
/>
</Route>
</Routes>
</EuiPageTemplate.Section>
</EuiPageTemplate>
</RedirectAppLinks>
</Router>
</KibanaRenderContextProvider>,
element
);

View file

@ -29,5 +29,6 @@
"@kbn/shared-ux-router",
"@kbn/saved-objects-finder-plugin",
"@kbn/content-management-table-list-view-common",
"@kbn/react-kibana-context-render",
]
}

View file

@ -22,16 +22,29 @@ import {
EuiLink,
EuiButtonIcon,
} from '@elastic/eui';
import { AppMountParameters } from '@kbn/core/public';
import {
AnalyticsServiceStart,
AppMountParameters,
I18nStart,
ThemeServiceStart,
} from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { ExampleDefinition } from './types';
interface StartServices {
analytics: Pick<AnalyticsServiceStart, 'reportEvent'>;
i18n: I18nStart;
theme: Pick<ThemeServiceStart, 'theme$'>;
}
interface Props {
startServices: StartServices;
examples: ExampleDefinition[];
navigateToApp: (appId: string) => void;
getUrlForApp: (appId: string) => string;
}
function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
function DeveloperExamples({ startServices, examples, navigateToApp, getUrlForApp }: Props) {
const [search, setSearch] = useState<string>('');
const lcSearch = search.toLowerCase();
@ -44,7 +57,7 @@ function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
});
return (
<>
<KibanaRenderContextProvider {...startServices}>
<EuiPageTemplate.Header>
<EuiFlexGroup justifyContent={'spaceBetween'}>
<EuiFlexItem>
@ -103,7 +116,7 @@ function DeveloperExamples({ examples, navigateToApp, getUrlForApp }: Props) {
))}
</EuiFlexGroup>
</EuiPageTemplate.Section>
</>
</KibanaRenderContextProvider>
);
}

View file

@ -27,8 +27,10 @@ export class DeveloperExamplesPlugin implements Plugin<DeveloperExamplesSetup, v
async mount(params: AppMountParameters) {
const { renderApp } = await import('./app');
const [coreStart] = await core.getStartServices();
const { analytics, i18n, theme } = coreStart;
return renderApp(
{
startServices: { analytics, i18n, theme },
examples,
navigateToApp: (appId: string) => coreStart.application.navigateToApp(appId),
getUrlForApp: (appId: string) => coreStart.application.getUrlForApp(appId),

View file

@ -14,6 +14,7 @@
"target/**/*",
],
"kbn_references": [
"@kbn/core"
"@kbn/core",
"@kbn/react-kibana-context-render"
]
}

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiButton } from '@elastic/eui';
import { EuiButton, EuiProvider } from '@elastic/eui';
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
@ -74,23 +74,27 @@ export class ErrorBoundaryExamplePlugin implements Plugin<void, void, SetupDeps>
id: 'errorBoundaryExample',
title: 'Error Boundary Example',
async mount({ element }: AppMountParameters) {
// Using the "EuiProvider" here rather than KibanaRenderContextProvider, because KibanaRenderContextProvider
// wraps KibanaErrorBoundaryProvider and KibanaErrorBoundary and we want to test it directly, not a wrapper.
ReactDOM.render(
<KibanaErrorBoundaryProvider analytics={core.analytics}>
<KibanaErrorBoundary>
<KibanaPageTemplate>
<KibanaPageTemplate.Header
pageTitle="KibanaErrorBoundary example"
data-test-subj="errorBoundaryExampleHeader"
/>
<KibanaPageTemplate.Section grow={false}>
<FatalComponent />
</KibanaPageTemplate.Section>
<KibanaPageTemplate.Section>
<RecoverableComponent />
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
</KibanaErrorBoundary>
</KibanaErrorBoundaryProvider>,
<EuiProvider>
<KibanaErrorBoundaryProvider analytics={core.analytics}>
<KibanaErrorBoundary>
<KibanaPageTemplate>
<KibanaPageTemplate.Header
pageTitle="KibanaErrorBoundary example"
data-test-subj="errorBoundaryExampleHeader"
/>
<KibanaPageTemplate.Section grow={false}>
<FatalComponent />
</KibanaPageTemplate.Section>
<KibanaPageTemplate.Section>
<RecoverableComponent />
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
</KibanaErrorBoundary>
</KibanaErrorBoundaryProvider>
</EuiProvider>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);

View file

@ -42,6 +42,7 @@ export class StateContainersExamplesPlugin implements Plugin {
const { renderApp, History } = await import('./todo/app');
const [coreStart] = await core.getStartServices();
return renderApp(
coreStart,
params,
{
appTitle: examples.stateContainersExampleBrowserHistory.title,
@ -59,6 +60,7 @@ export class StateContainersExamplesPlugin implements Plugin {
const { renderApp, History } = await import('./todo/app');
const [coreStart] = await core.getStartServices();
return renderApp(
coreStart,
params,
{
appTitle: examples.stateContainersExampleHashHistory.title,

View file

@ -6,9 +6,11 @@
* Side Public License, v 1.
*/
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import ReactDOM from 'react-dom';
import React from 'react';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { createHashHistory } from 'history';
import { TodoAppPage } from './todo';
import { StateContainersExamplesPage, ExampleLink } from '../common/example_page';
@ -29,15 +31,18 @@ export interface Deps {
}
export const renderApp = (
core: CoreStart,
{ appBasePath, element, history: platformHistory }: AppMountParameters,
{ appTitle, historyType }: AppOptions,
{ navigateToApp, exampleLinks }: Deps
) => {
const history = historyType === History.Browser ? platformHistory : createHashHistory();
ReactDOM.render(
<StateContainersExamplesPage navigateToApp={navigateToApp} exampleLinks={exampleLinks}>
<TodoAppPage history={history} appTitle={appTitle} appBasePath={appBasePath} />
</StateContainersExamplesPage>,
<KibanaRenderContextProvider {...core}>
<StateContainersExamplesPage navigateToApp={navigateToApp} exampleLinks={exampleLinks}>
<TodoAppPage history={history} appTitle={appTitle} appBasePath={appBasePath} />
</StateContainersExamplesPage>
</KibanaRenderContextProvider>,
element
);

View file

@ -23,5 +23,6 @@
"@kbn/developer-examples-plugin",
"@kbn/es-query",
"@kbn/shared-ux-router",
"@kbn/react-kibana-context-render",
]
}

View file

@ -18,6 +18,7 @@ import {
EuiPageHeader,
} from '@elastic/eui';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { TriggerContextExample } from './trigger_context_example';
import { ContextMenuExamples } from './context_menu_examples';
@ -31,30 +32,35 @@ interface Props {
const ActionsExplorer = ({ uiActionsStartService, core }: Props) => {
return (
<EuiPage>
<EuiPageBody>
<EuiPageSection>
<EuiPageHeader pageTitle="Actions and Triggers" />
</EuiPageSection>
<EuiPageTemplate.Section>
<KibanaRenderContextProvider {...core}>
<EuiPage>
<EuiPageBody>
<EuiPageSection>
<Overview />
<EuiSpacer />
<HelloWorldExample uiActionsStartService={uiActionsStartService} startServices={core} />
<EuiSpacer />
<TriggerContextExample uiActionsApi={uiActionsStartService} />
<EuiSpacer />
<ContextMenuExamples />
<EuiPageHeader pageTitle="Actions and Triggers" />
</EuiPageSection>
</EuiPageTemplate.Section>
</EuiPageBody>
</EuiPage>
<EuiPageTemplate.Section>
<EuiPageSection>
<Overview />
<EuiSpacer />
<HelloWorldExample
uiActionsStartService={uiActionsStartService}
startServices={core}
/>
<EuiSpacer />
<TriggerContextExample uiActionsApi={uiActionsStartService} />
<EuiSpacer />
<ContextMenuExamples />
</EuiPageSection>
</EuiPageTemplate.Section>
</EuiPageBody>
</EuiPage>
</KibanaRenderContextProvider>
);
};

View file

@ -18,5 +18,6 @@
"@kbn/ui-actions-examples-plugin",
"@kbn/developer-examples-plugin",
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-render",
]
}

View file

@ -21,7 +21,10 @@ import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import { getAppInfo } from '@kbn/core-application-browser-internal';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { findTestSubject } from '@kbn/test-jest-helpers';
import { ChromeService } from './chrome_service';
@ -48,6 +51,9 @@ Object.defineProperty(window, 'localStorage', {
function defaultStartDeps(availableApps?: App[], currentAppId?: string) {
const deps = {
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
application: applicationServiceMock.createInternalStartContract(currentAppId),
docLinks: docLinksServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
@ -94,7 +100,7 @@ async function start({
startDeps.injectedMetadata.getCspConfig.mockReturnValue(cspConfigMock);
}
await service.setup({ analytics: analyticsServiceMock.createAnalyticsServiceSetup() });
service.setup({ analytics: analyticsServiceMock.createAnalyticsServiceSetup() });
const chromeStart = await service.start(startDeps);
return {
@ -118,7 +124,7 @@ describe('setup', () => {
it('calls registerAnalyticsContextProvider with the correct parameters', async () => {
const service = new ChromeService(defaultStartTestOptions({}));
const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
await service.setup({ analytics });
service.setup({ analytics });
expect(registerAnalyticsContextProviderMock).toHaveBeenCalledTimes(1);
expect(registerAnalyticsContextProviderMock).toHaveBeenCalledWith(
@ -206,7 +212,7 @@ describe('start', () => {
});
it('renders the custom project side navigation', async () => {
const { chrome } = await start({
const { chrome, startDeps } = await start({
startDeps: defaultStartDeps([{ id: 'foo', title: 'Foo' } as App], 'foo'),
});
@ -216,7 +222,11 @@ describe('start', () => {
chrome.setChromeStyle('project');
chrome.project.setSideNavComponent(MyNav);
const component = mount(chrome.getHeaderComponent());
const component = mount(
<KibanaRenderContextProvider {...startDeps}>
{chrome.getHeaderComponent()}
</KibanaRenderContextProvider>
);
const projectHeader = findTestSubject(component, 'kibanaProjectHeader');
expect(projectHeader.length).toBe(1);
@ -229,11 +239,15 @@ describe('start', () => {
});
it('renders chromeless header', async () => {
const { chrome } = await start({ startDeps: defaultStartDeps() });
const { chrome, startDeps } = await start({ startDeps: defaultStartDeps() });
chrome.setIsVisible(false);
const component = mount(chrome.getHeaderComponent());
const component = mount(
<KibanaRenderContextProvider {...startDeps}>
{chrome.getHeaderComponent()}
</KibanaRenderContextProvider>
);
const chromeless = findTestSubject(component, 'kibanaHeaderChromeless');
expect(chromeless.length).toBe(1);
@ -418,7 +432,7 @@ describe('start', () => {
const promise = chrome.getBreadcrumbsAppendExtension$().pipe(toArray()).toPromise();
chrome.setBreadcrumbsAppendExtension({
content: (element) => () => {},
content: () => () => {},
});
service.stop();

View file

@ -52,6 +52,9 @@
"@kbn/core-base-browser-mocks",
"@kbn/logging",
"@kbn/logging-mocks",
"@kbn/core-i18n-browser-mocks",
"@kbn/core-theme-browser-mocks",
"@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",

View file

@ -10,30 +10,37 @@ import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiFlyoutBody } from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { ActionDefinition } from '../../actions';
const MenuItem: React.FC = () => {
return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>Hello world!</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color={'danger'}>{'secret'}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
);
type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
const getMenuItem = (core: StartServices) => {
return () => {
return (
<KibanaRenderContextProvider {...core}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>Hello world!</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge color={'danger'}>{'secret'}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
</KibanaRenderContextProvider>
);
};
};
export const ACTION_HELLO_WORLD = 'ACTION_HELLO_WORLD';
export function createHelloWorldAction(
coreStart: Pick<CoreStart, 'overlays' | 'analytics' | 'i18n' | 'theme'>
coreStart: StartServices & Pick<CoreStart, 'overlays'>
): ActionDefinition {
const { overlays, ...startServices } = coreStart;
return {
id: ACTION_HELLO_WORLD,
type: ACTION_HELLO_WORLD,
getIconType: () => 'lock',
MenuItem,
MenuItem: getMenuItem(startServices),
execute: async () => {
overlays.openFlyout(
toMountPoint(

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, useContext, useState } from 'react';
import React, { useCallback, useState } from 'react';
import {
EuiButton,
EuiCallOut,
@ -24,11 +24,12 @@ import {
EuiTextArea,
EuiTitle,
} from '@elastic/eui';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { API_ENDPOINT, ScreenshottingExpressionResponse } from '../../common';
import { HttpContext } from './http_context';
import { useAppContext } from './http_context';
export function App() {
const http = useContext(HttpContext);
const { http, ...startServices } = useAppContext();
const [expression, setExpression] = useState<string>();
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState<ScreenshottingExpressionResponse>();
@ -37,7 +38,7 @@ export function App() {
try {
setLoading(true);
setResponse(
await http?.get<ScreenshottingExpressionResponse>(API_ENDPOINT, {
await http.get<ScreenshottingExpressionResponse>(API_ENDPOINT, {
query: { expression },
})
);
@ -50,85 +51,87 @@ export function App() {
}, []);
return (
<EuiPage>
<EuiPageBody style={{ maxWidth: 1200, margin: '0 auto' }}>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>Screenshotting Demo</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageSection>
<EuiText>
<p>This example captures a screenshot of an expression provided below.</p>
</EuiText>
<EuiSpacer size={'m'} />
<EuiTextArea
placeholder="Expression to render"
fullWidth
onChange={handleChange}
data-test-subj="expression"
/>
<EuiSpacer size={'m'} />
<EuiButton
iconType="play"
onClick={handleClick}
isDisabled={!expression}
isLoading={loading}
data-test-subj="run"
>
Run
</EuiButton>
{!!response && <EuiHorizontalRule />}
{response?.errors && (
<>
<EuiCallOut
title="Sorry, there was an error"
color="danger"
iconType="warning"
data-test-subj="error"
>
<p>{response.errors.join('\n')}</p>
</EuiCallOut>
<EuiSpacer size={'m'} />
</>
)}
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
{response?.image && (
<EuiImage
src={`data:image/png;base64,${response.image}`}
alt="Screenshot"
size="xl"
allowFullScreen
hasShadow
data-test-subj="image"
/>
)}
</EuiFlexItem>
<EuiFlexItem>
{response?.metrics && (
<>
<EuiStat
title={`${response.metrics.cpuInPercentage ?? 'N/A'}%`}
description="CPU"
titleColor="primary"
data-test-subj="cpu"
<KibanaRenderContextProvider {...startServices}>
<EuiPage>
<EuiPageBody style={{ maxWidth: 1200, margin: '0 auto' }}>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>Screenshotting Demo</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageSection>
<EuiText>
<p>This example captures a screenshot of an expression provided below.</p>
</EuiText>
<EuiSpacer size={'m'} />
<EuiTextArea
placeholder="Expression to render"
fullWidth
onChange={handleChange}
data-test-subj="expression"
/>
<EuiSpacer size={'m'} />
<EuiButton
iconType="play"
onClick={handleClick}
isDisabled={!expression}
isLoading={loading}
data-test-subj="run"
>
Run
</EuiButton>
{!!response && <EuiHorizontalRule />}
{response?.errors && (
<>
<EuiCallOut
title="Sorry, there was an error"
color="danger"
iconType="warning"
data-test-subj="error"
>
<p>{response.errors.join('\n')}</p>
</EuiCallOut>
<EuiSpacer size={'m'} />
</>
)}
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
{response?.image && (
<EuiImage
src={`data:image/png;base64,${response.image}`}
alt="Screenshot"
size="xl"
allowFullScreen
hasShadow
data-test-subj="image"
/>
<EuiSpacer size={'m'} />
<EuiStat
title={`${response.metrics.memoryInMegabytes ?? 'N/A'} MB`}
description="Memory"
titleColor="primary"
data-test-subj="memory"
/>
</>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageSection>
</EuiPageBody>
</EuiPage>
)}
</EuiFlexItem>
<EuiFlexItem>
{response?.metrics && (
<>
<EuiStat
title={`${response.metrics.cpuInPercentage ?? 'N/A'}%`}
description="CPU"
titleColor="primary"
data-test-subj="cpu"
/>
<EuiSpacer size={'m'} />
<EuiStat
title={`${response.metrics.memoryInMegabytes ?? 'N/A'} MB`}
description="Memory"
titleColor="primary"
data-test-subj="memory"
/>
</>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageSection>
</EuiPageBody>
</EuiPage>
</KibanaRenderContextProvider>
);
}

View file

@ -5,7 +5,29 @@
* 2.0.
*/
import { createContext } from 'react';
import type { HttpStart } from '@kbn/core/public';
import { createContext, useContext } from 'react';
import type {
AnalyticsServiceStart,
HttpStart,
I18nStart,
ThemeServiceStart,
} from '@kbn/core/public';
export const HttpContext = createContext<HttpStart | undefined>(undefined);
export interface StartServices {
http: HttpStart;
analytics: Pick<AnalyticsServiceStart, 'reportEvent'>;
i18n: I18nStart;
theme: Pick<ThemeServiceStart, 'theme$'>;
}
export const AppContext = createContext<StartServices | undefined>(undefined);
export const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('App Context Error!');
}
return context;
};

View file

@ -10,7 +10,7 @@ import ReactDOM from 'react-dom';
import type { AppMountParameters, CoreSetup, Plugin } from '@kbn/core/public';
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { App, HttpContext } from './app';
import { App, AppContext } from './app';
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
@ -26,12 +26,13 @@ export class ScreenshottingExamplePlugin implements Plugin<void, void> {
title: APPLICATION_NAME,
visibleIn: [],
mount: async ({ element }: AppMountParameters) => {
const [{ http }] = await getStartServices();
const [{ http, analytics, i18n, theme }] = await getStartServices();
const startServices = { analytics, http, i18n, theme };
ReactDOM.render(
<HttpContext.Provider value={http}>
<AppContext.Provider value={startServices}>
<App />
</HttpContext.Provider>,
</AppContext.Provider>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);

View file

@ -19,5 +19,6 @@
"@kbn/developer-examples-plugin",
"@kbn/screenshotting-plugin",
"@kbn/config-schema",
"@kbn/react-kibana-context-render",
]
}