diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
index f66235ff44c6a..88a900f69c5ec 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts
@@ -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';
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
index 8e6b0baa5fc00..bad6beaa1652e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
@@ -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,
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts
similarity index 79%
rename from x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts
rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts
index 7c37ecc7cde1b..51b32e7a877b2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts
@@ -6,6 +6,8 @@
import { licensingMock } from '../../../../licensing/public/mocks';
-export const mockLicenseContext = {
+export const mockLicensingValues = {
license: licensingMock.createLicense(),
+ hasPlatinumLicense: false,
+ hasGoldLicense: false,
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
index 5e56f17c8e7f3..646c3104c286f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
@@ -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
@@ -34,9 +32,7 @@ export const mountWithContext = (children: React.ReactNode, context?: object) =>
return mount(
-
- {children}
-
+ {children}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
index 3a2193db646de..df9e58994e36b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
@@ -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
}));
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
index 928d92d791094..44afce96c1a6c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx
@@ -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(, {
- license: { type: 'platinum', isActive: true },
+ setMockValues({
+ hasPlatinumLicense: true,
+ http: { ...mockHttpValues.http, get: mockApi },
});
+ const wrapper = await mountWithAsyncContext();
expect(wrapper.find(EngineTable)).toHaveLength(2);
expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
index c0aedbe7dc6b4..0cb9ba106dbb8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx
@@ -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,
@@ -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';
@@ -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([]);
@@ -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 ;
if (!engines.length) return ;
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
index 053c450ab925e..6ee63ee22cae2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
@@ -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';
@@ -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 = () =>
Hello world!
;
- 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();
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx
index 0869ef7b22729..4a25ecf6067cc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx
@@ -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';
@@ -39,15 +39,18 @@ export const KibanaContext = React.createContext({});
export const renderApp = (
App: React.FC,
- 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,
@@ -67,19 +70,18 @@ export const renderApp = (
setDocTitle: core.chrome.docTitle.change,
}}
>
-
-
-
-
-
-
-
+
+
+
+
+
,
params.element
);
return () => {
ReactDOM.unmountComponentAtNode(params.element);
+ unmountLicensingLogic();
unmountHttpLogic();
unmountFlashMessagesLogic();
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
index 29c11ffa1cef8..4e371b337c40a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts
@@ -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';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts
deleted file mode 100644
index 40f0f6380c21c..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 { hasPlatinumLicense, hasGoldLicense } from './license_checks';
-
-describe('hasPlatinumLicense', () => {
- it('is true for platinum licenses', () => {
- expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true);
- });
-
- it('is true for enterprise licenses', () => {
- expect(hasPlatinumLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true);
- });
-
- it('is true for trial licenses', () => {
- expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true);
- });
-
- it('is false if the current license is expired', () => {
- expect(hasPlatinumLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false);
- expect(hasPlatinumLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false);
- expect(hasPlatinumLicense({ isActive: false, type: 'trial' } as any)).toEqual(false);
- });
-
- it('is false for licenses below platinum', () => {
- expect(hasPlatinumLicense({ isActive: true, type: 'basic' } as any)).toEqual(false);
- expect(hasPlatinumLicense({ isActive: false, type: 'standard' } as any)).toEqual(false);
- expect(hasPlatinumLicense({ isActive: true, type: 'gold' } as any)).toEqual(false);
- });
-});
-
-describe('hasGoldLicense', () => {
- it('is true for gold+ and trial licenses', () => {
- expect(hasGoldLicense({ isActive: true, type: 'gold' } as any)).toEqual(true);
- expect(hasGoldLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true);
- expect(hasGoldLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true);
- expect(hasGoldLicense({ isActive: true, type: 'trial' } as any)).toEqual(true);
- });
-
- it('is false if the current license is expired', () => {
- expect(hasGoldLicense({ isActive: false, type: 'gold' } as any)).toEqual(false);
- expect(hasGoldLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false);
- expect(hasGoldLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false);
- expect(hasGoldLicense({ isActive: false, type: 'trial' } as any)).toEqual(false);
- });
-
- it('is false for licenses below gold', () => {
- expect(hasGoldLicense({ isActive: true, type: 'basic' } as any)).toEqual(false);
- expect(hasGoldLicense({ isActive: false, type: 'standard' } as any)).toEqual(false);
- });
-});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts
deleted file mode 100644
index d13d0909243be..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * 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 { ILicense } from '../../../../../licensing/public';
-
-export const hasPlatinumLicense = (license?: ILicense) => {
- const qualifyingLicenses = ['platinum', 'enterprise', 'trial'];
- return license?.isActive && qualifyingLicenses.includes(license?.type as string);
-};
-
-export const hasGoldLicense = (license?: ILicense) => {
- const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial'];
- return license?.isActive && qualifyingLicenses.includes(license?.type as string);
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx
deleted file mode 100644
index c65474ec1f590..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 React, { useContext } from 'react';
-
-import { mountWithContext } from '../../__mocks__';
-import { LicenseContext, ILicenseContext } from './';
-
-describe('LicenseProvider', () => {
- const MockComponent: React.FC = () => {
- const { license } = useContext(LicenseContext) as ILicenseContext;
- return {license?.type}
;
- };
-
- it('renders children', () => {
- const wrapper = mountWithContext(, { license: { type: 'basic' } });
-
- expect(wrapper.find('.license-test')).toHaveLength(1);
- expect(wrapper.text()).toEqual('basic');
- });
-});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx
deleted file mode 100644
index 9b47959ff7544..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 React from 'react';
-import useObservable from 'react-use/lib/useObservable';
-import { Observable } from 'rxjs';
-
-import { ILicense } from '../../../../../licensing/public';
-
-export interface ILicenseContext {
- license: ILicense;
-}
-interface ILicenseContextProps {
- license$: Observable;
- children: React.ReactNode;
-}
-
-export const LicenseContext = React.createContext({});
-
-export const LicenseProvider: React.FC = ({ license$, children }) => {
- // Listen for changes to license subscription
- const license = useObservable(license$);
-
- // Render rest of application and pass down license via context
- return ;
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts
new file mode 100644
index 0000000000000..153a5ae765468
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts
@@ -0,0 +1,161 @@
+/*
+ * 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 { BehaviorSubject } from 'rxjs';
+
+import { licensingMock } from '../../../../../licensing/public/mocks';
+
+import { LicensingLogic, mountLicensingLogic } from './licensing_logic';
+
+describe('LicensingLogic', () => {
+ const mockLicense = licensingMock.createLicense();
+ const mockLicense$ = new BehaviorSubject(mockLicense);
+ const mount = () => mountLicensingLogic({ license$: mockLicense$ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ resetContext({});
+ });
+
+ describe('setLicense()', () => {
+ it('sets license value', () => {
+ mount();
+ LicensingLogic.actions.setLicense('test' as any);
+ expect(LicensingLogic.values.license).toEqual('test');
+ });
+ });
+
+ describe('setLicenseSubscription()', () => {
+ it('sets licenseSubscription value', () => {
+ mount();
+ LicensingLogic.actions.setLicenseSubscription('test' as any);
+ expect(LicensingLogic.values.licenseSubscription).toEqual('test');
+ });
+ });
+
+ describe('licensing subscription', () => {
+ describe('on mount', () => {
+ it('subscribes to the license observable', () => {
+ mount();
+ expect(LicensingLogic.values.license).toEqual(mockLicense);
+ expect(LicensingLogic.values.licenseSubscription).not.toBeNull();
+ });
+ });
+
+ describe('on subscription update', () => {
+ it('updates the license value', () => {
+ mount();
+
+ const nextMockLicense = licensingMock.createLicense({ license: { status: 'invalid' } });
+ mockLicense$.next(nextMockLicense);
+
+ expect(LicensingLogic.values.license).toEqual(nextMockLicense);
+ });
+ });
+
+ describe('on unmount', () => {
+ it('unsubscribes to the license observable', () => {
+ const mockUnsubscribe = jest.fn();
+ const unmount = mountLicensingLogic({
+ license$: { subscribe: () => ({ unsubscribe: mockUnsubscribe }) } as any,
+ });
+ unmount();
+ expect(mockUnsubscribe).toHaveBeenCalled();
+ });
+
+ it('does not crash if no subscription exists', () => {
+ const unmount = mount();
+ LicensingLogic.actions.setLicenseSubscription(null as any);
+ unmount();
+ });
+ });
+ });
+
+ describe('license check selectors', () => {
+ beforeEach(() => {
+ mount();
+ });
+
+ const updateLicense = (license: any) => {
+ const updatedLicense = licensingMock.createLicense({ license });
+ mockLicense$.next(updatedLicense);
+ };
+
+ describe('hasPlatinumLicense', () => {
+ it('is true for platinum+ and trial licenses', () => {
+ updateLicense({ status: 'active', type: 'platinum' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true);
+
+ updateLicense({ status: 'active', type: 'enterprise' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true);
+
+ updateLicense({ status: 'active', type: 'trial' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true);
+ });
+
+ it('is false if the current license is expired', () => {
+ updateLicense({ status: 'expired', type: 'platinum' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+
+ updateLicense({ status: 'expired', type: 'enterprise' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+
+ updateLicense({ status: 'expired', type: 'trial' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+ });
+
+ it('is false for licenses below platinum', () => {
+ updateLicense({ status: 'active', type: 'basic' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+
+ updateLicense({ status: 'active', type: 'standard' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+
+ updateLicense({ status: 'active', type: 'gold' });
+ expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false);
+ });
+ });
+
+ describe('hasGoldLicense', () => {
+ it('is true for gold+ and trial licenses', () => {
+ updateLicense({ status: 'active', type: 'gold' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(true);
+
+ updateLicense({ status: 'active', type: 'platinum' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(true);
+
+ updateLicense({ status: 'active', type: 'enterprise' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(true);
+
+ updateLicense({ status: 'active', type: 'trial' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(true);
+ });
+
+ it('is false if the current license is expired', () => {
+ updateLicense({ status: 'expired', type: 'gold' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+
+ updateLicense({ status: 'expired', type: 'platinum' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+
+ updateLicense({ status: 'expired', type: 'enterprise' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+
+ updateLicense({ status: 'expired', type: 'trial' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+ });
+
+ it('is false for licenses below gold', () => {
+ updateLicense({ status: 'active', type: 'basic' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+
+ updateLicense({ status: 'active', type: 'standard' });
+ expect(LicensingLogic.values.hasGoldLicense).toEqual(false);
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts
new file mode 100644
index 0000000000000..ae31b2ec6168a
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts
@@ -0,0 +1,82 @@
+/*
+ * 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, MakeLogicType } from 'kea';
+import { Observable, Subscription } from 'rxjs';
+
+import { ILicense } from '../../../../../licensing/public';
+
+export interface ILicensingValues {
+ license: ILicense | null;
+ licenseSubscription: Subscription | null;
+ hasPlatinumLicense: boolean;
+ hasGoldLicense: boolean;
+}
+export interface ILicensingActions {
+ setLicense(license: ILicense): ILicense;
+ setLicenseSubscription(licenseSubscription: Subscription): Subscription;
+}
+
+export const LicensingLogic = kea>({
+ path: ['enterprise_search', 'licensing_logic'],
+ actions: {
+ setLicense: (license) => license,
+ setLicenseSubscription: (licenseSubscription) => licenseSubscription,
+ },
+ reducers: {
+ license: [
+ null,
+ {
+ setLicense: (_, license) => license,
+ },
+ ],
+ licenseSubscription: [
+ null,
+ {
+ setLicenseSubscription: (_, licenseSubscription) => licenseSubscription,
+ },
+ ],
+ },
+ selectors: {
+ hasPlatinumLicense: [
+ (selectors) => [selectors.license],
+ (license) => {
+ const qualifyingLicenses = ['platinum', 'enterprise', 'trial'];
+ return license?.isActive && qualifyingLicenses.includes(license?.type);
+ },
+ ],
+ hasGoldLicense: [
+ (selectors) => [selectors.license],
+ (license) => {
+ const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial'];
+ return license?.isActive && qualifyingLicenses.includes(license?.type);
+ },
+ ],
+ },
+ events: ({ props, actions, values }) => ({
+ afterMount: () => {
+ const licenseSubscription = props.license$.subscribe(async (license: ILicense) => {
+ actions.setLicense(license);
+ });
+ actions.setLicenseSubscription(licenseSubscription);
+ },
+ beforeUnmount: () => {
+ if (values.licenseSubscription) values.licenseSubscription.unsubscribe();
+ },
+ }),
+});
+
+/**
+ * Mount/props helper
+ */
+interface ILicensingLogicProps {
+ license$: Observable;
+}
+export const mountLicensingLogic = (props: ILicensingLogicProps) => {
+ LicensingLogic(props);
+ const unmount = LicensingLogic.mount();
+ return unmount;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx
index ce9071ad7b9d0..62c0af31cffd9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import '../../__mocks__/shallow_usecontext.mock';
+import '../../__mocks__/kea.mock';
-import React, { useContext } from 'react';
+import React from 'react';
+import { useValues } from 'kea';
import { shallow } from 'enzyme';
import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui';
@@ -18,13 +19,6 @@ import { WorkplaceSearchLogo } from './assets/workplace_search_logo';
import { NotFound } from './';
describe('NotFound', () => {
- const basicLicense = { isActive: true, type: 'basic' };
- const goldLicense = { isActive: true, type: 'gold' };
-
- beforeEach(() => {
- (useContext as jest.Mock).mockImplementation(() => ({ license: basicLicense }));
- });
-
it('renders an App Search 404 view', () => {
const wrapper = shallow();
const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow();
@@ -50,7 +44,7 @@ describe('NotFound', () => {
});
it('changes the support URL if the user has a gold+ license', () => {
- (useContext as jest.Mock).mockImplementation(() => ({ license: goldLicense }));
+ (useValues as jest.Mock).mockReturnValueOnce({ hasGoldLicense: true });
const wrapper = shallow();
const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow();
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx
index bd988854225fb..40bb5efcc6330 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useContext } from 'react';
+import React from 'react';
+import { useValues } from 'kea';
import { i18n } from '@kbn/i18n';
import {
EuiPageContent,
@@ -24,7 +25,7 @@ import {
import { EuiButton } from '../react_router_helpers';
import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome';
import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry';
-import { LicenseContext, ILicenseContext, hasGoldLicense } from '../licensing';
+import { LicensingLogic } from '../licensing';
import { AppSearchLogo } from './assets/app_search_logo';
import { WorkplaceSearchLogo } from './assets/workplace_search_logo';
@@ -39,8 +40,8 @@ interface NotFoundProps {
}
export const NotFound: React.FC = ({ product = {} }) => {
- const { license } = useContext(LicenseContext) as ILicenseContext;
- const supportUrl = hasGoldLicense(license) ? LICENSED_SUPPORT_URL : product.SUPPORT_URL;
+ const { hasGoldLicense } = useValues(LicensingLogic);
+ const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL;
let Logo;
let SetPageChrome;
diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts
index c23bb23be3979..f59ec830c812f 100644
--- a/x-pack/plugins/enterprise_search/public/plugin.ts
+++ b/x-pack/plugins/enterprise_search/public/plugin.ts
@@ -7,7 +7,6 @@
import {
AppMountParameters,
CoreSetup,
- CoreStart,
HttpSetup,
Plugin,
PluginInitializerContext,
@@ -17,7 +16,7 @@ import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../src/plugins/home/public';
-import { LicensingPluginSetup } from '../../licensing/public';
+import { LicensingPluginStart } from '../../licensing/public';
import {
APP_SEARCH_PLUGIN,
ENTERPRISE_SEARCH_PLUGIN,
@@ -36,7 +35,9 @@ export interface ClientData extends IInitialAppData {
export interface PluginsSetup {
home?: HomePublicPluginSetup;
- licensing: LicensingPluginSetup;
+}
+export interface PluginsStart {
+ licensing: LicensingPluginStart;
}
export class EnterpriseSearchPlugin implements Plugin {
@@ -57,16 +58,17 @@ export class EnterpriseSearchPlugin implements Plugin {
appRoute: ENTERPRISE_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
- const [coreStart] = await core.getStartServices();
- const { chrome } = coreStart;
- chrome.docTitle.change(ENTERPRISE_SEARCH_PLUGIN.NAME);
+ const kibanaDeps = await this.getKibanaDeps(core, params);
+ const pluginData = this.getPluginData();
- await this.getInitialData(coreStart.http);
+ const { chrome, http } = kibanaDeps.core;
+ chrome.docTitle.change(ENTERPRISE_SEARCH_PLUGIN.NAME);
+ await this.getInitialData(http);
const { renderApp } = await import('./applications');
const { EnterpriseSearch } = await import('./applications/enterprise_search');
- return renderApp(EnterpriseSearch, params, coreStart, plugins, this.config, this.data);
+ return renderApp(EnterpriseSearch, kibanaDeps, pluginData);
},
});
@@ -77,16 +79,17 @@ export class EnterpriseSearchPlugin implements Plugin {
appRoute: APP_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
- const [coreStart] = await core.getStartServices();
- const { chrome } = coreStart;
- chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
+ const kibanaDeps = await this.getKibanaDeps(core, params);
+ const pluginData = this.getPluginData();
- await this.getInitialData(coreStart.http);
+ const { chrome, http } = kibanaDeps.core;
+ chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
+ await this.getInitialData(http);
const { renderApp } = await import('./applications');
const { AppSearch } = await import('./applications/app_search');
- return renderApp(AppSearch, params, coreStart, plugins, this.config, this.data);
+ return renderApp(AppSearch, kibanaDeps, pluginData);
},
});
@@ -97,11 +100,12 @@ export class EnterpriseSearchPlugin implements Plugin {
appRoute: WORKPLACE_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
- const [coreStart] = await core.getStartServices();
- const { chrome } = coreStart;
- chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME);
+ const kibanaDeps = await this.getKibanaDeps(core, params);
+ const pluginData = this.getPluginData();
- await this.getInitialData(coreStart.http);
+ const { chrome, http } = kibanaDeps.core;
+ chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
+ await this.getInitialData(http);
const { renderApp, renderHeaderActions } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
@@ -113,7 +117,7 @@ export class EnterpriseSearchPlugin implements Plugin {
renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl)
);
- return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data);
+ return renderApp(WorkplaceSearch, kibanaDeps, pluginData);
},
});
@@ -149,10 +153,22 @@ export class EnterpriseSearchPlugin implements Plugin {
}
}
- public start(core: CoreStart) {}
+ public start() {}
public stop() {}
+ private async getKibanaDeps(core: CoreSetup, params: AppMountParameters) {
+ // Helper for using start dependencies on mount (instead of setup dependencies)
+ // and for grouping Kibana-related args together (vs. plugin-specific args)
+ const [coreStart, pluginsStart] = await core.getStartServices();
+ return { params, core: coreStart, plugins: pluginsStart as PluginsStart };
+ }
+
+ private getPluginData() {
+ // Small helper for grouping plugin data related args together
+ return { config: this.config, data: this.data };
+ }
+
private async getInitialData(http: HttpSetup) {
if (!this.config.host) return; // No API to call
if (this.hasInitialized) return; // We've already made an initial call