Skip to content

Commit

Permalink
[App Search] Convert Analytics views to new page template (#102851) (#…
Browse files Browse the repository at this point in the history
…102945)

* 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

Co-authored-by: Constance <constancecchen@users.noreply.github.com>
  • Loading branch information
kibanamachine and Constance committed Jun 22, 2021
1 parent 119a7a0 commit d16023c
Show file tree
Hide file tree
Showing 18 changed files with 199 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -45,18 +43,14 @@ describe('AnalyticsHeader', () => {
});

it('renders', () => {
wrapper = shallow(<AnalyticsHeader title="Hello world" />);

expect(wrapper.type()).toEqual(EuiPageHeader);
expect(wrapper.find('h1').text()).toEqual('Hello world');
wrapper = shallow(<AnalyticsFilters />);

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);
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};

This file was deleted.

Loading

0 comments on commit d16023c

Please sign in to comment.