Fixes error when viewing rollup v1 index patterns (#118019)

* Fixes error when viewing rollup v1 index patterns

* Fix time based data view detection

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Tim Roes 2021-11-11 18:07:13 +01:00 committed by GitHub
parent afa2392e72
commit 61acc0db7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 77 additions and 48 deletions

View file

@ -14,7 +14,7 @@ import { SearchSource } from './search_source';
import { ISearchStartSearchSource, ISearchSource, SearchSourceFields } from './types';
export const searchSourceInstanceMock: MockedKeys<ISearchSource> = {
setPreferredSearchStrategyId: jest.fn(),
setOverwriteDataViewType: jest.fn(),
setFields: jest.fn().mockReturnThis(),
setField: jest.fn().mockReturnThis(),
removeField: jest.fn().mockReturnThis(),

View file

@ -124,7 +124,8 @@ export interface SearchSourceDependencies extends FetchHandlers {
/** @public **/
export class SearchSource {
private id: string = uniqueId('data_source');
private searchStrategyId?: string;
private shouldOverwriteDataViewType: boolean = false;
private overwriteDataViewType?: string;
private parent?: SearchSource;
private requestStartHandlers: Array<
(searchSource: SearchSource, options?: ISearchOptions) => Promise<unknown>
@ -149,11 +150,22 @@ export class SearchSource {
*****/
/**
* internal, dont use
* @param searchStrategyId
* Used to make the search source overwrite the actual data view type for the
* specific requests done. This should only be needed very rarely, since it means
* e.g. we'd be treating a rollup index pattern as a regular one. Be very sure
* you understand the consequences of using this method before using it.
*
* @param overwriteType If `false` is passed in it will disable the overwrite, otherwise
* the passed in value will be used as the data view type for this search source.
*/
setPreferredSearchStrategyId(searchStrategyId: string) {
this.searchStrategyId = searchStrategyId;
setOverwriteDataViewType(overwriteType: string | undefined | false) {
if (overwriteType === false) {
this.shouldOverwriteDataViewType = false;
this.overwriteDataViewType = undefined;
} else {
this.shouldOverwriteDataViewType = true;
this.overwriteDataViewType = overwriteType;
}
}
/**
@ -609,11 +621,7 @@ export class SearchSource {
}
private getIndexType(index?: IIndexPattern) {
if (this.searchStrategyId) {
return this.searchStrategyId === 'default' ? undefined : this.searchStrategyId;
} else {
return index?.type;
}
return this.shouldOverwriteDataViewType ? this.overwriteDataViewType : index?.type;
}
private readonly getFieldName = (fld: string | Record<string, any>): string =>

View file

@ -23,7 +23,7 @@ import { VIEW_MODE } from '../../../../components/view_mode_toggle';
setHeaderActionMenuMounter(jest.fn());
function getProps(timefield?: string) {
function getProps(isTimeBased: boolean = false) {
const searchSourceMock = createSearchSourceMock({});
const services = discoverServiceMock;
services.data.query.timefilter.timefilter.getAbsoluteTime = () => {
@ -85,6 +85,7 @@ function getProps(timefield?: string) {
}) as DataCharts$;
return {
isTimeBased,
resetSavedSearch: jest.fn(),
savedSearch: savedSearchMock,
savedSearchDataChart$: charts$,
@ -94,7 +95,6 @@ function getProps(timefield?: string) {
services,
state: { columns: [] },
stateContainer: {} as GetStateReturn,
timefield,
viewMode: VIEW_MODE.DOCUMENT_LEVEL,
setDiscoverViewMode: jest.fn(),
};
@ -106,7 +106,7 @@ describe('Discover chart', () => {
expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy();
});
test('render with filefield', () => {
const component = mountWithIntl(<DiscoverChart {...getProps('timefield')} />);
const component = mountWithIntl(<DiscoverChart {...getProps(true)} />);
expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy();
});
});

View file

@ -37,7 +37,7 @@ export function DiscoverChart({
services,
state,
stateContainer,
timefield,
isTimeBased,
viewMode,
setDiscoverViewMode,
}: {
@ -48,7 +48,7 @@ export function DiscoverChart({
services: DiscoverServices;
state: AppState;
stateContainer: GetStateReturn;
timefield?: string;
isTimeBased: boolean;
viewMode: VIEW_MODE;
setDiscoverViewMode: (viewMode: VIEW_MODE) => void;
}) {
@ -123,7 +123,7 @@ export function DiscoverChart({
/>
</EuiFlexItem>
)}
{timefield && (
{isTimeBased && (
<EuiFlexItem className="dscResultCount__toggle" grow={false}>
<EuiPopover
id="dscChartOptions"
@ -150,7 +150,7 @@ export function DiscoverChart({
)}
</EuiFlexGroup>
</EuiFlexItem>
{timefield && !state.hideChart && (
{isTimeBased && !state.hideChart && (
<EuiFlexItem grow={false}>
<section
ref={(element) => (chartRef.current.element = element)}

View file

@ -49,6 +49,7 @@ import {
DOCUMENTS_VIEW_CLICK,
FIELD_STATISTICS_VIEW_CLICK,
} from '../../../components/field_stats_table/constants';
import { DataViewType } from '../../../../../../data_views/common';
/**
* Local storage key for sidebar persistence state
@ -122,8 +123,12 @@ export function DiscoverLayout({
useSavedSearchAliasMatchRedirect({ savedSearch, spaces, history });
const timeField = useMemo(() => {
return indexPattern.type !== 'rollup' ? indexPattern.timeFieldName : undefined;
// We treat rollup v1 data views as non time based in Discover, since we query them
// in a non time based way using the regular _search API, since the internal
// representation of those documents does not have the time field that _field_caps
// reports us.
const isTimeBased = useMemo(() => {
return indexPattern.type !== DataViewType.ROLLUP && indexPattern.isTimeBased();
}, [indexPattern]);
const initialSidebarClosed = Boolean(storage.get(SIDEBAR_CLOSED_KEY));
@ -276,7 +281,7 @@ export function DiscoverLayout({
>
{resultState === 'none' && (
<DiscoverNoResults
timeFieldName={timeField}
isTimeBased={isTimeBased}
data={data}
error={dataState.error}
hasQuery={!!state.query?.query}
@ -307,7 +312,7 @@ export function DiscoverLayout({
savedSearchDataTotalHits$={totalHits$}
services={services}
stateContainer={stateContainer}
timefield={timeField}
isTimeBased={isTimeBased}
viewMode={viewMode}
setDiscoverViewMode={setDiscoverViewMode}
/>

View file

@ -62,7 +62,7 @@ describe('DiscoverNoResults', () => {
describe('timeFieldName', () => {
test('renders time range feedback', () => {
const result = mountAndFindSubjects({
timeFieldName: 'awesome_time_field',
isTimeBased: true,
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -94,7 +94,7 @@ describe('DiscoverNoResults', () => {
test('renders error message', () => {
const error = new Error('Fatal error');
const result = mountAndFindSubjects({
timeFieldName: 'awesome_time_field',
isTimeBased: true,
error,
});
expect(result).toMatchInlineSnapshot(`

View file

@ -22,7 +22,7 @@ import './_no_results.scss';
import { NoResultsIllustration } from './assets/no_results_illustration';
export interface DiscoverNoResultsProps {
timeFieldName?: string;
isTimeBased?: boolean;
error?: Error;
data?: DataPublicPluginStart;
hasQuery?: boolean;
@ -31,7 +31,7 @@ export interface DiscoverNoResultsProps {
}
export function DiscoverNoResults({
timeFieldName,
isTimeBased,
error,
data,
hasFilters,
@ -54,7 +54,7 @@ export function DiscoverNoResults({
<NoResultsIllustration />
</EuiFlexItem>
<EuiFlexItem grow={2}>
{!!timeFieldName && getTimeFieldMessage()}
{isTimeBased && getTimeFieldMessage()}
{(hasFilters || hasQuery) && (
<AdjustSearch
hasFilters={hasFilters}

View file

@ -12,6 +12,7 @@ import { getTopNavLinks } from './get_top_nav_links';
import { Query, TimeRange } from '../../../../../../data/common/query';
import { getHeaderActionMenuMounter } from '../../../../kibana_services';
import { GetStateReturn } from '../../services/discover_state';
import { DataViewType } from '../../../../../../data_views/common';
export type DiscoverTopNavProps = Pick<
DiscoverLayoutProps,
@ -39,7 +40,10 @@ export const DiscoverTopNav = ({
resetSavedSearch,
}: DiscoverTopNavProps) => {
const history = useHistory();
const showDatePicker = useMemo(() => indexPattern.isTimeBased(), [indexPattern]);
const showDatePicker = useMemo(
() => indexPattern.isTimeBased() && indexPattern.type !== DataViewType.ROLLUP,
[indexPattern]
);
const { TopNavMenu } = services.navigation.ui;
const onOpenSavedSearch = useCallback(

View file

@ -26,6 +26,7 @@ import { DataPublicPluginStart } from '../../../../../data/public';
import { SavedSearchData } from './use_saved_search';
import { DiscoverServices } from '../../../build_services';
import { ReduxLikeStateContainer } from '../../../../../kibana_utils/common';
import { DataViewType } from '../../../../../data_views/common';
export function fetchAll(
dataSubjects: SavedSearchData,
@ -72,16 +73,17 @@ export function fetchAll(
},
};
const isChartVisible =
!hideChart && indexPattern.isTimeBased() && indexPattern.type !== DataViewType.ROLLUP;
const all = forkJoin({
documents: fetchDocuments(dataSubjects, searchSource.createCopy(), subFetchDeps),
totalHits:
hideChart || !indexPattern.timeFieldName
? fetchTotalHits(dataSubjects, searchSource.createCopy(), subFetchDeps)
: of(null),
chart:
!hideChart && indexPattern.timeFieldName
? fetchChart(dataSubjects, searchSource.createCopy(), subFetchDeps)
: of(null),
totalHits: !isChartVisible
? fetchTotalHits(dataSubjects, searchSource.createCopy(), subFetchDeps)
: of(null),
chart: isChartVisible
? fetchChart(dataSubjects, searchSource.createCopy(), subFetchDeps)
: of(null),
});
all.subscribe(

View file

@ -38,6 +38,13 @@ export const fetchDocuments = (
searchSource.setField('trackTotalHits', false);
searchSource.setField('highlightAll', true);
searchSource.setField('version', true);
if (searchSource.getField('index')?.type === 'rollup') {
// We treat that index pattern as "normal" even if it was a rollup index pattern,
// since the rollup endpoint does not support querying individual documents, but we
// can get them from the regular _search API that will be used if the index pattern
// not a rollup index pattern.
searchSource.setOverwriteDataViewType(undefined);
}
sendLoadingMsg(documents$);

View file

@ -13,6 +13,7 @@ import {
isCompleteResponse,
ISearchSource,
} from '../../../../../data/public';
import { DataViewType } from '../../../../../data_views/common';
import { Adapters } from '../../../../../inspector/common';
import { FetchStatus } from '../../types';
import { SavedSearchData } from './use_saved_search';
@ -36,13 +37,18 @@ export function fetchTotalHits(
}
) {
const { totalHits$ } = data$;
const indexPattern = searchSource.getField('index');
searchSource.setField('trackTotalHits', true);
searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(indexPattern!));
searchSource.setField('size', 0);
searchSource.removeField('sort');
searchSource.removeField('fields');
searchSource.removeField('aggs');
if (searchSource.getField('index')?.type === DataViewType.ROLLUP) {
// We treat that index pattern as "normal" even if it was a rollup index pattern,
// since the rollup endpoint does not support querying individual documents, but we
// can get them from the regular _search API that will be used if the index pattern
// not a rollup index pattern.
searchSource.setOverwriteDataViewType(undefined);
}
sendLoadingMsg(totalHits$);

View file

@ -8,6 +8,7 @@
import { SORT_DEFAULT_ORDER_SETTING } from '../../../../common';
import { IndexPattern, ISearchSource } from '../../../../../data/common';
import { DataViewType } from '../../../../../data_views/common';
import type { SortOrder } from '../../../services/saved_searches';
import { DiscoverServices } from '../../../build_services';
import { getSortForSearchSource } from '../../../components/doc_table';
@ -44,14 +45,9 @@ export function updateSearchSource(
indexPattern,
uiSettings.get(SORT_DEFAULT_ORDER_SETTING)
);
searchSource
.setField('trackTotalHits', true)
.setField('sort', usedSort)
// Even when searching rollups, we want to use the default strategy so that we get back a
// document-like response.
.setPreferredSearchStrategyId('default');
searchSource.setField('trackTotalHits', true).setField('sort', usedSort);
if (indexPattern.type !== 'rollup') {
if (indexPattern.type !== DataViewType.ROLLUP) {
// Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range
searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(indexPattern));
}

View file

@ -124,8 +124,8 @@ describe('getSavedSearch', () => {
"serialize": [MockFunction],
"setField": [MockFunction],
"setFields": [MockFunction],
"setOverwriteDataViewType": [MockFunction],
"setParent": [MockFunction],
"setPreferredSearchStrategyId": [MockFunction],
},
"sharingSavedObjectProps": Object {
"aliasTargetId": undefined,

View file

@ -68,9 +68,10 @@ describe('saved_searches_utils', () => {
"history": Array [],
"id": "data_source1",
"inheritOptions": Object {},
"overwriteDataViewType": undefined,
"parent": undefined,
"requestStartHandlers": Array [],
"searchStrategyId": undefined,
"shouldOverwriteDataViewType": false,
},
"sharingSavedObjectProps": Object {},
"sort": Array [],

View file

@ -6,7 +6,7 @@
"source": {
"index-pattern": {
"fields":"[]",
"timeFieldName": "@timestamp",
"timeFieldName": "nested.timestamp",
"title": "date-nested"
},
"type": "index-pattern"