Skip to content

Commit

Permalink
[Discover][Main] Split single query into 2 queries for faster results (
Browse files Browse the repository at this point in the history
…#104818) (#107392)

Co-authored-by: Tim Roes <tim.roes@elastic.co>

Co-authored-by: Matthias Wilhelm <matthias.wilhelm@elastic.co>
Co-authored-by: Tim Roes <tim.roes@elastic.co>
  • Loading branch information
3 people authored Aug 2, 2021
1 parent 432a543 commit 53afa0d
Show file tree
Hide file tree
Showing 54 changed files with 2,320 additions and 814 deletions.
50 changes: 50 additions & 0 deletions src/plugins/charts/public/services/theme/theme.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
* Side Public License, v 1.
*/

import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { renderHook, act } from '@testing-library/react-hooks';
import { render, act as renderAct } from '@testing-library/react';

import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';

Expand Down Expand Up @@ -105,6 +107,30 @@ describe('ThemeService', () => {
act(() => darkMode$.next(false));
expect(result.current).toBe(EUI_CHARTS_THEME_LIGHT.theme);
});

it('should not rerender when emitting the same value', () => {
const darkMode$ = new BehaviorSubject(false);
setupMockUiSettings.get$.mockReturnValue(darkMode$);
const themeService = new ThemeService();
themeService.init(setupMockUiSettings);
const { useChartsTheme } = themeService;

const renderCounter = jest.fn();
const Wrapper = () => {
useChartsTheme();
renderCounter();
return null;
};

render(<Wrapper />);
expect(renderCounter).toHaveBeenCalledTimes(1);
renderAct(() => darkMode$.next(true));
expect(renderCounter).toHaveBeenCalledTimes(2);
renderAct(() => darkMode$.next(true));
renderAct(() => darkMode$.next(true));
renderAct(() => darkMode$.next(true));
expect(renderCounter).toHaveBeenCalledTimes(2);
});
});

describe('useBaseChartTheme', () => {
Expand All @@ -123,5 +149,29 @@ describe('ThemeService', () => {
act(() => darkMode$.next(false));
expect(result.current).toBe(LIGHT_THEME);
});

it('should not rerender when emitting the same value', () => {
const darkMode$ = new BehaviorSubject(false);
setupMockUiSettings.get$.mockReturnValue(darkMode$);
const themeService = new ThemeService();
themeService.init(setupMockUiSettings);
const { useChartsBaseTheme } = themeService;

const renderCounter = jest.fn();
const Wrapper = () => {
useChartsBaseTheme();
renderCounter();
return null;
};

render(<Wrapper />);
expect(renderCounter).toHaveBeenCalledTimes(1);
renderAct(() => darkMode$.next(true));
expect(renderCounter).toHaveBeenCalledTimes(2);
renderAct(() => darkMode$.next(true));
renderAct(() => darkMode$.next(true));
renderAct(() => darkMode$.next(true));
expect(renderCounter).toHaveBeenCalledTimes(2);
});
});
});
24 changes: 19 additions & 5 deletions src/plugins/charts/public/services/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Observable, BehaviorSubject } from 'rxjs';

import { CoreSetup } from 'kibana/public';
Expand Down Expand Up @@ -54,11 +54,18 @@ export class ThemeService {
/** A React hook for consuming the charts theme */
public useChartsTheme = (): PartialTheme => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [value, update] = useState(this.chartsDefaultTheme);
const [value, update] = useState(this._chartsTheme$.getValue());
// eslint-disable-next-line react-hooks/rules-of-hooks
const ref = useRef(value);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
const s = this.chartsTheme$.subscribe(update);
const s = this.chartsTheme$.subscribe((val) => {
if (val !== ref.current) {
ref.current = val;
update(val);
}
});
return () => s.unsubscribe();
}, []);

Expand All @@ -68,11 +75,18 @@ export class ThemeService {
/** A React hook for consuming the charts theme */
public useChartsBaseTheme = (): Theme => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [value, update] = useState(this.chartsDefaultBaseTheme);
const [value, update] = useState(this._chartsBaseTheme$.getValue());
// eslint-disable-next-line react-hooks/rules-of-hooks
const ref = useRef(value);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
const s = this.chartsBaseTheme$.subscribe(update);
const s = this.chartsBaseTheme$.subscribe((val) => {
if (val !== ref.current) {
ref.current = val;
update(val);
}
});
return () => s.unsubscribe();
}, []);

Expand Down
16 changes: 10 additions & 6 deletions src/plugins/data/common/search/search_source/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ export const searchSourceCommonMock: jest.Mocked<ISearchStartSearchSource> = {
createEmpty: jest.fn().mockReturnValue(searchSourceInstanceMock),
};

export const createSearchSourceMock = (fields?: SearchSourceFields) =>
export const createSearchSourceMock = (fields?: SearchSourceFields, response?: any) =>
new SearchSource(fields, {
getConfig: uiSettingsServiceMock.createStartContract().get,
search: jest
.fn()
.mockReturnValue(
of({ rawResponse: { hits: { hits: [], total: 0 } }, isPartial: false, isRunning: false })
),
search: jest.fn().mockReturnValue(
of(
response ?? {
rawResponse: { hits: { hits: [], total: 0 } },
isPartial: false,
isRunning: false,
}
)
),
onResponse: jest.fn().mockImplementation((req, res) => res),
});
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const indexPattern = ({
getSourceFiltering: () => ({}),
getFieldByName: (name: string) => fields.getByName(name),
timeFieldName: 'timestamp',
getFormatterForField: () => ({ convert: () => 'formatted' }),
} as unknown) as IndexPattern;

indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields);
Expand Down
23 changes: 23 additions & 0 deletions src/plugins/discover/public/__mocks__/saved_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { SavedSearch } from '../saved_searches';
import { createSearchSourceMock } from '../../../data/public/mocks';
import { indexPatternMock } from './index_pattern';
import { indexPatternWithTimefieldMock } from './index_pattern_with_timefield';

export const savedSearchMock = ({
id: 'the-saved-search-id',
Expand All @@ -31,3 +32,25 @@ export const savedSearchMock = ({
error: undefined,
searchSource: createSearchSourceMock({ index: indexPatternMock }),
} as unknown) as SavedSearch;

export const savedSearchMockWithTimeField = ({
id: 'the-saved-search-id-with-timefield',
type: 'search',
attributes: {
title: 'the-saved-search-title',
kibanaSavedObjectMeta: {
searchSourceJSON:
'{"highlightAll":true,"version":true,"query":{"query":"foo : \\"bar\\" ","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
},
},
references: [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: 'the-index-pattern-id',
},
],
migrationVersion: { search: '7.5.0' },
error: undefined,
searchSource: createSearchSourceMock({ index: indexPatternWithTimefieldMock }),
} as unknown) as SavedSearch;
18 changes: 17 additions & 1 deletion src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
import { DiscoverServices } from '../build_services';
import { dataPluginMock } from '../../../data/public/mocks';
import { chromeServiceMock, coreMock, docLinksServiceMock } from '../../../../core/public/mocks';
import { DEFAULT_COLUMNS_SETTING } from '../../common';
import {
DEFAULT_COLUMNS_SETTING,
SAMPLE_SIZE_SETTING,
SORT_DEFAULT_ORDER_SETTING,
} from '../../common';
import { savedSearchMock } from './saved_search';
import { UI_SETTINGS } from '../../../data/common';
import { TopNavMenu } from '../../../navigation/public';
Expand Down Expand Up @@ -44,8 +49,15 @@ export const discoverServiceMock = ({
return [];
} else if (key === UI_SETTINGS.META_FIELDS) {
return [];
} else if (key === SAMPLE_SIZE_SETTING) {
return 250;
} else if (key === SORT_DEFAULT_ORDER_SETTING) {
return 'desc';
}
},
isDefault: (key: string) => {
return true;
},
},
indexPatternFieldEditor: {
openEditor: jest.fn(),
Expand All @@ -60,4 +72,8 @@ export const discoverServiceMock = ({
metadata: {
branch: 'test',
},
theme: {
useChartsTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
useChartsBaseTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
},
} as unknown) as DiscoverServices;
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { Subject, BehaviorSubject } from 'rxjs';
import { mountWithIntl } from '@kbn/test/jest';
import { setHeaderActionMenuMounter } from '../../../../../kibana_services';
import { esHits } from '../../../../../__mocks__/es_hits';
import { savedSearchMock } from '../../../../../__mocks__/saved_search';
import { createSearchSourceMock } from '../../../../../../../data/common/search/search_source/mocks';
import { GetStateReturn } from '../../services/discover_state';
import { DataCharts$, DataTotalHits$ } from '../../services/use_saved_search';
import { discoverServiceMock } from '../../../../../__mocks__/services';
import { FetchStatus } from '../../../../types';
import { Chart } from './point_series';
import { DiscoverChart } from './discover_chart';

setHeaderActionMenuMounter(jest.fn());

function getProps(timefield?: string) {
const searchSourceMock = createSearchSourceMock({});
const services = discoverServiceMock;
services.data.query.timefilter.timefilter.getTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};

const totalHits$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
result: Number(esHits.length),
}) as DataTotalHits$;

