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] [index pattern management] Restore cross cluster search functionality (#108756) #108977

Merged
merged 1 commit into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
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 @@ -36,7 +36,7 @@ export const EmptyPrompts: FC<Props> = ({
loadSources,
}) => {
const {
services: { docLinks, application, http },
services: { docLinks, application, http, searchClient },
} = useKibana<IndexPatternEditorContext>();

const [remoteClustersExist, setRemoteClustersExist] = useState<boolean>(false);
Expand All @@ -47,15 +47,21 @@ export const EmptyPrompts: FC<Props> = ({
useCallback(() => {
let isMounted = true;
if (!hasDataIndices)
getIndices(http, () => false, '*:*', false).then((dataSources) => {
getIndices({
http,
isRollupIndex: () => false,
pattern: '*:*',
showAllIndices: false,
searchClient,
}).then((dataSources) => {
if (isMounted) {
setRemoteClustersExist(!!dataSources.filter(removeAliases).length);
}
});
return () => {
isMounted = false;
};
}, [http, hasDataIndices]);
}, [http, hasDataIndices, searchClient]);

if (!hasExistingIndexPatterns && !goToForm) {
if (!hasDataIndices && !remoteClustersExist) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
}: Props) => {
const isMounted = useRef<boolean>(false);
const {
services: { http, indexPatternService, uiSettings },
services: { http, indexPatternService, uiSettings, searchClient },
} = useKibana<IndexPatternEditorContext>();

const { form } = useForm<IndexPatternConfig, FormInternal>({
Expand Down Expand Up @@ -128,13 +128,19 @@ const IndexPatternEditorFlyoutContentComponent = ({

// load all data sources and set initial matchedIndices
const loadSources = useCallback(() => {
getIndices(http, () => false, '*', allowHidden).then((dataSources) => {
getIndices({
http,
isRollupIndex: () => false,
pattern: '*',
showAllIndices: allowHidden,
searchClient,
}).then((dataSources) => {
setAllSources(dataSources);
const matchedSet = getMatchedIndices(dataSources, [], [], allowHidden);
setMatchedIndices(matchedSet);
setIsLoadingSources(false);
});
}, [http, allowHidden]);
}, [http, allowHidden, searchClient]);

// loading list of index patterns
useEffect(() => {
Expand Down Expand Up @@ -223,13 +229,31 @@ const IndexPatternEditorFlyoutContentComponent = ({
const indexRequests = [];

if (query?.endsWith('*')) {
const exactMatchedQuery = getIndices(http, isRollupIndex, query, allowHidden);
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, query, allowHidden);
const partialMatchQuery = getIndices(http, isRollupIndex, `${query}*`, allowHidden);
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);
Expand Down Expand Up @@ -264,7 +288,7 @@ const IndexPatternEditorFlyoutContentComponent = ({

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

useEffect(() => {
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/index_pattern_editor/public/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
export const pluginName = 'index_pattern_editor';
export const MAX_NUMBER_OF_MATCHING_INDICES = 100;
export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns';

// This isn't ideal. We want to avoid searching for 20 indices
// then filtering out the majority of them because they are system indices.
// We'd like to filter system indices out in the query
// so if we can accomplish that in the future, this logic can go away
export const ESTIMATED_NUMBER_OF_SYSTEM_INDICES = 100;
export const MAX_SEARCH_SIZE = MAX_NUMBER_OF_MATCHING_INDICES + ESTIMATED_NUMBER_OF_SYSTEM_INDICES;
136 changes: 113 additions & 23 deletions src/plugins/index_pattern_editor/public/lib/get_indices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@
* Side Public License, v 1.
*/

/*
* 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 { getIndices, responseToItemArray } from './get_indices';
import {
getIndices,
getIndicesViaSearch,
responseToItemArray,
dedupeMatchedItems,
} from './get_indices';
import { httpServiceMock } from '../../../../core/public/mocks';
import { ResolveIndexResponseItemIndexAttrs } from '../types';
import { ResolveIndexResponseItemIndexAttrs, MatchedItem } from '../types';
import { Observable } from 'rxjs';

export const successfulResponse = {
export const successfulResolveResponse = {
indices: [
{
name: 'remoteCluster1:bar-01',
Expand All @@ -40,28 +38,99 @@ export const successfulResponse = {
],
};

const mockGetTags = () => [];
const mockIsRollupIndex = () => false;
const successfulSearchResponse = {
isPartial: false,
isRunning: false,
rawResponse: {
aggregations: {
indices: {
buckets: [{ key: 'kibana_sample_data_ecommerce' }, { key: '.kibana_1' }],
},
},
},
};

const partialSearchResponse = {
isPartial: true,
isRunning: true,
rawResponse: {
hits: {
total: 2,
hits: [],
},
},
};

const errorSearchResponse = {
isPartial: true,
isRunning: false,
};

const isRollupIndex = () => false;
const getTags = () => [];
const searchClient = () =>
new Observable((observer) => {
observer.next(successfulSearchResponse);
observer.complete();
}) as any;

const http = httpServiceMock.createStartContract();
http.get.mockResolvedValue(successfulResponse);
http.get.mockResolvedValue(successfulResolveResponse);

describe('getIndices', () => {
it('should work in a basic case', async () => {
const result = await getIndices(http, mockIsRollupIndex, 'kibana', false);
const uncalledSearchClient = jest.fn();
const result = await getIndices({
http,
pattern: 'kibana',
searchClient: uncalledSearchClient,
isRollupIndex,
});
expect(http.get).toHaveBeenCalled();
expect(uncalledSearchClient).not.toHaveBeenCalled();
expect(result.length).toBe(3);
expect(result[0].name).toBe('f-alias');
expect(result[1].name).toBe('foo');
});

it('should make two calls in cross cluser case', async () => {
http.get.mockResolvedValue(successfulResolveResponse);
const result = await getIndices({ http, pattern: '*:kibana', searchClient, isRollupIndex });

expect(http.get).toHaveBeenCalled();
expect(result.length).toBe(4);
expect(result[0].name).toBe('f-alias');
expect(result[1].name).toBe('foo');
expect(result[2].name).toBe('kibana_sample_data_ecommerce');
expect(result[3].name).toBe('remoteCluster1:bar-01');
});

it('should ignore ccs query-all', async () => {
expect((await getIndices(http, mockIsRollupIndex, '*:', false)).length).toBe(0);
expect((await getIndices({ http, pattern: '*:', searchClient, isRollupIndex })).length).toBe(0);
});

it('should ignore a single comma', async () => {
expect((await getIndices(http, mockIsRollupIndex, ',', false)).length).toBe(0);
expect((await getIndices(http, mockIsRollupIndex, ',*', false)).length).toBe(0);
expect((await getIndices(http, mockIsRollupIndex, ',foobar', false)).length).toBe(0);
expect((await getIndices({ http, pattern: ',', searchClient, isRollupIndex })).length).toBe(0);
expect((await getIndices({ http, pattern: ',*', searchClient, isRollupIndex })).length).toBe(0);
expect(
(await getIndices({ http, pattern: ',foobar', searchClient, isRollupIndex })).length
).toBe(0);
});

it('should work with partial responses', async () => {
const searchClientPartialResponse = () =>
new Observable((observer) => {
observer.next(partialSearchResponse);
observer.next(successfulSearchResponse);
observer.complete();
}) as any;
const result = await getIndices({
http,
pattern: '*:kibana',
searchClient: searchClientPartialResponse,
isRollupIndex,
});
expect(result.length).toBe(4);
});

it('response object to item array', () => {
Expand Down Expand Up @@ -89,16 +158,37 @@ describe('getIndices', () => {
},
],
};
expect(responseToItemArray(result, mockGetTags)).toMatchSnapshot();
expect(responseToItemArray({}, mockGetTags)).toEqual([]);
expect(responseToItemArray(result, getTags)).toMatchSnapshot();
expect(responseToItemArray({}, getTags)).toEqual([]);
});

it('matched items are deduped', () => {
const setA = [{ name: 'a' }, { name: 'b' }] as MatchedItem[];
const setB = [{ name: 'b' }, { name: 'c' }] as MatchedItem[];
expect(dedupeMatchedItems(setA, setB)).toHaveLength(3);
});

describe('errors', () => {
it('should handle errors gracefully', async () => {
it('should handle thrown errors gracefully', async () => {
http.get.mockImplementationOnce(() => {
throw new Error('Test error');
});
const result = await getIndices(http, mockIsRollupIndex, 'kibana', false);
const result = await getIndices({ http, pattern: 'kibana', searchClient, isRollupIndex });
expect(result.length).toBe(0);
});

it('getIndicesViaSearch should handle error responses gracefully', async () => {
const searchClientErrorResponse = () =>
new Observable((observer) => {
observer.next(errorSearchResponse);
observer.complete();
}) as any;
const result = await getIndicesViaSearch({
pattern: '*:kibana',
searchClient: searchClientErrorResponse,
showAllIndices: false,
isRollupIndex,
});
expect(result.length).toBe(0);
});
});
Expand Down
Loading