From 7c18ee95881d1a63b5bca4eebafa6de0959d0e43 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 6 May 2020 19:36:05 -0700 Subject: [PATCH 1/4] Licensing plugin setup --- x-pack/plugins/enterprise_search/kibana.json | 2 +- .../enterprise_search/public/applications/index.tsx | 9 +++++++-- x-pack/plugins/enterprise_search/public/plugin.ts | 9 ++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/enterprise_search/kibana.json b/x-pack/plugins/enterprise_search/kibana.json index d0c4c9733da2a..3121d6bd470b0 100644 --- a/x-pack/plugins/enterprise_search/kibana.json +++ b/x-pack/plugins/enterprise_search/kibana.json @@ -2,7 +2,7 @@ "id": "enterpriseSearch", "version": "1.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["home"], + "requiredPlugins": ["home", "licensing"], "configPath": ["enterpriseSearch"], "optionalPlugins": ["usageCollection"], "server": true, diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 473d395c1e604..eade360a938f5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -9,7 +9,7 @@ import ReactDOM from 'react-dom'; import { BrowserRouter, Route, Redirect } from 'react-router-dom'; import { CoreStart, AppMountParams, HttpHandler } from 'src/core/public'; -import { ClientConfigType } from '../plugin'; +import { ClientConfigType, PluginsSetup } from '../plugin'; import { TSetBreadcrumbs } from './shared/kibana_breadcrumbs'; import { AppSearch } from './app_search'; @@ -22,7 +22,12 @@ export interface IKibanaContext { export const KibanaContext = React.createContext(); -export const renderApp = (core: CoreStart, params: AppMountParams, config: ClientConfigType) => { +export const renderApp = ( + core: CoreStart, + params: AppMountParams, + config: ClientConfigType, + plugins: PluginsSetup +) => { ReactDOM.render( { const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./applications'); - return renderApp(coreStart, params, config); + return renderApp(coreStart, params, config, plugins); }, }); From 7f2f31a3476fab9f1d02f95ccbe3a3bea56f4d94 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 6 May 2020 21:23:31 -0700 Subject: [PATCH 2/4] Add LicensingContext setup --- .../public/applications/index.test.ts | 10 +++++-- .../public/applications/index.tsx | 26 ++++++++++------- .../applications/shared/licensing/index.ts | 7 +++++ .../shared/licensing/license_context.test.tsx | 28 +++++++++++++++++++ .../shared/licensing/license_context.tsx | 27 ++++++++++++++++++ 5 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/index.test.ts index 7ece7e153c154..7ea5b97feac6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.ts @@ -5,14 +5,20 @@ */ import { coreMock } from 'src/core/public/mocks'; -import { renderApp } from '../applications'; +import { licensingMock } from '../../../licensing/public/mocks'; + +import { renderApp } from './'; describe('renderApp', () => { it('mounts and unmounts UI', () => { const params = coreMock.createAppMountParamters(); const core = coreMock.createStart(); + const config = {}; + const plugins = { + licensing: licensingMock.createSetup(), + }; - const unmount = renderApp(core, params, {}); + const unmount = renderApp(core, params, config, plugins); expect(params.element.querySelector('.setup-guide')).not.toBeNull(); unmount(); expect(params.element.innerHTML).toEqual(''); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index eade360a938f5..0fb18a8b627bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -11,6 +11,8 @@ import { BrowserRouter, Route, Redirect } from 'react-router-dom'; import { CoreStart, AppMountParams, HttpHandler } from 'src/core/public'; import { ClientConfigType, PluginsSetup } from '../plugin'; import { TSetBreadcrumbs } from './shared/kibana_breadcrumbs'; +import { ILicense } from '../../../../licensing/public'; +import { LicenseProvider } from './shared/licensing'; import { AppSearch } from './app_search'; @@ -18,6 +20,7 @@ export interface IKibanaContext { enterpriseSearchUrl?: string; http(): HttpHandler; setBreadCrumbs(): TSetBreadcrumbs; + license$: Observable; } export const KibanaContext = React.createContext(); @@ -34,18 +37,21 @@ export const renderApp = ( http: core.http, enterpriseSearchUrl: config.host, setBreadcrumbs: core.chrome.setBreadcrumbs, + license$: plugins.licensing.license$, }} > - - - {/* This will eventually contain an Enterprise Search landing page, - and we'll also actually have a /workplace_search route */} - - - - - - + + + + {/* This will eventually contain an Enterprise Search landing page, + and we'll also actually have a /workplace_search route */} + + + + + + + , params.element ); 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 new file mode 100644 index 0000000000000..173c8587ba0da --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context'; 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 new file mode 100644 index 0000000000000..3385f79d3d075 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx @@ -0,0 +1,28 @@ +/* + * 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 { mountWithKibanaContext } 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 = mountWithKibanaContext( + + + + ); + + 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 new file mode 100644 index 0000000000000..c8295196aedb5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx @@ -0,0 +1,27 @@ +/* + * 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 useObservable from 'react-use/lib/useObservable'; + +import { KibanaContext, IKibanaContext } from '../../'; + +import { ILicense } from '../../../../licensing/public'; + +export interface ILicenseContext { + license?: ILicense; +} + +export const LicenseContext = React.createContext(); + +export const LicenseProvider: React.FC<> = ({ children }) => { + // Listen for changes to license subscription + const { license$ } = useContext(KibanaContext) as IKibanaContext; + const license = useObservable(license$); + + // Render rest of application and pass down license via context + return ; +}; From bb3ccb16e73d259e3f281e10a7b2af071a7d3f06 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 6 May 2020 21:46:57 -0700 Subject: [PATCH 3/4] Update EngineOverview to not only hit meta engines API on platinum license --- .../engine_overview/engine_overview.test.tsx | 45 ++++++++++++++----- .../engine_overview/engine_overview.tsx | 12 +++-- .../applications/shared/licensing/index.ts | 1 + .../shared/licensing/license_checks.test.ts | 33 ++++++++++++++ .../shared/licensing/license_checks.ts | 11 +++++ 5 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts 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 2b712721a7fa5..5d029d6c4ba8a 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 @@ -11,6 +11,7 @@ import { act } from 'react-dom/test-utils'; import { render } from 'enzyme'; import { KibanaContext } from '../../../'; +import { LicenseContext } from '../../../shared/licensing'; import { mountWithKibanaContext, mockKibanaContext } from '../../../__mocks__'; import { EmptyState, ErrorState, NoUserState } from '../empty_states'; @@ -24,7 +25,9 @@ describe('EngineOverview', () => { // We use render() instead of mount() here to not trigger lifecycle methods (i.e., useEffect) const wrapper = render( - + + + ); @@ -85,7 +88,7 @@ describe('EngineOverview', () => { }); it('renders', () => { - expect(wrapper.find(EngineTable)).toHaveLength(2); + expect(wrapper.find(EngineTable)).toHaveLength(1); }); it('calls the engines API', () => { @@ -95,12 +98,6 @@ describe('EngineOverview', () => { pageIndex: 1, }, }); - expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', { - query: { - type: 'meta', - pageIndex: 1, - }, - }); }); describe('pagination', () => { @@ -130,13 +127,36 @@ describe('EngineOverview', () => { expect(getTablePagination().pageIndex).toEqual(4); }); }); + + describe('when on a platinum license', () => { + beforeAll(async () => { + mockApi.mockClear(); + wrapper = await mountWithApiMock({ + license: { type: 'platinum', isActive: true }, + get: mockApi, + }); + }); + + it('renders a 2nd meta engines table', () => { + expect(wrapper.find(EngineTable)).toHaveLength(2); + }); + + it('makes a 2nd call to the engines API with type meta', () => { + expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', { + query: { + type: 'meta', + pageIndex: 1, + }, + }); + }); + }); }); /** * Test helpers */ - const mountWithApiMock = async ({ get }) => { + const mountWithApiMock = async ({ get, license }) => { let wrapper; const httpMock = { ...mockKibanaContext.http, get }; @@ -144,7 +164,12 @@ describe('EngineOverview', () => { // TBH, I don't fully understand why since Enzyme's mount is supposed to // have act() baked in - could be because of the wrapping context provider? await act(async () => { - wrapper = mountWithKibanaContext(, { http: httpMock }); + wrapper = mountWithKibanaContext( + + + , + { http: httpMock } + ); }); wrapper.update(); // This seems to be required for the DOM to actually update 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 d87c36cd9b9d6..1e1a583b5bcdb 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 @@ -17,6 +17,7 @@ import { import { SetAppSearchBreadcrumbs as SetBreadcrumbs } from '../../../shared/kibana_breadcrumbs'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; +import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing'; import { KibanaContext, IKibanaContext } from '../../../index'; import EnginesIcon from '../../assets/engine.svg'; @@ -30,6 +31,7 @@ import './engine_overview.scss'; export const EngineOverview: ReactFC<> = () => { const { http } = useContext(KibanaContext) as IKibanaContext; + const { license } = useContext(LicenseContext) as ILicenseContext; const [isLoading, setIsLoading] = useState(true); const [hasNoAccount, setHasNoAccount] = useState(false); @@ -72,11 +74,13 @@ export const EngineOverview: ReactFC<> = () => { }, [enginesPage]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { - const params = { type: 'meta', pageIndex: metaEnginesPage }; - const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal }; + if (hasPlatinumLicense(license)) { + const params = { type: 'meta', pageIndex: metaEnginesPage }; + const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal }; - setEnginesData(params, callbacks); - }, [metaEnginesPage]); // eslint-disable-line react-hooks/exhaustive-deps + setEnginesData(params, callbacks); + } + }, [license, metaEnginesPage]); // eslint-disable-line react-hooks/exhaustive-deps if (hasErrorConnecting) return ; if (hasNoAccount) return ; 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 173c8587ba0da..9c8c1417d48db 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 @@ -5,3 +5,4 @@ */ export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context'; +export { hasPlatinumLicense } from './license_checks'; 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 new file mode 100644 index 0000000000000..e21bf004b39a2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts @@ -0,0 +1,33 @@ +/* + * 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 } from './license_checks'; + +describe('hasPlatinumLicense', () => { + it('is true for platinum licenses', () => { + expect(hasPlatinumLicense({ isActive: true, type: 'platinum' })).toEqual(true); + }); + + it('is true for enterprise licenses', () => { + expect(hasPlatinumLicense({ isActive: true, type: 'enterprise' })).toEqual(true); + }); + + it('is true for trial licenses', () => { + expect(hasPlatinumLicense({ isActive: true, type: 'platinum' })).toEqual(true); + }); + + it('is false if the current license is expired', () => { + expect(hasPlatinumLicense({ isActive: false, type: 'platinum' })).toEqual(false); + expect(hasPlatinumLicense({ isActive: false, type: 'enterprise' })).toEqual(false); + expect(hasPlatinumLicense({ isActive: false, type: 'trial' })).toEqual(false); + }); + + it('is false for licenses below platinum', () => { + expect(hasPlatinumLicense({ isActive: true, type: 'basic' })).toEqual(false); + expect(hasPlatinumLicense({ isActive: false, type: 'standard' })).toEqual(false); + expect(hasPlatinumLicense({ isActive: true, type: 'gold' })).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 new file mode 100644 index 0000000000000..7d0de8a093b31 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts @@ -0,0 +1,11 @@ +/* + * 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: ILicenseContext) => { + return license?.isActive && ['platinum', 'enterprise', 'trial'].includes(license?.type); +}; From cfeb1cf896b198c318b9dc7bcb5b4d5c5443b8ee Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Wed, 6 May 2020 21:49:14 -0700 Subject: [PATCH 4/4] Add Jest test helpers for future shallow/context use --- .../public/applications/__mocks__/index.ts | 1 + .../applications/__mocks__/kibana_context.mock.ts | 1 + .../applications/__mocks__/license_context.mock.ts | 11 +++++++++++ .../applications/__mocks__/shallow_usecontext.mock.ts | 5 +++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts 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 cfe5a1e4c4ee2..5b19055115fde 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts @@ -6,6 +6,7 @@ export { mockHistory } from './react_router_history.mock'; export { mockKibanaContext } from './kibana_context.mock'; +export { mockLicenseContext } from './license_context.mock'; export { mountWithKibanaContext } from './mount_with_context.mock'; // Note: shallow_usecontext must be imported directly as a file diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts index fcfa1b0a21f13..3363c031da8ae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts @@ -14,4 +14,5 @@ export const mockKibanaContext = { http: httpServiceMock.createSetupContract(), setBreadcrumbs: jest.fn(), enterpriseSearchUrl: 'http://localhost:3002', + license$: jest.fn(), }; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts new file mode 100644 index 0000000000000..7c37ecc7cde1b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts @@ -0,0 +1,11 @@ +/* + * 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 { licensingMock } from '../../../../licensing/public/mocks'; + +export const mockLicenseContext = { + license: licensingMock.createLicense(), +}; 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 5193a0cd299f8..20add45e16b58 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 @@ -5,14 +5,15 @@ */ /** - * NOTE: This variable name MUST start with 'mock*' in order for + * NOTE: These variable names MUST start with 'mock*' in order for * 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'), - useContext: jest.fn(() => mockKibanaContext), + useContext: jest.fn(() => ({ ...mockKibanaContext, ...mockLicenseContext })), })); /**