[App Search] Set up Analytics router (#88095)

* [Setup] Analytics routes & page title consts

* Add AnalyticsRouter
- with TODO views

* Update EngineRouter to use AnalyticsRouter

+ minor rearranging of import order

+ update EngineNav to show active flag for subroutes

* [Polish] Add 404 fallback to Analytics subroutes

+ add custom breadcrumb trail prop to NotFound component

* [PR feedback] DRY out typing
This commit is contained in:
Constance 2021-01-13 11:08:20 -08:00 committed by GitHub
parent e07c541036
commit c1e21deac6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 12 deletions

View file

@ -0,0 +1,21 @@
/*
* 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 { Route, Switch } from 'react-router-dom';
import { AnalyticsRouter } from './';
describe('AnalyticsRouter', () => {
// Detailed route testing is better done via E2E tests
it('renders', () => {
const wrapper = shallow(<AnalyticsRouter engineBreadcrumb={['Engines', 'some-engine']} />);
expect(wrapper.find(Switch)).toHaveLength(1);
expect(wrapper.find(Route)).toHaveLength(8);
});
});

View file

@ -0,0 +1,72 @@
/*
* 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 { Route, Switch } from 'react-router-dom';
import { APP_SEARCH_PLUGIN } from '../../../../../common/constants';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { NotFound } from '../../../shared/not_found';
import {
ENGINE_PATH,
ENGINE_ANALYTICS_PATH,
ENGINE_ANALYTICS_TOP_QUERIES_PATH,
ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH,
ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH,
ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH,
ENGINE_ANALYTICS_RECENT_QUERIES_PATH,
ENGINE_ANALYTICS_QUERY_DETAIL_PATH,
} from '../../routes';
import {
ANALYTICS_TITLE,
TOP_QUERIES,
TOP_QUERIES_NO_RESULTS,
TOP_QUERIES_NO_CLICKS,
TOP_QUERIES_WITH_CLICKS,
RECENT_QUERIES,
} from './constants';
interface Props {
engineBreadcrumb: string[];
}
export const AnalyticsRouter: React.FC<Props> = ({ engineBreadcrumb }) => {
const ANALYTICS_BREADCRUMB = [...engineBreadcrumb, ANALYTICS_TITLE];
return (
<Switch>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_PATH}>
<SetPageChrome trail={ANALYTICS_BREADCRUMB} />
TODO: Analytics overview
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_TOP_QUERIES_PATH}>
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES]} />
TODO: Top queries
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH}>
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_NO_RESULTS]} />
TODO: Top queries with no results
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH}>
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_NO_CLICKS]} />
TODO: Top queries with no clicks
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH}>
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_WITH_CLICKS]} />
TODO: Top queries with clicks
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_RECENT_QUERIES_PATH}>
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, RECENT_QUERIES]} />
TODO: Recent queries
</Route>
<Route exact path={ENGINE_PATH + ENGINE_ANALYTICS_QUERY_DETAIL_PATH}>
TODO: Query detail page
</Route>
<Route>
<NotFound breadcrumbs={ANALYTICS_BREADCRUMB} product={APP_SEARCH_PLUGIN} />
</Route>
</Switch>
);
};

View file

@ -11,26 +11,46 @@ export const ANALYTICS_TITLE = i18n.translate(
{ defaultMessage: 'Analytics' }
);
// Total card titles
export const TOTAL_DOCUMENTS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments',
{ defaultMessage: 'Total documents' }
);
export const TOTAL_API_OPERATIONS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations',
{ defaultMessage: 'Total API operations' }
);
export const TOTAL_QUERIES = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries',
{ defaultMessage: 'Total queries' }
);
export const TOTAL_CLICKS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks',
{ defaultMessage: 'Total clicks' }
);
// Queries sub-pages
export const TOP_QUERIES = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesTitle',
{ defaultMessage: 'Top queries' }
);
export const TOP_QUERIES_NO_RESULTS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoResultsTitle',
{ defaultMessage: 'Top queries with no results' }
);
export const TOP_QUERIES_NO_CLICKS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoClicksTitle',
{ defaultMessage: 'Top queries with no clicks' }
);
export const TOP_QUERIES_WITH_CLICKS = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesWithClicksTitle',
{ defaultMessage: 'Top queries with clicks' }
);
export const RECENT_QUERIES = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesTitle',
{ defaultMessage: 'Recent queries' }
);
// Moment date format conversions
export const SERVER_DATE_FORMAT = 'YYYY-MM-DD';
export const TOOLTIP_DATE_FORMAT = 'MMMM D, YYYY';

View file

@ -5,5 +5,6 @@
*/
export { ANALYTICS_TITLE } from './constants';
export { AnalyticsRouter } from './analytics_router';
export { AnalyticsChart } from './components';
export { convertToChartData } from './utils';

View file

@ -103,7 +103,11 @@ export const EngineNav: React.FC = () => {
{OVERVIEW_TITLE}
</SideNavLink>
{canViewEngineAnalytics && (
<SideNavLink to={engineRoute + ENGINE_ANALYTICS_PATH} data-test-subj="EngineAnalyticsLink">
<SideNavLink
to={engineRoute + ENGINE_ANALYTICS_PATH}
shouldShowActiveForSubroutes={true}
data-test-subj="EngineAnalyticsLink"
>
{ANALYTICS_TITLE}
</SideNavLink>
)}

View file

@ -19,6 +19,7 @@ import { setQueuedErrorMessage } from '../../../shared/flash_messages';
import { Loading } from '../../../shared/loading';
import { EngineOverview } from '../engine_overview';
import { AnalyticsRouter } from '../analytics';
import { EngineRouter } from './';
@ -93,6 +94,6 @@ describe('EngineRouter', () => {
setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } });
const wrapper = shallow(<EngineRouter />);
expect(wrapper.find('[data-test-subj="AnalyticsTODO"]')).toHaveLength(1);
expect(wrapper.find(AnalyticsRouter)).toHaveLength(1);
});
});

View file

@ -33,13 +33,13 @@ import {
} from '../../routes';
import { ENGINES_TITLE } from '../engines';
import { OVERVIEW_TITLE } from '../engine_overview';
import { ANALYTICS_TITLE } from '../analytics';
import { Loading } from '../../../shared/loading';
import { EngineOverview } from '../engine_overview';
import { AnalyticsRouter } from '../analytics';
import { DocumentDetail, Documents } from '../documents';
import { EngineLogic } from './';
import { DocumentDetail, Documents } from '../documents';
export const EngineRouter: React.FC = () => {
const {
@ -87,8 +87,7 @@ export const EngineRouter: React.FC = () => {
<Switch>
{canViewEngineAnalytics && (
<Route path={ENGINE_PATH + ENGINE_ANALYTICS_PATH}>
<SetPageChrome trail={[...engineBreadcrumb, ANALYTICS_TITLE]} />
<div data-test-subj="AnalyticsTODO">Just testing right now</div>
<AnalyticsRouter engineBreadcrumb={engineBreadcrumb} />
</Route>
)}
<Route path={ENGINE_PATH + ENGINE_DOCUMENT_DETAIL_PATH}>

View file

@ -25,7 +25,12 @@ export const SAMPLE_ENGINE_PATH = '/engines/national-parks-demo';
export const getEngineRoute = (engineName: string) => generatePath(ENGINE_PATH, { engineName });
export const ENGINE_ANALYTICS_PATH = '/analytics';
// TODO: Analytics sub-pages
export const ENGINE_ANALYTICS_TOP_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries`;
export const ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_clicks`;
export const ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_results`;
export const ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_with_clicks`;
export const ENGINE_ANALYTICS_RECENT_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/recent_queries`;
export const ENGINE_ANALYTICS_QUERY_DETAIL_PATH = `${ENGINE_ANALYTICS_PATH}/query_detail/:query`;
export const ENGINE_DOCUMENTS_PATH = '/documents';
export const ENGINE_DOCUMENT_DETAIL_PATH = `${ENGINE_DOCUMENTS_PATH}/:documentId`;

View file

@ -13,6 +13,7 @@ import { shallow } from 'enzyme';
import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../common/constants';
import { SetAppSearchChrome } from '../kibana_chrome';
import { AppSearchLogo } from './assets/app_search_logo';
import { WorkplaceSearchLogo } from './assets/workplace_search_logo';
@ -51,6 +52,14 @@ describe('NotFound', () => {
expect(prompt.find(EuiButtonExternal).prop('href')).toEqual('https://support.elastic.co');
});
it('passes down optional custom breadcrumbs', () => {
const wrapper = shallow(
<NotFound product={APP_SEARCH_PLUGIN} breadcrumbs={['Hello', 'World']} />
);
expect(wrapper.find(SetAppSearchChrome).prop('trail')).toEqual(['Hello', 'World']);
});
it('does not render anything without a valid product', () => {
const wrapper = shallow(<NotFound product={undefined as any} />);

View file

@ -23,6 +23,7 @@ import {
} from '../../../../common/constants';
import { EuiButtonTo } from '../react_router_helpers';
import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs';
import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome';
import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry';
import { LicensingLogic } from '../licensing';
@ -37,9 +38,11 @@ interface NotFoundProps {
ID: string;
SUPPORT_URL: string;
};
// Optional breadcrumbs
breadcrumbs?: BreadcrumbTrail;
}
export const NotFound: React.FC<NotFoundProps> = ({ product = {} }) => {
export const NotFound: React.FC<NotFoundProps> = ({ product = {}, breadcrumbs }) => {
const { hasGoldLicense } = useValues(LicensingLogic);
const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL;
@ -64,7 +67,7 @@ export const NotFound: React.FC<NotFoundProps> = ({ product = {} }) => {
return (
<>
<SetPageChrome />
<SetPageChrome trail={breadcrumbs} />
<SendTelemetry action="error" metric="not_found" />
<EuiPageContent>