Skip to content

Commit

Permalink
[Enterprise Search] Set up Workplace Search header action menu, creat…
Browse files Browse the repository at this point in the history
…e reusable renderHeaderActions (#78050) (#78099)

* Create reusable renderHeaderActions

- I put it in the same file as renderApp, since both functions behave similarly (one renders in the main Kibana app/body, the other renders in Kibana's header)

- slightly opinionated: renamed renderActionMenu to renderHeaderActions from Kibana's examples

* Add example renderHeaderActions usage

* Add example WorkplaceSearchHeaderActions component

- not sure if this should be where it lives or is exported from, feel free to change @scottybollinger

* Update WorkplaceSearchHeaderActions with realistic functionality

- pass required externalUrl helper as arg/prop
- TODO: Consider refactoring KibanaContext to a Kea store so that we can share it across our renderApp and renderHeaderActions

* Fix type errors
  • Loading branch information
Constance committed Sep 22, 2020
1 parent be7ae56 commit c95956f
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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('');
});
Expand All @@ -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 = () => <button className="hello-world">Hello World</button>;

const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl, {} as any);
expect(mockHeaderEl.querySelector('.hello-world')).not.toBeNull();

unmount();
expect(mockHeaderEl.innerHTML).toEqual('');
});
});
19 changes: 19 additions & 0 deletions x-pack/plugins/enterprise_search/public/applications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IHeaderActionsProps>,
kibanaHeaderEl: HTMLElement,
externalUrl: IExternalUrl
) => {
ReactDOM.render(<HeaderActions externalUrl={externalUrl} />, kibanaHeaderEl);
return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
*/

export { WorkplaceSearchNav } from './nav';
export { WorkplaceSearchHeaderActions } from './kibana_header_actions';
Original file line number Diff line number Diff line change
@@ -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(<WorkplaceSearchHeaderActions externalUrl={externalUrl} />);

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(<WorkplaceSearchHeaderActions externalUrl={{} as any} />);

expect(wrapper.isEmptyRender()).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -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<IProps> = ({ externalUrl }) => {
const { enterpriseSearchUrl, getWorkplaceSearchUrl } = externalUrl;
if (!enterpriseSearchUrl) return null;

return (
<EuiButtonEmpty href={getWorkplaceSearchUrl('/search')} target="_blank" iconType="search">
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.headerActions.searchApplication', {
defaultMessage: 'Go to search application',
})}
</EuiButtonEmpty>
);
};
9 changes: 8 additions & 1 deletion x-pack/plugins/enterprise_search/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
});
Expand Down

0 comments on commit c95956f

Please sign in to comment.