Skip to content

Commit

Permalink
Update Frontend for Custom Result Index Query and Fix Issues
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
kaituo committed Jun 7, 2024
1 parent 1eebf24 commit 794035c
Show file tree
Hide file tree
Showing 7 changed files with 361 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"babel-polyfill": "^6.26.0",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"jest-canvas-mock": "^2.5.2",
"jest-canvas-mock": "^2.5.1",
"lint-staged": "^9.2.0",
"moment": "^2.24.0",
"redux-mock-store": "^1.5.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,38 @@ describe('detector detail', () => {
// Assert that the element is not in the document
expect(element).toBeNull();
});

test('the result index prefix is found in the visible indices', () => {
const detector = getRandomDetector(true, resultIndex);

// Set up the mock implementation for useFetchDetectorInfo
(useFetchDetectorInfo as jest.Mock).mockImplementation(() => ({
detector: detector,
hasError: false,
isLoadingDetector: false,
errorMessage: undefined,
}));

const initialState = {
opensearch: {
indices: [
{ health: 'green', index: '.kibana_-962704462_v992471_1' },
{ health: 'green', index: resultIndex + '-history-2024.06.05-1' },
],
requesting: false,
},
ad: {
detectors: {},
},
alerting: {
monitors: {},
},
};

renderWithRouter(detectorId, initialState);
const element = screen.queryByTestId('missingResultIndexCallOut');

// Assert that the element is not in the document
expect(element).toBeNull();
});
});
13 changes: 12 additions & 1 deletion public/pages/DetectorDetail/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,22 @@ export const getDetectorStateDetails = (
);
};

