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 e0cf2814b46b4e..053c450ab925ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -10,7 +10,7 @@ import { AppMountParameters } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; -import { renderApp } from './'; +import { renderApp, renderHeaderActions } from './'; import { AppSearch } from './app_search'; import { WorkplaceSearch } from './workplace_search'; @@ -33,6 +33,7 @@ describe('renderApp', () => { const unmount = renderApp(MockApp, params, core, plugins, config, data); expect(params.element.querySelector('.hello-world')).not.toBeNull(); + unmount(); expect(params.element.innerHTML).toEqual(''); }); @@ -47,3 +48,16 @@ describe('renderApp', () => { expect(params.element.querySelector('.setupGuide')).not.toBeNull(); }); }); + +describe('renderHeaderActions', () => { + it('mounts and unmounts any HeaderActions component', () => { + const mockHeaderEl = document.createElement('header'); + const MockHeaderActions = () => ; + + const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl, {} as any); + expect(mockHeaderEl.querySelector('.hello-world')).not.toBeNull(); + + unmount(); + expect(mockHeaderEl.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 82f884644be4ad..43056f2f655385 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -88,3 +88,22 @@ export const renderApp = ( ReactDOM.unmountComponentAtNode(params.element); }; }; + +/** + * Render function for Kibana's header action menu chrome - + * reusable by any Enterprise Search plugin simply by passing in + * a custom HeaderActions component (e.g., WorkplaceSearchHeaderActions) + * @see https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md + */ +interface IHeaderActionsProps { + externalUrl: IExternalUrl; +} + +export const renderHeaderActions = ( + HeaderActions: React.FC, + kibanaHeaderEl: HTMLElement, + externalUrl: IExternalUrl +) => { + ReactDOM.render(, kibanaHeaderEl); + return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts index 41861a8ee2dc50..915638246c00e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts @@ -5,3 +5,4 @@ */ export { WorkplaceSearchNav } from './nav'; +export { WorkplaceSearchHeaderActions } from './kibana_header_actions'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx new file mode 100644 index 00000000000000..a006c5e3775d52 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiButtonEmpty } from '@elastic/eui'; +import { ExternalUrl } from '../../../shared/enterprise_search_url'; + +import { WorkplaceSearchHeaderActions } from './'; + +describe('WorkplaceSearchHeaderActions', () => { + const externalUrl = new ExternalUrl('http://localhost:3002'); + + it('renders a link to the search application', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('http://localhost:3002/ws/search'); + }); + + it('does not render without an Enterprise Search host URL set', () => { + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx new file mode 100644 index 00000000000000..fa32d598f848db --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.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 from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty } from '@elastic/eui'; + +import { IExternalUrl } from '../../../shared/enterprise_search_url'; + +interface IProps { + externalUrl: IExternalUrl; +} + +export const WorkplaceSearchHeaderActions: React.FC = ({ externalUrl }) => { + const { enterpriseSearchUrl, getWorkplaceSearchUrl } = externalUrl; + if (!enterpriseSearchUrl) return null; + + return ( + + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.headerActions.searchApplication', { + defaultMessage: 'Go to search application', + })} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 0ef58a7c03f10e..c23bb23be3979e 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -103,9 +103,16 @@ export class EnterpriseSearchPlugin implements Plugin { await this.getInitialData(coreStart.http); - const { renderApp } = await import('./applications'); + const { renderApp, renderHeaderActions } = await import('./applications'); const { WorkplaceSearch } = await import('./applications/workplace_search'); + const { WorkplaceSearchHeaderActions } = await import( + './applications/workplace_search/components/layout' + ); + params.setHeaderActionMenu((element) => + renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl) + ); + return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data); }, });