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 e0cf2814b46b4..053c450ab925e 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 82f884644be4a..43056f2f65538 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 41861a8ee2dc5..915638246c00e 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 0000000000000..a006c5e3775d5
--- /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 0000000000000..fa32d598f848d
--- /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 0ef58a7c03f10..c23bb23be3979 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);
},
});