[App Search] Add AnalyticsCards & AnalyticsChart components to analytics pages (#88930)
* Create reusable AnalyticsCards component * Update EngineOverview to use new AnalyticsCards component * Update Analytics overview with AnalyticsCards + data * Update QueryDetail with AnalyticsCards + data * Update Analytics overview with AnalyticsChart + data - turns out we do need startDate after all for charts, so I added it back to types * Update QueryDetail with AnalyticsChart + data * [Polish] Dash click and no result lines to match standalone UI Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
474af9f3eb
commit
a31c3eba13
|
@ -29,6 +29,15 @@ describe('AnalyticsLogic', () => {
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
analyticsUnavailable: false,
|
analyticsUnavailable: false,
|
||||||
allTags: [],
|
allTags: [],
|
||||||
|
totalQueries: 0,
|
||||||
|
totalQueriesNoResults: 0,
|
||||||
|
totalClicks: 0,
|
||||||
|
totalQueriesForQuery: 0,
|
||||||
|
queriesPerDay: [],
|
||||||
|
queriesNoResultsPerDay: [],
|
||||||
|
clicksPerDay: [],
|
||||||
|
queriesPerDayForQuery: [],
|
||||||
|
startDate: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOCK_TOP_QUERIES = [
|
const MOCK_TOP_QUERIES = [
|
||||||
|
@ -66,6 +75,7 @@ describe('AnalyticsLogic', () => {
|
||||||
const MOCK_ANALYTICS_RESPONSE = {
|
const MOCK_ANALYTICS_RESPONSE = {
|
||||||
analyticsUnavailable: false,
|
analyticsUnavailable: false,
|
||||||
allTags: ['some-tag'],
|
allTags: ['some-tag'],
|
||||||
|
startDate: '1970-01-01',
|
||||||
recentQueries: MOCK_RECENT_QUERIES,
|
recentQueries: MOCK_RECENT_QUERIES,
|
||||||
topQueries: MOCK_TOP_QUERIES,
|
topQueries: MOCK_TOP_QUERIES,
|
||||||
topQueriesNoResults: MOCK_TOP_QUERIES,
|
topQueriesNoResults: MOCK_TOP_QUERIES,
|
||||||
|
@ -81,6 +91,7 @@ describe('AnalyticsLogic', () => {
|
||||||
const MOCK_QUERY_RESPONSE = {
|
const MOCK_QUERY_RESPONSE = {
|
||||||
analyticsUnavailable: false,
|
analyticsUnavailable: false,
|
||||||
allTags: ['some-tag'],
|
allTags: ['some-tag'],
|
||||||
|
startDate: '1970-01-01',
|
||||||
totalQueriesForQuery: 50,
|
totalQueriesForQuery: 50,
|
||||||
queriesPerDayForQuery: [25, 0, 25],
|
queriesPerDayForQuery: [25, 0, 25],
|
||||||
topClicksForQuery: MOCK_TOP_CLICKS,
|
topClicksForQuery: MOCK_TOP_CLICKS,
|
||||||
|
@ -120,7 +131,14 @@ describe('AnalyticsLogic', () => {
|
||||||
dataLoading: false,
|
dataLoading: false,
|
||||||
analyticsUnavailable: false,
|
analyticsUnavailable: false,
|
||||||
allTags: ['some-tag'],
|
allTags: ['some-tag'],
|
||||||
// TODO: more state will get set here in future PRs
|
startDate: '1970-01-01',
|
||||||
|
totalClicks: 1000,
|
||||||
|
totalQueries: 5000,
|
||||||
|
totalQueriesNoResults: 500,
|
||||||
|
queriesPerDay: [10, 50, 100],
|
||||||
|
queriesNoResultsPerDay: [1, 2, 3],
|
||||||
|
clicksPerDay: [0, 10, 50],
|
||||||
|
// TODO: Replace this with ...MOCK_ANALYTICS_RESPONSE once all data is set
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -135,7 +153,10 @@ describe('AnalyticsLogic', () => {
|
||||||
dataLoading: false,
|
dataLoading: false,
|
||||||
analyticsUnavailable: false,
|
analyticsUnavailable: false,
|
||||||
allTags: ['some-tag'],
|
allTags: ['some-tag'],
|
||||||
// TODO: more state will get set here in future PRs
|
startDate: '1970-01-01',
|
||||||
|
totalQueriesForQuery: 50,
|
||||||
|
queriesPerDayForQuery: [25, 0, 25],
|
||||||
|
// TODO: Replace this with ...MOCK_QUERY_RESPONSE once all data is set
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -62,6 +62,61 @@ export const AnalyticsLogic = kea<MakeLogicType<AnalyticsValues, AnalyticsAction
|
||||||
onQueryDataLoad: (_, { allTags }) => allTags,
|
onQueryDataLoad: (_, { allTags }) => allTags,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
totalQueries: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { totalQueries }) => totalQueries,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalQueriesNoResults: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { totalQueriesNoResults }) => totalQueriesNoResults,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalClicks: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { totalClicks }) => totalClicks,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queriesPerDay: [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { queriesPerDay }) => queriesPerDay,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queriesNoResultsPerDay: [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { queriesNoResultsPerDay }) => queriesNoResultsPerDay,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
clicksPerDay: [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { clicksPerDay }) => clicksPerDay,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totalQueriesForQuery: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
onQueryDataLoad: (_, { totalQueriesForQuery }) => totalQueriesForQuery,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queriesPerDayForQuery: [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
onQueryDataLoad: (_, { queriesPerDayForQuery }) => queriesPerDayForQuery,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startDate: [
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
onAnalyticsDataLoad: (_, { startDate }) => startDate,
|
||||||
|
onQueryDataLoad: (_, { startDate }) => startDate,
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
listeners: ({ actions }) => ({
|
listeners: ({ actions }) => ({
|
||||||
loadAnalyticsData: async () => {
|
loadAnalyticsData: async () => {
|
||||||
|
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { EuiStat } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { AnalyticsCards } from './';
|
||||||
|
|
||||||
|
describe('AnalyticsCards', () => {
|
||||||
|
it('renders', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AnalyticsCards
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
stat: 100,
|
||||||
|
text: 'Red fish',
|
||||||
|
dataTestSubj: 'RedFish',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat: 2000,
|
||||||
|
text: 'Blue fish',
|
||||||
|
dataTestSubj: 'BlueFish',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(EuiStat)).toHaveLength(2);
|
||||||
|
expect(wrapper.find('[data-test-subj="RedFish"]').prop('title')).toEqual(100);
|
||||||
|
expect(wrapper.find('[data-test-subj="RedFish"]').prop('description')).toEqual('Red fish');
|
||||||
|
expect(wrapper.find('[data-test-subj="BlueFish"]').prop('title')).toEqual(2000);
|
||||||
|
expect(wrapper.find('[data-test-subj="BlueFish"]').prop('description')).toEqual('Blue fish');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stats: Array<{
|
||||||
|
text: string;
|
||||||
|
stat: number;
|
||||||
|
dataTestSubj?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
export const AnalyticsCards: React.FC<Props> = ({ stats }) => (
|
||||||
|
<EuiFlexGroup>
|
||||||
|
{stats.map(({ text, stat, dataTestSubj }) => (
|
||||||
|
<EuiFlexItem key={text}>
|
||||||
|
<EuiPanel>
|
||||||
|
<EuiStat
|
||||||
|
title={stat}
|
||||||
|
description={text}
|
||||||
|
titleColor="primary"
|
||||||
|
data-test-subj={dataTestSubj}
|
||||||
|
/>
|
||||||
|
</EuiPanel>
|
||||||
|
</EuiFlexItem>
|
||||||
|
))}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
|
@ -54,6 +54,14 @@ describe('AnalyticsChart', () => {
|
||||||
expect(wrapper.find(LineSeries)).toHaveLength(3);
|
expect(wrapper.find(LineSeries)).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders dashed lines', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AnalyticsChart lines={[{ id: 'dashed 1', data: MOCK_DATA, isDashed: true }]} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find(LineSeries).prop('lineSeriesStyle')?.line?.dash).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('formats x-axis dates correctly', () => {
|
it('formats x-axis dates correctly', () => {
|
||||||
const wrapper = shallow(<AnalyticsChart lines={[{ id: 'test', data: MOCK_DATA }]} />);
|
const wrapper = shallow(<AnalyticsChart lines={[{ id: 'test', data: MOCK_DATA }]} />);
|
||||||
const dateFormatter: Function = wrapper.find('#bottom-axis').prop('tickFormat');
|
const dateFormatter: Function = wrapper.find('#bottom-axis').prop('tickFormat');
|
||||||
|
|
|
@ -25,6 +25,7 @@ interface Props {
|
||||||
lines: Array<{
|
lines: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
|
isDashed?: boolean;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
export const AnalyticsChart: React.FC<Props> = ({ height = 300, lines }) => {
|
export const AnalyticsChart: React.FC<Props> = ({ height = 300, lines }) => {
|
||||||
|
@ -39,7 +40,7 @@ export const AnalyticsChart: React.FC<Props> = ({ height = 300, lines }) => {
|
||||||
headerFormatter: (tooltip) => moment(tooltip.value).format(TOOLTIP_DATE_FORMAT),
|
headerFormatter: (tooltip) => moment(tooltip.value).format(TOOLTIP_DATE_FORMAT),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{lines.map(({ id, data }) => (
|
{lines.map(({ id, data, isDashed }) => (
|
||||||
<LineSeries
|
<LineSeries
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -47,6 +48,7 @@ export const AnalyticsChart: React.FC<Props> = ({ height = 300, lines }) => {
|
||||||
xAccessor={'x'}
|
xAccessor={'x'}
|
||||||
yAccessors={['y']}
|
yAccessors={['y']}
|
||||||
curve={CurveType.CURVE_MONOTONE_X}
|
curve={CurveType.CURVE_MONOTONE_X}
|
||||||
|
lineSeriesStyle={isDashed ? { line: { dash: [5, 5] } } : undefined}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Axis
|
<Axis
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export { AnalyticsCards } from './analytics_cards';
|
||||||
export { AnalyticsChart } from './analytics_chart';
|
export { AnalyticsChart } from './analytics_chart';
|
||||||
export { AnalyticsHeader } from './analytics_header';
|
export { AnalyticsHeader } from './analytics_header';
|
||||||
export { AnalyticsUnavailable } from './analytics_unavailable';
|
export { AnalyticsUnavailable } from './analytics_unavailable';
|
||||||
|
|
|
@ -25,6 +25,10 @@ export const TOTAL_QUERIES = i18n.translate(
|
||||||
'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries',
|
'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries',
|
||||||
{ defaultMessage: 'Total queries' }
|
{ defaultMessage: 'Total queries' }
|
||||||
);
|
);
|
||||||
|
export const TOTAL_QUERIES_NO_RESULTS = i18n.translate(
|
||||||
|
'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueriesNoResults',
|
||||||
|
{ defaultMessage: 'Total queries with no results' }
|
||||||
|
);
|
||||||
export const TOTAL_CLICKS = i18n.translate(
|
export const TOTAL_CLICKS = i18n.translate(
|
||||||
'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks',
|
'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks',
|
||||||
{ defaultMessage: 'Total clicks' }
|
{ defaultMessage: 'Total clicks' }
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
export { ANALYTICS_TITLE } from './constants';
|
export { ANALYTICS_TITLE } from './constants';
|
||||||
export { AnalyticsLogic } from './analytics_logic';
|
export { AnalyticsLogic } from './analytics_logic';
|
||||||
export { AnalyticsRouter } from './analytics_router';
|
export { AnalyticsRouter } from './analytics_router';
|
||||||
export { AnalyticsChart } from './components';
|
export { AnalyticsCards, AnalyticsChart } from './components';
|
||||||
export { convertToChartData } from './utils';
|
export { convertToChartData } from './utils';
|
||||||
|
|
|
@ -34,8 +34,9 @@ interface RecentQuery {
|
||||||
interface BaseData {
|
interface BaseData {
|
||||||
analyticsUnavailable: boolean;
|
analyticsUnavailable: boolean;
|
||||||
allTags: string[];
|
allTags: string[];
|
||||||
|
startDate: string;
|
||||||
// NOTE: The API sends us back even more data than this (e.g.,
|
// NOTE: The API sends us back even more data than this (e.g.,
|
||||||
// startDate, endDate, currentTag, logRetentionSettings, query),
|
// endDate, currentTag, logRetentionSettings, query),
|
||||||
// but we currently don't need that data in our front-end code,
|
// but we currently don't need that data in our front-end code,
|
||||||
// so I'm opting not to list them in our types
|
// so I'm opting not to list them in our types
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,28 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { setMockValues } from '../../../../__mocks__';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import { AnalyticsCards, AnalyticsChart } from '../components';
|
||||||
import { Analytics } from './';
|
import { Analytics } from './';
|
||||||
|
|
||||||
describe('Analytics overview', () => {
|
describe('Analytics overview', () => {
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
|
setMockValues({
|
||||||
|
totalQueries: 3,
|
||||||
|
totalQueriesNoResults: 2,
|
||||||
|
totalClicks: 1,
|
||||||
|
queriesPerDay: [10, 20, 30],
|
||||||
|
queriesNoResultsPerDay: [1, 2, 3],
|
||||||
|
clicksPerDay: [0, 1, 5],
|
||||||
|
startDate: '1970-01-01',
|
||||||
|
});
|
||||||
const wrapper = shallow(<Analytics />);
|
const wrapper = shallow(<Analytics />);
|
||||||
|
|
||||||
expect(wrapper.isEmptyRender()).toBe(false); // TODO
|
expect(wrapper.find(AnalyticsCards)).toHaveLength(1);
|
||||||
|
expect(wrapper.find(AnalyticsChart)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,13 +5,73 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useValues } from 'kea';
|
||||||
|
|
||||||
import { ANALYTICS_TITLE } from '../constants';
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ANALYTICS_TITLE,
|
||||||
|
TOTAL_QUERIES,
|
||||||
|
TOTAL_QUERIES_NO_RESULTS,
|
||||||
|
TOTAL_CLICKS,
|
||||||
|
} from '../constants';
|
||||||
import { AnalyticsLayout } from '../analytics_layout';
|
import { AnalyticsLayout } from '../analytics_layout';
|
||||||
|
import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../';
|
||||||
|
|
||||||
export const Analytics: React.FC = () => {
|
export const Analytics: React.FC = () => {
|
||||||
|
const {
|
||||||
|
totalQueries,
|
||||||
|
totalQueriesNoResults,
|
||||||
|
totalClicks,
|
||||||
|
queriesPerDay,
|
||||||
|
queriesNoResultsPerDay,
|
||||||
|
clicksPerDay,
|
||||||
|
startDate,
|
||||||
|
} = useValues(AnalyticsLogic);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnalyticsLayout isAnalyticsView title={ANALYTICS_TITLE}>
|
<AnalyticsLayout isAnalyticsView title={ANALYTICS_TITLE}>
|
||||||
|
<AnalyticsCards
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
stat: totalQueries,
|
||||||
|
text: TOTAL_QUERIES,
|
||||||
|
dataTestSubj: 'TotalQueriesCard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat: totalQueriesNoResults,
|
||||||
|
text: TOTAL_QUERIES_NO_RESULTS,
|
||||||
|
dataTestSubj: 'TotalQueriesNoResultsCard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stat: totalClicks,
|
||||||
|
text: TOTAL_CLICKS,
|
||||||
|
dataTestSubj: 'TotalClicksCard',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
|
||||||
|
<AnalyticsChart
|
||||||
|
lines={[
|
||||||
|
{
|
||||||
|
id: TOTAL_QUERIES,
|
||||||
|
data: convertToChartData({ startDate, data: queriesPerDay }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TOTAL_QUERIES_NO_RESULTS,
|
||||||
|
data: convertToChartData({ startDate, data: queriesNoResultsPerDay }),
|
||||||
|
isDashed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TOTAL_CLICKS,
|
||||||
|
data: convertToChartData({ startDate, data: clicksPerDay }),
|
||||||
|
isDashed: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
|
||||||
<p>TODO: Analytics overview</p>
|
<p>TODO: Analytics overview</p>
|
||||||
</AnalyticsLayout>
|
</AnalyticsLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import '../../../../__mocks__/react_router_history.mock';
|
import '../../../../__mocks__/react_router_history.mock';
|
||||||
|
import { setMockValues } from '../../../../__mocks__';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
@ -12,6 +13,7 @@ import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
||||||
|
|
||||||
|
import { AnalyticsCards, AnalyticsChart } from '../components';
|
||||||
import { QueryDetail } from './';
|
import { QueryDetail } from './';
|
||||||
|
|
||||||
describe('QueryDetail', () => {
|
describe('QueryDetail', () => {
|
||||||
|
@ -19,6 +21,11 @@ describe('QueryDetail', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(useParams as jest.Mock).mockReturnValueOnce({ query: 'some-query' });
|
(useParams as jest.Mock).mockReturnValueOnce({ query: 'some-query' });
|
||||||
|
|
||||||
|
setMockValues({
|
||||||
|
totalQueriesForQuery: 100,
|
||||||
|
queriesPerDayForQuery: [0, 5, 10],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
|
@ -31,5 +38,8 @@ describe('QueryDetail', () => {
|
||||||
'Query',
|
'Query',
|
||||||
'some-query',
|
'some-query',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(wrapper.find(AnalyticsCards)).toHaveLength(1);
|
||||||
|
expect(wrapper.find(AnalyticsChart)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,12 +6,16 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useValues } from 'kea';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
|
||||||
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
|
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
|
||||||
|
|
||||||
import { AnalyticsLayout } from '../analytics_layout';
|
import { AnalyticsLayout } from '../analytics_layout';
|
||||||
|
import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../';
|
||||||
|
|
||||||
const QUERY_DETAIL_TITLE = i18n.translate(
|
const QUERY_DETAIL_TITLE = i18n.translate(
|
||||||
'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title',
|
'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title',
|
||||||
|
@ -24,10 +28,41 @@ interface Props {
|
||||||
export const QueryDetail: React.FC<Props> = ({ breadcrumbs }) => {
|
export const QueryDetail: React.FC<Props> = ({ breadcrumbs }) => {
|
||||||
const { query } = useParams() as { query: string };
|
const { query } = useParams() as { query: string };
|
||||||
|
|
||||||
|
const { totalQueriesForQuery, queriesPerDayForQuery, startDate } = useValues(AnalyticsLogic);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnalyticsLayout isQueryView title={`"${query}"`}>
|
<AnalyticsLayout isQueryView title={`"${query}"`}>
|
||||||
<SetPageChrome trail={[...breadcrumbs, QUERY_DETAIL_TITLE, query]} />
|
<SetPageChrome trail={[...breadcrumbs, QUERY_DETAIL_TITLE, query]} />
|
||||||
|
|
||||||
|
<AnalyticsCards
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
stat: totalQueriesForQuery,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.cardDescription',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Queries for {queryTitle}',
|
||||||
|
values: { queryTitle: query },
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
|
||||||
|
<AnalyticsChart
|
||||||
|
lines={[
|
||||||
|
{
|
||||||
|
id: i18n.translate(
|
||||||
|
'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.chartTooltip',
|
||||||
|
{ defaultMessage: 'Queries per day' }
|
||||||
|
),
|
||||||
|
data: convertToChartData({ startDate, data: queriesPerDayForQuery }),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<EuiSpacer />
|
||||||
|
|
||||||
<p>TODO: Query detail page</p>
|
<p>TODO: Query detail page</p>
|
||||||
</AnalyticsLayout>
|
</AnalyticsLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,45 +7,19 @@
|
||||||
import { setMockValues } from '../../../../__mocks__/kea.mock';
|
import { setMockValues } from '../../../../__mocks__/kea.mock';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { EuiStat } from '@elastic/eui';
|
|
||||||
|
|
||||||
|
import { AnalyticsCards } from '../../analytics';
|
||||||
import { TotalStats } from './total_stats';
|
import { TotalStats } from './total_stats';
|
||||||
|
|
||||||
describe('TotalStats', () => {
|
describe('TotalStats', () => {
|
||||||
let wrapper: ShallowWrapper;
|
it('renders', () => {
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
setMockValues({
|
setMockValues({
|
||||||
totalQueries: 11,
|
totalQueries: 11,
|
||||||
documentCount: 22,
|
documentCount: 22,
|
||||||
totalClicks: 33,
|
totalClicks: 33,
|
||||||
});
|
});
|
||||||
wrapper = shallow(<TotalStats />);
|
const wrapper = shallow(<TotalStats />);
|
||||||
});
|
expect(wrapper.find(AnalyticsCards)).toHaveLength(1);
|
||||||
|
|
||||||
it('renders the total queries stat', () => {
|
|
||||||
expect(wrapper.find('[data-test-subj="TotalQueriesCard"]')).toHaveLength(1);
|
|
||||||
|
|
||||||
const card = wrapper.find(EuiStat).at(0);
|
|
||||||
expect(card.prop('title')).toEqual(11);
|
|
||||||
expect(card.prop('description')).toEqual('Total queries');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the total documents stat', () => {
|
|
||||||
expect(wrapper.find('[data-test-subj="TotalDocumentsCard"]')).toHaveLength(1);
|
|
||||||
|
|
||||||
const card = wrapper.find(EuiStat).at(1);
|
|
||||||
expect(card.prop('title')).toEqual(22);
|
|
||||||
expect(card.prop('description')).toEqual('Total documents');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the total clicks stat', () => {
|
|
||||||
expect(wrapper.find('[data-test-subj="TotalClicksCard"]')).toHaveLength(1);
|
|
||||||
|
|
||||||
const card = wrapper.find(EuiStat).at(2);
|
|
||||||
expect(card.prop('title')).toEqual(33);
|
|
||||||
expect(card.prop('description')).toEqual('Total clicks');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useValues } from 'kea';
|
import { useValues } from 'kea';
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
|
|
||||||
|
|
||||||
import { TOTAL_QUERIES, TOTAL_DOCUMENTS, TOTAL_CLICKS } from '../../analytics/constants';
|
import { TOTAL_QUERIES, TOTAL_DOCUMENTS, TOTAL_CLICKS } from '../../analytics/constants';
|
||||||
|
import { AnalyticsCards } from '../../analytics';
|
||||||
|
|
||||||
import { EngineOverviewLogic } from '../';
|
import { EngineOverviewLogic } from '../';
|
||||||
|
|
||||||
|
@ -16,22 +16,24 @@ export const TotalStats: React.FC = () => {
|
||||||
const { totalQueries, documentCount, totalClicks } = useValues(EngineOverviewLogic);
|
const { totalQueries, documentCount, totalClicks } = useValues(EngineOverviewLogic);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup>
|
<AnalyticsCards
|
||||||
<EuiFlexItem>
|
stats={[
|
||||||
<EuiPanel data-test-subj="TotalQueriesCard">
|
{
|
||||||
<EuiStat title={totalQueries} description={TOTAL_QUERIES} titleColor="primary" />
|
stat: totalQueries,
|
||||||
</EuiPanel>
|
text: TOTAL_QUERIES,
|
||||||
</EuiFlexItem>
|
dataTestSubj: 'TotalQueriesCard',
|
||||||
<EuiFlexItem>
|
},
|
||||||
<EuiPanel data-test-subj="TotalDocumentsCard">
|
{
|
||||||
<EuiStat title={documentCount} description={TOTAL_DOCUMENTS} titleColor="primary" />
|
stat: documentCount,
|
||||||
</EuiPanel>
|
text: TOTAL_DOCUMENTS,
|
||||||
</EuiFlexItem>
|
dataTestSubj: 'TotalDocumentsCard',
|
||||||
<EuiFlexItem>
|
},
|
||||||
<EuiPanel data-test-subj="TotalClicksCard">
|
{
|
||||||
<EuiStat title={totalClicks} description={TOTAL_CLICKS} titleColor="primary" />
|
stat: totalClicks,
|
||||||
</EuiPanel>
|
text: TOTAL_CLICKS,
|
||||||
</EuiFlexItem>
|
dataTestSubj: 'TotalClicksCard',
|
||||||
</EuiFlexGroup>
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue