[ResponseOps] Remove usage of deprecated React rendering utilities (#180098)

## Summary

Partially addresses https://github.com/elastic/kibana-team/issues/805

Follows https://github.com/elastic/kibana/pull/180003

These changes come up from searching in the code and finding where
certain kinds of deprecated AppEx-SharedUX modules are imported.
**Reviewers: Please interact with critical paths through the UI
components touched in this PR, ESPECIALLY in terms of testing dark mode
and i18n.**

* Started with focusing on code owned by ResponseOps.
* Stack Management changes trickled into this PR because ResponseOps
needs the `theme` field from `ManagementAppMountParams`
* Security changes trickled into this PR, in its test code, because of
the Stack Management changes.

### 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-04-09 09:31:05 -07:00 committed by GitHub
parent 3839523be9
commit 09228a3ed6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 217 additions and 280 deletions

View file

@ -10,13 +10,13 @@ import './management_app.scss';
import React, { useState, useEffect, useCallback } from 'react';
import { BehaviorSubject } from 'rxjs';
import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public';
import { CoreStart } from '@kbn/core/public';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { reactRouterNavigate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
import useObservable from 'react-use/lib/useObservable';
import { AppContextProvider } from './management_context';
@ -32,7 +32,6 @@ import { SectionsServiceStart, NavigationCardsSubject } from '../../types';
interface ManagementAppProps {
appBasePath: string;
history: AppMountParameters['history'];
theme$: AppMountParameters['theme$'];
dependencies: ManagementAppDependencies;
}
@ -45,13 +44,8 @@ export interface ManagementAppDependencies {
cardsNavigationConfig$: BehaviorSubject<NavigationCardsSubject>;
}
export const ManagementApp = ({
dependencies,
history,
theme$,
appBasePath,
}: ManagementAppProps) => {
const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies;
export const ManagementApp = ({ dependencies, history, appBasePath }: ManagementAppProps) => {
const { coreStart, setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies;
const [selectedId, setSelectedId] = useState<string>('');
const [sections, setSections] = useState<ManagementSection[]>();
const isSidebarEnabled = useObservable(isSidebarEnabled$);
@ -114,30 +108,28 @@ export const ManagementApp = ({
};
return (
<RedirectAppLinks coreStart={dependencies.coreStart}>
<I18nProvider>
<KibanaRenderContextProvider i18n={coreStart.i18n} theme={coreStart.theme}>
<RedirectAppLinks coreStart={dependencies.coreStart}>
<AppContextProvider value={contextDependencies}>
<KibanaThemeProvider theme$={theme$}>
<KibanaPageTemplate
restrictWidth={false}
solutionNav={solution}
// @ts-expect-error Techincally `paddingSize` isn't supported but it is passed through,
// this is a stop-gap for Stack managmement specifically until page components can be converted to template components
mainProps={{ paddingSize: 'l' }}
panelled
>
<ManagementRouter
history={history}
theme$={theme$}
setBreadcrumbs={setBreadcrumbsScoped}
onAppMounted={onAppMounted}
sections={sections}
analytics={dependencies.coreStart.analytics}
/>
</KibanaPageTemplate>
</KibanaThemeProvider>
<KibanaPageTemplate
restrictWidth={false}
solutionNav={solution}
// @ts-expect-error Techincally `paddingSize` isn't supported but it is passed through,
// this is a stop-gap for Stack managmement specifically until page components can be converted to template components
mainProps={{ paddingSize: 'l' }}
panelled
>
<ManagementRouter
history={history}
theme={coreStart.theme}
setBreadcrumbs={setBreadcrumbsScoped}
onAppMounted={onAppMounted}
sections={sections}
analytics={coreStart.analytics}
/>
</KibanaPageTemplate>
</AppContextProvider>
</I18nProvider>
</RedirectAppLinks>
</RedirectAppLinks>
</KibanaRenderContextProvider>
);
};

View file

@ -14,6 +14,7 @@ import {
AppMountParameters,
ChromeBreadcrumb,
ScopedHistory,
ThemeServiceStart,
} from '@kbn/core/public';
import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary';
import { ManagementAppWrapper } from '../management_app_wrapper';
@ -22,7 +23,7 @@ import { ManagementSection } from '../../utils';
interface ManagementRouterProps {
history: AppMountParameters['history'];
theme$: AppMountParameters['theme$'];
theme: ThemeServiceStart;
setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void;
onAppMounted: (id: string) => void;
sections: ManagementSection[];
@ -35,7 +36,7 @@ export const ManagementRouter = memo(
setBreadcrumbs,
onAppMounted,
sections,
theme$,
theme,
analytics,
}: ManagementRouterProps) => {
return (
@ -55,7 +56,7 @@ export const ManagementRouter = memo(
setBreadcrumbs={setBreadcrumbs}
onAppMounted={onAppMounted}
history={history}
theme$={theme$}
theme={theme}
/>
)}
/>

View file

@ -8,7 +8,12 @@
import React, { createRef, Component } from 'react';
import { ChromeBreadcrumb, AppMountParameters, ScopedHistory } from '@kbn/core/public';
import {
ChromeBreadcrumb,
AppMountParameters,
ScopedHistory,
ThemeServiceStart,
} from '@kbn/core/public';
import classNames from 'classnames';
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
import { ThrowIfError } from '@kbn/shared-ux-error-boundary';
@ -20,7 +25,7 @@ interface ManagementSectionWrapperProps {
setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], history?: ScopedHistory) => void;
onAppMounted: (id: string) => void;
history: AppMountParameters['history'];
theme$: AppMountParameters['theme$'];
theme: ThemeServiceStart;
}
interface ManagementSectionWrapperState {
@ -40,15 +45,19 @@ export class ManagementAppWrapper extends Component<
}
componentDidMount() {
const { setBreadcrumbs, app, onAppMounted, history, theme$ } = this.props;
const { setBreadcrumbs, app, onAppMounted, history, theme } = this.props;
const { mount, basePath } = app;
const appHistory = history.createSubHistory(app.basePath);
// TODO: Remove this: it provides a deprecated field still needed in ManagementAppMountParams
const { theme$ } = theme;
const mountResult = mount({
basePath,
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => setBreadcrumbs(crumbs, appHistory),
element: this.mountElementRef.current!,
history: appHistory,
theme,
theme$,
});

View file

@ -7,7 +7,7 @@
*/
import { Observable } from 'rxjs';
import { ScopedHistory, Capabilities } from '@kbn/core/public';
import { ScopedHistory, Capabilities, ThemeServiceStart } from '@kbn/core/public';
import type { LocatorPublic } from '@kbn/share-plugin/common';
import { ChromeBreadcrumb, CoreTheme } from '@kbn/core/public';
import type { CardsNavigationComponentProps } from '@kbn/management-cards-navigation';
@ -70,6 +70,8 @@ export interface ManagementAppMountParams {
element: HTMLElement; // element the section should render into
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
history: ScopedHistory;
theme: ThemeServiceStart;
/** @deprecated - use `theme` **/
theme$: Observable<CoreTheme>;
}

View file

@ -27,6 +27,7 @@
"@kbn/serverless",
"@kbn/shared-ux-error-boundary",
"@kbn/deeplinks-management",
"@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*"

View file

@ -7,17 +7,18 @@
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { EuiLoadingSpinner } from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { EuiLoadingSpinner } from '@elastic/eui';
import { AlertingPluginStart } from '../plugin';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MAINTENANCE_WINDOW_PATHS } from '../../common';
import { useLicense } from '../hooks/use_license';
import { AlertingPluginStart } from '../plugin';
const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maintenance_windows'));
const MaintenanceWindowsCreateLazy: React.FC = React.lazy(
@ -76,14 +77,13 @@ export const renderApp = ({
mountParams: ManagementAppMountParams;
kibanaVersion: string;
}) => {
const { element, history, theme$ } = mountParams;
const i18nCore = core.i18n;
const isDarkMode = core.theme.getTheme().darkMode;
const { element, history } = mountParams;
const { i18n, theme } = core;
const queryClient = new QueryClient();
ReactDOM.render(
<KibanaThemeProvider theme$={theme$}>
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
<KibanaContextProvider
services={{
...core,
@ -93,16 +93,12 @@ export const renderApp = ({
}}
>
<Router history={history}>
<EuiThemeProvider darkMode={isDarkMode}>
<i18nCore.Context>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</i18nCore.Context>
</EuiThemeProvider>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</Router>
</KibanaContextProvider>
</KibanaThemeProvider>,
</KibanaRenderContextProvider>,
element
);
return () => {

View file

@ -6,14 +6,14 @@
*/
import React from 'react';
import { of, BehaviorSubject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
import { Capabilities, CoreStart } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';
import { euiDarkVars } from '@kbn/ui-theme';
import type { ILicense } from '@kbn/licensing-plugin/public';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
@ -40,8 +40,6 @@ export const createAppMockRenderer = ({
capabilities,
license,
}: AppMockRendererArgs = {}): AppMockRenderer => {
const theme$ = of({ eui: euiDarkVars, darkMode: true });
const licensingPluginMock = licensingMock.createStart();
const queryClient = new QueryClient({
@ -83,7 +81,7 @@ export const createAppMockRenderer = ({
};
const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => (
<I18nProvider>
<KibanaThemeProvider theme$={theme$}>
<KibanaThemeProvider theme={core.theme}>
<KibanaContextProvider services={services}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</KibanaContextProvider>

View file

@ -68,7 +68,9 @@
"@kbn/core-ui-settings-server-mocks",
"@kbn/core-test-helpers-kbn-server",
"@kbn/core-http-router-server-internal",
"@kbn/core-execution-context-server-mocks"
"@kbn/core-execution-context-server-mocks",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-context-theme"
],
"exclude": [
"target/**/*"

View file

@ -9,7 +9,7 @@ import type { ErrorToastOptions } from '@kbn/core/public';
import { EuiButtonEmpty, EuiText, logicalCSS, useEuiTheme } from '@elastic/eui';
import React, { useMemo } from 'react';
import { css } from '@emotion/react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { isValidOwner } from '../../common/utils/owner';
import type { CaseUI } from '../../common';
import { AttachmentType } from '../../common/types/domain';
@ -106,7 +106,8 @@ const getErrorMessage = (error: Error | ServerError): string => {
export const useCasesToast = () => {
const { appId } = useApplication();
const { getUrlForApp, navigateToUrl } = useKibana().services.application;
const { application, i18n, theme } = useKibana().services;
const { getUrlForApp, navigateToUrl } = application;
const toasts = useToasts();
@ -147,12 +148,13 @@ export const useCasesToast = () => {
return toasts.addSuccess({
color: 'success',
iconType: 'check',
title: toMountPoint(<TruncatedText text={renderTitle} />),
title: toMountPoint(<TruncatedText text={renderTitle} />, { i18n, theme }),
text: toMountPoint(
<CaseToastSuccessContent
content={renderContent}
onViewCaseClick={url != null ? onViewCaseClick : undefined}
/>
/>,
{ i18n, theme }
),
});
},
@ -175,7 +177,7 @@ export const useCasesToast = () => {
});
},
}),
[appId, getUrlForApp, navigateToUrl, toasts]
[i18n, theme, appId, getUrlForApp, navigateToUrl, toasts]
);
};

View file

@ -1,46 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { BehaviorSubject } from 'rxjs';
import { renderHook } from '@testing-library/react-hooks';
import { useKibana } from './lib/kibana';
import { useIsDarkTheme } from './use_is_dark_theme';
import type { CoreTheme } from '@kbn/core-theme-browser';
import type { AppMockRenderer } from './mock';
import { createAppMockRenderer } from './mock';
jest.mock('./lib/kibana');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
describe('useIsDarkTheme', () => {
let appMockRender: AppMockRenderer;
beforeEach(() => {
appMockRender = createAppMockRenderer();
useKibanaMock().services.theme.theme$ = new BehaviorSubject<CoreTheme>({ darkMode: true });
});
it('returns true if the theme is in dark mode', async () => {
const { result } = renderHook(() => useIsDarkTheme(), {
wrapper: appMockRender.AppWrapper,
});
expect(result.current).toBe(true);
});
it('returns the default theme if the theme service is undefined', async () => {
// @ts-expect-error: service maybe undefined
useKibanaMock().services.theme = undefined;
const { result } = renderHook(() => useIsDarkTheme(), {
wrapper: appMockRender.AppWrapper,
});
expect(result.current).toBe(false);
});
});

View file

@ -1,31 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMemo } from 'react';
import { of } from 'rxjs';
import useObservable from 'react-use/lib/useObservable';
import { useKibana } from './lib/kibana';
const themeDefault = { darkMode: false };
/**
* Indicates if the currently applied theme is either dark or light.
* @return {boolean} - Returns true if the currently applied theme is dark.
*/
export function useIsDarkTheme(): boolean {
const {
services: { theme },
} = useKibana();
const themeObservable$ = useMemo(() => {
return theme?.theme$ ?? of(themeDefault);
}, [theme]);
const { darkMode } = useObservable(themeObservable$, themeDefault);
return darkMode;
}

View file

@ -8,9 +8,8 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { Router } from '@kbn/shared-ux-router';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { useIsDarkTheme } from '../../../common/use_is_dark_theme';
import { SECURITY_SOLUTION_OWNER } from '../../../../common';
import type { CasesUIActionProps } from './types';
import { KibanaContextProvider, useKibana } from '../../../common/lib/kibana';
@ -30,8 +29,7 @@ const ActionWrapperWithContext: React.FC<PropsWithChildren<Props>> = ({
caseContextProps,
currentAppId,
}) => {
const { application } = useKibana().services;
const isDarkTheme = useIsDarkTheme();
const { application, i18n, theme } = useKibana().services;
const owner = getCaseOwnerByAppId(currentAppId);
const casePermissions = canUseCases(application.capabilities)(owner ? [owner] : undefined);
@ -39,7 +37,7 @@ const ActionWrapperWithContext: React.FC<PropsWithChildren<Props>> = ({
const syncAlerts = owner === SECURITY_SOLUTION_OWNER;
return (
<EuiThemeProvider darkMode={isDarkTheme}>
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
<CasesProvider
value={{
...caseContextProps,
@ -50,7 +48,7 @@ const ActionWrapperWithContext: React.FC<PropsWithChildren<Props>> = ({
>
{children}
</CasesProvider>
</EuiThemeProvider>
</KibanaRenderContextProvider>
);
};

View file

@ -14,7 +14,7 @@ import { createAddToExistingCaseLensAction } from './add_to_existing_case';
import type { ActionContext } from './types';
import { useCasesAddToExistingCaseModal } from '../../all_cases/selector_modal/use_cases_add_to_existing_case_modal';
import React from 'react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import {
getMockApplications$,
getMockCaseUiActionProps,
@ -44,10 +44,13 @@ jest.mock('../../../client/helpers/can_use_cases', () => {
});
jest.mock('@kbn/kibana-react-plugin/public', () => ({
toMountPoint: jest.fn(),
KibanaThemeProvider: jest.fn().mockImplementation(({ children }) => <>{children}</>),
}));
jest.mock('@kbn/react-kibana-mount', () => ({
toMountPoint: jest.fn(),
}));
jest.mock('../../../common/lib/kibana', () => {
return {
useKibana: jest.fn(),

View file

@ -10,7 +10,7 @@ import type { Embeddable as LensEmbeddable } from '@kbn/lens-plugin/public';
import { createAction } from '@kbn/ui-actions-plugin/public';
import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { CaseUI } from '../../../../common';
import { isLensEmbeddable, hasInput, getLensCaseAttachment } from './utils';
@ -59,7 +59,7 @@ export const createAddToExistingCaseLensAction = ({
history,
caseContextProps,
}: CasesUIActionProps) => {
const { application: applicationService, theme } = core;
const { application: applicationService, i18n, theme } = core;
let currentAppId: string | undefined;
@ -124,7 +124,7 @@ export const createAddToExistingCaseLensAction = ({
onSuccess={onSuccess}
/>
</ActionWrapper>,
{ theme$: theme.theme$ }
{ i18n, theme }
);
mount(targetDomElement);

View file

@ -4,23 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreTheme, PublicAppInfo } from '@kbn/core/public';
import { BehaviorSubject, of } from 'rxjs';
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import { createBrowserHistory } from 'history';
import { BehaviorSubject } from 'rxjs';
import type { PublicAppInfo } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { CasesUIActionProps } from './types';
const mockTheme: CoreTheme = {
darkMode: false,
};
const createThemeMock = (): CoreTheme => {
return { ...mockTheme };
};
export const createTheme$Mock = () => {
return of(createThemeMock());
};
const coreStart = coreMock.createStart();
export class MockEmbeddable {
public type;
@ -76,8 +69,8 @@ export const getMockApplications$ = () =>
export const getMockCaseUiActionProps = () => {
const core = {
...coreStart,
application: { currentAppId$: getMockCurrentAppId$(), capabilities: {} },
theme: { theme$: createTheme$Mock() },
uiSettings: {
get: jest.fn().mockReturnValue(true),
},

View file

@ -64,7 +64,6 @@
"@kbn/ui-actions-plugin",
"@kbn/core-lifecycle-browser",
"@kbn/core-saved-objects-api-server-mocks",
"@kbn/core-theme-browser",
"@kbn/serverless",
"@kbn/core-http-server",
"@kbn/alerting-plugin",
@ -73,6 +72,7 @@
"@kbn/rison",
"@kbn/core-application-browser",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-mount",
],
"exclude": [
"target/**/*",

View file

@ -56,7 +56,8 @@ describe('apiKeysManagementApp', () => {
element,
setBreadcrumbs,
history,
theme$: themeServiceMock.createTheme$(),
theme: coreStartMock.theme,
theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams
});
});

View file

@ -40,7 +40,7 @@ async function mountApp(
const setBreadcrumbs = jest.fn();
const startServices = await coreMock.createSetup().getStartServices();
const [{ application }] = startServices;
const [{ application, theme }] = startServices;
application.capabilities = {
...application.capabilities,
role_mappings: {
@ -57,7 +57,8 @@ async function mountApp(
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create({ pathname }),
theme$: themeServiceMock.createTheme$(),
theme,
theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams
});
});

View file

@ -55,7 +55,8 @@ async function mountApp(basePath: string, pathname: string) {
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create({ pathname }),
theme$: themeServiceMock.createTheme$(),
theme: coreStart.theme,
theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams
});
});

View file

@ -38,7 +38,8 @@ describe('usersManagementApp', () => {
element,
setBreadcrumbs,
history,
theme$: themeServiceMock.createTheme$(),
theme: coreStartMock.theme,
theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams
});
});

View file

@ -58,7 +58,8 @@ async function mountApp(basePath: string, pathname: string, spaceId?: string) {
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create({ pathname }),
theme$: themeServiceMock.createTheme$(),
theme: coreStart.theme,
theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams
});
return { unmount, container, setBreadcrumbs, docTitle: coreStart.chrome.docTitle };

View file

@ -6,14 +6,12 @@
*/
import React, { useCallback } from 'react';
import { of } from 'rxjs';
import { I18nProvider } from '@kbn/i18n-react';
import { EuiButton } from '@elastic/eui';
import { Form, useForm, FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import { act } from 'react-dom/test-utils';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ConnectorServices } from '@kbn/triggers-actions-ui-plugin/public/types';
import { TriggersAndActionsUiServices } from '@kbn/triggers-actions-ui-plugin/public';
@ -93,13 +91,10 @@ export interface AppMockRenderer {
export const createAppMockRenderer = (): AppMockRenderer => {
const services = createStartServicesMock();
const theme$ = of({ darkMode: false });
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
<I18nProvider>
<KibanaContextProvider services={services}>
<KibanaThemeProvider theme$={theme$}>{children}</KibanaThemeProvider>
</KibanaContextProvider>
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
</I18nProvider>
);
AppWrapper.displayName = 'AppWrapper';

View file

@ -8,10 +8,8 @@
import React, { lazy } from 'react';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
import { setDataViewsService } from '../common/lib/data_apis';
@ -29,23 +27,18 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => {
};
export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
const { dataViews, theme, theme$ } = deps;
const isDarkMode = theme.getTheme().darkMode;
const { dataViews, i18n, theme } = deps;
setDataViewsService(dataViews);
return (
<I18nProvider>
<EuiThemeProvider darkMode={isDarkMode}>
<KibanaThemeProvider theme$={theme$}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<Routes>
<Route path={`/`} component={suspendedComponentWithProps(GlobalAlertsPage, 'xl')} />
</Routes>
</Router>
</KibanaContextProvider>
</KibanaThemeProvider>
</EuiThemeProvider>
</I18nProvider>
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<Routes>
<Route path={`/`} component={suspendedComponentWithProps(GlobalAlertsPage, 'xl')} />
</Routes>
</Router>
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
};

View file

@ -8,12 +8,16 @@
import React, { lazy } from 'react';
import { Redirect } from 'react-router-dom';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public';
import {
ChromeBreadcrumb,
CoreStart,
I18nStart,
ScopedHistory,
ThemeServiceStart,
} from '@kbn/core/public';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { Observable } from 'rxjs';
import { KibanaFeature } from '@kbn/features-plugin/common';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
@ -26,7 +30,6 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public';
import { ruleDetailsRoute } from '@kbn/rule-data-utils';
import { QueryClientProvider } from '@tanstack/react-query';
@ -74,7 +77,8 @@ export interface TriggersAndActionsUiServices extends CoreStart {
history: ScopedHistory;
kibanaFeatures: KibanaFeature[];
element: HTMLElement;
theme$: Observable<CoreTheme>;
i18n: I18nStart;
theme: ThemeServiceStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
licensing: LicensingPluginStart;
expressions: ExpressionsStart;
@ -92,26 +96,21 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => {
};
export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
const { dataViews, theme } = deps;
const { dataViews, i18n, theme } = deps;
const sections: Section[] = ['rules', 'logs'];
const isDarkMode = theme.getTheme().darkMode;
const sectionsRegex = sections.join('|');
setDataViewsService(dataViews);
return (
<I18nProvider>
<EuiThemeProvider darkMode={isDarkMode}>
<KibanaThemeProvider theme$={theme.theme$}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<QueryClientProvider client={queryClient}>
<AppWithoutRouter sectionsRegex={sectionsRegex} />
</QueryClientProvider>
</Router>
</KibanaContextProvider>
</KibanaThemeProvider>
</EuiThemeProvider>
</I18nProvider>
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<QueryClientProvider client={queryClient}>
<AppWithoutRouter sectionsRegex={sectionsRegex} />
</QueryClientProvider>
</Router>
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
};

View file

@ -10,10 +10,9 @@ import { Redirect } from 'react-router-dom';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { Observable } from 'rxjs';
import { KibanaFeature } from '@kbn/features-plugin/common';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
@ -24,7 +23,6 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { QueryClientProvider } from '@tanstack/react-query';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public';
import { DashboardStart } from '@kbn/dashboard-plugin/public';
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
@ -75,26 +73,21 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => {
};
export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
const { dataViews, theme } = deps;
const isDarkMode = theme.getTheme().darkMode;
const { dataViews, i18n, theme } = deps;
const sections: Section[] = ['connectors', 'logs'];
const sectionsRegex = sections.join('|');
setDataViewsService(dataViews);
return (
<I18nProvider>
<EuiThemeProvider darkMode={isDarkMode}>
<KibanaThemeProvider theme$={theme.theme$}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<QueryClientProvider client={queryClient}>
<AppWithoutRouter sectionsRegex={sectionsRegex} />
</QueryClientProvider>
</Router>
</KibanaContextProvider>
</KibanaThemeProvider>
</EuiThemeProvider>
</I18nProvider>
<KibanaRenderContextProvider i18n={i18n} theme={theme}>
<KibanaContextProvider services={{ ...deps }}>
<Router history={deps.history}>
<QueryClientProvider client={queryClient}>
<AppWithoutRouter sectionsRegex={sectionsRegex} />
</QueryClientProvider>
</Router>
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
};

View file

@ -8,7 +8,7 @@ import React, { useCallback, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { useKibana } from '../../common/lib/kibana';
import { BulkEditResponse } from '../../types';
@ -63,6 +63,8 @@ export interface UseBulkEditResponseProps {
export function useBulkEditResponse(props: UseBulkEditResponseProps) {
const { onSearchPopulate } = props;
const {
i18n: i18nStart,
theme,
notifications: { toasts },
} = useKibana().services;
@ -122,7 +124,7 @@ export function useBulkEditResponse(props: UseBulkEditResponseProps) {
if (numberOfErrors === total) {
toasts.addDanger({
title: failureMessage(numberOfErrors, translationMap[property]),
text: toMountPoint(renderToastErrorBody(response)),
text: toMountPoint(renderToastErrorBody(response), { i18n: i18nStart, theme }),
});
return;
}
@ -130,10 +132,10 @@ export function useBulkEditResponse(props: UseBulkEditResponseProps) {
// Some failure
toasts.addWarning({
title: someSuccessMessage(numberOfSuccess, numberOfErrors, translationMap[property]),
text: toMountPoint(renderToastErrorBody(response)),
text: toMountPoint(renderToastErrorBody(response), { i18n: i18nStart, theme }),
});
},
[toasts, renderToastErrorBody]
[i18nStart, theme, toasts, renderToastErrorBody]
);
return useMemo(() => {

View file

@ -7,7 +7,7 @@
import React, { useCallback, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { BulkOperationError } from '@kbn/alerting-plugin/server';
import { useKibana } from '../../common/lib/kibana';
import {
@ -48,6 +48,8 @@ export const useBulkOperationToast = ({
onSearchPopulate?: (filter: string) => void;
}) => {
const {
i18n,
theme,
notifications: { toasts },
} = useKibana().services;
@ -120,7 +122,7 @@ export const useBulkOperationToast = ({
SINGLE_RULE_TITLE,
MULTIPLE_RULE_TITLE
),
text: toMountPoint(renderToastErrorBody(errors, 'danger')),
text: toMountPoint(renderToastErrorBody(errors, 'danger'), { i18n, theme }),
});
return;
}
@ -133,10 +135,10 @@ export const useBulkOperationToast = ({
SINGLE_RULE_TITLE,
MULTIPLE_RULE_TITLE
),
text: toMountPoint(renderToastErrorBody(errors, 'warning')),
text: toMountPoint(renderToastErrorBody(errors, 'warning'), { i18n, theme }),
});
},
[toasts, renderToastErrorBody]
[i18n, theme, toasts, renderToastErrorBody]
);
return useMemo(() => {

View file

@ -6,15 +6,17 @@
*/
import * as React from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { render, screen } from '@testing-library/react';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { ConnectorsSelection } from './connectors_selection';
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
import { ActionType, GenericValidationResult } from '../../../types';
import { EuiFieldText } from '@elastic/eui';
describe('connectors_selection', () => {
const core = coreMock.createStart();
const mockedActionParamsFields = React.lazy(async () => ({
default() {
return (
@ -92,7 +94,7 @@ describe('connectors_selection', () => {
it('renders a selector', () => {
const wrapper = mountWithIntl(
<EuiThemeProvider>
<KibanaThemeProvider theme={core.theme}>
<ConnectorsSelection
accordionIndex={0}
actionItem={actionItem}
@ -101,7 +103,7 @@ describe('connectors_selection', () => {
connectors={connectors}
onConnectorSelected={jest.fn()}
/>
</EuiThemeProvider>
</KibanaThemeProvider>
);
expect(
@ -111,7 +113,7 @@ describe('connectors_selection', () => {
it('renders the title of the connector', () => {
render(
<EuiThemeProvider>
<KibanaThemeProvider theme={core.theme}>
<ConnectorsSelection
accordionIndex={0}
actionItem={actionItem}
@ -120,7 +122,7 @@ describe('connectors_selection', () => {
connectors={connectors}
onConnectorSelected={jest.fn()}
/>
</EuiThemeProvider>
</KibanaThemeProvider>
);
expect(screen.getByRole('combobox')).toHaveValue('test pagerduty');

View file

@ -6,8 +6,9 @@
*/
import React from 'react';
import { of } from 'rxjs';
import { fireEvent, render, screen } from '@testing-library/react';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import type { ModalInspectProps } from './modal';
import { ModalInspectQuery } from './modal';
@ -42,8 +43,11 @@ describe('Modal Inspect', () => {
};
const renderModalInspectQuery = () => {
const theme = { theme$: of({ darkMode: false }) };
return render(<ModalInspectQuery {...defaultProps} />, {
wrapper: ({ children }) => <EuiThemeProvider>{children}</EuiThemeProvider>,
wrapper: ({ children }) => (
<KibanaThemeProvider theme={theme}>{children}</KibanaThemeProvider>
),
});
};

View file

@ -24,7 +24,7 @@ import {
EuiIconTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common';
import { getRuleDetailsRoute } from '@kbn/rule-data-utils';
import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation';
@ -90,12 +90,9 @@ const ruleDetailStyle = {
export const RuleDetails: React.FunctionComponent<RuleDetailsProps> = ({
rule,
ruleType,
actionTypes,
bulkDisableRules,
bulkEnableRules,
bulkDeleteRules,
snoozeRule,
unsnoozeRule,
requestRefresh,
refreshToken,
}) => {
@ -107,6 +104,8 @@ export const RuleDetails: React.FunctionComponent<RuleDetailsProps> = ({
setBreadcrumbs,
chrome,
http,
i18n: i18nStart,
theme,
notifications: { toasts },
} = useKibana().services;
const ruleReducer = useMemo(() => getRuleReducer(actionTypeRegistry), [actionTypeRegistry]);
@ -218,12 +217,20 @@ export const RuleDetails: React.FunctionComponent<RuleDetailsProps> = ({
</EuiFlexItem>
</EuiFlexGroup>
)}
</>
</>,
{ i18n: i18nStart, theme }
),
});
}
}
}, [rule.schedule.interval, config.minimumScheduleInterval, toasts, hasEditButton]);
}, [
i18nStart,
theme,
rule.schedule.interval,
config.minimumScheduleInterval,
toasts,
hasEditButton,
]);
const setRule = async () => {
history.push(getRuleDetailsRoute(rule.id));

View file

@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiTitle, EuiFlyoutHeader, EuiFlyout, EuiFlyoutBody, EuiPortal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
import {
Rule,
@ -125,6 +125,8 @@ const RuleAdd = <
http,
notifications: { toasts },
application: { capabilities },
i18n: i18nStart,
theme,
} = useKibana().services;
const canShowActions = hasShowActionsCapability(capabilities);
@ -270,7 +272,8 @@ const RuleAdd = <
title: message.summary,
...(message.details && {
text: toMountPoint(
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>,
{ i18n: i18nStart, theme }
),
}),
});

View file

@ -26,7 +26,7 @@ import {
} from '@elastic/eui';
import { cloneDeep, omit } from 'lodash';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
import {
Rule,
@ -138,6 +138,8 @@ export const RuleEdit = <
const {
http,
notifications: { toasts },
i18n: i18nStart,
theme,
} = useKibana().services;
const setRule = (value: Rule) => {
@ -223,7 +225,8 @@ export const RuleEdit = <
title: message.summary,
...(message.details && {
text: toMountPoint(
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>,
{ i18n: i18nStart, theme }
),
}),
});

View file

@ -9,7 +9,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import type { RuleSnooze } from '@kbn/alerting-plugin/common';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
import {
EuiLoadingSpinner,
@ -65,6 +65,8 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
const {
notifications: { toasts },
i18n: i18nStart,
theme,
} = useKibana().services;
useEffect(() => {
@ -92,12 +94,13 @@ export const RuleStatusDropdown: React.FunctionComponent<ComponentOpts> = ({
title: message.summary,
...(message.details && {
text: toMountPoint(
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>
<ToastWithCircuitBreakerContent>{message.details}</ToastWithCircuitBreakerContent>,
{ i18n: i18nStart, theme }
),
}),
});
throw new Error();
}, [enableRule, toasts]);
}, [i18nStart, theme, enableRule, toasts]);
const onEnable = useCallback(async () => {
setIsUpdating(true);

View file

@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n';
import { capitalize, isEmpty, isEqual, sortBy } from 'lodash';
import { KueryNode } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common';
import { RuleTypeModal } from '@kbn/alerts-ui-shared';
import React, {
@ -190,6 +190,8 @@ export const RulesList = ({
kibanaFeatures,
notifications: { toasts },
ruleTypeRegistry,
i18n: i18nStart,
theme,
} = kibanaServices;
const canExecuteActions = hasExecuteActionsCapability(capabilities);
@ -692,7 +694,8 @@ export const RulesList = ({
toasts.addDanger({
title: parsedError.summary,
text: toMountPoint(
<ToastWithCircuitBreakerContent>{parsedError.details}</ToastWithCircuitBreakerContent>
<ToastWithCircuitBreakerContent>{parsedError.details}</ToastWithCircuitBreakerContent>,
{ theme, i18n: i18nStart }
),
});
} else {

View file

@ -7,11 +7,11 @@
import React from 'react';
import { QueryClient, QueryClientProvider, QueryClientProviderProps } from '@tanstack/react-query';
import { of } from 'rxjs';
import { coreMock } from '@kbn/core/public/mocks';
import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { TriggersAndActionsUiServices } from '../..';
import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock';
@ -31,7 +31,7 @@ export const createAppMockRenderer = (
queryClientContext?: QueryClientProviderProps['context']
): AppMockRenderer => {
const services = createStartServicesMock();
const theme$ = of({ darkMode: false });
const core = coreMock.createStart();
const queryClient = new QueryClient({
defaultOptions: {
@ -53,7 +53,7 @@ export const createAppMockRenderer = (
const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => (
<I18nProvider>
<KibanaContextProvider services={services}>
<KibanaThemeProvider theme$={theme$}>
<KibanaThemeProvider theme={core.theme}>
<QueryClientProvider client={queryClient} context={queryClientContext}>
{children}
</QueryClientProvider>

View file

@ -313,7 +313,7 @@ export class Plugin
unifiedSearch: pluginsStart.unifiedSearch,
isCloud: Boolean(plugins.cloud?.isCloudEnabled),
element: params.element,
theme$: params.theme$,
theme: params.theme,
storage: new Storage(window.localStorage),
setBreadcrumbs: params.setBreadcrumbs,
history: params.history,
@ -411,7 +411,7 @@ export class Plugin
unifiedSearch: pluginsStart.unifiedSearch,
isCloud: Boolean(plugins.cloud?.isCloudEnabled),
element: params.element,
theme$: params.theme$,
theme: params.theme,
storage: new Storage(window.localStorage),
setBreadcrumbs: params.setBreadcrumbs,
history: params.history,

View file

@ -61,7 +61,10 @@
"@kbn/code-editor",
"@kbn/code-editor-mock",
"@kbn/io-ts-utils",
"@kbn/lens-plugin"
"@kbn/lens-plugin",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-theme"
],
"exclude": ["target/**/*"]
}