mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Workplace Search] Add Account Settings page imported from Security plugin (#99791)
* Copy lazy_wrapper and suspense_error_boundary from Spaces plugin These components are needed to enable async loading of Security components into Enterprise Search. The components are copied without any changes except for i18n ids, so it's easier to DRY out in the future if needed. * Create async versions of personal_info and change_password components * Create ui_api that allows to load Security components asuncronously The patterns were mostly copied from Spaces plugin * Make ui_api available through Security components's lifecycle methods * Import Security plugin into Enterprise Search * Add Security plugin and Notifications service to Kibana Logic file * Export the required components from the Security plugin and use them in the new AccountSettings component * Update link to the Account Settings page * Move getUiApi call to security start and pass core instead of getStartServices * Simplify import of change_password_async component by providing... ... `notifications` and `userAPIClient` props in the security plugin * Remove UserAPIClient from ui_api It's not needed anymore since the components are initiated with this prop already passed * Export ChangePasswordProps and PersonalInfoProps from account_management/index.ts This makes it easier to import these props from outside the account_management folder * Remove notifications service from kibana_logic It is not needed anymore since we're initializing security components with notifications already provided * Add UiApi to SecurityPluginStart interface * Utilize index files for exporting Props types * Replace Pick<...> with two separate interfaces as it doesn't work well with our docs * Add a comment explaining why we're not loading async components through index file
This commit is contained in:
parent
5ee5720cb7
commit
a9a834a105
26 changed files with 338 additions and 8 deletions
|
@ -7,6 +7,8 @@
|
|||
|
||||
import { chartPluginMock } from '../../../../../../../src/plugins/charts/public/mocks';
|
||||
|
||||
import { securityMock } from '../../../../../security/public/mocks';
|
||||
|
||||
import { mockHistory } from '../react_router/state.mock';
|
||||
|
||||
export const mockKibanaValues = {
|
||||
|
@ -18,6 +20,7 @@ export const mockKibanaValues = {
|
|||
},
|
||||
history: mockHistory,
|
||||
navigateToUrl: jest.fn(),
|
||||
security: securityMock.createStart(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setChromeIsVisible: jest.fn(),
|
||||
setDocTitle: jest.fn(),
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getContext } from 'kea';
|
|||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
|
||||
import { licensingMock } from '../../../licensing/public/mocks';
|
||||
import { securityMock } from '../../../security/public/mocks';
|
||||
|
||||
import { AppSearch } from './app_search';
|
||||
import { EnterpriseSearch } from './enterprise_search';
|
||||
|
@ -27,6 +28,7 @@ describe('renderApp', () => {
|
|||
plugins: {
|
||||
licensing: licensingMock.createStart(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
security: securityMock.createStart(),
|
||||
},
|
||||
} as any;
|
||||
const pluginData = {
|
||||
|
|
|
@ -48,6 +48,7 @@ export const renderApp = (
|
|||
cloud: plugins.cloud || {},
|
||||
history: params.history,
|
||||
navigateToUrl: core.application.navigateToUrl,
|
||||
security: plugins.security || {},
|
||||
setBreadcrumbs: core.chrome.setBreadcrumbs,
|
||||
setChromeIsVisible: core.chrome.setIsVisible,
|
||||
setDocTitle: core.chrome.docTitle.change,
|
||||
|
|
|
@ -13,6 +13,7 @@ import { kea, MakeLogicType } from 'kea';
|
|||
import { ApplicationStart, ChromeBreadcrumb } from '../../../../../../../src/core/public';
|
||||
import { ChartsPluginStart } from '../../../../../../../src/plugins/charts/public';
|
||||
import { CloudSetup } from '../../../../../cloud/public';
|
||||
import { SecurityPluginStart } from '../../../../../security/public';
|
||||
|
||||
import { HttpLogic } from '../http';
|
||||
import { createHref, CreateHrefOptions } from '../react_router_helpers';
|
||||
|
@ -23,6 +24,7 @@ interface KibanaLogicProps {
|
|||
cloud: Partial<CloudSetup>;
|
||||
charts: ChartsPluginStart;
|
||||
navigateToUrl: ApplicationStart['navigateToUrl'];
|
||||
security: Partial<SecurityPluginStart>;
|
||||
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
|
||||
setChromeIsVisible(isVisible: boolean): void;
|
||||
setDocTitle(title: string): void;
|
||||
|
@ -47,6 +49,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
|
|||
},
|
||||
{},
|
||||
],
|
||||
security: [props.security || {}, {}],
|
||||
setBreadcrumbs: [props.setBreadcrumbs, {}],
|
||||
setChromeIsVisible: [props.setChromeIsVisible, {}],
|
||||
setDocTitle: [props.setDocTitle, {}],
|
||||
|
|
|
@ -27,7 +27,7 @@ import { getWorkplaceSearchUrl } from '../../../../shared/enterprise_search_url'
|
|||
import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers';
|
||||
import { AppLogic } from '../../../app_logic';
|
||||
import { WORKPLACE_SEARCH_TITLE, ACCOUNT_NAV } from '../../../constants';
|
||||
import { PERSONAL_SOURCES_PATH, LOGOUT_ROUTE, KIBANA_ACCOUNT_ROUTE } from '../../../routes';
|
||||
import { PERSONAL_SOURCES_PATH, LOGOUT_ROUTE, PERSONAL_SETTINGS_PATH } from '../../../routes';
|
||||
|
||||
export const AccountHeader: React.FC = () => {
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
@ -44,8 +44,7 @@ export const AccountHeader: React.FC = () => {
|
|||
|
||||
const accountNavItems = [
|
||||
<EuiContextMenuItem key="accountSettings">
|
||||
{/* TODO: Once auth is completed, we need to have non-admins redirect to the self-hosted form */}
|
||||
<EuiButtonEmpty href={KIBANA_ACCOUNT_ROUTE}>{ACCOUNT_NAV.SETTINGS}</EuiButtonEmpty>
|
||||
<EuiButtonEmptyTo to={PERSONAL_SETTINGS_PATH}>{ACCOUNT_NAV.SETTINGS}</EuiButtonEmptyTo>
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem key="logout">
|
||||
<EuiButtonEmpty href={LOGOUT_ROUTE}>{ACCOUNT_NAV.LOGOUT}</EuiButtonEmpty>
|
||||
|
|
|
@ -28,7 +28,9 @@ import {
|
|||
ORG_SETTINGS_PATH,
|
||||
ROLE_MAPPINGS_PATH,
|
||||
SECURITY_PATH,
|
||||
PERSONAL_SETTINGS_PATH,
|
||||
} from './routes';
|
||||
import { AccountSettings } from './views/account_settings';
|
||||
import { SourcesRouter } from './views/content_sources';
|
||||
import { SourceAdded } from './views/content_sources/components/source_added';
|
||||
import { SourceSubNav } from './views/content_sources/components/source_sub_nav';
|
||||
|
@ -103,6 +105,11 @@ export const WorkplaceSearchConfigured: React.FC<InitialAppData> = (props) => {
|
|||
<SourcesRouter />
|
||||
</PrivateSourcesLayout>
|
||||
</Route>
|
||||
<Route path={PERSONAL_SETTINGS_PATH}>
|
||||
<PrivateSourcesLayout restrictWidth readOnlyMode={readOnlyMode}>
|
||||
<AccountSettings />
|
||||
</PrivateSourcesLayout>
|
||||
</Route>
|
||||
<Route path={SOURCES_PATH}>
|
||||
<Layout
|
||||
navigation={<WorkplaceSearchNav sourcesSubNav={showSourcesSubnav && <SourceSubNav />} />}
|
||||
|
|
|
@ -13,7 +13,6 @@ export const SETUP_GUIDE_PATH = '/setup_guide';
|
|||
|
||||
export const NOT_FOUND_PATH = '/404';
|
||||
export const LOGOUT_ROUTE = '/logout';
|
||||
export const KIBANA_ACCOUNT_ROUTE = '/security/account';
|
||||
|
||||
export const LEAVE_FEEDBACK_EMAIL = 'support@elastic.co';
|
||||
export const LEAVE_FEEDBACK_URL = `mailto:${LEAVE_FEEDBACK_EMAIL}?Subject=Elastic%20Workplace%20Search%20Feedback`;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 React, { useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import type { AuthenticatedUser } from '../../../../../../security/public';
|
||||
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
|
||||
|
||||
export const AccountSettings: React.FC = () => {
|
||||
const { security } = useValues(KibanaLogic);
|
||||
|
||||
const [currentUser, setCurrentUser] = useState<AuthenticatedUser | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
security!.authc!.getCurrentUser().then(setCurrentUser);
|
||||
}, [security.authc]);
|
||||
|
||||
const PersonalInfo = useMemo(() => security!.uiApi!.components.getPersonalInfo, [security.uiApi]);
|
||||
const ChangePassword = useMemo(() => security!.uiApi!.components.getChangePassword, [
|
||||
security.uiApi,
|
||||
]);
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PersonalInfo user={currentUser} />
|
||||
<ChangePassword user={currentUser} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { AccountSettings } from './account_settings';
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from '../../../../src/plugins/home/public';
|
||||
import { CloudSetup } from '../../cloud/public';
|
||||
import { LicensingPluginStart } from '../../licensing/public';
|
||||
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/public';
|
||||
|
||||
import {
|
||||
APP_SEARCH_PLUGIN,
|
||||
|
@ -42,11 +43,13 @@ export interface ClientData extends InitialAppData {
|
|||
interface PluginsSetup {
|
||||
cloud?: CloudSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
security?: SecurityPluginSetup;
|
||||
}
|
||||
export interface PluginsStart {
|
||||
cloud?: CloudSetup;
|
||||
licensing: LicensingPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
security?: SecurityPluginStart;
|
||||
}
|
||||
|
||||
export class EnterpriseSearchPlugin implements Plugin {
|
||||
|
|
|
@ -17,13 +17,16 @@ import { canUserChangePassword } from '../../../common/model';
|
|||
import type { UserAPIClient } from '../../management/users';
|
||||
import { ChangePasswordForm } from '../../management/users/components/change_password_form';
|
||||
|
||||
interface Props {
|
||||
export interface ChangePasswordProps {
|
||||
user: AuthenticatedUser;
|
||||
}
|
||||
|
||||
export interface ChangePasswordPropsInternal extends ChangePasswordProps {
|
||||
userAPIClient: PublicMethodsOf<UserAPIClient>;
|
||||
notifications: NotificationsSetup;
|
||||
}
|
||||
|
||||
export class ChangePassword extends Component<Props, {}> {
|
||||
export class ChangePassword extends Component<ChangePasswordPropsInternal, {}> {
|
||||
public render() {
|
||||
const canChangePassword = canUserChangePassword(this.props.user);
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import type { CoreStart } from 'src/core/public';
|
||||
|
||||
import { UserAPIClient } from '../../management/users';
|
||||
import type { ChangePasswordProps } from './change_password';
|
||||
|
||||
export const getChangePasswordComponent = async (
|
||||
core: CoreStart
|
||||
): Promise<React.FC<ChangePasswordProps>> => {
|
||||
const { ChangePassword } = await import('./change_password');
|
||||
|
||||
return (props: ChangePasswordProps) => {
|
||||
return (
|
||||
<ChangePassword
|
||||
notifications={core.notifications}
|
||||
userAPIClient={new UserAPIClient(core.http)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
|
@ -6,3 +6,5 @@
|
|||
*/
|
||||
|
||||
export { ChangePassword } from './change_password';
|
||||
|
||||
export type { ChangePasswordProps } from './change_password';
|
||||
|
|
|
@ -6,3 +6,6 @@
|
|||
*/
|
||||
|
||||
export { accountManagementApp } from './account_management_app';
|
||||
|
||||
export type { ChangePasswordProps } from './change_password';
|
||||
export type { PersonalInfoProps } from './personal_info';
|
||||
|
|
|
@ -6,3 +6,5 @@
|
|||
*/
|
||||
|
||||
export { PersonalInfo } from './personal_info';
|
||||
|
||||
export type { PersonalInfoProps } from './personal_info';
|
||||
|
|
|
@ -12,11 +12,11 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
|
||||
import type { AuthenticatedUser } from '../../../common/model';
|
||||
|
||||
interface Props {
|
||||
export interface PersonalInfoProps {
|
||||
user: AuthenticatedUser;
|
||||
}
|
||||
|
||||
export const PersonalInfo = (props: Props) => {
|
||||
export const PersonalInfo = (props: PersonalInfoProps) => {
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
fullWidth
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import type { PersonalInfoProps } from './personal_info';
|
||||
|
||||
export const getPersonalInfoComponent = async (): Promise<React.FC<PersonalInfoProps>> => {
|
||||
const { PersonalInfo } = await import('./personal_info');
|
||||
return (props: PersonalInfoProps) => {
|
||||
return <PersonalInfo {...props} />;
|
||||
};
|
||||
};
|
|
@ -11,6 +11,7 @@ import { mockAuthenticatedUser } from '../common/model/authenticated_user.mock';
|
|||
import { authenticationMock } from './authentication/index.mock';
|
||||
import { navControlServiceMock } from './nav_control/index.mock';
|
||||
import { createSessionTimeoutMock } from './session/session_timeout.mock';
|
||||
import { getUiApiMock } from './ui_api/index.mock';
|
||||
|
||||
function createSetupMock() {
|
||||
return {
|
||||
|
@ -23,6 +24,7 @@ function createStartMock() {
|
|||
return {
|
||||
authc: authenticationMock.createStart(),
|
||||
navControlService: navControlServiceMock.createStart(),
|
||||
uiApi: getUiApiMock.createStart(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,12 @@ describe('Security Plugin', () => {
|
|||
features: {} as FeaturesPluginStart,
|
||||
})
|
||||
).toEqual({
|
||||
uiApi: {
|
||||
components: {
|
||||
getChangePassword: expect.any(Function),
|
||||
getPersonalInfo: expect.any(Function),
|
||||
},
|
||||
},
|
||||
authc: {
|
||||
getCurrentUser: expect.any(Function),
|
||||
areAPIKeysEnabled: expect.any(Function),
|
||||
|
|
|
@ -30,6 +30,8 @@ import type { SecurityNavControlServiceStart } from './nav_control';
|
|||
import { SecurityNavControlService } from './nav_control';
|
||||
import { SecurityCheckupService } from './security_checkup';
|
||||
import { SessionExpired, SessionTimeout, UnauthorizedResponseHttpInterceptor } from './session';
|
||||
import type { UiApi } from './ui_api';
|
||||
import { getUiApi } from './ui_api';
|
||||
|
||||
export interface PluginSetupDependencies {
|
||||
licensing: LicensingPluginSetup;
|
||||
|
@ -150,6 +152,7 @@ export class SecurityPlugin
|
|||
}
|
||||
|
||||
return {
|
||||
uiApi: getUiApi({ core }),
|
||||
navControlService: this.navControlService.start({ core }),
|
||||
authc: this.authc as AuthenticationServiceStart,
|
||||
};
|
||||
|
@ -184,4 +187,8 @@ export interface SecurityPluginStart {
|
|||
* Exposes authentication information about the currently logged in user.
|
||||
*/
|
||||
authc: AuthenticationServiceStart;
|
||||
/**
|
||||
* Exposes UI components that will be loaded asynchronously.
|
||||
*/
|
||||
uiApi: UiApi;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { SuspenseErrorBoundary } from './suspense_error_boundary';
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { Component, Suspense } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { NotificationsStart } from 'src/core/public';
|
||||
|
||||
interface Props {
|
||||
notifications: NotificationsStart;
|
||||
}
|
||||
|
||||
interface State {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export class SuspenseErrorBoundary extends Component<PropsWithChildren<Props>, State> {
|
||||
state: State = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
// Update state so next render shows fallback UI.
|
||||
return { error };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error) {
|
||||
const { notifications } = this.props;
|
||||
if (notifications) {
|
||||
const title = i18n.translate('xpack.security.uiApi.errorBoundaryToastTitle', {
|
||||
defaultMessage: 'Failed to load Kibana asset',
|
||||
});
|
||||
const toastMessage = i18n.translate('xpack.security.uiApi.errorBoundaryToastMessage', {
|
||||
defaultMessage: 'Reload page to continue.',
|
||||
});
|
||||
notifications.toasts.addError(error, { title, toastMessage });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, notifications } = this.props;
|
||||
const { error } = this.state;
|
||||
if (!notifications || error) {
|
||||
return null;
|
||||
}
|
||||
return <Suspense fallback={<EuiLoadingSpinner />}>{children}</Suspense>;
|
||||
}
|
||||
}
|
43
x-pack/plugins/security/public/ui_api/components.tsx
Normal file
43
x-pack/plugins/security/public/ui_api/components.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FC, PropsWithChildren, PropsWithRef } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { CoreStart } from 'src/core/public';
|
||||
|
||||
/**
|
||||
* We're importing specific files here instead of passing them
|
||||
* through the index file. It helps to keep the bundle size low.
|
||||
*
|
||||
* Importing async components through the index file increases the bundle size.
|
||||
* It happens because the bundle starts to also include all the sync dependencies
|
||||
* available through the index file.
|
||||
*/
|
||||
import { getChangePasswordComponent } from '../account_management/change_password/change_password_async';
|
||||
import { getPersonalInfoComponent } from '../account_management/personal_info/personal_info_async';
|
||||
import { LazyWrapper } from './lazy_wrapper';
|
||||
|
||||
export interface GetComponentsOptions {
|
||||
core: CoreStart;
|
||||
}
|
||||
|
||||
export const getComponents = ({ core }: GetComponentsOptions) => {
|
||||
/**
|
||||
* Returns a function that creates a lazy-loading version of a component.
|
||||
*/
|
||||
function wrapLazy<T>(fn: () => Promise<FC<T>>) {
|
||||
return (props: JSX.IntrinsicAttributes & PropsWithRef<PropsWithChildren<T>>) => (
|
||||
<LazyWrapper fn={fn} core={core} props={props} />
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
getPersonalInfo: wrapLazy(getPersonalInfoComponent),
|
||||
getChangePassword: wrapLazy(() => getChangePasswordComponent(core)),
|
||||
};
|
||||
};
|
17
x-pack/plugins/security/public/ui_api/index.mock.ts
Normal file
17
x-pack/plugins/security/public/ui_api/index.mock.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UiApi } from './';
|
||||
|
||||
export const getUiApiMock = {
|
||||
createStart: (): jest.Mocked<UiApi> => ({
|
||||
components: {
|
||||
getPersonalInfo: jest.fn(),
|
||||
getChangePassword: jest.fn(),
|
||||
},
|
||||
}),
|
||||
};
|
34
x-pack/plugins/security/public/ui_api/index.ts
Normal file
34
x-pack/plugins/security/public/ui_api/index.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 type { ReactElement } from 'react';
|
||||
|
||||
import type { CoreStart } from 'src/core/public';
|
||||
|
||||
import type { ChangePasswordProps, PersonalInfoProps } from '../account_management';
|
||||
import { getComponents } from './components';
|
||||
|
||||
interface GetUiApiOptions {
|
||||
core: CoreStart;
|
||||
}
|
||||
|
||||
type LazyComponentFn<T> = (props: T) => ReactElement;
|
||||
|
||||
export interface UiApi {
|
||||
components: {
|
||||
getPersonalInfo: LazyComponentFn<PersonalInfoProps>;
|
||||
getChangePassword: LazyComponentFn<ChangePasswordProps>;
|
||||
};
|
||||
}
|
||||
|
||||
export const getUiApi = ({ core }: GetUiApiOptions): UiApi => {
|
||||
const components = getComponents({ core });
|
||||
|
||||
return {
|
||||
components,
|
||||
};
|
||||
};
|
39
x-pack/plugins/security/public/ui_api/lazy_wrapper.tsx
Normal file
39
x-pack/plugins/security/public/ui_api/lazy_wrapper.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 type { FC, PropsWithChildren, PropsWithRef, ReactElement } from 'react';
|
||||
import React, { lazy, useMemo } from 'react';
|
||||
|
||||
import type { CoreStart } from 'src/core/public';
|
||||
|
||||
import { SuspenseErrorBoundary } from '../suspense_error_boundary';
|
||||
|
||||
interface InternalProps<T> {
|
||||
fn: () => Promise<FC<T>>;
|
||||
core: CoreStart;
|
||||
props: JSX.IntrinsicAttributes & PropsWithRef<PropsWithChildren<T>>;
|
||||
}
|
||||
|
||||
export const LazyWrapper: <T>(props: InternalProps<T>) => ReactElement | null = ({
|
||||
fn,
|
||||
core,
|
||||
props,
|
||||
}) => {
|
||||
const { notifications } = core;
|
||||
|
||||
const LazyComponent = useMemo(() => lazy(() => fn().then((x) => ({ default: x }))), [fn]);
|
||||
|
||||
if (!notifications) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SuspenseErrorBoundary notifications={notifications}>
|
||||
<LazyComponent {...props} />
|
||||
</SuspenseErrorBoundary>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue