mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
4ac0e81554
commit
0b28dc2f48
26 changed files with 212 additions and 111 deletions
|
@ -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'),
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -35,6 +35,6 @@ jest.mock('react', () => ({
|
|||
* // ... etc.
|
||||
*
|
||||
* it('some test', () => {
|
||||
* useContext.mockImplementationOnce(() => ({ enterpriseSearchUrl: 'someOverride' }));
|
||||
* useContext.mockImplementationOnce(() => ({ config: { host: 'someOverride' } }));
|
||||
* });
|
||||
*/
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
})}
|
||||
|
|
|
@ -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}`;
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -5,3 +5,4 @@
|
|||
*/
|
||||
|
||||
export { getPublicUrl } from './get_enterprise_search_url';
|
||||
export { ExternalUrl, IExternalUrl } from './generate_external_url';
|
||||
|
|
|
@ -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 can’t establish a connection to Enterprise Search at the host URL: {enterpriseSearchUrl}"
|
||||
values={{
|
||||
enterpriseSearchUrl: <EuiCode>{enterpriseSearchUrl}</EuiCode>,
|
||||
enterpriseSearchUrl: <EuiCode>{config.host}</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
@ -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',
|
||||
})}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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';
|
|
@ -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 };
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue