Skip to content

Commit 583b699

Browse files
Constancekibanamachine
andauthored
[App Search] Engine Overview server route & Logic file (#83353) (#83471)
* Add overview server route * Add EngineOverviewLogic * tfw when you forget index.ts Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent d04ac61 commit 583b699

File tree

5 files changed

+340
-1
lines changed

5 files changed

+340
-1
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { resetContext } from 'kea';
8+
9+
import { mockHttpValues } from '../../../__mocks__';
10+
jest.mock('../../../shared/http', () => ({
11+
HttpLogic: { values: mockHttpValues },
12+
}));
13+
const { http } = mockHttpValues;
14+
15+
jest.mock('../../../shared/flash_messages', () => ({
16+
flashAPIErrors: jest.fn(),
17+
}));
18+
import { flashAPIErrors } from '../../../shared/flash_messages';
19+
20+
jest.mock('../engine', () => ({
21+
EngineLogic: { values: { engineName: 'some-engine' } },
22+
}));
23+
24+
import { EngineOverviewLogic } from './';
25+
26+
describe('EngineOverviewLogic', () => {
27+
const mockEngineMetrics = {
28+
apiLogsUnavailable: true,
29+
documentCount: 10,
30+
startDate: '1970-01-30',
31+
endDate: '1970-01-31',
32+
operationsPerDay: [0, 0, 0, 0, 0, 0, 0],
33+
queriesPerDay: [0, 0, 0, 0, 0, 25, 50],
34+
totalClicks: 50,
35+
totalQueries: 75,
36+
};
37+
38+
const DEFAULT_VALUES = {
39+
dataLoading: true,
40+
apiLogsUnavailable: false,
41+
documentCount: 0,
42+
startDate: '',
43+
endDate: '',
44+
operationsPerDay: [],
45+
queriesPerDay: [],
46+
totalClicks: 0,
47+
totalQueries: 0,
48+
timeoutId: null,
49+
};
50+
51+
const mount = () => {
52+
resetContext({});
53+
EngineOverviewLogic.mount();
54+
};
55+
56+
beforeEach(() => {
57+
jest.clearAllMocks();
58+
});
59+
60+
it('has expected default values', () => {
61+
mount();
62+
expect(EngineOverviewLogic.values).toEqual(DEFAULT_VALUES);
63+
});
64+
65+
describe('actions', () => {
66+
describe('setPolledData', () => {
67+
it('should set all received data as top-level values and set dataLoading to false', () => {
68+
mount();
69+
EngineOverviewLogic.actions.setPolledData(mockEngineMetrics);
70+
71+
expect(EngineOverviewLogic.values).toEqual({
72+
...DEFAULT_VALUES,
73+
...mockEngineMetrics,
74+
dataLoading: false,
75+
});
76+
});
77+
});
78+
79+
describe('setTimeoutId', () => {
80+
describe('timeoutId', () => {
81+
it('should be set to the provided value', () => {
82+
mount();
83+
EngineOverviewLogic.actions.setTimeoutId(123);
84+
85+
expect(EngineOverviewLogic.values).toEqual({
86+
...DEFAULT_VALUES,
87+
timeoutId: 123,
88+
});
89+
});
90+
});
91+
});
92+
93+
describe('pollForOverviewMetrics', () => {
94+
it('fetches data and calls onPollingSuccess', async () => {
95+
mount();
96+
jest.spyOn(EngineOverviewLogic.actions, 'onPollingSuccess');
97+
const promise = Promise.resolve(mockEngineMetrics);
98+
http.get.mockReturnValueOnce(promise);
99+
100+
EngineOverviewLogic.actions.pollForOverviewMetrics();
101+
await promise;
102+
103+
expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/overview');
104+
expect(EngineOverviewLogic.actions.onPollingSuccess).toHaveBeenCalledWith(
105+
mockEngineMetrics
106+
);
107+
});
108+
109+
it('handles errors', async () => {
110+
mount();
111+
const promise = Promise.reject('An error occurred');
112+
http.get.mockReturnValue(promise);
113+
114+
try {
115+
EngineOverviewLogic.actions.pollForOverviewMetrics();
116+
await promise;
117+
} catch {
118+
// Do nothing
119+
}
120+
expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred');
121+
});
122+
});
123+
124+
describe('onPollingSuccess', () => {
125+
it('starts a polling timeout and sets data', async () => {
126+
mount();
127+
jest.useFakeTimers();
128+
jest.spyOn(EngineOverviewLogic.actions, 'setTimeoutId');
129+
jest.spyOn(EngineOverviewLogic.actions, 'setPolledData');
130+
131+
EngineOverviewLogic.actions.onPollingSuccess(mockEngineMetrics);
132+
133+
expect(setTimeout).toHaveBeenCalledWith(
134+
EngineOverviewLogic.actions.pollForOverviewMetrics,
135+
5000
136+
);
137+
expect(EngineOverviewLogic.actions.setTimeoutId).toHaveBeenCalledWith(expect.any(Number));
138+
expect(EngineOverviewLogic.actions.setPolledData).toHaveBeenCalledWith(mockEngineMetrics);
139+
});
140+
});
141+
});
142+
143+
describe('unmount', () => {
144+
let unmount: Function;
145+
146+
beforeEach(() => {
147+
jest.useFakeTimers();
148+
resetContext({});
149+
unmount = EngineOverviewLogic.mount();
150+
});
151+
152+
it('clears existing polling timeouts on unmount', () => {
153+
EngineOverviewLogic.actions.setTimeoutId(123);
154+
unmount();
155+
expect(clearTimeout).toHaveBeenCalled();
156+
});
157+
158+
it("does not clear timeout if one hasn't been set", () => {
159+
unmount();
160+
expect(clearTimeout).not.toHaveBeenCalled();
161+
});
162+
});
163+
});
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { kea, MakeLogicType } from 'kea';
8+
9+
import { flashAPIErrors } from '../../../shared/flash_messages';
10+
import { HttpLogic } from '../../../shared/http';
11+
import { EngineLogic } from '../engine';
12+
13+
const POLLING_DURATION = 5000;
14+
15+
interface EngineOverviewApiData {
16+
apiLogsUnavailable: boolean;
17+
documentCount: number;
18+
startDate: string;
19+
endDate: string;
20+
operationsPerDay: number[];
21+
queriesPerDay: number[];
22+
totalClicks: number;
23+
totalQueries: number;
24+
}
25+
interface EngineOverviewValues extends EngineOverviewApiData {
26+
dataLoading: boolean;
27+
timeoutId: number | null;
28+
}
29+
30+
interface EngineOverviewActions {
31+
setPolledData(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
32+
setTimeoutId(timeoutId: number): { timeoutId: number };
33+
pollForOverviewMetrics(): void;
34+
onPollingSuccess(engineMetrics: EngineOverviewApiData): EngineOverviewApiData;
35+
}
36+
37+
export const EngineOverviewLogic = kea<MakeLogicType<EngineOverviewValues, EngineOverviewActions>>({
38+
path: ['enterprise_search', 'app_search', 'engine_overview_logic'],
39+
actions: () => ({
40+
setPolledData: (engineMetrics) => engineMetrics,
41+
setTimeoutId: (timeoutId) => ({ timeoutId }),
42+
pollForOverviewMetrics: true,
43+
onPollingSuccess: (engineMetrics) => engineMetrics,
44+
}),
45+
reducers: () => ({
46+
dataLoading: [
47+
true,
48+
{
49+
setPolledData: () => false,
50+
},
51+
],
52+
apiLogsUnavailable: [
53+
false,
54+
{
55+
setPolledData: (_, { apiLogsUnavailable }) => apiLogsUnavailable,
56+
},
57+
],
58+
startDate: [
59+
'',
60+
{
61+
setPolledData: (_, { startDate }) => startDate,
62+
},
63+
],
64+
endDate: [
65+
'',
66+
{
67+
setPolledData: (_, { endDate }) => endDate,
68+
},
69+
],
70+
queriesPerDay: [
71+
[],
72+
{
73+
setPolledData: (_, { queriesPerDay }) => queriesPerDay,
74+
},
75+
],
76+
operationsPerDay: [
77+
[],
78+
{
79+
setPolledData: (_, { operationsPerDay }) => operationsPerDay,
80+
},
81+
],
82+
totalQueries: [
83+
0,
84+
{
85+
setPolledData: (_, { totalQueries }) => totalQueries,
86+
},
87+
],
88+
totalClicks: [
89+
0,
90+
{
91+
setPolledData: (_, { totalClicks }) => totalClicks,
92+
},
93+
],
94+
documentCount: [
95+
0,
96+
{
97+
setPolledData: (_, { documentCount }) => documentCount,
98+
},
99+
],
100+
timeoutId: [
101+
null,
102+
{
103+
setTimeoutId: (_, { timeoutId }) => timeoutId,
104+
},
105+
],
106+
}),
107+
listeners: ({ actions }) => ({
108+
pollForOverviewMetrics: async () => {
109+
const { http } = HttpLogic.values;
110+
const { engineName } = EngineLogic.values;
111+
112+
try {
113+
const response = await http.get(`/api/app_search/engines/${engineName}/overview`);
114+
actions.onPollingSuccess(response);
115+
} catch (e) {
116+
flashAPIErrors(e);
117+
}
118+
},
119+
onPollingSuccess: (engineMetrics) => {
120+
const timeoutId = window.setTimeout(actions.pollForOverviewMetrics, POLLING_DURATION);
121+
actions.setTimeoutId(timeoutId);
122+
actions.setPolledData(engineMetrics);
123+
},
124+
}),
125+
events: ({ values }) => ({
126+
beforeUnmount() {
127+
if (values.timeoutId !== null) clearTimeout(values.timeoutId);
128+
},
129+
}),
130+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { EngineOverviewLogic } from './engine_overview_logic';

x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ describe('engine routes', () => {
116116
mockRouter = new MockRouter({
117117
method: 'get',
118118
path: '/api/app_search/engines/{name}',
119-
payload: 'params',
120119
});
121120

122121
registerEnginesRoutes({
@@ -133,4 +132,29 @@ describe('engine routes', () => {
133132
});
134133
});
135134
});
135+
136+
describe('GET /api/app_search/engines/{name}/overview', () => {
137+
let mockRouter: MockRouter;
138+
139+
beforeEach(() => {
140+
jest.clearAllMocks();
141+
mockRouter = new MockRouter({
142+
method: 'get',
143+
path: '/api/app_search/engines/{name}/overview',
144+
});
145+
146+
registerEnginesRoutes({
147+
...mockDependencies,
148+
router: mockRouter.router,
149+
});
150+
});
151+
152+
it('creates a request to enterprise search', () => {
153+
mockRouter.callRoute({ params: { name: 'some-engine' } });
154+
155+
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
156+
path: '/as/engines/some-engine/overview_metrics',
157+
});
158+
});
159+
});
136160
});

x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,19 @@ export function registerEnginesRoutes({
6060
})(context, request, response);
6161
}
6262
);
63+
router.get(
64+
{
65+
path: '/api/app_search/engines/{name}/overview',
66+
validate: {
67+
params: schema.object({
68+
name: schema.string(),
69+
}),
70+
},
71+
},
72+
async (context, request, response) => {
73+
return enterpriseSearchRequestHandler.createRequest({
74+
path: `/as/engines/${request.params.name}/overview_metrics`,
75+
})(context, request, response);
76+
}
77+
);
6378
}

0 commit comments

Comments
 (0)