Skip to content

Commit

Permalink
[Workplace Search] Add Account Settings page imported from Security p…
Browse files Browse the repository at this point in the history
…lugin (#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
  • Loading branch information
yakhinvadim authored Jun 10, 2021
1 parent 5ee5720 commit a9a834a
Show file tree
Hide file tree
Showing 26 changed files with 338 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -18,6 +20,7 @@ export const mockKibanaValues = {
},
history: mockHistory,
navigateToUrl: jest.fn(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
setChromeIsVisible: jest.fn(),
setDocTitle: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,6 +28,7 @@ describe('renderApp', () => {
plugins: {
licensing: licensingMock.createStart(),
charts: chartPluginMock.createStartContract(),
security: securityMock.createStart(),
},
} as any;
const pluginData = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -47,6 +49,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
},
{},
],
security: [props.security || {}, {}],
setBreadcrumbs: [props.setBreadcrumbs, {}],
setChromeIsVisible: [props.setChromeIsVisible, {}],
setDocTitle: [props.setDocTitle, {}],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 />} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down
Original file line number Diff line number Diff line change
@@ -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} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -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';
3 changes: 3 additions & 0 deletions x-pack/plugins/enterprise_search/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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}
/>
);
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*/

export { ChangePassword } from './change_password';

export type { ChangePasswordProps } from './change_password';
3 changes: 3 additions & 0 deletions x-pack/plugins/security/public/account_management/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
*/

export { accountManagementApp } from './account_management_app';

export type { ChangePasswordProps } from './change_password';
export type { PersonalInfoProps } from './personal_info';
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*/

export { PersonalInfo } from './personal_info';

export type { PersonalInfoProps } from './personal_info';
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; 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} />;
};
};
2 changes: 2 additions & 0 deletions x-pack/plugins/security/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,6 +24,7 @@ function createStartMock() {
return {
authc: authenticationMock.createStart(),
navControlService: navControlServiceMock.createStart(),
uiApi: getUiApiMock.createStart(),
};
}

Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/security/public/plugin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/security/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -150,6 +152,7 @@ export class SecurityPlugin
}

return {
uiApi: getUiApi({ core }),
navControlService: this.navControlService.start({ core }),
authc: this.authc as AuthenticationServiceStart,
};
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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';
Loading

0 comments on commit a9a834a

Please sign in to comment.