Skip to content

Commit 794035c

Browse files
committed
Update Frontend for Custom Result Index Query and Fix Issues
This PR finalizes the frontend changes related to PR #1225. The custom result index query now uses an index pattern instead of a single index. Additionally, this PR addresses an issue where missing custom result indices would appear because the original code checked for the existence of an index name, but now we use it as a prefix. We have updated the logic to perform a prefix search instead of checking for index name equality. This PR also resolves issue #765 by downgrading the version of jest-canvas-mock. Testing Done: * Added unit tests. * Verified that the custom result index missing callout is not shown. * Confirmed that the frontend can still display old and new results after a rollover. Signed-off-by: Kaituo Li <kaituo@amazon.com>
1 parent 1eebf24 commit 794035c

File tree

7 files changed

+361
-3
lines changed

7 files changed

+361
-3
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"babel-polyfill": "^6.26.0",
3030
"eslint-plugin-no-unsanitized": "^3.0.2",
3131
"eslint-plugin-prefer-object-spread": "^1.2.1",
32-
"jest-canvas-mock": "^2.5.2",
32+
"jest-canvas-mock": "^2.5.1",
3333
"lint-staged": "^9.2.0",
3434
"moment": "^2.24.0",
3535
"redux-mock-store": "^1.5.4",

public/pages/DetectorDetail/containers/__tests__/CustomIndexErrorMsg.test.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,38 @@ describe('detector detail', () => {
272272
// Assert that the element is not in the document
273273
expect(element).toBeNull();
274274
});
275+
276+
test('the result index prefix is found in the visible indices', () => {
277+
const detector = getRandomDetector(true, resultIndex);
278+
279+
// Set up the mock implementation for useFetchDetectorInfo
280+
(useFetchDetectorInfo as jest.Mock).mockImplementation(() => ({
281+
detector: detector,
282+
hasError: false,
283+
isLoadingDetector: false,
284+
errorMessage: undefined,
285+
}));
286+
287+
const initialState = {
288+
opensearch: {
289+
indices: [
290+
{ health: 'green', index: '.kibana_-962704462_v992471_1' },
291+
{ health: 'green', index: resultIndex + '-history-2024.06.05-1' },
292+
],
293+
requesting: false,
294+
},
295+
ad: {
296+
detectors: {},
297+
},
298+
alerting: {
299+
monitors: {},
300+
},
301+
};
302+
303+
renderWithRouter(detectorId, initialState);
304+
const element = screen.queryByTestId('missingResultIndexCallOut');
305+
306+
// Assert that the element is not in the document
307+
expect(element).toBeNull();
308+
});
275309
});

public/pages/DetectorDetail/utils/helpers.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,22 @@ export const getDetectorStateDetails = (
142142
);
143143
};
144144

