Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [DataViews] Fix excessive resolve_index requests in create data view flyout (#109500) #109701

Merged
merged 1 commit into from
Aug 23, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import memoizeOne from 'memoize-one';

import {
IndexPatternSpec,
Expand Down Expand Up @@ -105,12 +106,22 @@ const IndexPatternEditorFlyoutContentComponent = ({

const { getFields } = form;

const [{ title, allowHidden, type }] = useFormData<FormInternal>({ form });
// `useFormData` initially returns `undefined`,
// we override `undefined` with real default values from `schema`
// to get a stable reference to avoid hooks re-run and reduce number of excessive requests
const [
{
title = schema.title.defaultValue,
allowHidden = schema.allowHidden.defaultValue,
type = schema.type.defaultValue,
},
] = useFormData<FormInternal>({ form });
const [isLoadingSources, setIsLoadingSources] = useState<boolean>(true);

const [timestampFieldOptions, setTimestampFieldOptions] = useState<TimestampOption[]>([]);
const [isLoadingTimestampFields, setIsLoadingTimestampFields] = useState<boolean>(false);
const [isLoadingMatchedIndices, setIsLoadingMatchedIndices] = useState<boolean>(false);
const currentLoadingMatchedIndicesRef = useRef(0);
const [allSources, setAllSources] = useState<MatchedItem[]>([]);
const [isLoadingIndexPatterns, setIsLoadingIndexPatterns] = useState<boolean>(true);
const [existingIndexPatterns, setExistingIndexPatterns] = useState<string[]>([]);
Expand Down Expand Up @@ -165,7 +176,9 @@ const IndexPatternEditorFlyoutContentComponent = ({
try {
const response = await http.get('/api/rollup/indices');
if (isMounted.current) {
setRollupIndicesCapabilities(response || {});
if (response) {
setRollupIndicesCapabilities(response);
}
}
} catch (e) {
// Silently swallow failure responses such as expired trials
Expand Down Expand Up @@ -225,52 +238,31 @@ const IndexPatternEditorFlyoutContentComponent = ({
let newRollupIndexName: string | undefined;

const fetchIndices = async (query: string = '') => {
setIsLoadingMatchedIndices(true);
const indexRequests = [];

if (query?.endsWith('*')) {
const exactMatchedQuery = getIndices({
http,
isRollupIndex,
pattern: query,
showAllIndices: allowHidden,
searchClient,
});
indexRequests.push(exactMatchedQuery);
// provide default value when not making a request for the partialMatchQuery
indexRequests.push(Promise.resolve([]));
} else {
const exactMatchQuery = getIndices({
http,
isRollupIndex,
pattern: query,
showAllIndices: allowHidden,
searchClient,
});
const partialMatchQuery = getIndices({
http,
isRollupIndex,
pattern: `${query}*`,
showAllIndices: allowHidden,
searchClient,
});

indexRequests.push(exactMatchQuery);
indexRequests.push(partialMatchQuery);
}

const [exactMatched, partialMatched] = (await ensureMinimumTime(
indexRequests
)) as MatchedItem[][];
const currentLoadingMatchedIndicesIdx = ++currentLoadingMatchedIndicesRef.current;

const matchedIndicesResult = getMatchedIndices(
allSources,
partialMatched,
exactMatched,
allowHidden
);
setIsLoadingMatchedIndices(true);

if (isMounted.current) {
const { matchedIndicesResult, exactMatched } = !isLoadingSources
? await loadMatchedIndices(query, allowHidden, allSources, {
isRollupIndex,
http,
searchClient,
})
: {
matchedIndicesResult: {
exactMatchedIndices: [],
allIndices: [],
partialMatchedIndices: [],
visibleIndices: [],
},
exactMatched: [],
};

if (
currentLoadingMatchedIndicesIdx === currentLoadingMatchedIndicesRef.current &&
isMounted.current
) {
// we are still interested in this result
if (type === INDEX_PATTERN_TYPE.ROLLUP) {
const rollupIndices = exactMatched.filter((index) => isRollupIndex(index.name));
newRollupIndexName = rollupIndices.length === 1 ? rollupIndices[0].name : undefined;
Expand All @@ -288,7 +280,7 @@ const IndexPatternEditorFlyoutContentComponent = ({

return fetchIndices(newTitle);
},
[http, allowHidden, allSources, type, rollupIndicesCapabilities, searchClient]
[http, allowHidden, allSources, type, rollupIndicesCapabilities, searchClient, isLoadingSources]
);

useEffect(() => {
Expand Down Expand Up @@ -396,3 +388,76 @@ const IndexPatternEditorFlyoutContentComponent = ({
};

export const IndexPatternEditorFlyoutContent = React.memo(IndexPatternEditorFlyoutContentComponent);

// loadMatchedIndices is called both as an side effect inside of a parent component and the inside forms validation functions
// that are challenging to synchronize without a larger refactor
// Use memoizeOne as a caching layer to avoid excessive network requests on each key type
// TODO: refactor to remove `memoize` when https://github.com/elastic/kibana/pull/109238 is done
const loadMatchedIndices = memoizeOne(
async (
query: string,
allowHidden: boolean,
allSources: MatchedItem[],
{
isRollupIndex,
http,
searchClient,
}: {
isRollupIndex: (index: string) => boolean;
http: IndexPatternEditorContext['http'];
searchClient: IndexPatternEditorContext['searchClient'];
}
): Promise<{
matchedIndicesResult: MatchedIndicesSet;
exactMatched: MatchedItem[];
partialMatched: MatchedItem[];
}> => {
const indexRequests = [];

if (query?.endsWith('*')) {
const exactMatchedQuery = getIndices({
http,
isRollupIndex,
pattern: query,
showAllIndices: allowHidden,
searchClient,
});
indexRequests.push(exactMatchedQuery);
// provide default value when not making a request for the partialMatchQuery
indexRequests.push(Promise.resolve([]));
} else {
const exactMatchQuery = getIndices({
http,
isRollupIndex,
pattern: query,
showAllIndices: allowHidden,
searchClient,
});
const partialMatchQuery = getIndices({
http,
isRollupIndex,
pattern: `${query}*`,
showAllIndices: allowHidden,
searchClient,
});

indexRequests.push(exactMatchQuery);
indexRequests.push(partialMatchQuery);
}

const [exactMatched, partialMatched] = (await ensureMinimumTime(
indexRequests
)) as MatchedItem[][];

const matchedIndicesResult = getMatchedIndices(
allSources,
partialMatched,
exactMatched,
allowHidden
);

return { matchedIndicesResult, exactMatched, partialMatched };
},
// compare only query and allowHidden
(newArgs, oldArgs) => newArgs[0] === oldArgs[0] && newArgs[1] === oldArgs[1]
);