Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Move LicenseContext to Kea #78231

Merged
merged 5 commits into from
Sep 23, 2020
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 @@ -6,7 +6,7 @@

export { mockHistory, mockLocation } from './react_router_history.mock';
export { mockKibanaContext } from './kibana_context.mock';
export { mockLicenseContext } from './license_context.mock';
export { mockLicensingValues } from './licensing_logic.mock';
export { mockHttpValues } from './http_logic.mock';
export { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';
export { mockAllValues, mockAllActions, setMockValues } from './kea.mock';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
*/

/**
* Combine all shared mock values/actions into a single obj
*
* NOTE: These variable names MUST start with 'mock*' in order for
* Jest to accept its use within a jest.mock()
*/
import { mockLicensingValues } from './licensing_logic.mock';
import { mockHttpValues } from './http_logic.mock';
import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';

export const mockAllValues = {
...mockLicensingValues,
...mockHttpValues,
...mockFlashMessagesValues,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import { licensingMock } from '../../../../licensing/public/mocks';

export const mockLicenseContext = {
export const mockLicensingValues = {
license: licensingMock.createLicense(),
hasPlatinumLicense: false,
hasGoldLicense: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import { getContext, resetContext } from 'kea';
import { I18nProvider } from '@kbn/i18n/react';
import { KibanaContext } from '../';
import { mockKibanaContext } from './kibana_context.mock';
import { LicenseContext } from '../shared/licensing';
import { mockLicenseContext } from './license_context.mock';

/**
* This helper mounts a component with all the contexts/providers used
Expand All @@ -34,9 +32,7 @@ export const mountWithContext = (children: React.ReactNode, context?: object) =>
return mount(
<I18nProvider>
<KibanaContext.Provider value={{ ...mockKibanaContext, ...context }}>
<LicenseContext.Provider value={{ ...mockLicenseContext, ...context }}>
<Provider store={store}>{children}</Provider>
</LicenseContext.Provider>
<Provider store={store}>{children}</Provider>
</KibanaContext.Provider>
</I18nProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
* Jest to accept its use within a jest.mock()
*/
import { mockKibanaContext } from './kibana_context.mock';
import { mockLicenseContext } from './license_context.mock';

jest.mock('react', () => ({
...(jest.requireActual('react') as object),
useContext: jest.fn(() => ({ ...mockKibanaContext, ...mockLicenseContext })),
useContext: jest.fn(() => ({ ...mockKibanaContext })),
useEffect: jest.fn((fn) => fn()), // Calls on mount/every update - use mount for more complex behavior
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ describe('EngineOverview', () => {

describe('when on a platinum license', () => {
it('renders a 2nd meta engines table & makes a 2nd meta engines API call', async () => {
const wrapper = await mountWithAsyncContext(<EngineOverview />, {
license: { type: 'platinum', isActive: true },
setMockValues({
hasPlatinumLicense: true,
http: { ...mockHttpValues.http, get: mockApi },
});
const wrapper = await mountWithAsyncContext(<EngineOverview />);

expect(wrapper.find(EngineTable)).toHaveLength(2);
expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useValues } from 'kea';
import {
EuiPageContent,
Expand All @@ -19,7 +19,7 @@ import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chro
import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
import { FlashMessages } from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing';
import { LicensingLogic } from '../../../shared/licensing';

import { EngineIcon } from './assets/engine_icon';
import { MetaEngineIcon } from './assets/meta_engine_icon';
Expand All @@ -40,7 +40,7 @@ interface ISetEnginesCallbacks {

export const EngineOverview: React.FC = () => {
const { http } = useValues(HttpLogic);
const { license } = useContext(LicenseContext) as ILicenseContext;
const { hasPlatinumLicense } = useValues(LicensingLogic);

const [isLoading, setIsLoading] = useState(true);
const [engines, setEngines] = useState([]);
Expand Down Expand Up @@ -72,13 +72,13 @@ export const EngineOverview: React.FC = () => {
}, [enginesPage]);

useEffect(() => {
if (hasPlatinumLicense(license)) {
if (hasPlatinumLicense) {
const params = { type: 'meta', pageIndex: metaEnginesPage };
const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal };

setEnginesData(params, callbacks);
}
}, [license, metaEnginesPage]);
}, [hasPlatinumLicense, metaEnginesPage]);

if (isLoading) return <LoadingState />;
if (!engines.length) return <EmptyState />;
Expand Down
30 changes: 15 additions & 15 deletions x-pack/plugins/enterprise_search/public/applications/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import React from 'react';

import { AppMountParameters } from 'src/core/public';
import { coreMock } from 'src/core/public/mocks';
import { licensingMock } from '../../../licensing/public/mocks';

Expand All @@ -15,37 +14,38 @@ import { AppSearch } from './app_search';
import { WorkplaceSearch } from './workplace_search';

describe('renderApp', () => {
let params: AppMountParameters;
const core = coreMock.createStart();
const plugins = {
licensing: licensingMock.createSetup(),
const kibanaDeps = {
params: coreMock.createAppMountParamters(),
core: coreMock.createStart(),
plugins: { licensing: licensingMock.createStart() },
} as any;
const pluginData = {
config: {},
data: {},
} as any;
const config = {};
const data = {} as any;

beforeEach(() => {
jest.clearAllMocks();
params = coreMock.createAppMountParamters();
});

it('mounts and unmounts UI', () => {
const MockApp = () => <div className="hello-world">Hello world!</div>;

const unmount = renderApp(MockApp, params, core, plugins, config, data);
expect(params.element.querySelector('.hello-world')).not.toBeNull();
const unmount = renderApp(MockApp, kibanaDeps, pluginData);
expect(kibanaDeps.params.element.querySelector('.hello-world')).not.toBeNull();

unmount();
expect(params.element.innerHTML).toEqual('');
expect(kibanaDeps.params.element.innerHTML).toEqual('');
});

it('renders AppSearch', () => {
renderApp(AppSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
renderApp(AppSearch, kibanaDeps, pluginData);
expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull();
});

it('renders WorkplaceSearch', () => {
renderApp(WorkplaceSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
renderApp(WorkplaceSearch, kibanaDeps, pluginData);
expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull();
});
});

Expand Down
30 changes: 16 additions & 14 deletions x-pack/plugins/enterprise_search/public/applications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { getContext, resetContext } from 'kea';

import { I18nProvider } from '@kbn/i18n/react';
import { AppMountParameters, CoreStart, ApplicationStart, ChromeBreadcrumb } from 'src/core/public';
import { ClientConfigType, ClientData, PluginsSetup } from '../plugin';
import { LicenseProvider } from './shared/licensing';
import { PluginsStart, ClientConfigType, ClientData } from '../plugin';
import { mountLicensingLogic } from './shared/licensing';
import { mountHttpLogic } from './shared/http';
import { mountFlashMessagesLogic } from './shared/flash_messages';
import { IExternalUrl } from './shared/enterprise_search_url';
Expand All @@ -39,15 +39,18 @@ export const KibanaContext = React.createContext({});

export const renderApp = (
App: React.FC<IInitialAppData>,
params: AppMountParameters,
core: CoreStart,
plugins: PluginsSetup,
config: ClientConfigType,
{ externalUrl, errorConnecting, ...initialData }: ClientData
{ params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart },
{ config, data }: { config: ClientConfigType; data: ClientData }
) => {
const { externalUrl, errorConnecting, ...initialData } = data;

resetContext({ createStore: true });
const store = getContext().store as Store;

const unmountLicensingLogic = mountLicensingLogic({
license$: plugins.licensing.license$,
});

const unmountHttpLogic = mountHttpLogic({
http: core.http,
errorConnecting,
Expand All @@ -67,19 +70,18 @@ export const renderApp = (
setDocTitle: core.chrome.docTitle.change,
}}
>
<LicenseProvider license$={plugins.licensing.license$}>
<Provider store={store}>
<Router history={params.history}>
<App {...initialData} />
</Router>
</Provider>
</LicenseProvider>
<Provider store={store}>
<Router history={params.history}>
<App {...initialData} />
</Router>
</Provider>
</KibanaContext.Provider>
Copy link
Member Author

Choose a reason for hiding this comment

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

Just one more wrapper to go! 🔥

</I18nProvider>,
params.element
);
return () => {
ReactDOM.unmountComponentAtNode(params.element);
unmountLicensingLogic();
Copy link
Contributor

Choose a reason for hiding this comment

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

If this list gets much longer I might try to think of a better solution than losting all of these inputs.

const configs = [ LicenseLogic: {<LicenseLogic props>}, HttpLogic: {<HttpLogic props}, etc ] 
const unmount = Object.entries(config).map((Logic, props) => { Logic(props); return Logic.mount() })

return () => {
  ReactDOM.unmountComponentAtNode(params.element);
  unmounts.forEach(unmount => unmount())
}

then we'd never forget to unmount!

Copy link
Member Author

@cee-chen cee-chen Sep 23, 2020

Choose a reason for hiding this comment

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

For sure! FWIW, it stops at 4 total (by the end of my series of PRs) but I can definitely see a case for pulling all our mounts out to a separate file/helper and returning an array of unmounts. I think 4 is just on the border to not a completely warrant a separate helper, but let me know if you disagree 🤔

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

export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context';
export { hasPlatinumLicense, hasGoldLicense } from './license_checks';
export { LicensingLogic, mountLicensingLogic } from './licensing_logic';

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading