[Enterprise Search][tech debt] Refactor external URL generation to a single DRY helper (#75093)

* Set up App Search routes constants file

* Create new helper/generator for external URLs

* Update plugin to pass externalUrl helper in KibanaContext

* Update AS & WS navs to use new external url generator

* Update App Search views to use new externalUrl.getAppSearchUrl helper

* Update Workplace Search to use externalUrl.getWorkplaceSearchUrl helper

+ remove old useRoutes.getWSRoute helper, which was the inspiration for this

* Rename top-level enterpriseSearchUrl to config.host

- This allows us to more clearly separate concerns between the URL set in config.host and externalUrl.enterpriseSearchUrl (used for front-end links, may be a vanity/public URL)
- This change also enables us to not mutate Kibana's config obj, which is much cleaner

Misc tech debt:
- Reorder renderApp args (from least to most likely to change)
- Rename our public url methods/vars to more generic "init data" names - this is in preparation for upcoming changes where we pull more than just external_url from our client endpoint

* Fix broken Workplace Search nav links

- that require a hash for routing to work

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Constance 2020-08-17 09:37:21 -07:00 committed by GitHub
parent 4ac0e81554
commit 0b28dc2f48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 212 additions and 111 deletions

View file

@ -5,6 +5,7 @@
*/
import { httpServiceMock } from 'src/core/public/mocks';
import { ExternalUrl } from '../shared/enterprise_search_url';
/**
* A set of default Kibana context values to use across component tests.
@ -14,5 +15,6 @@ export const mockKibanaContext = {
http: httpServiceMock.createSetupContract(),
setBreadcrumbs: jest.fn(),
setDocTitle: jest.fn(),
enterpriseSearchUrl: 'http://localhost:3002',
config: { host: 'http://localhost:3002' },
externalUrl: new ExternalUrl('http://localhost:3002'),
};

View file

@ -21,7 +21,7 @@ import { mockLicenseContext } from './license_context.mock';
*
* Example usage:
*
* const wrapper = mountWithContext(<Component />, { enterpriseSearchUrl: 'someOverride', license: {} });
* const wrapper = mountWithContext(<Component />, { config: { host: 'someOverride' } });
*/
export const mountWithContext = (children: React.ReactNode, context?: object) => {
return mount(

View file

@ -35,6 +35,6 @@ jest.mock('react', () => ({
* // ... etc.
*
* it('some test', () => {
* useContext.mockImplementationOnce(() => ({ enterpriseSearchUrl: 'someOverride' }));
* useContext.mockImplementationOnce(() => ({ config: { host: 'someOverride' } }));
* });
*/

View file

@ -11,16 +11,20 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { sendTelemetry } from '../../../shared/telemetry';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { KibanaContext, IKibanaContext } from '../../../index';
import { CREATE_ENGINES_PATH } from '../../routes';
import { EngineOverviewHeader } from '../engine_overview_header';
import './empty_states.scss';
export const EmptyState: React.FC = () => {
const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
const {
externalUrl: { getAppSearchUrl },
http,
} = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
href: `${enterpriseSearchUrl}/as/engines/new`,
href: getAppSearchUrl(CREATE_ENGINES_PATH),
target: '_blank',
onClick: () =>
sendTelemetry({

View file

@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { getEngineRoute } from '../../routes';
import { ENGINES_PAGE_SIZE } from '../../../../../common/constants';
@ -39,9 +40,13 @@ export const EngineTable: React.FC<IEngineTableProps> = ({
data,
pagination: { totalEngines, pageIndex, onPaginate },
}) => {
const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
const {
externalUrl: { getAppSearchUrl },
http,
} = useContext(KibanaContext) as IKibanaContext;
const engineLinkProps = (name: string) => ({
href: `${enterpriseSearchUrl}/as/engines/${name}`,
href: getAppSearchUrl(getEngineRoute(name)),
target: '_blank',
onClick: () =>
sendTelemetry({

View file

@ -19,13 +19,16 @@ import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
export const EngineOverviewHeader: React.FC = () => {
const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
const {
externalUrl: { getAppSearchUrl },
http,
} = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
fill: true,
iconType: 'popout',
'data-test-subj': 'launchButton',
href: `${enterpriseSearchUrl}/as`,
href: getAppSearchUrl(),
target: '_blank',
onClick: () =>
sendTelemetry({

View file

@ -20,8 +20,8 @@ describe('AppSearch', () => {
expect(wrapper.find(Layout)).toHaveLength(1);
});
it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({ enterpriseSearchUrl: '' }));
it('redirects to Setup Guide when config.host is not set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } }));
const wrapper = shallow(<AppSearch />);
expect(wrapper.find(Redirect)).toHaveLength(1);

View file

@ -13,19 +13,29 @@ import { APP_SEARCH_PLUGIN } from '../../../common/constants';
import { KibanaContext, IKibanaContext } from '../index';
import { Layout, SideNav, SideNavLink } from '../shared/layout';
import {
ROOT_PATH,
SETUP_GUIDE_PATH,
SETTINGS_PATH,
CREDENTIALS_PATH,
ROLE_MAPPINGS_PATH,
ENGINES_PATH,
} from './routes';
import { SetupGuide } from './components/setup_guide';
import { EngineOverview } from './components/engine_overview';
export const AppSearch: React.FC = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
if (!enterpriseSearchUrl)
const { config } = useContext(KibanaContext) as IKibanaContext;
if (!config.host)
return (
<Switch>
<Route exact path="/setup_guide">
<Route exact path={SETUP_GUIDE_PATH}>
<SetupGuide />
</Route>
<Route>
<Redirect to="/setup_guide" />
<Redirect to={SETUP_GUIDE_PATH} />
<SetupGuide /> {/* Kibana displays a blank page on redirect if this isn't included */}
</Route>
</Switch>
@ -33,17 +43,17 @@ export const AppSearch: React.FC = () => {
return (
<Switch>
<Route exact path="/setup_guide">
<Route exact path={SETUP_GUIDE_PATH}>
<SetupGuide />
</Route>
<Route>
<Layout navigation={<AppSearchNav />}>
<Switch>
<Route exact path="/">
<Route exact path={ROOT_PATH}>
{/* For some reason a Redirect to /engines just doesn't work here - it shows a blank page */}
<EngineOverview />
</Route>
<Route exact path="/engines">
<Route exact path={ENGINES_PATH}>
<EngineOverview />
</Route>
</Switch>
@ -54,27 +64,28 @@ export const AppSearch: React.FC = () => {
};
export const AppSearchNav: React.FC = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
const externalUrl = `${enterpriseSearchUrl}/as#`;
const {
externalUrl: { getAppSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
return (
<SideNav product={APP_SEARCH_PLUGIN}>
<SideNavLink to="/engines" isRoot>
<SideNavLink to={ENGINES_PATH} isRoot>
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.engines', {
defaultMessage: 'Engines',
})}
</SideNavLink>
<SideNavLink isExternal to={`${externalUrl}/settings/account`}>
<SideNavLink isExternal to={getAppSearchUrl(SETTINGS_PATH)}>
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.settings', {
defaultMessage: 'Account Settings',
})}
</SideNavLink>
<SideNavLink isExternal to={`${externalUrl}/credentials`}>
<SideNavLink isExternal to={getAppSearchUrl(CREDENTIALS_PATH)}>
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.credentials', {
defaultMessage: 'Credentials',
})}
</SideNavLink>
<SideNavLink isExternal to={`${externalUrl}/role-mappings`}>
<SideNavLink isExternal to={getAppSearchUrl(ROLE_MAPPINGS_PATH)}>
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.roleMappings', {
defaultMessage: 'Role Mappings',
})}

View file

@ -0,0 +1,17 @@
/*
* 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 const ROOT_PATH = '/';
export const SETUP_GUIDE_PATH = '/setup_guide';
export const SETTINGS_PATH = '/settings/account';
export const CREDENTIALS_PATH = '/credentials';
export const ROLE_MAPPINGS_PATH = '#/role-mappings'; // This page seems to 404 if the # isn't included
export const ENGINES_PATH = '/engines';
export const CREATE_ENGINES_PATH = `${ENGINES_PATH}/new`;
export const ENGINE_PATH = '/engines/:engineName';
export const getEngineRoute = (engineName: string) => `${ENGINES_PATH}/${engineName}`;

View file

@ -17,10 +17,11 @@ import { WorkplaceSearch } from './workplace_search';
describe('renderApp', () => {
let params: AppMountParameters;
const core = coreMock.createStart();
const config = {};
const plugins = {
licensing: licensingMock.createSetup(),
} as any;
const config = {};
const data = {} as any;
beforeEach(() => {
jest.clearAllMocks();
@ -30,19 +31,19 @@ describe('renderApp', () => {
it('mounts and unmounts UI', () => {
const MockApp = () => <div className="hello-world">Hello world!</div>;
const unmount = renderApp(MockApp, core, params, config, plugins);
const unmount = renderApp(MockApp, params, core, plugins, config, data);
expect(params.element.querySelector('.hello-world')).not.toBeNull();
unmount();
expect(params.element.innerHTML).toEqual('');
});
it('renders AppSearch', () => {
renderApp(AppSearch, core, params, config, plugins);
renderApp(AppSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
});
it('renders WorkplaceSearch', () => {
renderApp(WorkplaceSearch, core, params, config, plugins);
renderApp(WorkplaceSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
});
});

View file

@ -10,11 +10,13 @@ import { Router } from 'react-router-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { CoreStart, AppMountParameters, HttpSetup, ChromeBreadcrumb } from 'src/core/public';
import { ClientConfigType, PluginsSetup } from '../plugin';
import { ClientConfigType, ClientData, PluginsSetup } from '../plugin';
import { LicenseProvider } from './shared/licensing';
import { IExternalUrl } from './shared/enterprise_search_url';
export interface IKibanaContext {
enterpriseSearchUrl?: string;
config: { host?: string };
externalUrl: IExternalUrl;
http: HttpSetup;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setDocTitle(title: string): void;
@ -30,17 +32,19 @@ export const KibanaContext = React.createContext({});
export const renderApp = (
App: React.FC,
core: CoreStart,
params: AppMountParameters,
core: CoreStart,
plugins: PluginsSetup,
config: ClientConfigType,
plugins: PluginsSetup
data: ClientData
) => {
ReactDOM.render(
<I18nProvider>
<KibanaContext.Provider
value={{
config,
http: core.http,
enterpriseSearchUrl: config.host,
externalUrl: data.externalUrl,
setBreadcrumbs: core.chrome.setBreadcrumbs,
setDocTitle: core.chrome.docTitle.change,
}}

View file

@ -0,0 +1,25 @@
/*
* 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 { ExternalUrl } from './';
describe('Enterprise Search external URL helper', () => {
const externalUrl = new ExternalUrl('http://localhost:3002');
it('exposes a public enterpriseSearchUrl string', () => {
expect(externalUrl.enterpriseSearchUrl).toEqual('http://localhost:3002');
});
it('generates a public App Search URL', () => {
expect(externalUrl.getAppSearchUrl()).toEqual('http://localhost:3002/as');
expect(externalUrl.getAppSearchUrl('/path')).toEqual('http://localhost:3002/as/path');
});
it('generates a public Workplace Search URL', () => {
expect(externalUrl.getWorkplaceSearchUrl()).toEqual('http://localhost:3002/ws');
expect(externalUrl.getWorkplaceSearchUrl('/path')).toEqual('http://localhost:3002/ws/path');
});
});

View file

@ -0,0 +1,38 @@
/*
* 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.
*/
/**
* Small helper for generating external public-facing URLs
* to the legacy/standalone Enterprise Search app
*/
export interface IExternalUrl {
enterpriseSearchUrl?: string;
getAppSearchUrl(path?: string): string;
getWorkplaceSearchUrl(path?: string): string;
}
export class ExternalUrl {
public enterpriseSearchUrl: string;
constructor(externalUrl: string) {
this.enterpriseSearchUrl = externalUrl;
this.getAppSearchUrl = this.getAppSearchUrl.bind(this);
this.getWorkplaceSearchUrl = this.getWorkplaceSearchUrl.bind(this);
}
private getExternalUrl(path: string): string {
return this.enterpriseSearchUrl + path;
}
public getAppSearchUrl(path: string = ''): string {
return this.getExternalUrl('/as' + path);
}
public getWorkplaceSearchUrl(path: string = ''): string {
return this.getExternalUrl('/ws' + path);
}
}

View file

@ -5,3 +5,4 @@
*/
export { getPublicUrl } from './get_enterprise_search_url';
export { ExternalUrl, IExternalUrl } from './generate_external_url';

View file

@ -14,7 +14,7 @@ import { KibanaContext, IKibanaContext } from '../../index';
import './error_state_prompt.scss';
export const ErrorStatePrompt: React.FC = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
const { config } = useContext(KibanaContext) as IKibanaContext;
return (
<EuiEmptyPrompt
@ -36,7 +36,7 @@ export const ErrorStatePrompt: React.FC = () => {
id="xpack.enterpriseSearch.errorConnectingState.description1"
defaultMessage="We cant establish a connection to Enterprise Search at the host URL: {enterpriseSearchUrl}"
values={{
enterpriseSearchUrl: <EuiCode>{enterpriseSearchUrl}</EuiCode>,
enterpriseSearchUrl: <EuiCode>{config.host}</EuiCode>,
}}
/>
</p>

View file

@ -22,8 +22,9 @@ import {
} from '../../routes';
export const WorkplaceSearchNav: React.FC = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
const legacyUrl = (path: string) => `${enterpriseSearchUrl}/ws#${path}`;
const {
externalUrl: { getWorkplaceSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
// TODO: icons
return (
@ -33,38 +34,38 @@ export const WorkplaceSearchNav: React.FC = () => {
defaultMessage: 'Overview',
})}
</SideNavLink>
<SideNavLink isExternal to={legacyUrl(ORG_SOURCES_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(ORG_SOURCES_PATH)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.sources', {
defaultMessage: 'Sources',
})}
</SideNavLink>
<SideNavLink isExternal to={legacyUrl(GROUPS_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(`#${GROUPS_PATH}`)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', {
defaultMessage: 'Groups',
})}
</SideNavLink>
<SideNavLink isExternal to={legacyUrl(ROLE_MAPPINGS_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(`#${ROLE_MAPPINGS_PATH}`)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
defaultMessage: 'Role Mappings',
})}
</SideNavLink>
<SideNavLink isExternal to={legacyUrl(SECURITY_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(`#${SECURITY_PATH}`)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
defaultMessage: 'Security',
})}
</SideNavLink>
<SideNavLink isExternal to={legacyUrl(ORG_SETTINGS_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(ORG_SETTINGS_PATH)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.settings', {
defaultMessage: 'Settings',
})}
</SideNavLink>
<EuiSpacer />
<SideNavLink isExternal to={legacyUrl(SOURCES_PATH)}>
<SideNavLink isExternal to={getWorkplaceSearchUrl(`#${SOURCES_PATH}`)}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard', {
defaultMessage: 'View my personal dashboard',
})}
</SideNavLink>
<SideNavLink isExternal to={`${enterpriseSearchUrl}/ws/search`}>
<SideNavLink isExternal to={getWorkplaceSearchUrl('/search')}>
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.search', {
defaultMessage: 'Go to search application',
})}

View file

@ -17,7 +17,6 @@ import {
EuiButtonEmptyProps,
EuiLinkProps,
} from '@elastic/eui';
import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
@ -40,8 +39,10 @@ export const OnboardingCard: React.FC<IOnboardingCardProps> = ({
actionPath,
complete,
}) => {
const { http } = useContext(KibanaContext) as IKibanaContext;
const { getWSRoute } = useRoutes();
const {
http,
externalUrl: { getWorkplaceSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@ -53,7 +54,7 @@ export const OnboardingCard: React.FC<IOnboardingCardProps> = ({
const buttonActionProps = actionPath
? {
onClick,
href: getWSRoute(actionPath),
href: getWorkplaceSearchUrl(actionPath),
target: '_blank',
'data-test-subj': testSubj,
}

View file

@ -22,7 +22,6 @@ import {
EuiLinkProps,
} from '@elastic/eui';
import sharedSourcesIcon from '../shared/assets/share_circle.svg';
import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes';
@ -133,8 +132,10 @@ export const OnboardingSteps: React.FC = () => {
};
export const OrgNameOnboarding: React.FC = () => {
const { http } = useContext(KibanaContext) as IKibanaContext;
const { getWSRoute } = useRoutes();
const {
http,
externalUrl: { getWorkplaceSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@ -148,7 +149,7 @@ export const OrgNameOnboarding: React.FC = () => {
onClick,
target: '_blank',
color: 'primary',
href: getWSRoute(ORG_SETTINGS_PATH),
href: getWorkplaceSearchUrl(ORG_SETTINGS_PATH),
'data-test-subj': 'orgNameChangeButton',
} as EuiButtonEmptyProps & EuiLinkProps;

View file

@ -13,7 +13,6 @@ import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiSpacer, EuiLinkProps } from '@ela
import { FormattedMessage } from '@kbn/i18n/react';
import { ContentSection } from '../shared/content_section';
import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { getSourcePath } from '../../routes';
@ -92,8 +91,10 @@ export const RecentActivityItem: React.FC<IFeedActivity> = ({
timestamp,
sourceId,
}) => {
const { http } = useContext(KibanaContext) as IKibanaContext;
const { getWSRoute } = useRoutes();
const {
http,
externalUrl: { getWorkplaceSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@ -106,7 +107,7 @@ export const RecentActivityItem: React.FC<IFeedActivity> = ({
const linkProps = {
onClick,
target: '_blank',
href: getWSRoute(getSourcePath(sourceId)),
href: getWorkplaceSearchUrl(getSourcePath(sourceId)),
external: true,
color: status === 'error' ? 'danger' : 'primary',
'data-test-subj': 'viewSourceDetailsLink',

View file

@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { useContext } from 'react';
import { EuiCard, EuiFlexItem, EuiTitle, EuiTextColor } from '@elastic/eui';
import { useRoutes } from '../shared/use_routes';
import { KibanaContext, IKibanaContext } from '../../../index';
interface IStatisticCardProps {
title: string;
@ -17,11 +17,13 @@ interface IStatisticCardProps {
}
export const StatisticCard: React.FC<IStatisticCardProps> = ({ title, count = 0, actionPath }) => {
const { getWSRoute } = useRoutes();
const {
externalUrl: { getWorkplaceSearchUrl },
} = useContext(KibanaContext) as IKibanaContext;
const linkProps = actionPath
? {
href: getWSRoute(actionPath),
href: getWorkplaceSearchUrl(actionPath),
target: '_blank',
rel: 'noopener',
}

View file

@ -13,14 +13,17 @@ import { sendTelemetry } from '../../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../../index';
export const ProductButton: React.FC = () => {
const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
const {
externalUrl: { getWorkplaceSearchUrl },
http,
} = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
fill: true,
iconType: 'popout',
'data-test-subj': 'launchButton',
} as EuiButtonProps & EuiLinkProps;
buttonProps.href = `${enterpriseSearchUrl}/ws`;
buttonProps.href = getWorkplaceSearchUrl();
buttonProps.target = '_blank';
buttonProps.onClick = () =>
sendTelemetry({

View file

@ -1,7 +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.
*/
export { useRoutes } from './use_routes';

View file

@ -1,15 +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 { useContext } from 'react';
import { KibanaContext, IKibanaContext } from '../../../../index';
export const useRoutes = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
const getWSRoute = (path: string): string => `${enterpriseSearchUrl}/ws${path}`;
return { getWSRoute };
};

View file

@ -10,7 +10,6 @@ import React, { useContext } from 'react';
import { Redirect } from 'react-router-dom';
import { shallow } from 'enzyme';
import { SetupGuide } from './components/setup_guide';
import { Overview } from './components/overview';
import { WorkplaceSearch } from './';
@ -18,16 +17,18 @@ import { WorkplaceSearch } from './';
describe('Workplace Search', () => {
describe('/', () => {
it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({ enterpriseSearchUrl: '' }));
(useContext as jest.Mock).mockImplementationOnce(() => ({
config: { host: '' },
}));
const wrapper = shallow(<WorkplaceSearch />);
expect(wrapper.find(Redirect)).toHaveLength(1);
expect(wrapper.find(Overview)).toHaveLength(0);
});
it('renders Engine Overview when enterpriseSearchUrl is set', () => {
it('renders the Overview when enterpriseSearchUrl is set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({
enterpriseSearchUrl: 'https://foo.bar',
config: { host: 'https://foo.bar' },
}));
const wrapper = shallow(<WorkplaceSearch />);
@ -35,12 +36,4 @@ describe('Workplace Search', () => {
expect(wrapper.find(Redirect)).toHaveLength(0);
});
});
describe('/setup_guide', () => {
it('renders', () => {
const wrapper = shallow(<WorkplaceSearch />);
expect(wrapper.find(SetupGuide)).toHaveLength(1);
});
});
});

View file

@ -24,8 +24,8 @@ import { SetupGuide } from './components/setup_guide';
import { Overview } from './components/overview';
export const WorkplaceSearch: React.FC = () => {
const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
if (!enterpriseSearchUrl)
const { config } = useContext(KibanaContext) as IKibanaContext;
if (!config.host)
return (
<Switch>
<Route exact path={SETUP_GUIDE_PATH}>

View file

@ -21,13 +21,21 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { LicensingPluginSetup } from '../../licensing/public';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../common/constants';
import { getPublicUrl } from './applications/shared/enterprise_search_url';
import {
getPublicUrl,
ExternalUrl,
IExternalUrl,
} from './applications/shared/enterprise_search_url';
import AppSearchLogo from './applications/app_search/assets/logo.svg';
import WorkplaceSearchLogo from './applications/workplace_search/assets/logo.svg';
export interface ClientConfigType {
host?: string;
}
export interface ClientData {
externalUrl: IExternalUrl;
}
export interface PluginsSetup {
home: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
@ -35,15 +43,15 @@ export interface PluginsSetup {
export class EnterpriseSearchPlugin implements Plugin {
private config: ClientConfigType;
private hasCheckedPublicUrl: boolean = false;
private hasInitialized: boolean = false;
private data: ClientData = {} as ClientData;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<ClientConfigType>();
this.data.externalUrl = new ExternalUrl(this.config.host || '');
}
public setup(core: CoreSetup, plugins: PluginsSetup) {
const config = { host: this.config.host };
core.application.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
@ -54,12 +62,12 @@ export class EnterpriseSearchPlugin implements Plugin {
const { chrome } = coreStart;
chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
await this.setPublicUrl(config, coreStart.http);
await this.getInitialData(coreStart.http);
const { renderApp } = await import('./applications');
const { AppSearch } = await import('./applications/app_search');
return renderApp(AppSearch, coreStart, params, config, plugins);
return renderApp(AppSearch, params, coreStart, plugins, this.config, this.data);
},
});
@ -73,12 +81,12 @@ export class EnterpriseSearchPlugin implements Plugin {
const { chrome } = coreStart;
chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME);
await this.setPublicUrl(config, coreStart.http);
await this.getInitialData(coreStart.http);
const { renderApp } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
return renderApp(WorkplaceSearch, coreStart, params, config, plugins);
return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data);
},
});
@ -107,12 +115,14 @@ export class EnterpriseSearchPlugin implements Plugin {
public stop() {}
private async setPublicUrl(config: ClientConfigType, http: HttpSetup) {
if (!config.host) return; // No API to check
if (this.hasCheckedPublicUrl) return; // We've already performed the check
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
// TODO: Rename to something more generic once we start fetching more data than just external_url from this endpoint
const publicUrl = await getPublicUrl(http);
if (publicUrl) config.host = publicUrl;
this.hasCheckedPublicUrl = true;
if (publicUrl) this.data.externalUrl = new ExternalUrl(publicUrl);
this.hasInitialized = true;
}
}