const chartData = ({
xAxisOrderedValues: [
1623880800000,
1623967200000,
1624053600000,
1624140000000,
1624226400000,
1624312800000,
1624399200000,
1624485600000,
1624572000000,
1624658400000,
1624744800000,
1624831200000,
1624917600000,
1625004000000,
1625090400000,
],
xAxisFormat: { id: 'date', params: { pattern: 'YYYY-MM-DD' } },
xAxisLabel: 'order_date per day',
yAxisFormat: { id: 'number' },
ordered: {
date: true,
interval: {
asMilliseconds: jest.fn(),
},
intervalESUnit: 'd',
intervalESValue: 1,
min: '2021-03-18T08:28:56.411Z',
max: '2021-07-01T07:28:56.411Z',
},
yAxisLabel: 'Count',
values: [
{ x: 1623880800000, y: 134 },
{ x: 1623967200000, y: 152 },
{ x: 1624053600000, y: 141 },
{ x: 1624140000000, y: 138 },
{ x: 1624226400000, y: 142 },
{ x: 1624312800000, y: 157 },
{ x: 1624399200000, y: 149 },
{ x: 1624485600000, y: 146 },
{ x: 1624572000000, y: 170 },
{ x: 1624658400000, y: 137 },
{ x: 1624744800000, y: 150 },
{ x: 1624831200000, y: 144 },
{ x: 1624917600000, y: 147 },
{ x: 1625004000000, y: 137 },
{ x: 1625090400000, y: 66 },
],
} as unknown) as Chart;

const charts$ = new BehaviorSubject({
fetchStatus: FetchStatus.COMPLETE,
chartData,
bucketInterval: {
scaled: true,
description: 'test',
scale: 2,
},
}) as DataCharts$;

return {
isLegacy: false,
resetQuery: jest.fn(),
savedSearch: savedSearchMock,
savedSearchDataChart$: charts$,
savedSearchDataTotalHits$: totalHits$,
savedSearchRefetch$: new Subject(),
searchSource: searchSourceMock,
services,
state: { columns: [] },
stateContainer: {} as GetStateReturn,
timefield,
};
}

describe('Discover chart', () => {
test('render without timefield', () => {
const component = mountWithIntl(<DiscoverChart {...getProps()} />);
expect(component.find('[data-test-subj="discoverChartToggle"]').exists()).toBeFalsy();
});
test('render with filefield', () => {
const component = mountWithIntl(<DiscoverChart {...getProps('timefield')} />);
expect(component.find('[data-test-subj="discoverChartToggle"]').exists()).toBeTruthy();
});
});
Loading

0 comments on commit 53afa0d

Please sign in to comment.