[App Search] Convert Analytics views to new page template (#102851)
* Convert AnalyticsHeader to AnalyticsFilters - it's basically the same component as before, but without the title section/log retention tooltip, since the header/title will be handled by the new page template * Update AnalyticsLayout to use new page template + add new test_helper for header children * Update breadcrumb behavior - Set analytic breadcrumbs in AnalyticsLayout rather than AnalyticsRouter - Update individual views to pass breadcrumbs (consistent with new page template API) * Update router
This commit is contained in:
parent
fd0c1fa490
commit
00a9f84951
18 changed files with 199 additions and 242 deletions
|
@ -8,18 +8,22 @@
|
|||
import '../../../__mocks__/shallow_useeffect.mock';
|
||||
import { mockKibanaValues, setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../__mocks__/react_router';
|
||||
import '../../__mocks__/engine_logic.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { FlashMessages } from '../../../shared/flash_messages';
|
||||
import { Loading } from '../../../shared/loading';
|
||||
import { rerender } from '../../../test_helpers';
|
||||
import { LogRetentionCallout } from '../log_retention';
|
||||
import {
|
||||
rerender,
|
||||
getPageTitle,
|
||||
getPageHeaderActions,
|
||||
getPageHeaderChildren,
|
||||
} from '../../../test_helpers';
|
||||
import { LogRetentionTooltip, LogRetentionCallout } from '../log_retention';
|
||||
|
||||
import { AnalyticsLayout } from './analytics_layout';
|
||||
import { AnalyticsHeader } from './components';
|
||||
import { AnalyticsFilters } from './components';
|
||||
|
||||
describe('AnalyticsLayout', () => {
|
||||
const { history } = mockKibanaValues;
|
||||
|
@ -47,18 +51,20 @@ describe('AnalyticsLayout', () => {
|
|||
</AnalyticsLayout>
|
||||
);
|
||||
|
||||
expect(wrapper.find(FlashMessages)).toHaveLength(1);
|
||||
expect(wrapper.find(LogRetentionCallout)).toHaveLength(1);
|
||||
expect(getPageHeaderActions(wrapper).find(LogRetentionTooltip)).toHaveLength(1);
|
||||
expect(getPageHeaderChildren(wrapper).find(AnalyticsFilters)).toHaveLength(1);
|
||||
|
||||
expect(wrapper.find(AnalyticsHeader).prop('title')).toEqual('Hello');
|
||||
expect(getPageTitle(wrapper)).toEqual('Hello');
|
||||
expect(wrapper.find('[data-test-subj="world"]').text()).toEqual('World!');
|
||||
|
||||
expect(wrapper.prop('pageChrome')).toEqual(['Engines', 'some-engine', 'Analytics']);
|
||||
});
|
||||
|
||||
it('renders a loading component if data is not done loading', () => {
|
||||
setMockValues({ ...values, dataLoading: true });
|
||||
const wrapper = shallow(<AnalyticsLayout title="" />);
|
||||
it('passes analytics breadcrumbs', () => {
|
||||
const wrapper = shallow(<AnalyticsLayout title="Some page" breadcrumbs={['Queries']} />);
|
||||
|
||||
expect(wrapper.type()).toEqual(Loading);
|
||||
expect(wrapper.prop('pageChrome')).toEqual(['Engines', 'some-engine', 'Analytics', 'Queries']);
|
||||
});
|
||||
|
||||
describe('data loading', () => {
|
||||
|
|
|
@ -10,25 +10,27 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { FlashMessages } from '../../../shared/flash_messages';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { Loading } from '../../../shared/loading';
|
||||
import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs';
|
||||
import { getEngineBreadcrumbs } from '../engine';
|
||||
import { AppSearchPageTemplate } from '../layout';
|
||||
|
||||
import { LogRetentionCallout, LogRetentionOptions } from '../log_retention';
|
||||
import { LogRetentionTooltip, LogRetentionCallout, LogRetentionOptions } from '../log_retention';
|
||||
|
||||
import { AnalyticsHeader } from './components';
|
||||
import { AnalyticsFilters } from './components';
|
||||
import { ANALYTICS_TITLE } from './constants';
|
||||
|
||||
import { AnalyticsLogic } from './';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
breadcrumbs?: BreadcrumbTrail;
|
||||
isQueryView?: boolean;
|
||||
isAnalyticsView?: boolean;
|
||||
}
|
||||
export const AnalyticsLayout: React.FC<Props> = ({
|
||||
title,
|
||||
breadcrumbs = [],
|
||||
isQueryView,
|
||||
isAnalyticsView,
|
||||
children,
|
||||
|
@ -43,15 +45,21 @@ export const AnalyticsLayout: React.FC<Props> = ({
|
|||
if (isAnalyticsView) loadAnalyticsData();
|
||||
}, [history.location.search]);
|
||||
|
||||
if (dataLoading) return <Loading />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnalyticsHeader title={title} />
|
||||
<FlashMessages />
|
||||
<AppSearchPageTemplate
|
||||
pageChrome={getEngineBreadcrumbs([ANALYTICS_TITLE, ...breadcrumbs])}
|
||||
pageHeader={{
|
||||
pageTitle: title,
|
||||
rightSideItems: [
|
||||
<LogRetentionTooltip type={LogRetentionOptions.Analytics} position="left" />,
|
||||
],
|
||||
children: <AnalyticsFilters />,
|
||||
responsive: false,
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<LogRetentionCallout type={LogRetentionOptions.Analytics} />
|
||||
{children}
|
||||
<EuiSpacer />
|
||||
</>
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ import React from 'react';
|
|||
import { Route, Switch, Redirect } 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_ANALYTICS_PATH,
|
||||
|
@ -23,14 +22,7 @@ import {
|
|||
} from '../../routes';
|
||||
import { generateEnginePath, getEngineBreadcrumbs } from '../engine';
|
||||
|
||||
import {
|
||||
ANALYTICS_TITLE,
|
||||
TOP_QUERIES,
|
||||
TOP_QUERIES_NO_RESULTS,
|
||||
TOP_QUERIES_NO_CLICKS,
|
||||
TOP_QUERIES_WITH_CLICKS,
|
||||
RECENT_QUERIES,
|
||||
} from './constants';
|
||||
import { ANALYTICS_TITLE } from './constants';
|
||||
import {
|
||||
Analytics,
|
||||
TopQueries,
|
||||
|
@ -42,42 +34,37 @@ import {
|
|||
} from './views';
|
||||
|
||||
export const AnalyticsRouter: React.FC = () => {
|
||||
const ANALYTICS_BREADCRUMB = getEngineBreadcrumbs([ANALYTICS_TITLE]);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path={ENGINE_ANALYTICS_PATH}>
|
||||
<SetPageChrome trail={ANALYTICS_BREADCRUMB} />
|
||||
<Analytics />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_TOP_QUERIES_PATH}>
|
||||
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES]} />
|
||||
<TopQueries />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH}>
|
||||
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_NO_RESULTS]} />
|
||||
<TopQueriesNoResults />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH}>
|
||||
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_NO_CLICKS]} />
|
||||
<TopQueriesNoClicks />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH}>
|
||||
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, TOP_QUERIES_WITH_CLICKS]} />
|
||||
<TopQueriesWithClicks />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_RECENT_QUERIES_PATH}>
|
||||
<SetPageChrome trail={[...ANALYTICS_BREADCRUMB, RECENT_QUERIES]} />
|
||||
<RecentQueries />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_QUERY_DETAIL_PATH}>
|
||||
<QueryDetail breadcrumbs={ANALYTICS_BREADCRUMB} />
|
||||
<QueryDetail />
|
||||
</Route>
|
||||
<Route exact path={ENGINE_ANALYTICS_QUERY_DETAILS_PATH}>
|
||||
<Redirect to={generateEnginePath(ENGINE_ANALYTICS_PATH)} />
|
||||
</Route>
|
||||
<Route>
|
||||
<NotFound breadcrumbs={ANALYTICS_BREADCRUMB} product={APP_SEARCH_PLUGIN} />
|
||||
<NotFound
|
||||
breadcrumbs={getEngineBreadcrumbs([ANALYTICS_TITLE])}
|
||||
product={APP_SEARCH_PLUGIN}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -12,15 +12,13 @@ import React, { ReactElement } from 'react';
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
||||
import { EuiPageHeader, EuiSelect, EuiDatePickerRange, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { LogRetentionTooltip } from '../../log_retention';
|
||||
import { EuiSelect, EuiDatePickerRange, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { DEFAULT_START_DATE, DEFAULT_END_DATE } from '../constants';
|
||||
|
||||
import { AnalyticsHeader } from './';
|
||||
import { AnalyticsFilters } from './';
|
||||
|
||||
describe('AnalyticsHeader', () => {
|
||||
describe('AnalyticsFilters', () => {
|
||||
const { history } = mockKibanaValues;
|
||||
|
||||
const values = {
|
||||
|
@ -45,18 +43,14 @@ describe('AnalyticsHeader', () => {
|
|||
});
|
||||
|
||||
it('renders', () => {
|
||||
wrapper = shallow(<AnalyticsHeader title="Hello world" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
|
||||
expect(wrapper.type()).toEqual(EuiPageHeader);
|
||||
expect(wrapper.find('h1').text()).toEqual('Hello world');
|
||||
|
||||
expect(wrapper.find(LogRetentionTooltip)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiSelect)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiDatePickerRange)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders tags & dates with default values when no search query params are present', () => {
|
||||
wrapper = shallow(<AnalyticsHeader title="" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
|
||||
expect(getTagsSelect().prop('value')).toEqual('');
|
||||
expect(getStartDatePicker().props.startDate._i).toEqual(DEFAULT_START_DATE);
|
||||
|
@ -69,7 +63,7 @@ describe('AnalyticsHeader', () => {
|
|||
const allTags = [...values.allTags, 'tag1', 'tag2', 'tag3'];
|
||||
setMockValues({ ...values, allTags });
|
||||
|
||||
wrapper = shallow(<AnalyticsHeader title="" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
});
|
||||
|
||||
it('renders the tags select with currentTag value and allTags options', () => {
|
||||
|
@ -95,7 +89,7 @@ describe('AnalyticsHeader', () => {
|
|||
beforeEach(() => {
|
||||
history.location.search = '?start=1970-01-01&end=1970-01-02';
|
||||
|
||||
wrapper = shallow(<AnalyticsHeader title="" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
});
|
||||
|
||||
it('renders the start date picker', () => {
|
||||
|
@ -127,7 +121,7 @@ describe('AnalyticsHeader', () => {
|
|||
beforeEach(() => {
|
||||
history.location.search = '?start=1970-01-02&end=1970-01-01';
|
||||
|
||||
wrapper = shallow(<AnalyticsHeader title="" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
});
|
||||
|
||||
it('renders the date pickers as invalid', () => {
|
||||
|
@ -148,7 +142,7 @@ describe('AnalyticsHeader', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AnalyticsHeader title="" />);
|
||||
wrapper = shallow(<AnalyticsFilters />);
|
||||
});
|
||||
|
||||
it('pushes up new tag & date state to the search query', () => {
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
import moment from 'moment';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSelect,
|
||||
EuiDatePickerRange,
|
||||
EuiDatePicker,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AnalyticsLogic } from '../';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
|
||||
import { DEFAULT_START_DATE, DEFAULT_END_DATE, SERVER_DATE_FORMAT } from '../constants';
|
||||
import { convertTagsToSelectOptions } from '../utils';
|
||||
|
||||
export const AnalyticsFilters: React.FC = () => {
|
||||
const { allTags } = useValues(AnalyticsLogic);
|
||||
const { history } = useValues(KibanaLogic);
|
||||
|
||||
// Parse out existing filters from URL query string
|
||||
const { start, end, tag } = queryString.parse(history.location.search);
|
||||
const [startDate, setStartDate] = useState(
|
||||
start ? moment(start, SERVER_DATE_FORMAT) : moment(DEFAULT_START_DATE)
|
||||
);
|
||||
const [endDate, setEndDate] = useState(
|
||||
end ? moment(end, SERVER_DATE_FORMAT) : moment(DEFAULT_END_DATE)
|
||||
);
|
||||
const [currentTag, setCurrentTag] = useState((tag as string) || '');
|
||||
|
||||
// Set the current URL query string on filter
|
||||
const onApplyFilters = () => {
|
||||
const search = queryString.stringify({
|
||||
start: moment(startDate).format(SERVER_DATE_FORMAT),
|
||||
end: moment(endDate).format(SERVER_DATE_FORMAT),
|
||||
tag: currentTag || undefined,
|
||||
});
|
||||
history.push({ search });
|
||||
};
|
||||
|
||||
const hasInvalidDateRange = startDate > endDate;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiSelect
|
||||
options={convertTagsToSelectOptions(allTags)}
|
||||
value={currentTag}
|
||||
onChange={(e) => setCurrentTag(e.target.value)}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel',
|
||||
{ defaultMessage: 'Filter by analytics tag"' }
|
||||
)}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDatePickerRange
|
||||
startDateControl={
|
||||
<EuiDatePicker
|
||||
selected={startDate}
|
||||
onChange={(date) => date && setStartDate(date)}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
isInvalid={hasInvalidDateRange}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel',
|
||||
{ defaultMessage: 'Filter by start date' }
|
||||
)}
|
||||
/>
|
||||
}
|
||||
endDateControl={
|
||||
<EuiDatePicker
|
||||
selected={endDate}
|
||||
onChange={(date) => date && setEndDate(date)}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
isInvalid={hasInvalidDateRange}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel',
|
||||
{ defaultMessage: 'Filter by end date' }
|
||||
)}
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill isDisabled={hasInvalidDateRange} onClick={onApplyFilters}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel',
|
||||
{ defaultMessage: 'Apply filters' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
.analyticsHeader {
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__filters.euiPageHeaderSection {
|
||||
width: 100%;
|
||||
margin: $euiSizeM 0;
|
||||
}
|
||||
}
|
|
@ -1,136 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
import moment from 'moment';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import {
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSelect,
|
||||
EuiDatePickerRange,
|
||||
EuiDatePicker,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AnalyticsLogic } from '../';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import { LogRetentionTooltip, LogRetentionOptions } from '../../log_retention';
|
||||
|
||||
import { DEFAULT_START_DATE, DEFAULT_END_DATE, SERVER_DATE_FORMAT } from '../constants';
|
||||
import { convertTagsToSelectOptions } from '../utils';
|
||||
|
||||
import './analytics_header.scss';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
export const AnalyticsHeader: React.FC<Props> = ({ title }) => {
|
||||
const { allTags } = useValues(AnalyticsLogic);
|
||||
const { history } = useValues(KibanaLogic);
|
||||
|
||||
// Parse out existing filters from URL query string
|
||||
const { start, end, tag } = queryString.parse(history.location.search);
|
||||
const [startDate, setStartDate] = useState(
|
||||
start ? moment(start, SERVER_DATE_FORMAT) : moment(DEFAULT_START_DATE)
|
||||
);
|
||||
const [endDate, setEndDate] = useState(
|
||||
end ? moment(end, SERVER_DATE_FORMAT) : moment(DEFAULT_END_DATE)
|
||||
);
|
||||
const [currentTag, setCurrentTag] = useState((tag as string) || '');
|
||||
|
||||
// Set the current URL query string on filter
|
||||
const onApplyFilters = () => {
|
||||
const search = queryString.stringify({
|
||||
start: moment(startDate).format(SERVER_DATE_FORMAT),
|
||||
end: moment(endDate).format(SERVER_DATE_FORMAT),
|
||||
tag: currentTag || undefined,
|
||||
});
|
||||
history.push({ search });
|
||||
};
|
||||
|
||||
const hasInvalidDateRange = startDate > endDate;
|
||||
|
||||
return (
|
||||
<EuiPageHeader className="analyticsHeader">
|
||||
<EuiPageHeaderSection>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="l">
|
||||
<h1>{title}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogRetentionTooltip type={LogRetentionOptions.Analytics} position="right" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageHeaderSection>
|
||||
<EuiPageHeaderSection className="analyticsHeader__filters">
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiSelect
|
||||
options={convertTagsToSelectOptions(allTags)}
|
||||
value={currentTag}
|
||||
onChange={(e) => setCurrentTag(e.target.value)}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel',
|
||||
{ defaultMessage: 'Filter by analytics tag"' }
|
||||
)}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDatePickerRange
|
||||
startDateControl={
|
||||
<EuiDatePicker
|
||||
selected={startDate}
|
||||
onChange={(date) => date && setStartDate(date)}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
isInvalid={hasInvalidDateRange}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel',
|
||||
{ defaultMessage: 'Filter by start date' }
|
||||
)}
|
||||
/>
|
||||
}
|
||||
endDateControl={
|
||||
<EuiDatePicker
|
||||
selected={endDate}
|
||||
onChange={(date) => date && setEndDate(date)}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
isInvalid={hasInvalidDateRange}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel',
|
||||
{ defaultMessage: 'Filter by end date' }
|
||||
)}
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill isDisabled={hasInvalidDateRange} onClick={onApplyFilters}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel',
|
||||
{ defaultMessage: 'Apply filters' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageHeaderSection>
|
||||
</EuiPageHeader>
|
||||
);
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
export { AnalyticsCards } from './analytics_cards';
|
||||
export { AnalyticsChart } from './analytics_chart';
|
||||
export { AnalyticsHeader } from './analytics_header';
|
||||
export { AnalyticsFilters } from './analytics_filters';
|
||||
export { AnalyticsSection } from './analytics_section';
|
||||
export { AnalyticsSearch } from './analytics_search';
|
||||
export { AnalyticsTable, RecentQueriesTable, QueryClicksTable } from './analytics_tables';
|
||||
|
|
|
@ -12,16 +12,12 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
||||
|
||||
import { AnalyticsLayout } from '../analytics_layout';
|
||||
import { AnalyticsCards, AnalyticsChart, QueryClicksTable } from '../components';
|
||||
|
||||
import { QueryDetail } from './';
|
||||
|
||||
describe('QueryDetail', () => {
|
||||
const mockBreadcrumbs = ['Engines', 'some-engine', 'Analytics'];
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseParams.mockReturnValue({ query: 'some-query' });
|
||||
|
||||
|
@ -32,16 +28,10 @@ describe('QueryDetail', () => {
|
|||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<QueryDetail breadcrumbs={mockBreadcrumbs} />);
|
||||
const wrapper = shallow(<QueryDetail />);
|
||||
|
||||
expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('"some-query"');
|
||||
expect(wrapper.find(SetPageChrome).prop('trail')).toEqual([
|
||||
'Engines',
|
||||
'some-engine',
|
||||
'Analytics',
|
||||
'Query',
|
||||
'some-query',
|
||||
]);
|
||||
expect(wrapper.find(AnalyticsLayout).prop('breadcrumbs')).toEqual(['Query', 'some-query']);
|
||||
|
||||
expect(wrapper.find(AnalyticsCards)).toHaveLength(1);
|
||||
expect(wrapper.find(AnalyticsChart)).toHaveLength(1);
|
||||
|
@ -50,7 +40,7 @@ describe('QueryDetail', () => {
|
|||
|
||||
it('renders empty "" search titles correctly', () => {
|
||||
mockUseParams.mockReturnValue({ query: '""' });
|
||||
const wrapper = shallow(<QueryDetail breadcrumbs={mockBreadcrumbs} />);
|
||||
const wrapper = shallow(<QueryDetail />);
|
||||
|
||||
expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('""');
|
||||
});
|
||||
|
|
|
@ -12,8 +12,6 @@ import { useValues } from 'kea';
|
|||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
||||
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
|
||||
import { useDecodedParams } from '../../../utils/encode_path_params';
|
||||
|
||||
import { AnalyticsLayout } from '../analytics_layout';
|
||||
|
@ -25,10 +23,7 @@ const QUERY_DETAIL_TITLE = i18n.translate(
|
|||
{ defaultMessage: 'Query' }
|
||||
);
|
||||
|
||||
interface Props {
|
||||
breadcrumbs: BreadcrumbTrail;
|
||||
}
|
||||
export const QueryDetail: React.FC<Props> = ({ breadcrumbs }) => {
|
||||
export const QueryDetail: React.FC = () => {
|
||||
const { query } = useDecodedParams();
|
||||
const queryTitle = query === '""' ? query : `"${query}"`;
|
||||
|
||||
|
@ -37,9 +32,7 @@ export const QueryDetail: React.FC<Props> = ({ breadcrumbs }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isQueryView title={queryTitle}>
|
||||
<SetPageChrome trail={[...breadcrumbs, QUERY_DETAIL_TITLE, query]} />
|
||||
|
||||
<AnalyticsLayout isQueryView title={queryTitle} breadcrumbs={[QUERY_DETAIL_TITLE, query]}>
|
||||
<AnalyticsCards
|
||||
stats={[
|
||||
{
|
||||
|
|
|
@ -18,7 +18,7 @@ export const RecentQueries: React.FC = () => {
|
|||
const { recentQueries } = useValues(AnalyticsLogic);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isAnalyticsView title={RECENT_QUERIES}>
|
||||
<AnalyticsLayout isAnalyticsView title={RECENT_QUERIES} breadcrumbs={[RECENT_QUERIES]}>
|
||||
<AnalyticsSearch />
|
||||
<RecentQueriesTable items={recentQueries} />
|
||||
</AnalyticsLayout>
|
||||
|
|
|
@ -18,7 +18,7 @@ export const TopQueries: React.FC = () => {
|
|||
const { topQueries } = useValues(AnalyticsLogic);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isAnalyticsView title={TOP_QUERIES}>
|
||||
<AnalyticsLayout isAnalyticsView title={TOP_QUERIES} breadcrumbs={[TOP_QUERIES]}>
|
||||
<AnalyticsSearch />
|
||||
<AnalyticsTable items={topQueries} hasClicks />
|
||||
</AnalyticsLayout>
|
||||
|
|
|
@ -18,7 +18,11 @@ export const TopQueriesNoClicks: React.FC = () => {
|
|||
const { topQueriesNoClicks } = useValues(AnalyticsLogic);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isAnalyticsView title={TOP_QUERIES_NO_CLICKS}>
|
||||
<AnalyticsLayout
|
||||
isAnalyticsView
|
||||
title={TOP_QUERIES_NO_CLICKS}
|
||||
breadcrumbs={[TOP_QUERIES_NO_CLICKS]}
|
||||
>
|
||||
<AnalyticsSearch />
|
||||
<AnalyticsTable items={topQueriesNoClicks} hasClicks />
|
||||
</AnalyticsLayout>
|
||||
|
|
|
@ -18,7 +18,11 @@ export const TopQueriesNoResults: React.FC = () => {
|
|||
const { topQueriesNoResults } = useValues(AnalyticsLogic);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isAnalyticsView title={TOP_QUERIES_NO_RESULTS}>
|
||||
<AnalyticsLayout
|
||||
isAnalyticsView
|
||||
title={TOP_QUERIES_NO_RESULTS}
|
||||
breadcrumbs={[TOP_QUERIES_NO_RESULTS]}
|
||||
>
|
||||
<AnalyticsSearch />
|
||||
<AnalyticsTable items={topQueriesNoResults} hasClicks />
|
||||
</AnalyticsLayout>
|
||||
|
|
|
@ -18,7 +18,11 @@ export const TopQueriesWithClicks: React.FC = () => {
|
|||
const { topQueriesWithClicks } = useValues(AnalyticsLogic);
|
||||
|
||||
return (
|
||||
<AnalyticsLayout isAnalyticsView title={TOP_QUERIES_WITH_CLICKS}>
|
||||
<AnalyticsLayout
|
||||
isAnalyticsView
|
||||
title={TOP_QUERIES_WITH_CLICKS}
|
||||
breadcrumbs={[TOP_QUERIES_WITH_CLICKS]}
|
||||
>
|
||||
<AnalyticsSearch />
|
||||
<AnalyticsTable items={topQueriesWithClicks} hasClicks />
|
||||
</AnalyticsLayout>
|
||||
|
|
|
@ -94,6 +94,11 @@ export const EngineRouter: React.FC = () => {
|
|||
<Route exact path={ENGINE_PATH}>
|
||||
<EngineOverview />
|
||||
</Route>
|
||||
{canViewEngineAnalytics && (
|
||||
<Route path={ENGINE_ANALYTICS_PATH}>
|
||||
<AnalyticsRouter />
|
||||
</Route>
|
||||
)}
|
||||
{canViewEngineDocuments && (
|
||||
<Route path={ENGINE_DOCUMENT_DETAIL_PATH}>
|
||||
<DocumentDetail />
|
||||
|
@ -106,11 +111,6 @@ export const EngineRouter: React.FC = () => {
|
|||
)}
|
||||
{/* TODO: Remove layout once page template migration is over */}
|
||||
<Layout navigation={<AppSearchNav />}>
|
||||
{canViewEngineAnalytics && (
|
||||
<Route path={ENGINE_ANALYTICS_PATH}>
|
||||
<AnalyticsRouter />
|
||||
</Route>
|
||||
)}
|
||||
{canViewEngineSchema && (
|
||||
<Route path={ENGINE_SCHEMA_PATH}>
|
||||
<SchemaRouter />
|
||||
|
|
|
@ -41,3 +41,9 @@ export const getPageHeaderActions = (wrapper: ShallowWrapper) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getPageHeaderChildren = (wrapper: ShallowWrapper) => {
|
||||
const children = getPageHeader(wrapper).children || null;
|
||||
|
||||
return shallow(<div>{children}</div>);
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ export {
|
|||
getPageTitle,
|
||||
getPageDescription,
|
||||
getPageHeaderActions,
|
||||
getPageHeaderChildren,
|
||||
} from './get_page_header';
|
||||
|
||||
// Misc
|
||||
|
|
Loading…
Reference in a new issue