145+
/**
146+
* Checks if any of the given indices contain the specified index as a prefix.
147+
*
148+
* This function iterates through an array of `CatIndex` objects and checks if the `index` property of any
149+
* `CatIndex` object starts with the specified `index` string. It returns `true` if such an `index` is found,
150+
* otherwise it returns `false`.
151+
*
152+
* @param index - The prefix string to check against the `index` properties of the `CatIndex` objects.
153+
* @param indices - An array of `CatIndex` objects to search through.
154+
* @returns A boolean value indicating whether any `CatIndex` object's `index` property starts with the specified prefix.
155+
*/
145156
export const containsIndex = (index: string, indices: CatIndex[]) => {
146157
let containsIndex = false;
147158
if (!isEmpty(indices)) {
148159
indices.forEach((catIndex: CatIndex) => {
149-
if (get(catIndex, 'index', '') == index) {
160+
if (get(catIndex, 'index', '').startsWith(index)) {
150161
containsIndex = true;
151162
}
152163
});

public/redux/reducers/__tests__/anomalyResults.test.ts

+160-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import { mockedStore } from '../../utils/testUtils';
1818
import reducer, {
1919
getDetectorResults,
2020
initialDetectorsState,
21+
searchResults,
2122
} from '../anomalyResults';
23+
import { ALL_CUSTOM_AD_RESULT_INDICES } from '../../../pages/utils/constants'
24+
import { getAnomalySummaryQuery } from '../../../pages/utils/anomalyResultUtils'
2225

2326
jest.mock('../../../services', () => ({
2427
...jest.requireActual('../../../services'),
@@ -78,7 +81,7 @@ describe('anomaly results reducer actions', () => {
7881
expect(httpMockedClient.get).toHaveBeenCalledWith(
7982
`..${
8083
AD_NODE_API.DETECTOR
81-
}/${tempDetectorId}/results/${false}/${resultIndex}/true`,
84+
}/${tempDetectorId}/results/${false}/${resultIndex}*/true`,
8285
{ query: queryParams }
8386
);
8487
});
@@ -117,5 +120,161 @@ describe('anomaly results reducer actions', () => {
117120
);
118121
}
119122
});
123+
test('result index pattern will not result in appended wildcard star', async () => {
124+
const response = {
125+
totalAnomalies: 1,
126+
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
127+
};
128+
httpMockedClient.get = jest
129+
.fn()
130+
.mockResolvedValue({ ok: true, response });
131+
const tempDetectorId = '123';
132+
let queryParams: DetectorResultsQueryParams = {
133+
from: 0,
134+
size: 20,
135+
sortDirection: SORT_DIRECTION.ASC,
136+
sortField: 'startTime',
137+
};
138+
await store.dispatch(
139+
getDetectorResults(
140+
tempDetectorId,
141+
'',
142+
queryParams,
143+
false,
144+
ALL_CUSTOM_AD_RESULT_INDICES,
145+
true
146+
)
147+
);
148+
const actions = store.getActions();
149+
150+
expect(actions[0].type).toBe('ad/DETECTOR_RESULTS_REQUEST');
151+
expect(reducer(initialDetectorsState, actions[0])).toEqual({
152+
...initialDetectorsState,
153+
requesting: true,
154+
});
155+
expect(actions[1].type).toBe('ad/DETECTOR_RESULTS_SUCCESS');
156+
expect(reducer(initialDetectorsState, actions[1])).toEqual({
157+
...initialDetectorsState,
158+
requesting: false,
159+
total: response.totalAnomalies,
160+
anomalies: response.results,
161+
featureData: undefined,
162+
});
163+
expect(httpMockedClient.get).toHaveBeenCalledWith(
164+
`..${
165+
AD_NODE_API.DETECTOR
166+
}/${tempDetectorId}/results/${false}/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
167+
{ query: queryParams }
168+
);
169+
});
170+
});
171+
test('searchResults should append wildcard star at the end of custom result index', async () => {
172+
const response = {
173+
aggregations: {
174+
top_entities: {
175+
doc_count: 0,
176+
top_entity_aggs: {
177+
doc_count_error_upper_bound: 0,
178+
sum_other_doc_count: 0,
179+
buckets: []
180+
}
181+
}
182+
}
183+
};
184+
185+
httpMockedClient.post = jest
186+
.fn()
187+
.mockResolvedValue({ ok: true, response });
188+
const tempDetectorId = '123';
189+
const resultIndex = 'opensearch-ad-plugin-result-test';
190+
const requestBody = getAnomalySummaryQuery(1717529636479, 1717529736479, tempDetectorId, undefined, false, undefined, undefined)
191+
await store.dispatch(
192+
searchResults(
193+
requestBody,
194+
resultIndex,
195+
'',
196+
true
197+
)
198+
);
199+
const actions = store.getActions();
200+
201+
expect(actions[0].type).toBe('ad/SEARCH_ANOMALY_RESULTS_REQUEST');
202+
expect(reducer(initialDetectorsState, actions[0])).toEqual({
203+
...initialDetectorsState,
204+
requesting: true,
205+
});
206+
expect(actions[1].type).toBe('ad/SEARCH_ANOMALY_RESULTS_SUCCESS');
207+
expect(reducer(initialDetectorsState, actions[1])).toEqual({
208+
...initialDetectorsState,
209+
requesting: false,
210+
});
211+
expect(httpMockedClient.post).toHaveBeenCalledWith(
212+
`..${
213+
AD_NODE_API.DETECTOR
214+
}/results/_search/${resultIndex}*/true`,
215+
{ body: JSON.stringify(requestBody) }
216+
);
217+
});
218+
test('searchResults should not append wildcard star at the end of custom result index', async () => {
219+
const response = {
220+
took: 1,
221+
timed_out: false,
222+
_shards: {
223+
total: 2,
224+
successful: 2,
225+
skipped: 0,
226+
failed: 0
227+
},
228+
hits: {
229+
total: {
230+
value: 0,
231+
relation: "eq"
232+
},
233+
max_score: null,
234+
hits: []
235+
},
236+
aggregations: {
237+
top_entities: {
238+
doc_count: 0,
239+
top_entity_aggs: {
240+
doc_count_error_upper_bound: 0,
241+
sum_other_doc_count: 0,
242+
buckets: []
243+
}
244+
}
245+
}
246+
};
247+
248+
httpMockedClient.post = jest
249+
.fn()
250+
.mockResolvedValue({ ok: true, response });
251+
const tempDetectorId = '123';
252+
const requestBody = getAnomalySummaryQuery(1717529636479, 1717529736479, tempDetectorId, undefined, false, undefined, undefined)
253+
await store.dispatch(
254+
searchResults(
255+
requestBody,
256+
ALL_CUSTOM_AD_RESULT_INDICES,
257+
'',
258+
true
259+
)
260+
);
261+
const actions = store.getActions();
262+
263+
expect(actions[0].type).toBe('ad/SEARCH_ANOMALY_RESULTS_REQUEST');
264+
expect(reducer(initialDetectorsState, actions[0])).toEqual({
265+
...initialDetectorsState,
266+
requesting: true,
267+
});
268+
expect(actions[1].type).toBe('ad/SEARCH_ANOMALY_RESULTS_SUCCESS');
269+
expect(reducer(initialDetectorsState, actions[1])).toEqual({
270+
...initialDetectorsState,
271+
requesting: false,
272+
});
273+
expect(httpMockedClient.post).toHaveBeenCalledWith(
274+
`..${
275+
AD_NODE_API.DETECTOR
276+
}/results/_search/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
277+
{ body: JSON.stringify(requestBody) }
278+
);
120279
});
121280
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
import { MockStore } from 'redux-mock-store';
13+
import { DetectorResultsQueryParams } from '../../../../server/models/types';
14+
import { SORT_DIRECTION } from '../../../../server/utils/constants';
15+
import httpMockedClient from '../../../../test/mocks/httpClientMock';
16+
import { AD_NODE_API } from '../../../../utils/constants';
17+
import { mockedStore } from '../../utils/testUtils';
18+
import { ALL_CUSTOM_AD_RESULT_INDICES } from '../../../pages/utils/constants';
19+
import reducer, {
20+
getDetectorLiveResults,
21+
initialDetectorLiveResults,
22+
} from '../liveAnomalyResults';
23+
24+
jest.mock('../../../services', () => ({
25+
...jest.requireActual('../../../services'),
26+
27+
getDataSourceEnabled: () => ({
28+
enabled: false,
29+
}),
30+
}));
31+
32+
describe('live anomaly results reducer actions', () => {
33+
let store: MockStore;
34+
beforeEach(() => {
35+
store = mockedStore();
36+
});
37+
describe('getDetectorLiveResults', () => {
38+
test('getDetectorLiveResults should append wildcard star at the end of custom result index', async () => {
39+
const response = {
40+
totalAnomalies: 1,
41+
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
42+
};
43+
44+
httpMockedClient.get = jest
45+
.fn()
46+
.mockResolvedValue({ ok: true, response });
47+
const tempDetectorId = '123';
48+
const resultIndex = 'opensearch-ad-plugin-result-test';
49+
let queryParams: DetectorResultsQueryParams = {
50+
from: 0,
51+
size: 20,
52+
sortDirection: SORT_DIRECTION.ASC,
53+
sortField: 'startTime',
54+
};
55+
await store.dispatch(
56+
getDetectorLiveResults(
57+
tempDetectorId,
58+
'',
59+
queryParams,
60+
false,
61+
resultIndex,
62+
true
63+
)
64+
);
65+
const actions = store.getActions();
66+
67+
expect(actions[0].type).toBe('ad/DETECTOR_LIVE_RESULTS_REQUEST');
68+
expect(reducer(initialDetectorLiveResults, actions[0])).toEqual({
69+
...initialDetectorLiveResults,
70+
requesting: true,
71+
});
72+
expect(actions[1].type).toBe('ad/DETECTOR_LIVE_RESULTS_SUCCESS');
73+
expect(reducer(initialDetectorLiveResults, actions[1])).toEqual({
74+
...initialDetectorLiveResults,
75+
requesting: false,
76+
totalLiveAnomalies: response.totalAnomalies,
77+
liveAnomalies: response.results,
78+
errorMessage: '',
79+
});
80+
expect(httpMockedClient.get).toHaveBeenCalledWith(
81+
`..${
82+
AD_NODE_API.DETECTOR
83+
}/${tempDetectorId}/results/${false}/${resultIndex}*/true`,
84+
{ query: queryParams }
85+
);
86+
});
87+
test('getDetectorLiveResults should not append wildcard star at the end of custom result index', async () => {
88+
const response = {
89+
totalAnomalies: 1,
90+
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
91+
};
92+
93+
httpMockedClient.get = jest
94+
.fn()
95+
.mockResolvedValue({ ok: true, response });
96+
const tempDetectorId = '123';
97+
let queryParams: DetectorResultsQueryParams = {
98+
from: 0,
99+
size: 20,
100+
sortDirection: SORT_DIRECTION.ASC,
101+
sortField: 'startTime',
102+
};
103+
await store.dispatch(
104+
getDetectorLiveResults(
105+
tempDetectorId,
106+
'',
107+
queryParams,
108+
false,
109+
ALL_CUSTOM_AD_RESULT_INDICES,
110+
true
111+
)
112+
);
113+
const actions = store.getActions();
114+
115+
expect(actions[0].type).toBe('ad/DETECTOR_LIVE_RESULTS_REQUEST');
116+
expect(reducer(initialDetectorLiveResults, actions[0])).toEqual({
117+
...initialDetectorLiveResults,
118+
requesting: true,
119+
});
120+
expect(actions[1].type).toBe('ad/DETECTOR_LIVE_RESULTS_SUCCESS');
121+
expect(reducer(initialDetectorLiveResults, actions[1])).toEqual({
122+
...initialDetectorLiveResults,
123+
requesting: false,
124+
totalLiveAnomalies: response.totalAnomalies,
125+
liveAnomalies: response.results,
126+
errorMessage: '',
127+
});
128+
expect(httpMockedClient.get).toHaveBeenCalledWith(
129+
`..${
130+
AD_NODE_API.DETECTOR
131+
}/${tempDetectorId}/results/${false}/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
132+
{ query: queryParams }
133+
);
134+
});
135+
});
136+
});

0 commit comments

Comments
 (0)