mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Enterprise Search] Add filtering for search indices (#154720)
## Summary Adds a toggle to only show search-optimized indices to our indices page. Also splits up the fetch indices calls for our search applications and the indices page into two separate flows. <img width="1144" alt="Screenshot 2023-04-11 at 14 30 08" src="https://user-images.githubusercontent.com/94373878/231163183-21b58d58-e28e-44f2-a42e-033508e15ebd.png">
This commit is contained in:
parent
bada04c27b
commit
43fa6e549c
13 changed files with 758 additions and 604 deletions
|
@ -21,15 +21,24 @@ describe('FetchIndicesApiLogic', () => {
|
|||
const promise = Promise.resolve({ result: 'result' });
|
||||
http.get.mockReturnValue(promise);
|
||||
const result = fetchIndices({
|
||||
meta: { page: { current: 1, size: 20, total_pages: 10, total_results: 10 } },
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
await nextTick();
|
||||
expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/indices', {
|
||||
query: { page: 1, return_hidden_indices: false, search_query: null, size: 20 },
|
||||
query: {
|
||||
from: 0,
|
||||
only_show_search_optimized_indices: false,
|
||||
return_hidden_indices: false,
|
||||
search_query: null,
|
||||
size: 20,
|
||||
},
|
||||
});
|
||||
await expect(result).resolves.toEqual({
|
||||
isInitialRequest: true,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
result: 'result',
|
||||
returnHiddenIndices: false,
|
||||
searchQuery: undefined,
|
||||
|
@ -39,15 +48,24 @@ describe('FetchIndicesApiLogic', () => {
|
|||
const promise = Promise.resolve({ result: 'result' });
|
||||
http.get.mockReturnValue(promise);
|
||||
const result = fetchIndices({
|
||||
meta: { page: { current: 2, size: 20, total_pages: 10, total_results: 10 } },
|
||||
from: 1,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
await nextTick();
|
||||
expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/indices', {
|
||||
query: { page: 2, return_hidden_indices: false, search_query: null, size: 20 },
|
||||
query: {
|
||||
from: 1,
|
||||
only_show_search_optimized_indices: false,
|
||||
return_hidden_indices: false,
|
||||
search_query: null,
|
||||
size: 20,
|
||||
},
|
||||
});
|
||||
await expect(result).resolves.toEqual({
|
||||
isInitialRequest: false,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
result: 'result',
|
||||
returnHiddenIndices: false,
|
||||
searchQuery: undefined,
|
||||
|
@ -57,16 +75,25 @@ describe('FetchIndicesApiLogic', () => {
|
|||
const promise = Promise.resolve({ result: 'result' });
|
||||
http.get.mockReturnValue(promise);
|
||||
const result = fetchIndices({
|
||||
meta: { page: { current: 1, size: 20, total_pages: 10, total_results: 10 } },
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
searchQuery: 'a',
|
||||
size: 20,
|
||||
});
|
||||
await nextTick();
|
||||
expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/indices', {
|
||||
query: { page: 1, return_hidden_indices: false, search_query: 'a', size: 20 },
|
||||
query: {
|
||||
from: 0,
|
||||
only_show_search_optimized_indices: false,
|
||||
return_hidden_indices: false,
|
||||
search_query: 'a',
|
||||
size: 20,
|
||||
},
|
||||
});
|
||||
await expect(result).resolves.toEqual({
|
||||
isInitialRequest: false,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
result: 'result',
|
||||
returnHiddenIndices: false,
|
||||
searchQuery: 'a',
|
||||
|
|
|
@ -5,28 +5,44 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Meta } from '../../../../../common/types';
|
||||
import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
|
||||
import { Meta } from '../../../../../common/types/pagination';
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export const fetchIndices = async ({
|
||||
meta,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
}: {
|
||||
meta: Meta;
|
||||
export interface FetchIndicesParams {
|
||||
from: number;
|
||||
onlyShowSearchOptimizedIndices: boolean;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
}) => {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface FetchIndicesResponse {
|
||||
indices: ElasticsearchIndexWithIngestion[];
|
||||
isInitialRequest: boolean;
|
||||
meta: Meta;
|
||||
onlyShowSearchOptimizedIndices: boolean;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
}
|
||||
|
||||
export const fetchIndices = async ({
|
||||
from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
size,
|
||||
}: FetchIndicesParams): Promise<FetchIndicesResponse> => {
|
||||
const { http } = HttpLogic.values;
|
||||
const route = '/internal/enterprise_search/indices';
|
||||
const query = {
|
||||
page: meta.page.current,
|
||||
from,
|
||||
only_show_search_optimized_indices: onlyShowSearchOptimizedIndices,
|
||||
return_hidden_indices: returnHiddenIndices,
|
||||
search_query: searchQuery || null,
|
||||
size: 20,
|
||||
size: size ?? 20,
|
||||
};
|
||||
const response = await http.get<{ indices: ElasticsearchIndexWithIngestion[]; meta: Meta }>(
|
||||
route,
|
||||
|
@ -36,9 +52,17 @@ export const fetchIndices = async ({
|
|||
);
|
||||
|
||||
// We need this to determine whether to show the empty state on the indices page
|
||||
const isInitialRequest = meta.page.current === 1 && !searchQuery;
|
||||
const isInitialRequest = from === 0 && !searchQuery && !onlyShowSearchOptimizedIndices;
|
||||
|
||||
return { ...response, isInitialRequest, returnHiddenIndices, searchQuery };
|
||||
return {
|
||||
...response,
|
||||
isInitialRequest,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
};
|
||||
};
|
||||
|
||||
export const FetchIndicesAPILogic = createApiLogic(['content', 'indices_api_logic'], fetchIndices);
|
||||
|
||||
export type FetchIndicesApiActions = Actions<FetchIndicesParams, FetchIndicesResponse>;
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import { LogicMounter, mockFlashMessageHelpers } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { indices } from '../../__mocks__/search_indices.mock';
|
||||
|
||||
import { connectorIndex, elasticsearchViewIndices } from '../../__mocks__/view_index.mock';
|
||||
|
||||
import moment from 'moment';
|
||||
|
@ -18,7 +16,6 @@ import { nextTick } from '@kbn/test-jest-helpers';
|
|||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
|
||||
import { ConnectorStatus, SyncStatus } from '../../../../../common/types/connectors';
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
|
||||
import { FetchIndicesAPILogic } from '../../api/index/fetch_indices_api_logic';
|
||||
|
||||
|
@ -26,6 +23,22 @@ import { IngestionMethod, IngestionStatus } from '../../types';
|
|||
|
||||
import { IndicesLogic } from './indices_logic';
|
||||
|
||||
const DEFAULT_META = {
|
||||
page: {
|
||||
from: 0,
|
||||
size: 20,
|
||||
total: 20,
|
||||
},
|
||||
};
|
||||
|
||||
const EMPTY_META = {
|
||||
page: {
|
||||
from: 0,
|
||||
size: 20,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
data: undefined,
|
||||
deleteModalIndex: null,
|
||||
|
@ -42,8 +55,13 @@ const DEFAULT_VALUES = {
|
|||
isFetchIndexDetailsLoading: true,
|
||||
isFirstRequest: true,
|
||||
isLoading: true,
|
||||
meta: DEFAULT_META,
|
||||
searchParams: { meta: DEFAULT_META, returnHiddenIndices: false },
|
||||
meta: EMPTY_META,
|
||||
searchParams: {
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
},
|
||||
status: Status.IDLE,
|
||||
};
|
||||
|
||||
|
@ -69,15 +87,9 @@ describe('IndicesLogic', () => {
|
|||
IndicesLogic.actions.onPaginate(3);
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
meta: {
|
||||
page: {
|
||||
...DEFAULT_META.page,
|
||||
current: 3,
|
||||
},
|
||||
},
|
||||
searchParams: {
|
||||
...DEFAULT_VALUES.searchParams,
|
||||
meta: { page: { ...DEFAULT_META.page, current: 3 } },
|
||||
from: 40,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -132,7 +144,8 @@ describe('IndicesLogic', () => {
|
|||
IndicesLogic.actions.apiSuccess({
|
||||
indices: [],
|
||||
isInitialRequest: false,
|
||||
meta: DEFAULT_VALUES.meta,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -141,54 +154,15 @@ describe('IndicesLogic', () => {
|
|||
data: {
|
||||
indices: [],
|
||||
isInitialRequest: false,
|
||||
meta: DEFAULT_VALUES.meta,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
indices: [],
|
||||
isFirstRequest: false,
|
||||
isLoading: false,
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('meta', () => {
|
||||
it('updates when apiSuccess listener triggered', () => {
|
||||
const newMeta = {
|
||||
page: {
|
||||
current: 2,
|
||||
size: 5,
|
||||
total_pages: 10,
|
||||
total_results: 52,
|
||||
},
|
||||
};
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
IndicesLogic.actions.apiSuccess({
|
||||
indices,
|
||||
isInitialRequest: true,
|
||||
meta: newMeta,
|
||||
returnHiddenIndices: true,
|
||||
searchQuery: 'a',
|
||||
});
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
data: {
|
||||
indices,
|
||||
isInitialRequest: true,
|
||||
meta: newMeta,
|
||||
returnHiddenIndices: true,
|
||||
searchQuery: 'a',
|
||||
},
|
||||
hasNoIndices: false,
|
||||
indices: elasticsearchViewIndices,
|
||||
isFirstRequest: false,
|
||||
isLoading: false,
|
||||
meta: newMeta,
|
||||
searchParams: {
|
||||
meta: newMeta,
|
||||
returnHiddenIndices: true,
|
||||
searchQuery: 'a',
|
||||
},
|
||||
meta: DEFAULT_META,
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
});
|
||||
|
@ -197,10 +171,9 @@ describe('IndicesLogic', () => {
|
|||
it('updates to true when apiSuccess returns initialRequest: true with no indices', () => {
|
||||
const meta = {
|
||||
page: {
|
||||
current: 1,
|
||||
from: 0,
|
||||
size: 0,
|
||||
total_pages: 1,
|
||||
total_results: 0,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
|
@ -208,6 +181,7 @@ describe('IndicesLogic', () => {
|
|||
indices: [],
|
||||
isInitialRequest: true,
|
||||
meta,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
|
@ -216,6 +190,7 @@ describe('IndicesLogic', () => {
|
|||
indices: [],
|
||||
isInitialRequest: true,
|
||||
meta,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: true,
|
||||
|
@ -225,7 +200,8 @@ describe('IndicesLogic', () => {
|
|||
meta,
|
||||
searchParams: {
|
||||
...DEFAULT_VALUES.searchParams,
|
||||
meta,
|
||||
from: 0,
|
||||
size: 0,
|
||||
},
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
|
@ -233,10 +209,9 @@ describe('IndicesLogic', () => {
|
|||
it('updates to false when apiSuccess returns initialRequest: false with no indices', () => {
|
||||
const meta = {
|
||||
page: {
|
||||
current: 1,
|
||||
from: 0,
|
||||
size: 0,
|
||||
total_pages: 1,
|
||||
total_results: 0,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
|
@ -244,6 +219,7 @@ describe('IndicesLogic', () => {
|
|||
indices: [],
|
||||
isInitialRequest: false,
|
||||
meta,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
|
@ -252,6 +228,7 @@ describe('IndicesLogic', () => {
|
|||
indices: [],
|
||||
isInitialRequest: false,
|
||||
meta,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
@ -261,7 +238,8 @@ describe('IndicesLogic', () => {
|
|||
meta,
|
||||
searchParams: {
|
||||
...DEFAULT_VALUES.searchParams,
|
||||
meta,
|
||||
from: 0,
|
||||
size: 0,
|
||||
},
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
|
@ -301,7 +279,12 @@ describe('IndicesLogic', () => {
|
|||
|
||||
describe('listeners', () => {
|
||||
it('calls clearFlashMessages on new makeRequest', () => {
|
||||
IndicesLogic.actions.makeRequest({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.makeRequest({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
expect(mockFlashMessageHelpers.clearFlashMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('calls flashSuccessToast, closeDeleteModal and fetchIndices on deleteSuccess', () => {
|
||||
|
@ -317,41 +300,72 @@ describe('IndicesLogic', () => {
|
|||
it('calls makeRequest on fetchIndices', async () => {
|
||||
jest.useFakeTimers({ legacyFakeTimers: true });
|
||||
IndicesLogic.actions.makeRequest = jest.fn();
|
||||
IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.fetchIndices({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
jest.advanceTimersByTime(150);
|
||||
await nextTick();
|
||||
expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledWith({
|
||||
meta: DEFAULT_META,
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
});
|
||||
it('calls makeRequest once on two fetchIndices calls within 150ms', async () => {
|
||||
jest.useFakeTimers({ legacyFakeTimers: true });
|
||||
IndicesLogic.actions.makeRequest = jest.fn();
|
||||
IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.fetchIndices({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
jest.advanceTimersByTime(130);
|
||||
await nextTick();
|
||||
IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.fetchIndices({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
jest.advanceTimersByTime(150);
|
||||
await nextTick();
|
||||
expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledWith({
|
||||
meta: DEFAULT_META,
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('calls makeRequest twice on two fetchIndices calls outside 150ms', async () => {
|
||||
jest.useFakeTimers({ legacyFakeTimers: true });
|
||||
IndicesLogic.actions.makeRequest = jest.fn();
|
||||
IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.fetchIndices({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
jest.advanceTimersByTime(150);
|
||||
await nextTick();
|
||||
IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false });
|
||||
IndicesLogic.actions.fetchIndices({
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
jest.advanceTimersByTime(150);
|
||||
await nextTick();
|
||||
expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledWith({
|
||||
meta: DEFAULT_META,
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
});
|
||||
expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
@ -365,6 +379,7 @@ describe('IndicesLogic', () => {
|
|||
indices: elasticsearchViewIndices,
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -374,6 +389,7 @@ describe('IndicesLogic', () => {
|
|||
indices: elasticsearchViewIndices,
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
@ -401,6 +417,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -419,6 +436,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
@ -450,6 +468,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -464,6 +483,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
@ -494,6 +514,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -508,6 +529,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
@ -539,6 +561,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
});
|
||||
|
||||
|
@ -557,6 +580,7 @@ describe('IndicesLogic', () => {
|
|||
],
|
||||
isInitialRequest: true,
|
||||
meta: DEFAULT_META,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
},
|
||||
hasNoIndices: false,
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { Meta } from '../../../../../common/types';
|
||||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
|
||||
import { Status } from '../../../../../common/types/api';
|
||||
import { Meta } from '../../../../../common/types/pagination';
|
||||
import { Actions } from '../../../shared/api_logic/create_api_logic';
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
import { updateMetaPageIndex } from '../../../shared/table_pagination';
|
||||
import {
|
||||
CancelSyncsActions,
|
||||
CancelSyncsApiLogic,
|
||||
|
@ -27,31 +24,16 @@ import {
|
|||
FetchIndexApiLogic,
|
||||
FetchIndexApiResponse,
|
||||
} from '../../api/index/fetch_index_api_logic';
|
||||
import { FetchIndicesAPILogic } from '../../api/index/fetch_indices_api_logic';
|
||||
import {
|
||||
FetchIndicesApiActions,
|
||||
FetchIndicesAPILogic,
|
||||
} from '../../api/index/fetch_indices_api_logic';
|
||||
import { ElasticsearchViewIndex, IngestionMethod } from '../../types';
|
||||
import { getIngestionMethod, indexToViewIndex } from '../../utils/indices';
|
||||
|
||||
export interface IndicesActions {
|
||||
apiError(error: HttpError): HttpError;
|
||||
apiSuccess({
|
||||
indices,
|
||||
isInitialRequest,
|
||||
meta,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
}: {
|
||||
indices: ElasticsearchIndexWithIngestion[];
|
||||
isInitialRequest: boolean;
|
||||
meta: Meta;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
}): {
|
||||
indices: ElasticsearchIndexWithIngestion[];
|
||||
isInitialRequest: boolean;
|
||||
meta: Meta;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
};
|
||||
apiError: FetchIndicesApiActions['apiError'];
|
||||
apiSuccess: FetchIndicesApiActions['apiSuccess'];
|
||||
cancelSuccess: CancelSyncsActions['apiSuccess'];
|
||||
closeDeleteModal(): void;
|
||||
deleteError: Actions<DeleteIndexApiLogicArgs, DeleteIndexApiLogicValues>['apiError'];
|
||||
|
@ -59,15 +41,26 @@ export interface IndicesActions {
|
|||
deleteSuccess: Actions<DeleteIndexApiLogicArgs, DeleteIndexApiLogicValues>['apiSuccess'];
|
||||
fetchIndexDetails: FetchIndexActions['makeRequest'];
|
||||
fetchIndices({
|
||||
meta,
|
||||
from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
size,
|
||||
}: {
|
||||
meta: Meta;
|
||||
from: number;
|
||||
onlyShowSearchOptimizedIndices: boolean;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
}): { meta: Meta; returnHiddenIndices: boolean; searchQuery?: string };
|
||||
makeRequest: typeof FetchIndicesAPILogic.actions.makeRequest;
|
||||
size: number;
|
||||
}): {
|
||||
from: number;
|
||||
meta: Meta;
|
||||
onlyShowSearchOptimizedIndices: boolean;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
size: number;
|
||||
};
|
||||
makeRequest: FetchIndicesApiActions['makeRequest'];
|
||||
onPaginate(newPageIndex: number): { newPageIndex: number };
|
||||
openDeleteModal(indexName: string): { indexName: string };
|
||||
setIsFirstRequest(): void;
|
||||
|
@ -89,17 +82,31 @@ export interface IndicesValues {
|
|||
isFirstRequest: boolean;
|
||||
isLoading: boolean;
|
||||
meta: Meta;
|
||||
searchParams: { meta: Meta; returnHiddenIndices: boolean; searchQuery?: string };
|
||||
searchParams: {
|
||||
from: number;
|
||||
onlyShowSearchOptimizedIndices: boolean;
|
||||
returnHiddenIndices: boolean;
|
||||
searchQuery?: string;
|
||||
size: number;
|
||||
};
|
||||
status: typeof FetchIndicesAPILogic.values.status;
|
||||
}
|
||||
|
||||
export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
|
||||
actions: {
|
||||
closeDeleteModal: true,
|
||||
fetchIndices: ({ meta, returnHiddenIndices, searchQuery }) => ({
|
||||
meta,
|
||||
fetchIndices: ({
|
||||
from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
size,
|
||||
}) => ({
|
||||
from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
size,
|
||||
}),
|
||||
onPaginate: (newPageIndex) => ({ newPageIndex }),
|
||||
openDeleteModal: (indexName) => ({ indexName }),
|
||||
|
@ -166,16 +173,26 @@ export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
|
|||
},
|
||||
],
|
||||
searchParams: [
|
||||
{ meta: DEFAULT_META, returnHiddenIndices: false },
|
||||
{
|
||||
apiSuccess: (_, { meta, returnHiddenIndices, searchQuery }) => ({
|
||||
meta,
|
||||
from: 0,
|
||||
onlyShowSearchOptimizedIndices: false,
|
||||
returnHiddenIndices: false,
|
||||
size: 20,
|
||||
},
|
||||
{
|
||||
apiSuccess: (
|
||||
_,
|
||||
{ meta, onlyShowSearchOptimizedIndices, returnHiddenIndices, searchQuery }
|
||||
) => ({
|
||||
from: meta.page.from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery,
|
||||
size: meta.page.size,
|
||||
}),
|
||||
onPaginate: (state, { newPageIndex }) => ({
|
||||
...state,
|
||||
meta: updateMetaPageIndex(state.meta, newPageIndex),
|
||||
from: (newPageIndex - 1) * state.size,
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
@ -218,6 +235,9 @@ export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
|
|||
() => [selectors.status, selectors.isFirstRequest],
|
||||
(status, isFirstRequest) => [Status.LOADING, Status.IDLE].includes(status) && isFirstRequest,
|
||||
],
|
||||
meta: [() => [selectors.searchParams], (searchParams) => searchParams.meta],
|
||||
meta: [
|
||||
() => [selectors.data],
|
||||
(data) => data?.meta ?? { page: { from: 0, size: 20, total: 0 } },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -21,13 +21,12 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { NATIVE_CONNECTOR_DEFINITIONS } from '../../../../../common/connectors/native_connectors';
|
||||
|
||||
import { Meta } from '../../../../../common/types';
|
||||
import { Meta } from '../../../../../common/types/pagination';
|
||||
import { healthColorsMap } from '../../../shared/constants/health_colors';
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { EuiLinkTo } from '../../../shared/react_router_helpers';
|
||||
import { EuiBadgeTo } from '../../../shared/react_router_helpers/eui_components';
|
||||
import { convertMetaToPagination } from '../../../shared/table_pagination';
|
||||
import { SEARCH_INDEX_PATH } from '../../routes';
|
||||
import { ElasticsearchViewIndex } from '../../types';
|
||||
import { ingestionMethodToText, isConnectorIndex } from '../../utils/indices';
|
||||
|
@ -211,7 +210,12 @@ export const IndicesTable: React.FC<IndicesTableProps> = ({
|
|||
items={indices}
|
||||
columns={columns}
|
||||
onChange={onChange}
|
||||
pagination={{ ...convertMetaToPagination(meta), showPerPageOptions: false }}
|
||||
pagination={{
|
||||
pageIndex: meta.page.from / (meta.page.size || 1),
|
||||
pageSize: meta.page.size,
|
||||
showPerPageOptions: false,
|
||||
totalItemCount: meta.page.total,
|
||||
}}
|
||||
tableLayout="fixed"
|
||||
loading={isLoading}
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,6 @@ import { shallow } from 'enzyme';
|
|||
import { EuiCallOut, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
import { ElasticsearchResources } from '../../../shared/elasticsearch_resources';
|
||||
import { GettingStartedSteps } from '../../../shared/getting_started_steps';
|
||||
|
||||
|
@ -27,7 +26,10 @@ import { SearchIndices } from './search_indices';
|
|||
|
||||
const mockValues = {
|
||||
indices,
|
||||
meta: DEFAULT_META,
|
||||
searchParams: {
|
||||
from: 0,
|
||||
size: 20,
|
||||
},
|
||||
};
|
||||
|
||||
const mockActions = {
|
||||
|
|
|
@ -19,6 +19,8 @@ import {
|
|||
EuiSwitch,
|
||||
EuiSearchBar,
|
||||
EuiLink,
|
||||
EuiToolTip,
|
||||
EuiCode,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -49,8 +51,9 @@ export const baseBreadcrumbs = [
|
|||
|
||||
export const SearchIndices: React.FC = () => {
|
||||
const { fetchIndices, onPaginate, openDeleteModal, setIsFirstRequest } = useActions(IndicesLogic);
|
||||
const { meta, indices, hasNoIndices, isLoading } = useValues(IndicesLogic);
|
||||
const { meta, indices, hasNoIndices, isLoading, searchParams } = useValues(IndicesLogic);
|
||||
const [showHiddenIndices, setShowHiddenIndices] = useState(false);
|
||||
const [onlyShowSearchOptimizedIndices, setOnlyShowSearchOptimizedIndices] = useState(false);
|
||||
const [searchQuery, setSearchValue] = useState('');
|
||||
|
||||
const [calloutDismissed, setCalloutDismissed] = useLocalStorage<boolean>(
|
||||
|
@ -66,11 +69,19 @@ export const SearchIndices: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
fetchIndices({
|
||||
meta,
|
||||
from: searchParams.from,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices: showHiddenIndices,
|
||||
searchQuery,
|
||||
size: searchParams.size,
|
||||
});
|
||||
}, [searchQuery, meta.page.current, showHiddenIndices]);
|
||||
}, [
|
||||
searchQuery,
|
||||
searchParams.from,
|
||||
searchParams.size,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
showHiddenIndices,
|
||||
]);
|
||||
|
||||
const pageTitle = isLoading
|
||||
? ''
|
||||
|
@ -180,6 +191,30 @@ export const SearchIndices: React.FC = () => {
|
|||
onChange={(event) => setShowHiddenIndices(event.target.checked)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.searchIndices.searchIndices.onlySearchOptimized.tooltipContent"
|
||||
defaultMessage="Search-optimized indices are prefixed with {code}. They are managed by ingestion mechanisms such as crawlers, connectors or ingestion APIs."
|
||||
values={{ code: <EuiCode>search-</EuiCode> }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={onlyShowSearchOptimizedIndices}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.searchIndices.onlySearchOptimized.label',
|
||||
{
|
||||
defaultMessage: 'Only show search-optimized indices',
|
||||
}
|
||||
)}
|
||||
onChange={(event) =>
|
||||
setOnlyShowSearchOptimizedIndices(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="entSearchIndicesSearchBar">
|
||||
<EuiSearchBar
|
||||
query={searchQuery}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { CONNECTORS_INDEX } from '../..';
|
||||
|
||||
export async function fetchConnectorIndexNames(client: IScopedClusterClient): Promise<string[]> {
|
||||
const result = await client.asCurrentUser.search({
|
||||
_source: false,
|
||||
fields: [{ field: 'index_name' }],
|
||||
index: CONNECTORS_INDEX,
|
||||
size: 10000,
|
||||
});
|
||||
return (result?.hits.hits ?? []).map((field) => field.fields?.index_name[0] ?? '');
|
||||
}
|
|
@ -15,9 +15,9 @@ import {
|
|||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { fetchIndices } from './fetch_indices';
|
||||
import { fetchIndices, fetchSearchIndices } from './fetch_indices';
|
||||
|
||||
describe('fetchIndices lib function', () => {
|
||||
describe('fetch indices lib functions', () => {
|
||||
const mockClient = {
|
||||
asCurrentUser: {
|
||||
count: jest.fn().mockReturnValue({ count: 100 }),
|
||||
|
@ -58,441 +58,308 @@ describe('fetchIndices lib function', () => {
|
|||
},
|
||||
};
|
||||
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: {
|
||||
'index-without-prefix': { manage: true, read: true },
|
||||
'search-aliased': { manage: true, read: true },
|
||||
'search-double-aliased': { manage: true, read: true },
|
||||
'search-regular-index': { manage: true, read: true },
|
||||
'second-index': { manage: true, read: true },
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return regular index without aliases', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => ({
|
||||
...regularIndexResponse,
|
||||
hidden: { aliases: {}, settings: { index: { hidden: 'true' } } },
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: {
|
||||
'index-without-prefix': { manage: true, read: true },
|
||||
'search-aliased': { manage: true, read: true },
|
||||
'search-double-aliased': { manage: true, read: true },
|
||||
'search-regular-index': { manage: true, read: true },
|
||||
'second-index': { manage: true, read: true },
|
||||
},
|
||||
}));
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => regularIndexStatsResponse);
|
||||
});
|
||||
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
alias: false,
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
describe('fetchSearchIndices', () => {
|
||||
it('should return index with unique aliases', async () => {
|
||||
const aliasedIndexResponse = {
|
||||
'index-without-prefix': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
'search-double-aliased': {},
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
]);
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['open'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: 'search-*',
|
||||
});
|
||||
'second-index': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
const aliasedStatsResponse = {
|
||||
indices: {
|
||||
'index-without-prefix': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
'second-index': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
},
|
||||
};
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['open'],
|
||||
index: 'search-*',
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => aliasedIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => aliasedStatsResponse);
|
||||
await expect(
|
||||
fetchSearchIndices(mockClient as unknown as IScopedClusterClient, {
|
||||
alias_pattern: 'search-',
|
||||
index_pattern: '.ent-search-engine-documents',
|
||||
})
|
||||
).resolves.toEqual([
|
||||
{
|
||||
names: ['search-regular-index', 'hidden'],
|
||||
privileges: ['read', 'manage'],
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'index-without-prefix',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
],
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-aliased',
|
||||
status: 'open',
|
||||
alias: true,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-double-aliased',
|
||||
status: 'open',
|
||||
alias: true,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'second-index',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('alwaysShowPattern', () => {
|
||||
const sortIndices = (index1: any, index2: any) => {
|
||||
if (index1.name < index2.name) return -1;
|
||||
if (index1.name > index2.name) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
it('should return hidden indices without aliases if specified', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => regularIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => regularIndexStatsResponse);
|
||||
it('overrides hidden indices setting', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => mockMultiIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => mockMultiStatsResponse);
|
||||
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', true, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
alias: false,
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
]);
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: 'search-*',
|
||||
});
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: mockPrivilegesResponse,
|
||||
}));
|
||||
const returnValue = await fetchSearchIndices(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
{ alias_pattern: 'search-', index_pattern: '.ent-search-engine-documents' }
|
||||
);
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: 'search-*',
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return index with unique aliases', async () => {
|
||||
const aliasedIndexResponse = {
|
||||
'index-without-prefix': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
'search-double-aliased': {},
|
||||
},
|
||||
},
|
||||
'second-index': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
const aliasedStatsResponse = {
|
||||
indices: {
|
||||
'index-without-prefix': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
'second-index': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
},
|
||||
};
|
||||
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => aliasedIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => aliasedStatsResponse);
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'index-without-prefix',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-aliased',
|
||||
status: 'open',
|
||||
alias: true,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-double-aliased',
|
||||
status: 'open',
|
||||
alias: true,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'second-index',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return index but not aliases when aliases excluded', async () => {
|
||||
const aliasedIndexResponse = {
|
||||
'index-without-prefix': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
'search-double-aliased': {},
|
||||
},
|
||||
},
|
||||
'second-index': {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
aliases: {
|
||||
'search-aliased': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
const aliasedStatsResponse = {
|
||||
indices: {
|
||||
'index-without-prefix': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
'second-index': { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
},
|
||||
};
|
||||
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => aliasedIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => aliasedStatsResponse);
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, false)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'index-without-prefix',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'second-index',
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
]);
|
||||
});
|
||||
it('should handle index missing in stats call', async () => {
|
||||
const missingStatsResponse = {
|
||||
indices: {
|
||||
some_other_index: { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
},
|
||||
};
|
||||
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => regularIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => missingStatsResponse);
|
||||
// simulates when an index has been deleted after get indices call
|
||||
// deleted index won't be present in the indices stats call response
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
count: 100,
|
||||
health: undefined,
|
||||
hidden: false,
|
||||
name: 'search-regular-index',
|
||||
status: undefined,
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 0,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '0b',
|
||||
},
|
||||
},
|
||||
uuid: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array when no index found', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => ({}));
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, true)
|
||||
).resolves.toEqual([]);
|
||||
expect(mockClient.asCurrentUser.indices.stats).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('alwaysShowPattern', () => {
|
||||
const sortIndices = (index1: any, index2: any) => {
|
||||
if (index1.name < index2.name) return -1;
|
||||
if (index1.name > index2.name) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => mockMultiIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => mockMultiStatsResponse);
|
||||
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: mockPrivilegesResponse,
|
||||
}));
|
||||
});
|
||||
|
||||
it('overrides hidden indices setting', async () => {
|
||||
const returnValue = await fetchIndices(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
false,
|
||||
true,
|
||||
{ alias_pattern: 'search-', index_pattern: '.ent-search-engine-documents' }
|
||||
);
|
||||
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Non-hidden indices and aliases
|
||||
// - hidden indices that starts with ".ent-search-engine-documents"
|
||||
// - search- prefixed aliases that point to hidden indices
|
||||
expect(returnValue.sort(sortIndices)).toEqual(
|
||||
[
|
||||
'regular-index',
|
||||
'alias-regular-index',
|
||||
'search-alias-regular-index',
|
||||
'search-prefixed-regular-index',
|
||||
'alias-search-prefixed-regular-index',
|
||||
'search-alias-search-prefixed-regular-index',
|
||||
'.ent-search-engine-documents-12345',
|
||||
'search-alias-.ent-search-engine-documents-12345',
|
||||
'search-alias-search-prefixed-.ent-search-engine-documents-12345',
|
||||
'search-alias-hidden-index',
|
||||
'search-alias-search-prefixed-hidden-index',
|
||||
]
|
||||
.map(getIndexReturnValue)
|
||||
.sort(sortIndices)
|
||||
);
|
||||
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Hidden indices
|
||||
// - aliases to hidden indices that has no prefix
|
||||
expect(returnValue).toEqual(
|
||||
expect.not.arrayContaining(
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Non-hidden indices and aliases
|
||||
// - hidden indices that starts with ".ent-search-engine-documents"
|
||||
// - search- prefixed aliases that point to hidden indices
|
||||
expect(returnValue.sort(sortIndices)).toEqual(
|
||||
[
|
||||
'hidden-index',
|
||||
'search-prefixed-hidden-index',
|
||||
'alias-hidden-index',
|
||||
'alias-search-prefixed-hidden-index',
|
||||
'alias-.ent-search-engine-documents-12345',
|
||||
'search-prefixed-.ent-search-engine-documents-12345',
|
||||
'alias-search-prefixed-.ent-search-engine-documents-12345',
|
||||
].map(getIndexReturnValue)
|
||||
)
|
||||
);
|
||||
'regular-index',
|
||||
'alias-regular-index',
|
||||
'search-alias-regular-index',
|
||||
'search-prefixed-regular-index',
|
||||
'alias-search-prefixed-regular-index',
|
||||
'search-alias-search-prefixed-regular-index',
|
||||
'.ent-search-engine-documents-12345',
|
||||
'search-alias-.ent-search-engine-documents-12345',
|
||||
'search-alias-search-prefixed-.ent-search-engine-documents-12345',
|
||||
'search-alias-hidden-index',
|
||||
'search-alias-search-prefixed-hidden-index',
|
||||
]
|
||||
.map(getIndexReturnValue)
|
||||
.sort(sortIndices)
|
||||
);
|
||||
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Hidden indices
|
||||
// - aliases to hidden indices that has no prefix
|
||||
expect(returnValue).toEqual(
|
||||
expect.not.arrayContaining(
|
||||
[
|
||||
'hidden-index',
|
||||
'search-prefixed-hidden-index',
|
||||
'alias-hidden-index',
|
||||
'alias-search-prefixed-hidden-index',
|
||||
'alias-.ent-search-engine-documents-12345',
|
||||
'search-prefixed-.ent-search-engine-documents-12345',
|
||||
'alias-search-prefixed-.ent-search-engine-documents-12345',
|
||||
].map(getIndexReturnValue)
|
||||
)
|
||||
);
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: '*',
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: '*',
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
{
|
||||
names: expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices)),
|
||||
privileges: ['read', 'manage'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchIndices', () => {
|
||||
it('should return regular index without aliases', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => ({
|
||||
...regularIndexResponse,
|
||||
hidden: { aliases: {}, settings: { index: { hidden: 'true' } } },
|
||||
}));
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => regularIndexStatsResponse);
|
||||
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search', false, false, 0, 20)
|
||||
).resolves.toEqual({
|
||||
indexNames: ['search-regular-index'],
|
||||
indices: [
|
||||
{
|
||||
alias: false,
|
||||
aliases: [],
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: false,
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
],
|
||||
totalResults: 1,
|
||||
});
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
expand_wildcards: ['open'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: '*',
|
||||
index: '*search*',
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: '*',
|
||||
index: ['search-regular-index'],
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
{
|
||||
names: expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices)),
|
||||
names: ['search-regular-index'],
|
||||
privileges: ['read', 'manage'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns everything if hidden indices set', async () => {
|
||||
const returnValue = await fetchIndices(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
true,
|
||||
true,
|
||||
{ alias_pattern: 'search-', index_pattern: '.ent-search-engine-documents' }
|
||||
);
|
||||
|
||||
expect(returnValue).toEqual(
|
||||
expect.not.arrayContaining(['alias-.ent-search-engine-documents-12345'])
|
||||
);
|
||||
|
||||
// this specific alias should not be returned because...
|
||||
const expectedIndices = Object.keys(mockMultiStatsResponse.indices).filter(
|
||||
(indexName) => indexName !== 'alias-.ent-search-engine-documents-12345'
|
||||
);
|
||||
expect(returnValue.sort(sortIndices)).toEqual(
|
||||
expectedIndices.map(getIndexReturnValue).sort(sortIndices)
|
||||
);
|
||||
it('should return hidden indices if specified', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => ({
|
||||
...regularIndexResponse,
|
||||
['search-regular-index']: {
|
||||
...regularIndexResponse['search-regular-index'],
|
||||
...{ settings: { index: { hidden: 'true' } } },
|
||||
},
|
||||
}));
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => regularIndexStatsResponse);
|
||||
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, undefined, true, false, 0, 20)
|
||||
).resolves.toEqual({
|
||||
indexNames: ['search-regular-index'],
|
||||
indices: [
|
||||
{
|
||||
alias: false,
|
||||
aliases: [],
|
||||
count: 100,
|
||||
health: 'green',
|
||||
hidden: true,
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
],
|
||||
totalResults: 1,
|
||||
});
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
features: ['aliases', 'settings'],
|
||||
|
@ -501,19 +368,58 @@ describe('fetchIndices lib function', () => {
|
|||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: '*',
|
||||
index: ['search-regular-index'],
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
it('should handle index missing in stats call', async () => {
|
||||
const missingStatsResponse = {
|
||||
indices: {
|
||||
some_other_index: { ...regularIndexStatsResponse.indices['search-regular-index'] },
|
||||
},
|
||||
};
|
||||
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => regularIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => missingStatsResponse);
|
||||
// simulates when an index has been deleted after get indices call
|
||||
// deleted index won't be present in the indices stats call response
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, false, 0, 20)
|
||||
).resolves.toEqual({
|
||||
indexNames: ['search-regular-index'],
|
||||
indices: [
|
||||
{
|
||||
names: expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices)),
|
||||
privileges: ['read', 'manage'],
|
||||
alias: false,
|
||||
aliases: [],
|
||||
count: 100,
|
||||
health: undefined,
|
||||
hidden: false,
|
||||
name: 'search-regular-index',
|
||||
status: undefined,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 0,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: '0b',
|
||||
},
|
||||
},
|
||||
uuid: undefined,
|
||||
},
|
||||
],
|
||||
totalResults: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array when no index found', async () => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementationOnce(() => ({}));
|
||||
await expect(
|
||||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, false, 0, 20)
|
||||
).resolves.toEqual({ indexNames: [], indices: [], totalResults: 0 });
|
||||
expect(mockClient.asCurrentUser.indices.stats).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,13 +14,14 @@ import {
|
|||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { AlwaysShowPattern, ElasticsearchIndexWithPrivileges } from '../../../common/types/indices';
|
||||
import { isNotNullish } from '../../../common/utils/is_not_nullish';
|
||||
|
||||
import { fetchIndexCounts } from './fetch_index_counts';
|
||||
import { fetchIndexPrivileges } from './fetch_index_privileges';
|
||||
import { fetchIndexStats } from './fetch_index_stats';
|
||||
import { expandAliases, getAlwaysShowAliases } from './utils/extract_always_show_indices';
|
||||
import { getIndexDataMapper } from './utils/get_index_data';
|
||||
import { getIndexData } from './utils/get_index_data';
|
||||
import { getIndexData, getSearchIndexData } from './utils/get_index_data';
|
||||
|
||||
export interface TotalIndexData {
|
||||
allIndexMatches: IndicesGetResponse;
|
||||
|
@ -29,35 +30,23 @@ export interface TotalIndexData {
|
|||
indicesStats: Record<string, IndicesStatsIndicesStats>;
|
||||
}
|
||||
|
||||
export const fetchIndices = async (
|
||||
export const fetchSearchIndices = async (
|
||||
client: IScopedClusterClient,
|
||||
indexPattern: string,
|
||||
returnHiddenIndices: boolean,
|
||||
includeAliases: boolean,
|
||||
alwaysShowPattern?: AlwaysShowPattern
|
||||
): Promise<ElasticsearchIndexWithPrivileges[]> => {
|
||||
// This call retrieves alias and settings information about indices
|
||||
// If we provide an override pattern with alwaysShowPattern we get everything and filter out hiddens.
|
||||
alwaysShowPattern: AlwaysShowPattern
|
||||
) => {
|
||||
const expandWildcards: ExpandWildcard[] =
|
||||
returnHiddenIndices || alwaysShowPattern?.alias_pattern || alwaysShowPattern?.index_pattern
|
||||
alwaysShowPattern.alias_pattern || alwaysShowPattern.index_pattern
|
||||
? ['hidden', 'all']
|
||||
: ['open'];
|
||||
|
||||
const { allIndexMatches, indexAndAliasNames, indicesNames, alwaysShowMatchNames } =
|
||||
await getIndexData(
|
||||
client,
|
||||
indexPattern,
|
||||
expandWildcards,
|
||||
returnHiddenIndices,
|
||||
includeAliases,
|
||||
alwaysShowPattern
|
||||
);
|
||||
await getSearchIndexData(client, '*', expandWildcards, false, true, alwaysShowPattern);
|
||||
|
||||
if (indicesNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const indicesStats = await fetchIndexStats(client, indexPattern, expandWildcards);
|
||||
const indicesStats = await fetchIndexStats(client, '*', expandWildcards);
|
||||
|
||||
const indexPrivileges = await fetchIndexPrivileges(client, indexAndAliasNames);
|
||||
|
||||
|
@ -81,23 +70,21 @@ export const fetchIndices = async (
|
|||
name,
|
||||
privileges: { manage: false, read: false, ...indexPrivileges[name] },
|
||||
};
|
||||
return includeAliases
|
||||
? [
|
||||
indexEntry,
|
||||
...expandAliases(
|
||||
name,
|
||||
aliases,
|
||||
indexData,
|
||||
totalIndexData,
|
||||
...(name.startsWith('.ent-search-engine-documents') ? [alwaysShowPattern] : [])
|
||||
),
|
||||
]
|
||||
: [indexEntry];
|
||||
return [
|
||||
indexEntry,
|
||||
...expandAliases(
|
||||
name,
|
||||
aliases,
|
||||
indexData,
|
||||
totalIndexData,
|
||||
...(name.startsWith('.ent-search-engine-documents') ? [alwaysShowPattern] : [])
|
||||
),
|
||||
];
|
||||
});
|
||||
|
||||
let indicesData = regularIndexData;
|
||||
|
||||
if (alwaysShowPattern?.alias_pattern && includeAliases) {
|
||||
if (alwaysShowPattern?.alias_pattern) {
|
||||
const indexNamesAlreadyIncluded = regularIndexData.map(({ name }) => name);
|
||||
|
||||
const itemsToInclude = getAlwaysShowAliases(indexNamesAlreadyIncluded, alwaysShowMatchNames)
|
||||
|
@ -116,3 +103,65 @@ export const fetchIndices = async (
|
|||
array.findIndex((engineData) => engineData.name === name) === index
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchIndices = async (
|
||||
client: IScopedClusterClient,
|
||||
searchQuery: string | undefined,
|
||||
returnHiddenIndices: boolean,
|
||||
onlyShowSearchOptimizedIndices: boolean,
|
||||
from: number,
|
||||
size: number
|
||||
): Promise<{
|
||||
indexNames: string[];
|
||||
indices: ElasticsearchIndexWithPrivileges[];
|
||||
totalResults: number;
|
||||
}> => {
|
||||
const { indexData, indexNames } = await getIndexData(
|
||||
client,
|
||||
onlyShowSearchOptimizedIndices,
|
||||
returnHiddenIndices,
|
||||
searchQuery
|
||||
);
|
||||
const indexNameSlice = indexNames.slice(from, from + size).filter(isNotNullish);
|
||||
if (indexNameSlice.length === 0) {
|
||||
return {
|
||||
indexNames: [],
|
||||
indices: [],
|
||||
totalResults: indexNames.length,
|
||||
};
|
||||
}
|
||||
|
||||
const { indices: indicesStats = {} } = await client.asCurrentUser.indices.stats({
|
||||
index: indexNameSlice,
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
const indexPrivileges = await fetchIndexPrivileges(client, indexNameSlice);
|
||||
|
||||
const indexCounts = await fetchIndexCounts(client, indexNameSlice);
|
||||
|
||||
const totalIndexData: TotalIndexData = {
|
||||
allIndexMatches: indexData,
|
||||
indexCounts,
|
||||
indexPrivileges,
|
||||
indicesStats,
|
||||
};
|
||||
|
||||
const indices = indexNameSlice
|
||||
.map(getIndexDataMapper(totalIndexData))
|
||||
.map(({ name, ...index }) => {
|
||||
return {
|
||||
...index,
|
||||
alias: false,
|
||||
count: indexCounts[name] ?? 0,
|
||||
name,
|
||||
privileges: { manage: false, read: false, ...indexPrivileges[name] },
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
indexNames: indexNameSlice,
|
||||
indices,
|
||||
totalResults: indexNames.length,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IScopedClusterClient } from '@kbn/core/server';
|
|||
|
||||
import { TotalIndexData } from '../fetch_indices';
|
||||
|
||||
import { getIndexData, getIndexDataMapper } from './get_index_data';
|
||||
import { getIndexDataMapper, getSearchIndexData } from './get_index_data';
|
||||
|
||||
import * as mapIndexStatsModule from './map_index_stats';
|
||||
|
||||
|
@ -34,7 +34,7 @@ describe('getIndexData util function', () => {
|
|||
return mockSingleIndexWithAliasesResponse;
|
||||
});
|
||||
|
||||
const indexData = await getIndexData(
|
||||
const indexData = await getSearchIndexData(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
['open'],
|
||||
|
@ -63,7 +63,7 @@ describe('getIndexData util function', () => {
|
|||
return mockSingleIndexWithAliasesResponse;
|
||||
});
|
||||
|
||||
const indexData = await getIndexData(
|
||||
const indexData = await getSearchIndexData(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
['open'],
|
||||
|
@ -92,7 +92,7 @@ describe('getIndexData util function', () => {
|
|||
return mockMultiIndexResponse;
|
||||
});
|
||||
|
||||
const indexData = await getIndexData(
|
||||
const indexData = await getSearchIndexData(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
['hidden', 'all'],
|
||||
|
@ -135,7 +135,7 @@ describe('getIndexData util function', () => {
|
|||
return mockMultiIndexResponse;
|
||||
});
|
||||
|
||||
const indexData = await getIndexData(
|
||||
const indexData = await getSearchIndexData(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
['hidden', 'all'],
|
||||
|
@ -195,7 +195,7 @@ describe('getIndexData util function', () => {
|
|||
return mockMultiIndexResponse;
|
||||
});
|
||||
|
||||
const indexData = await getIndexData(
|
||||
const indexData = await getSearchIndexData(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
['hidden', 'all'],
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ExpandWildcard } from '@elastic/elasticsearch/lib/api/types';
|
||||
import {
|
||||
ExpandWildcard,
|
||||
IndicesGetResponse,
|
||||
IndicesIndexState,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
|
@ -15,7 +19,7 @@ import { TotalIndexData } from '../fetch_indices';
|
|||
|
||||
import { mapIndexStats } from './map_index_stats';
|
||||
|
||||
export const getIndexData = async (
|
||||
export const getSearchIndexData = async (
|
||||
client: IScopedClusterClient,
|
||||
indexPattern: string,
|
||||
expandWildcards: ExpandWildcard[],
|
||||
|
@ -63,7 +67,7 @@ export const getIndexData = async (
|
|||
? Object.keys(totalIndices)
|
||||
: Object.keys(totalIndices).filter(
|
||||
(indexName) =>
|
||||
!(totalIndices[indexName]?.settings?.index?.hidden === 'true') ||
|
||||
!isHidden(totalIndices[indexName]) ||
|
||||
(alwaysShowPattern?.index_pattern &&
|
||||
indexName.startsWith(alwaysShowPattern.index_pattern))
|
||||
);
|
||||
|
@ -84,3 +88,41 @@ export const getIndexDataMapper = (totalIndexData: TotalIndexData) => {
|
|||
indexName
|
||||
);
|
||||
};
|
||||
|
||||
function isHidden(index: IndicesIndexState): boolean {
|
||||
return Boolean(index.settings?.index?.hidden) || index.settings?.index?.hidden === 'true';
|
||||
}
|
||||
|
||||
export const getIndexData = async (
|
||||
client: IScopedClusterClient,
|
||||
onlyShowSearchOptimizedIndices: boolean,
|
||||
returnHiddenIndices: boolean,
|
||||
searchQuery?: string
|
||||
): Promise<{ indexData: IndicesGetResponse; indexNames: string[] }> => {
|
||||
const expandWildcards: ExpandWildcard[] = returnHiddenIndices ? ['hidden', 'all'] : ['open'];
|
||||
const indexPattern = searchQuery ? `*${searchQuery}*` : '*';
|
||||
const allIndexMatches = await client.asCurrentUser.indices.get({
|
||||
expand_wildcards: expandWildcards,
|
||||
// for better performance only compute aliases and settings of indices but not mappings
|
||||
features: ['aliases', 'settings'],
|
||||
// only get specified index properties from ES to keep the response under 536MB
|
||||
// node.js string length limit: https://github.com/nodejs/node/issues/33960
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: onlyShowSearchOptimizedIndices ? 'search-*' : indexPattern,
|
||||
});
|
||||
|
||||
const allIndexNames = returnHiddenIndices
|
||||
? Object.keys(allIndexMatches)
|
||||
: Object.keys(allIndexMatches).filter(
|
||||
(indexName) => allIndexMatches[indexName] && !isHidden(allIndexMatches[indexName])
|
||||
);
|
||||
const indexNames =
|
||||
onlyShowSearchOptimizedIndices && searchQuery
|
||||
? allIndexNames.filter((indexName) => indexName.includes(searchQuery.toLowerCase()))
|
||||
: allIndexNames;
|
||||
|
||||
return {
|
||||
indexData: allIndexMatches,
|
||||
indexNames,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ import { fetchCrawlerByIndexName, fetchCrawlers } from '../../lib/crawler/fetch_
|
|||
import { createIndex } from '../../lib/indices/create_index';
|
||||
import { indexOrAliasExists } from '../../lib/indices/exists_index';
|
||||
import { fetchIndex } from '../../lib/indices/fetch_index';
|
||||
import { fetchIndices } from '../../lib/indices/fetch_indices';
|
||||
import { fetchIndices, fetchSearchIndices } from '../../lib/indices/fetch_indices';
|
||||
import { generateApiKey } from '../../lib/indices/generate_api_key';
|
||||
import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors';
|
||||
import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history';
|
||||
|
@ -67,7 +67,7 @@ export function registerIndexRoutes({
|
|||
alias_pattern: 'search-',
|
||||
index_pattern: '.ent-search-engine-documents',
|
||||
};
|
||||
const indices = await fetchIndices(client, '*', false, true, patterns);
|
||||
const indices = await fetchSearchIndices(client, patterns);
|
||||
|
||||
return response.ok({
|
||||
body: indices,
|
||||
|
@ -81,7 +81,8 @@ export function registerIndexRoutes({
|
|||
path: '/internal/enterprise_search/indices',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
page: schema.number({ defaultValue: 0, min: 0 }),
|
||||
from: schema.number({ defaultValue: 0, min: 0 }),
|
||||
only_show_search_optimized_indices: schema.maybe(schema.boolean()),
|
||||
return_hidden_indices: schema.maybe(schema.boolean()),
|
||||
search_query: schema.maybe(schema.string()),
|
||||
size: schema.number({ defaultValue: 10, min: 0 }),
|
||||
|
@ -90,24 +91,25 @@ export function registerIndexRoutes({
|
|||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const {
|
||||
page,
|
||||
from,
|
||||
only_show_search_optimized_indices: onlyShowSearchOptimizedIndices,
|
||||
size,
|
||||
return_hidden_indices: returnHiddenIndices,
|
||||
search_query: searchQuery,
|
||||
} = request.query;
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
|
||||
const indexPattern = searchQuery ? `*${searchQuery}*` : '*';
|
||||
const totalIndices = await fetchIndices(client, indexPattern, !!returnHiddenIndices, false);
|
||||
const totalResults = totalIndices.length;
|
||||
const totalPages = Math.ceil(totalResults / size) || 1;
|
||||
const startIndex = (page - 1) * size;
|
||||
const endIndex = page * size;
|
||||
const selectedIndices = totalIndices.slice(startIndex, endIndex);
|
||||
const indexNames = selectedIndices.map(({ name }) => name);
|
||||
const { indexNames, indices, totalResults } = await fetchIndices(
|
||||
client,
|
||||
searchQuery,
|
||||
!!returnHiddenIndices,
|
||||
!!onlyShowSearchOptimizedIndices,
|
||||
from,
|
||||
size
|
||||
);
|
||||
const connectors = await fetchConnectors(client, indexNames);
|
||||
const crawlers = await fetchCrawlers(client, indexNames);
|
||||
const indices = selectedIndices.map((index) => ({
|
||||
const enrichedIndices = indices.map((index) => ({
|
||||
...index,
|
||||
connector: connectors.find((connector) => connector.index_name === index.name),
|
||||
crawler: crawlers.find((crawler) => crawler.index_name === index.name),
|
||||
|
@ -115,13 +117,12 @@ export function registerIndexRoutes({
|
|||
|
||||
return response.ok({
|
||||
body: {
|
||||
indices,
|
||||
indices: enrichedIndices,
|
||||
meta: {
|
||||
page: {
|
||||
current: page,
|
||||
from,
|
||||
size,
|
||||
total_pages: totalPages,
|
||||
total_results: totalResults,
|
||||
total: totalResults,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue