mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[App Search] Engine Overview server route & Logic file (#83353)
* Add overview server route * Add EngineOverviewLogic * tfw when you forget index.ts Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
849fbcda6d
commit
bc3bb2afa8
5 changed files with 340 additions and 1 deletions
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { resetContext } from 'kea';
|
||||
|
||||
import { mockHttpValues } from '../../../__mocks__';
|
||||
jest.mock('../../../shared/http', () => ({
|
||||
HttpLogic: { values: mockHttpValues },
|
||||
}));
|
||||
const { http } = mockHttpValues;
|
||||
|
||||
jest.mock('../../../shared/flash_messages', () => ({
|
||||
flashAPIErrors: jest.fn(),
|
||||
}));
|
||||
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||
|
||||
jest.mock('../engine', () => ({
|
||||
EngineLogic: { values: { engineName: 'some-engine' } },
|
||||
}));
|
||||
|
||||
import { EngineOverviewLogic } from './';
|
||||
|
||||
describe('EngineOverviewLogic', () => {
|
||||
const mockEngineMetrics = {
|
||||
apiLogsUnavailable: true,
|
||||
documentCount: 10,
|
||||
startDate: '1970-01-30',
|
||||
endDate: '1970-01-31',
|
||||
operationsPerDay: [0, 0, 0, 0, 0, 0, 0],
|
||||
queriesPerDay: [0, 0, 0, 0, 0, 25, 50],
|
||||
totalClicks: 50,
|
||||
totalQueries: 75,
|
||||
};
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
dataLoading: true,
|
||||
apiLogsUnavailable: false,
|
||||
documentCount: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
operationsPerDay: [],
|
||||
queriesPerDay: [],
|
||||
totalClicks: 0,
|
||||
totalQueries: 0,
|
||||
timeoutId: null,
|
||||
};
|
||||
|
||||
const mount = () => {
|
||||
resetContext({});
|
||||
EngineOverviewLogic.mount();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
mount();
|
||||
expect(EngineOverviewLogic.values).toEqual(DEFAULT_VALUES);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
describe('setPolledData', () => {
|
||||
it('should set all received data as top-level values and set dataLoading to false', () => {
|
||||
mount();
|
||||
EngineOverviewLogic.actions.setPolledData(mockEngineMetrics);
|
||||
|
||||
expect(EngineOverviewLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
...mockEngineMetrics,
|
||||
dataLoading: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTimeoutId', () => {
|
||||
describe('timeoutId', () => {
|
||||
it('should be set to the provided value', () => {
|
||||
mount();
|
||||
EngineOverviewLogic.actions.setTimeoutId(123);
|
||||
|
||||
expect(EngineOverviewLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
timeoutId: 123,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pollForOverviewMetrics', () => {
|
||||
it('fetches data and calls onPollingSuccess', async () => {
|
||||
mount();
|
||||
jest.spyOn(EngineOverviewLogic.actions, 'onPollingSuccess');
|
||||
const promise = Promise.resolve(mockEngineMetrics);
|
||||
http.get.mockReturnValueOnce(promise);
|
||||
|
||||
EngineOverviewLogic.actions.pollForOverviewMetrics();
|
||||
await promise;
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/overview');
|
||||
expect(EngineOverviewLogic.actions.onPollingSuccess).toHaveBeenCalledWith(
|
||||
mockEngineMetrics
|
||||
);
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
mount();
|
||||
const promise = Promise.reject('An error occurred');
|
||||
http.get.mockReturnValue(promise);
|
||||
|
||||
try {
|
||||
EngineOverviewLogic.actions.pollForOverviewMetrics();
|
||||
await promise;
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onPollingSuccess', () => {
|
||||
it('starts a polling timeout and sets data', async () => {
|
||||
mount();
|
||||
jest.useFakeTimers();
|
||||
jest.spyOn(EngineOverviewLogic.actions, 'setTimeoutId');
|
||||
jest.spyOn(EngineOverviewLogic.actions, 'setPolledData');
|
||||
|
||||
EngineOverviewLogic.actions.onPollingSuccess(mockEngineMetrics);
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(
|
||||
EngineOverviewLogic.actions.pollForOverviewMetrics,
|
||||
5000
|
||||
);
|
||||
expect(EngineOverviewLogic.actions.setTimeoutId).toHaveBeenCalledWith(expect.any(Number));
|
||||
expect(EngineOverviewLogic.actions.setPolledData).toHaveBeenCalledWith(mockEngineMetrics);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unmount', () => {
|
||||
let unmount: Function;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
resetContext({});
|
||||
unmount = EngineOverviewLogic.mount();
|
||||
});
|
||||
|
||||
it('clears existing polling timeouts on unmount', () => {
|
||||
EngineOverviewLogic.actions.setTimeoutId(123);
|
||||
unmount();
|
||||
expect(clearTimeout).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not clear timeout if one hasn't been set", () => {
|
||||
unmount();
|
||||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { EngineLogic } from '../engine';
|
||||
|
||||
const POLLING_DURATION = 5000;
|
||||
|
||||
interface EngineOverviewApiData {
|
||||
apiLogsUnavailable: boolean;
|
||||
documentCount: number;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
operationsPerDay: number[];
|
||||
queriesPerDay: number[];
|
||||
totalClicks: number;
|
||||
totalQueries: number;
|
||||
}
|
||||
interface EngineOverviewValues extends EngineOverviewApiData {
|
||||
dataLoading: boolean;
|
||||
timeoutId: number | null;
|
||||
}
|
||||
|
||||
interface EngineOverviewActions {
|
||||
setPolledData(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
|
||||
setTimeoutId(timeoutId: number): { timeoutId: number };
|
||||
pollForOverviewMetrics(): void;
|
||||
onPollingSuccess(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
|
||||
}
|
||||
|
||||
export const EngineOverviewLogic = kea<MakeLogicType<EngineOverviewValues, EngineOverviewActions>>({
|
||||
path: ['enterprise_search', 'app_search', 'engine_overview_logic'],
|
||||
actions: () => ({
|
||||
setPolledData: (engineMetrics) => engineMetrics,
|
||||
setTimeoutId: (timeoutId) => ({ timeoutId }),
|
||||
pollForOverviewMetrics: true,
|
||||
onPollingSuccess: (engineMetrics) => engineMetrics,
|
||||
}),
|
||||
reducers: () => ({
|
||||
dataLoading: [
|
||||
true,
|
||||
{
|
||||
setPolledData: () => false,
|
||||
},
|
||||
],
|
||||
apiLogsUnavailable: [
|
||||
false,
|
||||
{
|
||||
setPolledData: (_, { apiLogsUnavailable }) => apiLogsUnavailable,
|
||||
},
|
||||
],
|
||||
startDate: [
|
||||
'',
|
||||
{
|
||||
setPolledData: (_, { startDate }) => startDate,
|
||||
},
|
||||
],
|
||||
endDate: [
|
||||
'',
|
||||
{
|
||||
setPolledData: (_, { endDate }) => endDate,
|
||||
},
|
||||
],
|
||||
queriesPerDay: [
|
||||
[],
|
||||
{
|
||||
setPolledData: (_, { queriesPerDay }) => queriesPerDay,
|
||||
},
|
||||
],
|
||||
operationsPerDay: [
|
||||
[],
|
||||
{
|
||||
setPolledData: (_, { operationsPerDay }) => operationsPerDay,
|
||||
},
|
||||
],
|
||||
totalQueries: [
|
||||
0,
|
||||
{
|
||||
setPolledData: (_, { totalQueries }) => totalQueries,
|
||||
},
|
||||
],
|
||||
totalClicks: [
|
||||
0,
|
||||
{
|
||||
setPolledData: (_, { totalClicks }) => totalClicks,
|
||||
},
|
||||
],
|
||||
documentCount: [
|
||||
0,
|
||||
{
|
||||
setPolledData: (_, { documentCount }) => documentCount,
|
||||
},
|
||||
],
|
||||
timeoutId: [
|
||||
null,
|
||||
{
|
||||
setTimeoutId: (_, { timeoutId }) => timeoutId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions }) => ({
|
||||
pollForOverviewMetrics: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
try {
|
||||
const response = await http.get(`/api/app_search/engines/${engineName}/overview`);
|
||||
actions.onPollingSuccess(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
onPollingSuccess: (engineMetrics) => {
|
||||
const timeoutId = window.setTimeout(actions.pollForOverviewMetrics, POLLING_DURATION);
|
||||
actions.setTimeoutId(timeoutId);
|
||||
actions.setPolledData(engineMetrics);
|
||||
},
|
||||
}),
|
||||
events: ({ values }) => ({
|
||||
beforeUnmount() {
|
||||
if (values.timeoutId !== null) clearTimeout(values.timeoutId);
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { EngineOverviewLogic } from './engine_overview_logic';
|
|
@ -116,7 +116,6 @@ describe('engine routes', () => {
|
|||
mockRouter = new MockRouter({
|
||||
method: 'get',
|
||||
path: '/api/app_search/engines/{name}',
|
||||
payload: 'params',
|
||||
});
|
||||
|
||||
registerEnginesRoutes({
|
||||
|
@ -133,4 +132,29 @@ describe('engine routes', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/app_search/engines/{name}/overview', () => {
|
||||
let mockRouter: MockRouter;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockRouter = new MockRouter({
|
||||
method: 'get',
|
||||
path: '/api/app_search/engines/{name}/overview',
|
||||
});
|
||||
|
||||
registerEnginesRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request to enterprise search', () => {
|
||||
mockRouter.callRoute({ params: { name: 'some-engine' } });
|
||||
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/as/engines/some-engine/overview_metrics',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,4 +60,19 @@ export function registerEnginesRoutes({
|
|||
})(context, request, response);
|
||||
}
|
||||
);
|
||||
router.get(
|
||||
{
|
||||
path: '/api/app_search/engines/{name}/overview',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
name: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return enterpriseSearchRequestHandler.createRequest({
|
||||
path: `/as/engines/${request.params.name}/overview_metrics`,
|
||||
})(context, request, response);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue