[Core] Remove usage of deprecated React rendering utilities (#180973)

## Summary

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

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

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.**

This focuses on code within Kibana Core.

<img width="1196" alt="image"
src="7f8d3707-94f0-4746-8dd5-dd858ce027f9">

Note: this also makes inclusion of `i18n` and `analytics` dependencies
consistent. Analytics is an optional dependency for the SharedUX
modules, which wrap `KibanaErrorBoundaryProvider` and is designed to
capture telemetry about errors that are caught in the error boundary.
### 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-18 16:36:08 -07:00 committed by GitHub
parent 2d2649f13c
commit e951c37322
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 279 additions and 182 deletions

View file

@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { EuiPageTemplate } from '@elastic/eui';
import { CoreThemeProvider } from '@kbn/core-theme-browser-internal';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import type { IBasePath } from '@kbn/core-http-browser';
import type { AppMountParameters } from '@kbn/core-application-browser';
import { UrlOverflowUi } from './url_overflow_ui';
@ -77,9 +77,9 @@ interface Deps {
export const renderApp = ({ element, history, theme$ }: AppMountParameters, { basePath }: Deps) => {
ReactDOM.render(
<I18nProvider>
<CoreThemeProvider theme$={theme$}>
<KibanaThemeProvider theme={{ theme$ }}>
<ErrorApp history={history} basePath={basePath} />
</CoreThemeProvider>
</KibanaThemeProvider>
</I18nProvider>,
element
);

View file

@ -9,7 +9,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { CoreThemeProvider } from '@kbn/core-theme-browser-internal';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import type { InternalHttpSetup } from '@kbn/core-http-browser-internal';
import type { NotificationsSetup } from '@kbn/core-notifications-browser';
import type { AppMountParameters } from '@kbn/core-application-browser';
@ -26,9 +26,9 @@ export const renderApp = (
) => {
ReactDOM.render(
<I18nProvider>
<CoreThemeProvider theme$={theme$}>
<KibanaThemeProvider theme={{ theme$ }}>
<StatusApp http={http} notifications={notifications} />
</CoreThemeProvider>
</KibanaThemeProvider>
</I18nProvider>,
element
);

View file

@ -23,7 +23,6 @@
"@kbn/core-notifications-browser",
"@kbn/core-application-browser",
"@kbn/core-application-browser-internal",
"@kbn/core-theme-browser-internal",
"@kbn/core-mount-utils-browser-internal",
"@kbn/core-status-common-internal",
"@kbn/core-http-browser-internal",
@ -35,6 +34,7 @@
"@kbn/core-status-common",
"@kbn/core-doc-links-browser-mocks",
"@kbn/test-jest-helpers",
"@kbn/react-kibana-context-theme",
],
"exclude": [
"target/**/*",

View file

@ -17,8 +17,8 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiSpacer,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
@ -26,14 +26,17 @@ import type { OverlayStart } from '@kbn/core-overlays-browser';
import { ThemeServiceStart } from '@kbn/core-theme-browser';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
interface ErrorToastProps {
interface StartServices {
analytics: AnalyticsServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
}
interface ErrorToastProps extends StartServices {
title: string;
error: Error;
toastMessage: string;
openModal: OverlayStart['openModal'];
analytics: AnalyticsServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
}
interface RequestError extends Error {
@ -57,9 +60,7 @@ export function showErrorDialog({
title,
error,
openModal,
analytics,
i18n,
theme,
...startServices
}: Pick<ErrorToastProps, 'error' | 'title' | 'openModal' | 'analytics' | 'i18n' | 'theme'>) {
let text = '';
@ -74,7 +75,7 @@ export function showErrorDialog({
const modal = openModal(
mount(
<KibanaRenderContextProvider analytics={analytics} i18n={i18n} theme={theme}>
<KibanaRenderContextProvider {...startServices}>
<EuiModalHeader>
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
</EuiModalHeader>
@ -107,9 +108,7 @@ export function ErrorToast({
error,
toastMessage,
openModal,
analytics,
i18n,
theme,
...startServices
}: ErrorToastProps) {
return (
<React.Fragment>
@ -119,7 +118,7 @@ export function ErrorToast({
size="s"
color="danger"
data-test-subj="errorToastBtn"
onClick={() => showErrorDialog({ title, error, openModal, analytics, i18n, theme })}
onClick={() => showErrorDialog({ title, error, openModal, ...startServices })}
>
<FormattedMessage
id="core.toasts.errorToast.seeFullError"

View file

@ -8,14 +8,18 @@
import { InternalOverlayBannersStart, OverlayBannersService } from './banners_service';
import { take } from 'rxjs';
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 { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
describe('OverlayBannersService', () => {
let service: InternalOverlayBannersStart;
beforeEach(() => {
service = new OverlayBannersService().start({
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
});
});

View file

@ -10,7 +10,9 @@ import React from 'react';
import { BehaviorSubject, type Observable } from 'rxjs';
import { map } from 'rxjs';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
import type { MountPoint } from '@kbn/core-mount-utils-browser';
import type { OverlayBannersStart } from '@kbn/core-overlays-browser';
@ -18,8 +20,13 @@ import { PriorityMap } from './priority_map';
import { BannersList } from './banners_list';
import { UserBannerService } from './user_banner_service';
interface StartDeps {
interface StartServices {
analytics: AnalyticsServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
}
interface StartDeps extends StartServices {
uiSettings: IUiSettingsClient;
}
@ -39,7 +46,7 @@ export interface OverlayBanner {
export class OverlayBannersService {
private readonly userBanner = new UserBannerService();
public start({ i18n, uiSettings }: StartDeps): InternalOverlayBannersStart {
public start({ uiSettings, ...startServices }: StartDeps): InternalOverlayBannersStart {
let uniqueId = 0;
const genId = () => `${uniqueId++}`;
const banners$ = new BehaviorSubject(new PriorityMap<string, OverlayBanner>());
@ -79,7 +86,7 @@ export class OverlayBannersService {
},
};
this.userBanner.start({ banners: service, i18n, uiSettings });
this.userBanner.start({ banners: service, uiSettings, ...startServices });
return service;
}

View file

@ -9,7 +9,9 @@
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { UserBannerService } from './user_banner_service';
import { overlayBannersServiceMock } from './banners_service.test.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 { Subject } from 'rxjs';
describe('OverlayBannersService', () => {
@ -33,7 +35,9 @@ describe('OverlayBannersService', () => {
service = new UserBannerService();
service.start({
banners,
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
uiSettings,
});
};

View file

@ -14,13 +14,21 @@ import { Subscription } from 'rxjs';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiButton, EuiLoadingSpinner } from '@elastic/eui';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
import type { OverlayBannersStart } from '@kbn/core-overlays-browser';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
interface StartDeps {
banners: OverlayBannersStart;
interface StartServices {
analytics: AnalyticsServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
}
interface StartDeps extends StartServices {
banners: OverlayBannersStart;
uiSettings: IUiSettingsClient;
}
@ -33,7 +41,7 @@ const ReactMarkdownLazy = React.lazy(() => import('react-markdown'));
export class UserBannerService {
private settingsSubscription?: Subscription;
public start({ banners, i18n, uiSettings }: StartDeps) {
public start({ banners, uiSettings, ...startServices }: StartDeps) {
let id: string | undefined;
let timeout: any;
@ -55,7 +63,7 @@ export class UserBannerService {
id,
(el) => {
ReactDOM.render(
<i18n.Context>
<KibanaRenderContextProvider {...startServices}>
<EuiCallOut
title={
<FormattedMessage
@ -82,7 +90,7 @@ export class UserBannerService {
/>
</EuiButton>
</EuiCallOut>
</i18n.Context>,
</KibanaRenderContextProvider>,
el
);

View file

@ -39,7 +39,7 @@ export class OverlayService {
targetDomElement: flyoutElement,
});
const banners = this.bannersService.start({ i18n, uiSettings });
const banners = this.bannersService.start({ uiSettings, analytics, i18n, theme });
const modalElement = document.createElement('div');
targetDomElement.appendChild(modalElement);

View file

@ -19,14 +19,17 @@ import type { ThemeServiceStart } from '@kbn/core-theme-browser';
import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root';
import { AppWrapper } from './app_containers';
export interface StartDeps {
interface StartServices {
analytics: AnalyticsServiceStart;
i18n: I18nStart;
theme: ThemeServiceStart;
}
export interface StartDeps extends StartServices {
application: InternalApplicationStart;
chrome: InternalChromeStart;
overlays: OverlayStart;
targetDomElement: HTMLDivElement;
theme: ThemeServiceStart;
i18n: I18nStart;
}
/**
@ -38,7 +41,7 @@ export interface StartDeps {
* @internal
*/
export class RenderingService {
start({ analytics, application, chrome, overlays, theme, i18n, targetDomElement }: StartDeps) {
start({ application, chrome, overlays, targetDomElement, ...startServices }: StartDeps) {
const chromeHeader = chrome.getHeaderComponent();
const appComponent = application.getComponent();
const bannerComponent = overlays.banners.getComponent();
@ -53,12 +56,7 @@ export class RenderingService {
});
ReactDOM.render(
<KibanaRootContextProvider
analytics={analytics}
i18n={i18n}
theme={theme}
globalStyles={true}
>
<KibanaRootContextProvider {...startServices} globalStyles={true}>
<>
{/* Fixed headers */}
{chromeHeader}

View file

@ -15,8 +15,6 @@
"kbn_references": [
"@kbn/core-application-common",
"@kbn/core-application-browser-internal",
"@kbn/core-theme-browser",
"@kbn/core-i18n-browser",
"@kbn/core-overlays-browser",
"@kbn/core-chrome-browser-internal",
"@kbn/core-application-browser-mocks",
@ -25,8 +23,10 @@
"@kbn/core-theme-browser-mocks",
"@kbn/core-i18n-browser-mocks",
"@kbn/react-kibana-context-root",
"@kbn/core-analytics-browser",
"@kbn/core-analytics-browser-mocks",
"@kbn/core-analytics-browser",
"@kbn/core-i18n-browser",
"@kbn/core-theme-browser"
],
"exclude": [
"target/**/*",

View file

@ -9,8 +9,6 @@
"requiredPlugins": [
"screenshotMode"
],
"requiredBundles": [
"kibanaReact"
]
"requiredBundles": []
}
}

View file

@ -11,15 +11,8 @@ import { catchError, takeUntil } from 'rxjs';
import ReactDOM from 'react-dom';
import React from 'react';
import moment from 'moment';
import { I18nProvider } from '@kbn/i18n-react';
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
CoreTheme,
Plugin,
} from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { NewsfeedPluginBrowserConfig, NewsfeedPluginStartDependencies } from './types';
import { NewsfeedNavButton } from './components/newsfeed_header_nav_button';
import { getApi, NewsfeedApi, NewsfeedApiEndpoint } from './lib/api';
@ -45,7 +38,7 @@ export class NewsfeedPublicPlugin
});
}
public setup(core: CoreSetup) {
public setup(_core: CoreSetup) {
return {};
}
@ -55,8 +48,7 @@ export class NewsfeedPublicPlugin
const api = this.createNewsfeedApi(this.config, NewsfeedApiEndpoint.KIBANA, isScreenshotMode);
core.chrome.navControls.registerRight({
order: 1000,
mount: (target) =>
this.mount(api, target, core.theme.theme$, core.customBranding.hasCustomBranding$),
mount: (target) => this.mount(api, target, core),
});
return {
@ -92,18 +84,12 @@ export class NewsfeedPublicPlugin
};
}
private mount(
api: NewsfeedApi,
targetDomElement: HTMLElement,
theme$: Rx.Observable<CoreTheme>,
hasCustomBranding$: Rx.Observable<boolean>
) {
private mount(api: NewsfeedApi, targetDomElement: HTMLElement, core: CoreStart) {
const hasCustomBranding$ = core.customBranding.hasCustomBranding$;
ReactDOM.render(
<KibanaThemeProvider theme$={theme$}>
<I18nProvider>
<NewsfeedNavButton newsfeedApi={api} hasCustomBranding$={hasCustomBranding$} />
</I18nProvider>
</KibanaThemeProvider>,
<KibanaRenderContextProvider {...core}>
<NewsfeedNavButton newsfeedApi={api} hasCustomBranding$={hasCustomBranding$} />
</KibanaRenderContextProvider>,
targetDomElement
);
return () => ReactDOM.unmountComponentAtNode(targetDomElement);

View file

@ -6,12 +6,12 @@
"include": ["public/**/*", "server/**/*", "common/*", "../../../typings/**/*"],
"kbn_references": [
"@kbn/core",
"@kbn/kibana-react-plugin",
"@kbn/screenshot-mode-plugin",
"@kbn/i18n-react",
"@kbn/i18n",
"@kbn/utility-types",
"@kbn/config-schema",
"@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",

View file

@ -11,8 +11,7 @@
"dataViews"
],
"requiredBundles": [
"kibanaUtils",
"kibanaReact"
"kibanaUtils"
]
}
}

View file

@ -13,5 +13,6 @@ export function setStartServices(core: CoreStart) {
coreStart = core;
}
export const getAnalytics = () => coreStart.analytics;
export const getI18n = () => coreStart.i18n;
export const getTheme = () => coreStart.theme;

View file

@ -57,6 +57,7 @@ export class SavedObjectsPublicPlugin
chrome: core.chrome,
overlays: core.overlays,
},
core,
this.decoratorRegistry
),
};

View file

@ -9,8 +9,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { getI18n, getTheme } from '../kibana_services';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { getAnalytics, getI18n, getTheme } from '../kibana_services';
/**
* Represents the result of trying to persist the saved object.
@ -59,9 +59,9 @@ export function showSaveModal(
const I18nContext = getI18n().Context;
ReactDOM.render(
<KibanaThemeProvider theme$={getTheme().theme$}>
<KibanaRenderContextProvider analytics={getAnalytics()} i18n={getI18n()} theme={getTheme()}>
<I18nContext>{Wrapper ? <Wrapper>{element}</Wrapper> : element}</I18nContext>
</KibanaThemeProvider>,
</KibanaRenderContextProvider>,
container
);
}

View file

@ -17,6 +17,7 @@ import {
SavedObjectConfig,
SavedObjectKibanaServices,
SavedObjectSaveOpts,
StartServices,
} from '../../types';
import { applyESResp } from './apply_es_resp';
import { saveSavedObject } from './save_saved_object';
@ -37,6 +38,7 @@ export function buildSavedObject(
savedObject: SavedObject,
config: SavedObjectConfig,
services: SavedObjectKibanaServices,
startServices: StartServices,
decorators: SavedObjectDecorator[] = []
) {
applyDecorators(savedObject, config, decorators);
@ -110,7 +112,7 @@ export function buildSavedObject(
savedObject.save = async (opts: SavedObjectSaveOpts) => {
try {
const result = await saveSavedObject(savedObject, config, opts, services);
const result = await saveSavedObject(savedObject, config, opts, services, startServices);
return Promise.resolve(result);
} catch (e) {
return Promise.reject(e);

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { SavedObject, SavedObjectKibanaServices } from '../../types';
import { SavedObject, SavedObjectKibanaServices, StartServices } from '../../types';
import { findObjectByTitle } from './find_object_by_title';
import { SAVE_DUPLICATE_REJECTED } from '../../constants';
import { displayDuplicateTitleConfirmModal } from './display_duplicate_title_confirm_modal';
@ -19,6 +19,7 @@ import { displayDuplicateTitleConfirmModal } from './display_duplicate_title_con
* @param isTitleDuplicateConfirmed
* @param onTitleDuplicate
* @param services
* @param startServices
*/
export async function checkForDuplicateTitle(
savedObject: Pick<
@ -27,7 +28,8 @@ export async function checkForDuplicateTitle(
>,
isTitleDuplicateConfirmed: boolean,
onTitleDuplicate: (() => void) | undefined,
services: Pick<SavedObjectKibanaServices, 'savedObjectsClient' | 'overlays'>
services: Pick<SavedObjectKibanaServices, 'savedObjectsClient' | 'overlays'>,
startServices: StartServices
): Promise<true> {
const { savedObjectsClient, overlays } = services;
// Don't check for duplicates if user has already confirmed save with duplicate title
@ -58,5 +60,5 @@ export async function checkForDuplicateTitle(
// TODO: make onTitleDuplicate a required prop and remove UI components from this class
// Need to leave here until all users pass onTitleDuplicate.
return displayDuplicateTitleConfirmModal(savedObject, overlays);
return displayDuplicateTitleConfirmModal(savedObject, overlays, startServices);
}

View file

@ -7,16 +7,19 @@
*/
import React from 'react';
import { OverlayStart } from '@kbn/core/public';
import { CoreStart, OverlayStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { EuiConfirmModal } from '@elastic/eui';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
export function confirmModalPromise(
message = '',
title = '',
confirmBtnText = '',
overlays: OverlayStart
overlays: OverlayStart,
startServices: StartServices
): Promise<true> {
return new Promise((resolve, reject) => {
const cancelButtonText = i18n.translate('savedObjects.confirmModal.cancelButtonLabel', {
@ -39,7 +42,8 @@ export function confirmModalPromise(
title={title}
>
{message}
</EuiConfirmModal>
</EuiConfirmModal>,
startServices
)
);
});

View file

@ -9,7 +9,7 @@
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { SavedObjectAttributes } from '@kbn/core/public';
import { SavedObject, SavedObjectKibanaServices } from '../../types';
import { SavedObject, SavedObjectKibanaServices, StartServices } from '../../types';
import { OVERWRITE_REJECTED } from '../../constants';
import { confirmModalPromise } from './confirm_modal_promise';
@ -33,7 +33,8 @@ export async function createSource(
savedObject: SavedObject,
esType: string,
options = {},
services: SavedObjectKibanaServices
services: SavedObjectKibanaServices,
startServices: StartServices
) {
const { savedObjectsClient, overlays } = services;
try {
@ -57,7 +58,7 @@ export async function createSource(
defaultMessage: 'Overwrite',
});
return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays)
return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays, startServices)
.then(() =>
savedObjectsClient.create(
esType,

View file

@ -10,11 +10,12 @@ import { i18n } from '@kbn/i18n';
import { OverlayStart } from '@kbn/core/public';
import { SAVE_DUPLICATE_REJECTED } from '../../constants';
import { confirmModalPromise } from './confirm_modal_promise';
import { SavedObject } from '../../types';
import { SavedObject, StartServices } from '../../types';
export function displayDuplicateTitleConfirmModal(
savedObject: Pick<SavedObject, 'title' | 'getDisplayName'>,
overlays: OverlayStart
overlays: OverlayStart,
startServices: StartServices
): Promise<true> {
const confirmMessage = i18n.translate(
'savedObjects.confirmModal.saveDuplicateConfirmationMessage',
@ -29,7 +30,7 @@ export function displayDuplicateTitleConfirmModal(
values: { name: savedObject.getDisplayName() },
});
try {
return confirmModalPromise(confirmMessage, '', confirmButtonText, overlays);
return confirmModalPromise(confirmMessage, '', confirmButtonText, overlays, startServices);
} catch (_) {
return Promise.reject(new Error(SAVE_DUPLICATE_REJECTED));
}

View file

@ -11,6 +11,7 @@ import {
SavedObjectConfig,
SavedObjectKibanaServices,
SavedObjectSaveOpts,
StartServices,
} from '../../types';
import { OVERWRITE_REJECTED, SAVE_DUPLICATE_REJECTED } from '../../constants';
import { createSource } from './create_source';
@ -38,6 +39,7 @@ export function isErrorNonFatal(error: { message: string }) {
* @property {func} [options.onTitleDuplicate] - function called if duplicate title exists.
* When not provided, confirm modal will be displayed asking user to confirm or cancel save.
* @param {SavedObjectKibanaServices} [services]
* @param {StartServices} [startServices]
* @return {Promise}
* @resolved {String} - The id of the doc
*/
@ -49,7 +51,8 @@ export async function saveSavedObject(
isTitleDuplicateConfirmed = false,
onTitleDuplicate,
}: SavedObjectSaveOpts = {},
services: SavedObjectKibanaServices
services: SavedObjectKibanaServices,
startServices: StartServices
): Promise<string> {
const { savedObjectsClient, chrome } = services;
@ -79,7 +82,8 @@ export async function saveSavedObject(
savedObject,
isTitleDuplicateConfirmed,
onTitleDuplicate,
services
services,
startServices
);
savedObject.isSaving = true;
const resp = confirmOverwrite
@ -88,7 +92,8 @@ export async function saveSavedObject(
savedObject,
esType,
savedObject.creationOpts({ references }),
services
services,
startServices
)
: await savedObjectsClient.create(
esType,

View file

@ -8,6 +8,7 @@
import { SavedObjectAttributes, SavedObjectsCreateOptions, OverlayStart } from '@kbn/core/public';
import { SavedObjectsClientContract } from '@kbn/core/public';
import { analyticsServiceMock, i18nServiceMock, themeServiceMock } from '@kbn/core/public/mocks';
import { saveWithConfirmation } from './save_with_confirmation';
import * as deps from './confirm_modal_promise';
import { OVERWRITE_REJECTED } from '../../constants';
@ -22,6 +23,11 @@ describe('saveWithConfirmation', () => {
title: 'test title',
displayName: 'test display name',
};
const startServices = {
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
};
beforeEach(() => {
savedObjectsClient.create = jest.fn();
@ -29,7 +35,13 @@ describe('saveWithConfirmation', () => {
});
test('should call create of savedObjectsClient', async () => {
await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
await saveWithConfirmation(
source,
savedObject,
options,
{ savedObjectsClient, overlays },
startServices
);
expect(savedObjectsClient.create).toHaveBeenCalledWith(
savedObject.getEsType(),
source,
@ -44,12 +56,23 @@ describe('saveWithConfirmation', () => {
opt && opt.overwrite ? Promise.resolve({} as any) : Promise.reject({ res: { status: 409 } })
);
await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
await saveWithConfirmation(
source,
savedObject,
options,
{ savedObjectsClient, overlays },
startServices
);
expect(deps.confirmModalPromise).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
expect.any(String),
overlays
overlays,
expect.objectContaining({
analytics: expect.any(Object),
i18n: expect.any(Object),
theme: expect.any(Object),
})
);
});
@ -60,7 +83,13 @@ describe('saveWithConfirmation', () => {
opt && opt.overwrite ? Promise.resolve({} as any) : Promise.reject({ res: { status: 409 } })
);
await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
await saveWithConfirmation(
source,
savedObject,
options,
{ savedObjectsClient, overlays },
startServices
);
expect(savedObjectsClient.create).toHaveBeenLastCalledWith(savedObject.getEsType(), source, {
overwrite: true,
...options,
@ -73,10 +102,16 @@ describe('saveWithConfirmation', () => {
expect.assertions(1);
await expect(
saveWithConfirmation(source, savedObject, options, {
savedObjectsClient,
overlays,
})
saveWithConfirmation(
source,
savedObject,
options,
{
savedObjectsClient,
overlays,
},
startServices
)
).rejects.toThrow(OVERWRITE_REJECTED);
});
});

View file

@ -15,6 +15,7 @@ import {
SavedObjectsClientContract,
} from '@kbn/core/public';
import { OVERWRITE_REJECTED } from '../../constants';
import type { StartServices } from '../../types';
import { confirmModalPromise } from './confirm_modal_promise';
/**
@ -38,7 +39,8 @@ export async function saveWithConfirmation(
displayName: string;
},
options: SavedObjectsCreateOptions,
services: { savedObjectsClient: SavedObjectsClientContract; overlays: OverlayStart }
services: { savedObjectsClient: SavedObjectsClientContract; overlays: OverlayStart },
startServices: StartServices
) {
const { savedObjectsClient, overlays } = services;
try {
@ -62,7 +64,7 @@ export async function saveWithConfirmation(
defaultMessage: 'Overwrite',
});
return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays)
return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays, startServices)
.then(() =>
savedObjectsClient.create(savedObject.getEsType(), source, {
overwrite: true,

View file

@ -15,7 +15,12 @@ import {
} from '../types';
import { SavedObjectDecorator } from './decorators';
import { coreMock } from '@kbn/core/public/mocks';
import {
analyticsServiceMock,
coreMock,
i18nServiceMock,
themeServiceMock,
} from '@kbn/core/public/mocks';
import { dataPluginMock, createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
import { createStubIndexPattern } from '@kbn/data-plugin/common/stubs';
import { SavedObjectAttributes, SimpleSavedObject } from '@kbn/core/public';
@ -27,6 +32,11 @@ describe('Saved Object', () => {
const dataStartMock = dataPluginMock.createStartContract();
const saveOptionsMock = {} as SavedObjectSaveOpts;
const savedObjectsClientStub = startMock.savedObjects.client;
const startServices = {
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
};
let decoratorRegistry: ReturnType<typeof savedObjectsDecoratorRegistryMock.create>;
let SavedObjectClass: new (config: SavedObjectConfig) => SavedObject;
@ -104,6 +114,7 @@ describe('Saved Object', () => {
},
},
} as unknown as SavedObjectKibanaServices,
startServices,
decoratorRegistry
);
};
@ -660,6 +671,7 @@ describe('Saved Object', () => {
...dataStartMock.search,
},
} as unknown as SavedObjectKibanaServices,
startServices,
decoratorRegistry
);
const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true });

View file

@ -16,12 +16,13 @@
* This class seems to interface with ES primarily through the es Angular
* service and the saved object api.
*/
import { SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../types';
import { SavedObject, SavedObjectConfig, SavedObjectKibanaServices, StartServices } from '../types';
import { ISavedObjectDecoratorRegistry } from './decorators';
import { buildSavedObject } from './helpers/build_saved_object';
export function createSavedObjectClass(
services: SavedObjectKibanaServices,
startServices: StartServices,
decoratorRegistry: ISavedObjectDecoratorRegistry
) {
/**
@ -35,7 +36,13 @@ export function createSavedObjectClass(
constructor(config: SavedObjectConfig = {}) {
// @ts-ignore
const self: SavedObject = this;
buildSavedObject(self, config, services, decoratorRegistry.getOrderedDecorators(services));
buildSavedObject(
self,
config,
services,
startServices,
decoratorRegistry.getOrderedDecorators(services)
);
}
}

View file

@ -8,6 +8,7 @@
import {
ChromeStart,
CoreStart,
OverlayStart,
SavedObjectsClientContract,
SavedObjectAttributes,
@ -68,6 +69,8 @@ export interface SavedObjectKibanaServices {
overlays: OverlayStart;
}
export type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
export interface SavedObjectAttributesAndRefs {
attributes: SavedObjectAttributes;
references: SavedObjectReference[];

View file

@ -8,12 +8,13 @@
"@kbn/core",
"@kbn/data-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/kibana-react-plugin",
"@kbn/i18n",
"@kbn/data-views-plugin",
"@kbn/i18n-react",
"@kbn/utility-types",
"@kbn/ui-theme",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-mount",
],
"exclude": [
"target/**/*",

View file

@ -9,12 +9,11 @@
import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom';
import { Router, Routes, Route } from '@kbn/shared-ux-router';
import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiLoadingSpinner } from '@elastic/eui';
import { CoreSetup } from '@kbn/core/public';
import { wrapWithTheme } from '@kbn/kibana-react-plugin/public';
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { SavedObjectManagementTypeInfo } from '../../common/types';
import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin';
import { getAllowedTypes } from '../lib';
@ -37,7 +36,6 @@ export const mountManagementSection = async ({ core, mountParams }: MountParams)
await core.getStartServices();
const { capabilities } = coreStart.application;
const { element, history, setBreadcrumbs } = mountParams;
const { theme$ } = core.theme;
if (!allowedObjectTypes) {
allowedObjectTypes = await getAllowedTypes(coreStart.http);
@ -56,43 +54,40 @@ export const mountManagementSection = async ({ core, mountParams }: MountParams)
};
ReactDOM.render(
wrapWithTheme(
<I18nProvider>
<Router history={history}>
<Routes>
<Route path={'/:type/:id'} exact={true}>
<RedirectToHomeIfUnauthorized>
<Suspense fallback={<EuiLoadingSpinner />}>
<SavedObjectsEditionPage
coreStart={coreStart}
setBreadcrumbs={setBreadcrumbs}
history={history}
/>
</Suspense>
</RedirectToHomeIfUnauthorized>
</Route>
<Route path={'/'} exact={false}>
<RedirectToHomeIfUnauthorized>
<Suspense fallback={<EuiLoadingSpinner />}>
<SavedObjectsTablePage
coreStart={coreStart}
taggingApi={savedObjectsTaggingOss?.getTaggingApi()}
spacesApi={spacesApi}
dataStart={data}
dataViewsApi={dataViews}
actionRegistry={pluginStart.actions}
columnRegistry={pluginStart.columns}
allowedTypes={allowedObjectTypes}
setBreadcrumbs={setBreadcrumbs}
/>
</Suspense>
</RedirectToHomeIfUnauthorized>
</Route>
</Routes>
</Router>
</I18nProvider>,
theme$
),
<KibanaRenderContextProvider {...coreStart}>
<Router history={history}>
<Routes>
<Route path={'/:type/:id'} exact={true}>
<RedirectToHomeIfUnauthorized>
<Suspense fallback={<EuiLoadingSpinner />}>
<SavedObjectsEditionPage
coreStart={coreStart}
setBreadcrumbs={setBreadcrumbs}
history={history}
/>
</Suspense>
</RedirectToHomeIfUnauthorized>
</Route>
<Route path={'/'} exact={false}>
<RedirectToHomeIfUnauthorized>
<Suspense fallback={<EuiLoadingSpinner />}>
<SavedObjectsTablePage
coreStart={coreStart}
taggingApi={savedObjectsTaggingOss?.getTaggingApi()}
spacesApi={spacesApi}
dataStart={data}
dataViewsApi={dataViews}
actionRegistry={pluginStart.actions}
columnRegistry={pluginStart.columns}
allowedTypes={allowedObjectTypes}
setBreadcrumbs={setBreadcrumbs}
/>
</Suspense>
</RedirectToHomeIfUnauthorized>
</Route>
</Routes>
</Router>
</KibanaRenderContextProvider>,
element
);

View file

@ -31,6 +31,7 @@
"@kbn/core-saved-objects-api-server",
"@kbn/shared-ux-link-redirect-app",
"@kbn/code-editor",
"@kbn/react-kibana-context-render",
],
"exclude": [
"target/**/*",

View file

@ -17,8 +17,7 @@
"security"
],
"requiredBundles": [
"kibanaUtils",
"kibanaReact"
"kibanaUtils"
],
"extraPublicDirs": [
"common/constants"

View file

@ -8,7 +8,9 @@
import {
overlayServiceMock,
analyticsServiceMock,
httpServiceMock,
i18nServiceMock,
notificationServiceMock,
themeServiceMock,
} from '@kbn/core/public/mocks';
@ -76,6 +78,8 @@ export function mockTelemetryNotifications({
return new TelemetryNotifications({
http: httpServiceMock.createSetupContract(),
overlays: overlayServiceMock.createStartContract(),
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
i18n: i18nServiceMock.createStartContract(),
theme: themeServiceMock.createStartContract(),
telemetryService,
telemetryConstants: mockTelemetryConstants(),

View file

@ -229,7 +229,7 @@ export class TelemetryPlugin
}
public start(
{ analytics, http, overlays, theme, application, docLinks }: CoreStart,
{ analytics, http, overlays, application, docLinks, ...startServices }: CoreStart,
{ screenshotMode }: TelemetryPluginStartDependencies
): TelemetryPluginStart {
if (!this.telemetryService) {
@ -243,9 +243,10 @@ export class TelemetryPlugin
const telemetryNotifications = new TelemetryNotifications({
http,
overlays,
theme,
telemetryService: this.telemetryService,
telemetryConstants,
analytics,
...startServices,
});
this.telemetryNotifications = telemetryNotifications;

View file

@ -7,7 +7,13 @@
*/
import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner';
import { overlayServiceMock, httpServiceMock, themeServiceMock } from '@kbn/core/public/mocks';
import {
analyticsServiceMock,
httpServiceMock,
i18nServiceMock,
overlayServiceMock,
themeServiceMock,
} from '@kbn/core/public/mocks';
import { mockTelemetryConstants, mockTelemetryService } from '../../mocks';
describe('renderOptInStatusNoticeBanner', () => {
@ -15,6 +21,8 @@ describe('renderOptInStatusNoticeBanner', () => {
const bannerID = 'brucer-wayne';
const overlays = overlayServiceMock.createStartContract();
const mockHttp = httpServiceMock.createStartContract();
const analytics = analyticsServiceMock.createAnalyticsServiceStart();
const i18n = i18nServiceMock.createStartContract();
const theme = themeServiceMock.createStartContract();
const telemetryConstants = mockTelemetryConstants();
const telemetryService = mockTelemetryService();
@ -24,6 +32,8 @@ describe('renderOptInStatusNoticeBanner', () => {
http: mockHttp,
onSeen: jest.fn(),
overlays,
analytics,
i18n,
theme,
telemetryConstants,
telemetryService,

View file

@ -7,16 +7,15 @@
*/
import React from 'react';
import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import type { CoreStart, HttpStart, OverlayStart } from '@kbn/core/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { withSuspense } from '@kbn/shared-ux-utility';
import { TelemetryService } from '..';
import type { TelemetryConstants } from '../..';
interface RenderBannerConfig {
interface RenderBannerConfig extends Pick<CoreStart, 'analytics' | 'i18n' | 'theme'> {
http: HttpStart;
overlays: OverlayStart;
theme: ThemeServiceStart;
onSeen: () => void;
telemetryConstants: TelemetryConstants;
telemetryService: TelemetryService;
@ -26,9 +25,9 @@ export function renderOptInStatusNoticeBanner({
onSeen,
overlays,
http,
theme,
telemetryConstants,
telemetryService,
...startServices
}: RenderBannerConfig) {
const OptedInNoticeBannerLazy = withSuspense(
React.lazy(() =>
@ -47,7 +46,7 @@ export function renderOptInStatusNoticeBanner({
telemetryConstants={telemetryConstants}
telemetryService={telemetryService}
/>,
{ theme$: theme.theme$ }
startServices
);
const bannerId = overlays.banners.add(mount, 10000);

View file

@ -6,15 +6,15 @@
* Side Public License, v 1.
*/
import type { HttpStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
import type { CoreStart, HttpStart, OverlayStart } from '@kbn/core/public';
import type { TelemetryService } from '../telemetry_service';
import type { TelemetryConstants } from '../..';
import { renderOptInStatusNoticeBanner } from './render_opt_in_status_notice_banner';
interface TelemetryNotificationsConstructor {
interface TelemetryNotificationsConstructor
extends Pick<CoreStart, 'analytics' | 'i18n' | 'theme'> {
http: HttpStart;
overlays: OverlayStart;
theme: ThemeServiceStart;
telemetryService: TelemetryService;
telemetryConstants: TelemetryConstants;
}
@ -25,7 +25,7 @@ interface TelemetryNotificationsConstructor {
export class TelemetryNotifications {
private readonly http: HttpStart;
private readonly overlays: OverlayStart;
private readonly theme: ThemeServiceStart;
private readonly startServices: Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
private readonly telemetryConstants: TelemetryConstants;
private readonly telemetryService: TelemetryService;
private optInStatusNoticeBannerId?: string;
@ -33,14 +33,14 @@ export class TelemetryNotifications {
constructor({
http,
overlays,
theme,
telemetryService,
telemetryConstants,
...startServices
}: TelemetryNotificationsConstructor) {
this.telemetryService = telemetryService;
this.http = http;
this.overlays = overlays;
this.theme = theme;
this.startServices = startServices;
this.telemetryConstants = telemetryConstants;
}
@ -61,9 +61,9 @@ export class TelemetryNotifications {
http: this.http,
onSeen: this.setOptInStatusNoticeSeen,
overlays: this.overlays,
theme: this.theme,
telemetryConstants: this.telemetryConstants,
telemetryService: this.telemetryService,
...this.startServices,
});
this.optInStatusNoticeBannerId = bannerId;

View file

@ -15,7 +15,6 @@
"kbn_references": [
"@kbn/core",
"@kbn/home-plugin",
"@kbn/kibana-react-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/screenshot-mode-plugin",
"@kbn/telemetry-collection-manager-plugin",
@ -36,6 +35,7 @@
"@kbn/core-http-browser",
"@kbn/core-http-server",
"@kbn/analytics-collection-utils",
"@kbn/react-kibana-mount",
"@kbn/core-node-server",
],
"exclude": [

View file

@ -57,7 +57,7 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C
);
}
public start(core: CoreStart, { cloud }: CloudChatStartDeps) {
public start(core: CoreStart) {
const CloudChatContextProvider: FC = ({ children }) => {
// There's a risk that the request for chat config will take too much time to complete, and the provider
// will maintain a stale value. To avoid this, we'll use an Observable.
@ -67,7 +67,7 @@ export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps, C
function ConnectedChat(props: { chatVariant: ChatVariant }) {
return (
<CloudChatContextProvider>
<KibanaRenderContextProvider theme={core.theme} i18n={core.i18n}>
<KibanaRenderContextProvider {...core}>
<ChatExperimentSwitcher
location$={core.application.currentLocation$}
variant={props.chatVariant}

View file

@ -10,8 +10,6 @@
"xpack",
"licensing"
],
"requiredBundles": [
"kibanaReact"
]
"requiredBundles": []
}
}

View file

@ -7,9 +7,10 @@
import React from 'react';
import { EuiCallOut } from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
interface Props {
type: string;
@ -46,5 +47,7 @@ const ExpiredBanner: React.FunctionComponent<Props> = (props) => (
</EuiCallOut>
);
export const mountExpiredBanner = (props: Props) =>
toMountPoint(<ExpiredBanner type={props.type!} uploadUrl={props.uploadUrl} />);
type MountProps = Props & Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
export const mountExpiredBanner = ({ type, uploadUrl, ...startServices }: MountProps) =>
toMountPoint(<ExpiredBanner type={type!} uploadUrl={uploadUrl} />, startServices);

View file

@ -406,10 +406,15 @@ describe('licensing plugin', () => {
expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1);
await refresh();
expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1);
expect(mountExpiredBannerMock).toHaveBeenCalledWith({
type: 'gold',
uploadUrl: '/app/management/stack/license_management/upload_license',
});
expect(mountExpiredBannerMock).toHaveBeenCalledWith(
expect.objectContaining({
type: 'gold',
uploadUrl: '/app/management/stack/license_management/upload_license',
analytics: expect.any(Object),
i18n: expect.any(Object),
theme: expect.any(Object),
})
);
});
});

View file

@ -46,7 +46,7 @@ export class LicensingPlugin implements Plugin<LicensingPluginSetup, LicensingPl
private featureUsage = new FeatureUsageService();
constructor(
context: PluginInitializerContext,
_context: PluginInitializerContext,
private readonly storage: Storage = sessionStorage
) {}
@ -169,13 +169,15 @@ export class LicensingPlugin implements Plugin<LicensingPluginSetup, LicensingPl
};
private showExpiredBanner(license: ILicense) {
const uploadUrl = this.coreStart!.http.basePath.prepend(
const coreStart = this.coreStart!;
const uploadUrl = coreStart.http.basePath.prepend(
'/app/management/stack/license_management/upload_license'
);
this.coreStart!.overlays.banners.add(
coreStart.overlays.banners.add(
mountExpiredBanner({
type: license.type!,
uploadUrl,
...coreStart,
})
);
}

View file

@ -7,14 +7,14 @@
"include": ["public/**/*", "server/**/*", "common/**/*"],
"kbn_references": [
"@kbn/core",
"@kbn/kibana-react-plugin",
"@kbn/i18n-react",
"@kbn/utility-types",
"@kbn/config-schema",
"@kbn/std",
"@kbn/i18n",
"@kbn/analytics-client",
"@kbn/logging-mocks"
"@kbn/logging-mocks",
"@kbn/react-kibana-mount"
],
"exclude": ["target/**/*"]
}