Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const DEFAULT_INITIAL_APP_DATA = {
},
},
workplaceSearch: {
canCreateInvitations: true,
isFederatedAuth: false,
organization: {
name: 'ACME Donuts',
defaultOrgName: 'My Organization',
Expand Down
7 changes: 2 additions & 5 deletions x-pack/plugins/enterprise_search/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
*/

import { IAccount as IAppSearchAccount } from './app_search';
import { IAccount as IWorkplaceSearchAccount, IOrganization } from './workplace_search';
import { IWorkplaceSearchInitialData } from './workplace_search';

export interface IInitialAppData {
readOnlyMode?: boolean;
ilmEnabled?: boolean;
configuredLimits?: IConfiguredLimits;
appSearch?: IAppSearchAccount;
workplaceSearch?: {
organization: IOrganization;
fpAccount: IWorkplaceSearchAccount;
};
workplaceSearch?: IWorkplaceSearchInitialData;
}

export interface IConfiguredLimits {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ export interface IOrganization {
name: string;
defaultOrgName: string;
}

export interface IWorkplaceSearchInitialData {
canCreateInvitations: boolean;
isFederatedAuth: boolean;
organization: IOrganization;
fpAccount: IAccount;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { resetContext } from 'kea';

import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__';
import { AppLogic } from './app_logic';

describe('AppLogic', () => {
beforeEach(() => {
resetContext({});
AppLogic.mount();
});

const DEFAULT_VALUES = {
hasInitialized: false,
};

it('has expected default values', () => {
expect(AppLogic.values).toEqual(DEFAULT_VALUES);
});

describe('initializeAppData()', () => {
it('sets values based on passed props', () => {
AppLogic.actions.initializeAppData(DEFAULT_INITIAL_APP_DATA);

expect(AppLogic.values).toEqual({
hasInitialized: true,
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { kea } from 'kea';

import { IInitialAppData } from '../../../common/types';
import { IWorkplaceSearchInitialData } from '../../../common/types/workplace_search';
import { IKeaLogic } from '../shared/types';

export interface IAppValues extends IWorkplaceSearchInitialData {
hasInitialized: boolean;
}
export interface IAppActions {
initializeAppData(props: IInitialAppData): void;
}

export const AppLogic = kea({
actions: (): IAppActions => ({
initializeAppData: ({ workplaceSearch }) => workplaceSearch,
}),
reducers: () => ({
hasInitialized: [
false,
{
initializeAppData: () => true,
},
],
}),
}) as IKeaLogic<IAppValues, IAppActions>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,76 @@ import '../__mocks__/kea.mock';
import React, { useContext } from 'react';
import { Redirect } from 'react-router-dom';
import { shallow } from 'enzyme';
import { useValues } from 'kea';
import { useValues, useActions } from 'kea';

import { Overview } from './views/overview';
import { SetupGuide } from './views/setup_guide';
import { ErrorState } from './views/error_state';
import { Overview } from './views/overview';

import { WorkplaceSearch } from './';
import { WorkplaceSearch, WorkplaceSearchUnconfigured, WorkplaceSearchConfigured } from './';

describe('Workplace Search', () => {
it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({
config: { host: '' },
}));
describe('WorkplaceSearch', () => {
it('renders WorkplaceSearchUnconfigured when config.host is not set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } }));
const wrapper = shallow(<WorkplaceSearch />);

expect(wrapper.find(Redirect)).toHaveLength(1);
expect(wrapper.find(Overview)).toHaveLength(0);
expect(wrapper.find(WorkplaceSearchUnconfigured)).toHaveLength(1);
});

it('renders the Overview when enterpriseSearchUrl is set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({
config: { host: 'https://foo.bar' },
}));
it('renders WorkplaceSearchConfigured when config.host set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'some.url' } }));
const wrapper = shallow(<WorkplaceSearch />);

expect(wrapper.find(WorkplaceSearchConfigured)).toHaveLength(1);
});
});

describe('WorkplaceSearchUnconfigured', () => {
it('renders the Setup Guide and redirects to the Setup Guide', () => {
const wrapper = shallow(<WorkplaceSearchUnconfigured />);

expect(wrapper.find(SetupGuide)).toHaveLength(1);
expect(wrapper.find(Redirect)).toHaveLength(1);
});
});

describe('WorkplaceSearchConfigured', () => {
beforeEach(() => {
// Mock resets
(useValues as jest.Mock).mockImplementation(() => ({}));
(useActions as jest.Mock).mockImplementation(() => ({ initializeAppData: () => {} }));
});

it('renders with layout', () => {
const wrapper = shallow(<WorkplaceSearchConfigured />);

expect(wrapper.find(Overview)).toHaveLength(1);
expect(wrapper.find(Redirect)).toHaveLength(0);
});

it('renders ErrorState when the app cannot connect to Enterprise Search', () => {
(useValues as jest.Mock).mockImplementationOnce(() => ({ errorConnecting: true }));
const wrapper = shallow(<WorkplaceSearch />);
it('initializes app data with passed props', () => {
const initializeAppData = jest.fn();
(useActions as jest.Mock).mockImplementation(() => ({ initializeAppData }));

shallow(<WorkplaceSearchConfigured readOnlyMode={true} />);

expect(initializeAppData).toHaveBeenCalledWith({ readOnlyMode: true });
});

it('does not re-initialize app data', () => {
const initializeAppData = jest.fn();
(useActions as jest.Mock).mockImplementation(() => ({ initializeAppData }));
(useValues as jest.Mock).mockImplementation(() => ({ hasInitialized: true }));

shallow(<WorkplaceSearchConfigured />);

expect(initializeAppData).not.toHaveBeenCalled();
});

it('renders ErrorState', () => {
(useValues as jest.Mock).mockImplementation(() => ({ errorConnecting: true }));

const wrapper = shallow(<WorkplaceSearchConfigured />);

expect(wrapper.find(ErrorState).exists()).toBe(true);
expect(wrapper.find(Overview)).toHaveLength(0);
expect(wrapper.find(ErrorState)).toHaveLength(2);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hidden routes, with navigation, also have an ErrorState

});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useContext } from 'react';
import React, { useContext, useEffect } from 'react';
import { Route, Redirect, Switch } from 'react-router-dom';
import { useValues } from 'kea';
import { useActions, useValues } from 'kea';

import { IInitialAppData } from '../../../common/types';
import { KibanaContext, IKibanaContext } from '../index';
import { HttpLogic, IHttpLogicValues } from '../shared/http';
import { AppLogic, IAppActions, IAppValues } from './app_logic';
import { Layout } from '../shared/layout';
import { WorkplaceSearchNav } from './components/layout/nav';

Expand All @@ -20,21 +21,19 @@ import { SetupGuide } from './views/setup_guide';
import { ErrorState } from './views/error_state';
import { Overview } from './views/overview';

export const WorkplaceSearch: React.FC<IInitialAppData> = () => {
export const WorkplaceSearch: React.FC<IInitialAppData> = (props) => {
const { config } = useContext(KibanaContext) as IKibanaContext;
return !config.host ? <WorkplaceSearchUnconfigured /> : <WorkplaceSearchConfigured {...props} />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Glad you liked this pattern I used in App Search!

};

export const WorkplaceSearchConfigured: React.FC<IInitialAppData> = (props) => {
const { hasInitialized } = useValues(AppLogic) as IAppValues;
const { initializeAppData } = useActions(AppLogic) as IAppActions;
Comment on lines +30 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to self: I should update App Search's IAppLogicValues/Actions types to remove the Logic

const { errorConnecting } = useValues(HttpLogic) as IHttpLogicValues;

if (!config.host)
return (
<Switch>
<Route exact path={SETUP_GUIDE_PATH}>
<SetupGuide />
</Route>
<Route>
<Redirect to={SETUP_GUIDE_PATH} />
</Route>
</Switch>
);
useEffect(() => {
if (!hasInitialized) initializeAppData(props);
}, [hasInitialized]);

return (
<Switch>
Expand All @@ -61,3 +60,14 @@ export const WorkplaceSearch: React.FC<IInitialAppData> = () => {
</Switch>
);
};

export const WorkplaceSearchUnconfigured: React.FC = () => (
<Switch>
<Route exact path={SETUP_GUIDE_PATH}>
<SetupGuide />
</Route>
<Route>
<Redirect to={SETUP_GUIDE_PATH} />
</Route>
</Switch>
);
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe('callEnterpriseSearchConfigAPI', () => {
onboarding_complete: true,
},
workplace_search: {
can_create_invitations: true,
is_federated_auth: false,
organization: {
name: 'ACME Donuts',
default_org_name: 'My Organization',
Expand Down Expand Up @@ -136,6 +138,8 @@ describe('callEnterpriseSearchConfigAPI', () => {
},
},
workplaceSearch: {
canCreateInvitations: false,
isFederatedAuth: false,
organization: {
name: undefined,
defaultOrgName: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export const callEnterpriseSearchConfigAPI = async ({
},
},
workplaceSearch: {
canCreateInvitations: !!data?.settings?.workplace_search?.can_create_invitations,
isFederatedAuth: !!data?.settings?.workplace_search?.is_federated_auth,
organization: {
name: data?.settings?.workplace_search?.organization?.name,
defaultOrgName: data?.settings?.workplace_search?.organization?.default_org_name,
Expand Down