/**
* Checks if any of the given indices contain the specified index as a prefix.
*
* This function iterates through an array of `CatIndex` objects and checks if the `index` property of any
* `CatIndex` object starts with the specified `index` string. It returns `true` if such an `index` is found,
* otherwise it returns `false`.
*
* @param index - The prefix string to check against the `index` properties of the `CatIndex` objects.
* @param indices - An array of `CatIndex` objects to search through.
* @returns A boolean value indicating whether any `CatIndex` object's `index` property starts with the specified prefix.
*/
export const containsIndex = (index: string, indices: CatIndex[]) => {
let containsIndex = false;
if (!isEmpty(indices)) {
indices.forEach((catIndex: CatIndex) => {
if (get(catIndex, 'index', '') == index) {
if (get(catIndex, 'index', '').startsWith(index)) {
containsIndex = true;
}
});
Expand Down
161 changes: 160 additions & 1 deletion public/redux/reducers/__tests__/anomalyResults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { mockedStore } from '../../utils/testUtils';
import reducer, {
getDetectorResults,
initialDetectorsState,
searchResults,
} from '../anomalyResults';
import { ALL_CUSTOM_AD_RESULT_INDICES } from '../../../pages/utils/constants'
import { getAnomalySummaryQuery } from '../../../pages/utils/anomalyResultUtils'

jest.mock('../../../services', () => ({
...jest.requireActual('../../../services'),
Expand Down Expand Up @@ -78,7 +81,7 @@ describe('anomaly results reducer actions', () => {
expect(httpMockedClient.get).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/${tempDetectorId}/results/${false}/${resultIndex}/true`,
}/${tempDetectorId}/results/${false}/${resultIndex}*/true`,
{ query: queryParams }
);
});
Expand Down Expand Up @@ -117,5 +120,161 @@ describe('anomaly results reducer actions', () => {
);
}
});
test('result index pattern will not result in appended wildcard star', async () => {
const response = {
totalAnomalies: 1,
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
};
httpMockedClient.get = jest
.fn()
.mockResolvedValue({ ok: true, response });
const tempDetectorId = '123';
let queryParams: DetectorResultsQueryParams = {
from: 0,
size: 20,
sortDirection: SORT_DIRECTION.ASC,
sortField: 'startTime',
};
await store.dispatch(
getDetectorResults(
tempDetectorId,
'',
queryParams,
false,
ALL_CUSTOM_AD_RESULT_INDICES,
true
)
);
const actions = store.getActions();

expect(actions[0].type).toBe('ad/DETECTOR_RESULTS_REQUEST');
expect(reducer(initialDetectorsState, actions[0])).toEqual({
...initialDetectorsState,
requesting: true,
});
expect(actions[1].type).toBe('ad/DETECTOR_RESULTS_SUCCESS');
expect(reducer(initialDetectorsState, actions[1])).toEqual({
...initialDetectorsState,
requesting: false,
total: response.totalAnomalies,
anomalies: response.results,
featureData: undefined,
});
expect(httpMockedClient.get).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/${tempDetectorId}/results/${false}/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
{ query: queryParams }
);
});
});
test('searchResults should append wildcard star at the end of custom result index', async () => {
const response = {
aggregations: {
top_entities: {
doc_count: 0,
top_entity_aggs: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: []
}
}
}
};

httpMockedClient.post = jest
.fn()
.mockResolvedValue({ ok: true, response });
const tempDetectorId = '123';
const resultIndex = 'opensearch-ad-plugin-result-test';
const requestBody = getAnomalySummaryQuery(1717529636479, 1717529736479, tempDetectorId, undefined, false, undefined, undefined)
await store.dispatch(
searchResults(
requestBody,
resultIndex,
'',
true
)
);
const actions = store.getActions();

expect(actions[0].type).toBe('ad/SEARCH_ANOMALY_RESULTS_REQUEST');
expect(reducer(initialDetectorsState, actions[0])).toEqual({
...initialDetectorsState,
requesting: true,
});
expect(actions[1].type).toBe('ad/SEARCH_ANOMALY_RESULTS_SUCCESS');
expect(reducer(initialDetectorsState, actions[1])).toEqual({
...initialDetectorsState,
requesting: false,
});
expect(httpMockedClient.post).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/results/_search/${resultIndex}*/true`,
{ body: JSON.stringify(requestBody) }
);
});
test('searchResults should not append wildcard star at the end of custom result index', async () => {
const response = {
took: 1,
timed_out: false,
_shards: {
total: 2,
successful: 2,
skipped: 0,
failed: 0
},
hits: {
total: {
value: 0,
relation: "eq"
},
max_score: null,
hits: []
},
aggregations: {
top_entities: {
doc_count: 0,
top_entity_aggs: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: []
}
}
}
};

httpMockedClient.post = jest
.fn()
.mockResolvedValue({ ok: true, response });
const tempDetectorId = '123';
const requestBody = getAnomalySummaryQuery(1717529636479, 1717529736479, tempDetectorId, undefined, false, undefined, undefined)
await store.dispatch(
searchResults(
requestBody,
ALL_CUSTOM_AD_RESULT_INDICES,
'',
true
)
);
const actions = store.getActions();

expect(actions[0].type).toBe('ad/SEARCH_ANOMALY_RESULTS_REQUEST');
expect(reducer(initialDetectorsState, actions[0])).toEqual({
...initialDetectorsState,
requesting: true,
});
expect(actions[1].type).toBe('ad/SEARCH_ANOMALY_RESULTS_SUCCESS');
expect(reducer(initialDetectorsState, actions[1])).toEqual({
...initialDetectorsState,
requesting: false,
});
expect(httpMockedClient.post).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/results/_search/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
{ body: JSON.stringify(requestBody) }
);
});
});
136 changes: 136 additions & 0 deletions public/redux/reducers/__tests__/liveAnomalyResults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import { MockStore } from 'redux-mock-store';
import { DetectorResultsQueryParams } from '../../../../server/models/types';
import { SORT_DIRECTION } from '../../../../server/utils/constants';
import httpMockedClient from '../../../../test/mocks/httpClientMock';
import { AD_NODE_API } from '../../../../utils/constants';
import { mockedStore } from '../../utils/testUtils';
import { ALL_CUSTOM_AD_RESULT_INDICES } from '../../../pages/utils/constants';
import reducer, {
getDetectorLiveResults,
initialDetectorLiveResults,
} from '../liveAnomalyResults';

jest.mock('../../../services', () => ({
...jest.requireActual('../../../services'),

getDataSourceEnabled: () => ({
enabled: false,
}),
}));

describe('live anomaly results reducer actions', () => {
let store: MockStore;
beforeEach(() => {
store = mockedStore();
});
describe('getDetectorLiveResults', () => {
test('getDetectorLiveResults should append wildcard star at the end of custom result index', async () => {
const response = {
totalAnomalies: 1,
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
};

httpMockedClient.get = jest
.fn()
.mockResolvedValue({ ok: true, response });
const tempDetectorId = '123';
const resultIndex = 'opensearch-ad-plugin-result-test';
let queryParams: DetectorResultsQueryParams = {
from: 0,
size: 20,
sortDirection: SORT_DIRECTION.ASC,
sortField: 'startTime',
};
await store.dispatch(
getDetectorLiveResults(
tempDetectorId,
'',
queryParams,
false,
resultIndex,
true
)
);
const actions = store.getActions();

expect(actions[0].type).toBe('ad/DETECTOR_LIVE_RESULTS_REQUEST');
expect(reducer(initialDetectorLiveResults, actions[0])).toEqual({
...initialDetectorLiveResults,
requesting: true,
});
expect(actions[1].type).toBe('ad/DETECTOR_LIVE_RESULTS_SUCCESS');
expect(reducer(initialDetectorLiveResults, actions[1])).toEqual({
...initialDetectorLiveResults,
requesting: false,
totalLiveAnomalies: response.totalAnomalies,
liveAnomalies: response.results,
errorMessage: '',
});
expect(httpMockedClient.get).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/${tempDetectorId}/results/${false}/${resultIndex}*/true`,
{ query: queryParams }
);
});
test('getDetectorLiveResults should not append wildcard star at the end of custom result index', async () => {
const response = {
totalAnomalies: 1,
results: [{ anomalyGrade: 0, confidence: 1, starTime: 1, endTime: 2 }],
};

httpMockedClient.get = jest
.fn()
.mockResolvedValue({ ok: true, response });
const tempDetectorId = '123';
let queryParams: DetectorResultsQueryParams = {
from: 0,
size: 20,
sortDirection: SORT_DIRECTION.ASC,
sortField: 'startTime',
};
await store.dispatch(
getDetectorLiveResults(
tempDetectorId,
'',
queryParams,
false,
ALL_CUSTOM_AD_RESULT_INDICES,
true
)
);
const actions = store.getActions();

expect(actions[0].type).toBe('ad/DETECTOR_LIVE_RESULTS_REQUEST');
expect(reducer(initialDetectorLiveResults, actions[0])).toEqual({
...initialDetectorLiveResults,
requesting: true,
});
expect(actions[1].type).toBe('ad/DETECTOR_LIVE_RESULTS_SUCCESS');
expect(reducer(initialDetectorLiveResults, actions[1])).toEqual({
...initialDetectorLiveResults,
requesting: false,
totalLiveAnomalies: response.totalAnomalies,
liveAnomalies: response.results,
errorMessage: '',
});
expect(httpMockedClient.get).toHaveBeenCalledWith(
`..${
AD_NODE_API.DETECTOR
}/${tempDetectorId}/results/${false}/${ALL_CUSTOM_AD_RESULT_INDICES}/true`,
{ query: queryParams }
);
});
});
});
Loading

0 comments on commit 794035c

Please sign in to comment.