mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] [Search application] List indices fetch indices stats from ES directly (#153696)
## Summary This PR adds a lib file to fetch indices stats of an search application from Elasticsearch directly ### Screenshot #### List page indices <img width="1728" alt="List page indices" src="https://user-images.githubusercontent.com/55930906/227617672-bfcdd3e4-85c2-4432-bcee-2aa3f5be07a4.png"> #### Overview page indices <img width="1728" alt="Overview page indices " src="https://user-images.githubusercontent.com/55930906/227617828-afa01bf6-5f23-4f3f-9ff6-75f6c5dbcff2.png"> ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b002cdc6eb
commit
327dd493bf
6 changed files with 181 additions and 34 deletions
|
@ -16,11 +16,11 @@ import { FetchEngineApiLogic } from '../../api/engines/fetch_engine_api_logic';
|
|||
import { EngineListFlyoutValues, EnginesListFlyoutLogic } from './engines_list_flyout_logic';
|
||||
|
||||
const DEFAULT_VALUES: EngineListFlyoutValues = {
|
||||
fetchEngineApiError: undefined,
|
||||
fetchEngineApiStatus: Status.IDLE,
|
||||
fetchEngineData: undefined,
|
||||
fetchEngineName: null,
|
||||
isFetchEngineFlyoutVisible: false,
|
||||
fetchEngineApiStatus: Status.IDLE,
|
||||
fetchEngineApiError: undefined,
|
||||
isFetchEngineLoading: false,
|
||||
};
|
||||
const mockEngineData: EnterpriseSearchEngineDetails = {
|
||||
|
@ -66,10 +66,10 @@ describe('EngineListFlyoutLogic', () => {
|
|||
EnginesListFlyoutLogic.actions.openFetchEngineFlyout('my-test-engine');
|
||||
expect(EnginesListFlyoutLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
isFetchEngineFlyoutVisible: true,
|
||||
fetchEngineName: 'my-test-engine',
|
||||
isFetchEngineLoading: true,
|
||||
fetchEngineApiStatus: Status.LOADING,
|
||||
fetchEngineName: 'my-test-engine',
|
||||
isFetchEngineFlyoutVisible: true,
|
||||
isFetchEngineLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,8 +50,8 @@ export type EnginesListActions = Pick<
|
|||
openDeleteEngineModal: (engine: EnterpriseSearchEngine | EnterpriseSearchEngineDetails) => {
|
||||
engine: EnterpriseSearchEngine;
|
||||
};
|
||||
setIsFirstRequest(): void;
|
||||
openEngineCreate(): void;
|
||||
setIsFirstRequest(): void;
|
||||
setSearchQuery(searchQuery: string): { searchQuery: string };
|
||||
};
|
||||
|
||||
|
@ -74,6 +74,16 @@ interface EngineListValues {
|
|||
}
|
||||
|
||||
export const EnginesListLogic = kea<MakeLogicType<EngineListValues, EnginesListActions>>({
|
||||
actions: {
|
||||
closeDeleteEngineModal: true,
|
||||
closeEngineCreate: true,
|
||||
fetchEngines: true,
|
||||
onPaginate: (args: EuiBasicTableOnChange) => ({ pageNumber: args.page.index }),
|
||||
openDeleteEngineModal: (engine) => ({ engine }),
|
||||
openEngineCreate: true,
|
||||
setIsFirstRequest: true,
|
||||
setSearchQuery: (searchQuery: string) => ({ searchQuery }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
FetchEnginesAPILogic,
|
||||
|
@ -88,17 +98,18 @@ export const EnginesListLogic = kea<MakeLogicType<EngineListValues, EnginesListA
|
|||
['status as deleteStatus'],
|
||||
],
|
||||
},
|
||||
actions: {
|
||||
closeDeleteEngineModal: true,
|
||||
closeEngineCreate: true,
|
||||
fetchEngines: true,
|
||||
onPaginate: (args: EuiBasicTableOnChange) => ({ pageNumber: args.page.index }),
|
||||
openDeleteEngineModal: (engine) => ({ engine }),
|
||||
openEngineCreate: true,
|
||||
setSearchQuery: (searchQuery: string) => ({ searchQuery }),
|
||||
setIsFirstRequest: true,
|
||||
},
|
||||
listeners: ({ actions, values }) => ({
|
||||
deleteSuccess: () => {
|
||||
actions.closeDeleteEngineModal();
|
||||
actions.fetchEngines();
|
||||
},
|
||||
fetchEngines: async () => {
|
||||
actions.makeRequest(values.parameters);
|
||||
},
|
||||
}),
|
||||
|
||||
path: ['enterprise_search', 'content', 'engine_list_logic'],
|
||||
|
||||
reducers: ({}) => ({
|
||||
createEngineFlyoutOpen: [
|
||||
false,
|
||||
|
@ -158,6 +169,11 @@ export const EnginesListLogic = kea<MakeLogicType<EngineListValues, EnginesListA
|
|||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
deleteModalEngineName: [() => [selectors.deleteModalEngine], (engine) => engine?.name ?? ''],
|
||||
hasNoEngines: [
|
||||
() => [selectors.data, selectors.results],
|
||||
(data: EngineListValues['data'], results: EngineListValues['results']) =>
|
||||
(data?.params?.from === 0 && results.length === 0 && !data?.params?.q) ?? false,
|
||||
],
|
||||
|
||||
isDeleteLoading: [
|
||||
() => [selectors.deleteStatus],
|
||||
|
@ -168,22 +184,7 @@ export const EnginesListLogic = kea<MakeLogicType<EngineListValues, EnginesListA
|
|||
(status: EngineListValues['status'], isFirstRequest: EngineListValues['isFirstRequest']) =>
|
||||
[Status.LOADING, Status.IDLE].includes(status) && isFirstRequest,
|
||||
],
|
||||
results: [() => [selectors.data], (data) => data?.results ?? []],
|
||||
|
||||
hasNoEngines: [
|
||||
() => [selectors.data, selectors.results],
|
||||
(data: EngineListValues['data'], results: EngineListValues['results']) =>
|
||||
(data?.params?.from === 0 && results.length === 0 && !data?.params?.q) ?? false,
|
||||
],
|
||||
meta: [() => [selectors.parameters], (parameters) => parameters.meta],
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
deleteSuccess: () => {
|
||||
actions.closeDeleteEngineModal();
|
||||
actions.fetchEngines();
|
||||
},
|
||||
fetchEngines: async () => {
|
||||
actions.makeRequest(values.parameters);
|
||||
},
|
||||
results: [() => [selectors.data], (data) => data?.results ?? []],
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 { fetchIndicesStats } from './fetch_indices_stats';
|
||||
|
||||
describe('fetchIndicesStats lib function', () => {
|
||||
const mockClient = {
|
||||
asCurrentUser: {
|
||||
indices: {
|
||||
stats: jest.fn(),
|
||||
},
|
||||
},
|
||||
asInternalUser: {},
|
||||
};
|
||||
const indices = ['test-index-name-1', 'test-index-name-2', 'test-index-name-3'];
|
||||
const indicesStats = {
|
||||
indices: {
|
||||
'test-index-name-1': {
|
||||
health: 'GREEN',
|
||||
primaries: { docs: [{}] },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 200,
|
||||
deleted: 0,
|
||||
},
|
||||
},
|
||||
uuid: 'YOLLiZ_mSRiDYDk0DJ-p8B',
|
||||
},
|
||||
'test-index-name-2': {
|
||||
health: 'YELLOW',
|
||||
primaries: { docs: [{}] },
|
||||
status: 'closed',
|
||||
total: {
|
||||
docs: {
|
||||
count: 0,
|
||||
deleted: 0,
|
||||
},
|
||||
},
|
||||
uuid: 'QOLLiZ_mGRiDYD30D2-p8B',
|
||||
},
|
||||
'test-index-name-3': {
|
||||
health: 'RED',
|
||||
primaries: { docs: [{}] },
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 150,
|
||||
deleted: 0,
|
||||
},
|
||||
},
|
||||
uuid: 'QYLLiZ_fGRiDYD3082-e7',
|
||||
},
|
||||
},
|
||||
};
|
||||
const fetchIndicesStatsResponse = [
|
||||
{
|
||||
count: 200,
|
||||
health: 'GREEN',
|
||||
name: 'test-index-name-1',
|
||||
},
|
||||
{
|
||||
count: 0,
|
||||
health: 'YELLOW',
|
||||
name: 'test-index-name-2',
|
||||
},
|
||||
{
|
||||
count: 150,
|
||||
health: 'RED',
|
||||
name: 'test-index-name-3',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return hydrated indices', async () => {
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementationOnce(() => indicesStats);
|
||||
|
||||
await expect(
|
||||
fetchIndicesStats(mockClient as unknown as IScopedClusterClient, indices)
|
||||
).resolves.toEqual(fetchIndicesStatsResponse);
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
index: indices,
|
||||
metric: ['docs'],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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-elasticsearch-server/src/client/scoped_cluster_client';
|
||||
|
||||
import { EnterpriseSearchEngineIndex } from '../../../common/types/engines';
|
||||
|
||||
export const fetchIndicesStats = async (client: IScopedClusterClient, indices: string[]) => {
|
||||
const { indices: indicesStats = {} } = await client.asCurrentUser.indices.stats({
|
||||
index: indices,
|
||||
metric: ['docs'],
|
||||
});
|
||||
|
||||
const indicesWithStats = indices.map((indexName: string) => {
|
||||
const indexStats = indicesStats[indexName];
|
||||
const hydratedIndex: EnterpriseSearchEngineIndex = {
|
||||
count: indexStats?.total?.docs?.count ?? 0,
|
||||
health: indexStats?.health ?? 'unknown',
|
||||
name: indexName,
|
||||
};
|
||||
return hydratedIndex;
|
||||
});
|
||||
|
||||
return indicesWithStats;
|
||||
};
|
|
@ -10,9 +10,12 @@ import { mockDependencies, MockRouter } from '../../__mocks__';
|
|||
jest.mock('../../lib/engines/field_capabilities', () => ({
|
||||
fetchEngineFieldCapabilities: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../lib/engines/fetch_indices_stats', () => ({
|
||||
fetchIndicesStats: jest.fn(),
|
||||
}));
|
||||
import { RequestHandlerContext } from '@kbn/core/server';
|
||||
|
||||
import { fetchIndicesStats } from '../../lib/engines/fetch_indices_stats';
|
||||
import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities';
|
||||
|
||||
import { registerEnginesRoutes } from './engines';
|
||||
|
@ -115,6 +118,22 @@ describe('engines routes', () => {
|
|||
method: 'GET',
|
||||
path: '/_application/search_application/engine-name',
|
||||
});
|
||||
const mock = jest.fn();
|
||||
|
||||
const fetchIndicesStatsResponse = [
|
||||
{ count: 5, health: 'green', name: 'test-index-name-1' },
|
||||
{ count: 10, health: 'yellow', name: 'test-index-name-2' },
|
||||
{ count: 0, health: 'red', name: 'test-index-name-3' },
|
||||
];
|
||||
const engineResult = {
|
||||
indices: mock(['test-index-name-1', 'test-index-name-2', 'test-index-name-3']),
|
||||
name: 'test-engine-1',
|
||||
updated_at_millis: 1679847286355,
|
||||
};
|
||||
|
||||
(fetchIndicesStats as jest.Mock).mockResolvedValueOnce(fetchIndicesStatsResponse);
|
||||
expect(fetchIndicesStats).toHaveBeenCalledWith(mockClient, engineResult.indices);
|
||||
|
||||
expect(mockRouter.response.ok).toHaveBeenCalledWith({
|
||||
body: {},
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '../../../common/types/engines';
|
||||
import { ErrorCode } from '../../../common/types/error_codes';
|
||||
import { createApiKey } from '../../lib/engines/create_api_key';
|
||||
import { fetchIndicesStats } from '../../lib/engines/fetch_indices_stats';
|
||||
|
||||
import { fetchEngineFieldCapabilities } from '../../lib/engines/field_capabilities';
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
|
@ -63,7 +64,9 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) {
|
|||
method: 'GET',
|
||||
path: `/_application/search_application/${request.params.engine_name}`,
|
||||
});
|
||||
return response.ok({ body: engine });
|
||||
const indicesStats = await fetchIndicesStats(client, engine.indices);
|
||||
|
||||
return response.ok({ body: { ...engine, indices: indicesStats } });
|
||||
})
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue