Skip to content

Commit 5d9eb9a

Browse files
authored
Improve error handling on missing result index errors on detector detail pages (opensearch-project#158)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent ec768b7 commit 5d9eb9a

File tree

7 files changed

+98
-39
lines changed

7 files changed

+98
-39
lines changed

.github/workflows/link-check-workflow.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
id: lychee
1616
uses: lycheeverse/lychee-action@master
1717
with:
18-
args: --accept=200,403,429 --exclude=localhost "**/*.html" "**/*.md" "**/*.txt" "**/*.json"
18+
args: --accept=200,403,429 --exclude=localhost **/*.html **/*.md **/*.txt **/*.json
1919
env:
2020
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
2121
- name: Fail if there were link errors

public/pages/DetectorDetail/containers/DetectorDetail.tsx

+43-1
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,24 @@ import {
2222
EuiText,
2323
EuiFieldText,
2424
EuiLoadingSpinner,
25+
EuiButton,
2526
} from '@elastic/eui';
2627
import { CoreStart } from '../../../../../../src/core/public';
2728
import { CoreServicesContext } from '../../../components/CoreServices/CoreServices';
2829
import { get, isEmpty } from 'lodash';
2930
import { RouteComponentProps, Switch, Route, Redirect } from 'react-router-dom';
30-
import { useDispatch } from 'react-redux';
31+
import { useDispatch, useSelector } from 'react-redux';
3132
import { useFetchDetectorInfo } from '../../CreateDetectorSteps/hooks/useFetchDetectorInfo';
3233
import { useHideSideNavBar } from '../../main/hooks/useHideSideNavBar';
34+
import { AppState } from '../../../redux/reducers';
3335
import {
3436
deleteDetector,
3537
startDetector,
3638
stopDetector,
3739
getDetector,
3840
stopHistoricalDetector,
3941
} from '../../../redux/reducers/ad';
42+
import { getIndices } from '../../../redux/reducers/opensearch';
4043
import { getErrorMessage, Listener } from '../../../utils/utils';
4144
import { darkModeEnabled } from '../../../utils/opensearchDashboardsUtils';
4245
import { BREADCRUMBS } from '../../../utils/constants';
@@ -53,6 +56,8 @@ import {
5356
prettifyErrorMessage,
5457
} from '../../../../server/utils/helpers';
5558
import { DETECTOR_STATE } from '../../../../server/utils/constants';
59+
import { CatIndex } from '../../../../server/models/types';
60+
import { containsIndex } from '../utils/helpers';
5661

5762
export interface DetectorRouterProps {
5863
detectorId?: string;
@@ -102,6 +107,15 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
102107
useFetchDetectorInfo(detectorId);
103108
const { monitor, fetchMonitorError, isLoadingMonitor } =
104109
useFetchMonitorInfo(detectorId);
110+
const visibleIndices = useSelector(
111+
(state: AppState) => state.opensearch.indices
112+
) as CatIndex[];
113+
const isResultIndexMissing = isLoadingDetector
114+
? false
115+
: isEmpty(get(detector, 'resultIndex', ''))
116+
? false
117+
: !containsIndex(get(detector, 'resultIndex', ''), visibleIndices);
118+
105119
// String to set in the modal if the realtime detector and/or historical analysis
106120
// are running when the user tries to edit the detector details or model config
107121
const isRTJobRunning = get(detector, 'enabled');
@@ -138,6 +152,18 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
138152
scroll(0, 0);
139153
}, []);
140154

155+
// Getting all visible indices. Will re-fetch if changes to the detector (e.g.,
156+
// detector starts, result index recreated or user switches tabs to re-fetch detector)
157+
useEffect(() => {
158+
const getInitialIndices = async () => {
159+
await dispatch(getIndices('')).catch((error: any) => {
160+
console.error(error);
161+
core.notifications.toasts.addDanger('Error getting all indices');
162+
});
163+
};
164+
getInitialIndices();
165+
}, [detector]);
166+
141167
useEffect(() => {
142168
if (hasError) {
143169
core.notifications.toasts.addDanger(
@@ -365,6 +391,22 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
365391
/>
366392
</EuiFlexItem>
367393
</EuiFlexGroup>
394+
{isResultIndexMissing ? (
395+
<EuiCallOut
396+
style={{
397+
marginLeft: '10px',
398+
marginRight: '10px',
399+
marginBottom: '10px',
400+
}}
401+
title={`Your detector is using custom result index '${get(
402+
detector,
403+
'resultIndex',
404+
''
405+
)}', but is not found in the cluster. The index will be recreated when you start a real-time or historical job.`}
406+
color="danger"
407+
iconType="alert"
408+
></EuiCallOut>
409+
) : null}
368410

369411
<EuiFlexGroup>
370412
<EuiFlexItem>

public/pages/DetectorDetail/utils/helpers.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
*/
1111

1212
import React, { Fragment } from 'react';
13-
import { get } from 'lodash';
13+
import { get, isEmpty } from 'lodash';
1414
import { DETECTOR_INIT_FAILURES } from './constants';
1515
import { DETECTOR_STATE_COLOR } from '../../utils/constants';
1616
import { DETECTOR_STATE } from '../../../../server/utils/constants';
1717
import { Detector } from '../../../models/interfaces';
1818
import { EuiHealth } from '@elastic/eui';
1919
import moment from 'moment';
20+
import { CatIndex } from '../../../../server/models/types';
2021

2122
export const getInitFailureMessageAndActionItem = (error: string): object => {
2223
const failureDetails = Object.values(DETECTOR_INIT_FAILURES);
@@ -107,3 +108,15 @@ export const getDetectorStateDetails = (
107108
</Fragment>
108109
);
109110
};
111+
112+
export const containsIndex = (index: string, indices: CatIndex[]) => {
113+
let containsIndex = false;
114+
if (!isEmpty(indices)) {
115+
indices.forEach((catIndex: CatIndex) => {
116+
if (get(catIndex, 'index', '') == index) {
117+
containsIndex = true;
118+
}
119+
});
120+
}
121+
return containsIndex;
122+
};

public/pages/DetectorResults/containers/AnomalyHistory.tsx

+14-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import React, {
1717
useRef,
1818
} from 'react';
1919

20-
import { isEmpty, get, stubTrue } from 'lodash';
20+
import { isEmpty, get } from 'lodash';
2121
import {
2222
EuiFlexItem,
2323
EuiFlexGroup,
@@ -59,7 +59,6 @@ import {
5959
import { AnomalyResultsTable } from './AnomalyResultsTable';
6060
import { AnomaliesChart } from '../../AnomalyCharts/containers/AnomaliesChart';
6161
import { FeatureBreakDown } from '../../AnomalyCharts/containers/FeatureBreakDown';
62-
import { minuteDateFormatter } from '../../utils/helpers';
6362
import {
6463
ANOMALY_HISTORY_TABS,
6564
MAX_TIME_SERIES_TO_DISPLAY,
@@ -383,7 +382,7 @@ export const AnomalyHistory = (props: AnomalyHistoryProps) => {
383382
if (showLoading) {
384383
setIsLoading(true);
385384
}
386-
385+
let errorFetchingResults = false;
387386
try {
388387
const params = buildParamsForGetAnomalyResultsWithDateRange(
389388
dateRange.startDate -
@@ -408,7 +407,11 @@ export const AnomalyHistory = (props: AnomalyHistoryProps) => {
408407
resultIndex,
409408
true
410409
)
411-
)
410+
).catch((error: any) => {
411+
setIsLoading(false);
412+
setIsLoadingAnomalyResults(false);
413+
errorFetchingResults = true;
414+
})
412415
: await dispatch(
413416
getDetectorResults(
414417
props.detector.id,
@@ -417,7 +420,12 @@ export const AnomalyHistory = (props: AnomalyHistoryProps) => {
417420
resultIndex,
418421
true
419422
)
420-
);
423+
).catch((error: any) => {
424+
setIsLoading(false);
425+
setIsLoadingAnomalyResults(false);
426+
errorFetchingResults = true;
427+
});
428+
421429
const rawAnomaliesData = get(detectorResultResponse, 'response', []);
422430
const rawAnomaliesResult = {
423431
anomalies: get(rawAnomaliesData, 'results', []),
@@ -441,7 +449,7 @@ export const AnomalyHistory = (props: AnomalyHistoryProps) => {
441449
// After fetching raw results, re-fetch the latest HC anomaly summaries, if applicable.
442450
// Also clear any selected heatmap cell data in all of the child charts,
443451
// in case a user selected one while the job was still running.
444-
if (isHCDetector) {
452+
if (isHCDetector && !errorFetchingResults) {
445453
setSelectedHeatmapCell(undefined);
446454
fetchHCAnomalySummaries();
447455
}

public/redux/reducers/opensearch.ts

+26-28
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* GitHub history for details.
1010
*/
1111

12-
1312
import {
1413
APIAction,
1514
APIResponseAction,
@@ -295,36 +294,35 @@ export const deleteIndex = (index: string): APIAction => ({
295294
client.post(`..${AD_NODE_API.DELETE_INDEX}`, { query: { index: index } }),
296295
});
297296

298-
export const getPrioritizedIndices = (searchKey: string): ThunkAction => async (
299-
dispatch,
300-
getState
301-
) => {
302-
//Fetch Indices and Aliases with text provided
303-
await dispatch(getIndices(searchKey));
304-
await dispatch(getAliases(searchKey));
305-
const osState = getState().opensearch;
306-
const exactMatchedIndices = osState.indices;
307-
const exactMatchedAliases = osState.aliases;
308-
if (exactMatchedAliases.length || exactMatchedIndices.length) {
309-
//If we have exact match just return that
310-
return {
311-
indices: exactMatchedIndices,
312-
aliases: exactMatchedAliases,
313-
};
314-
} else {
315-
//No results found for exact match, append wildCard and get partial matches if exists
316-
await dispatch(getIndices(`${searchKey}*`));
317-
await dispatch(getAliases(`${searchKey}*`));
297+
export const getPrioritizedIndices =
298+
(searchKey: string): ThunkAction =>
299+
async (dispatch, getState) => {
300+
//Fetch Indices and Aliases with text provided
301+
await dispatch(getIndices(searchKey));
302+
await dispatch(getAliases(searchKey));
318303
const osState = getState().opensearch;
319-
const partialMatchedIndices = osState.indices;
320-
const partialMatchedAliases = osState.aliases;
321-
if (partialMatchedAliases.length || partialMatchedIndices.length) {
304+
const exactMatchedIndices = osState.indices;
305+
const exactMatchedAliases = osState.aliases;
306+
if (exactMatchedAliases.length || exactMatchedIndices.length) {
307+
//If we have exact match just return that
322308
return {
323-
indices: partialMatchedIndices,
324-
aliases: partialMatchedAliases,
309+
indices: exactMatchedIndices,
310+
aliases: exactMatchedAliases,
325311
};
312+
} else {
313+
//No results found for exact match, append wildCard and get partial matches if exists
314+
await dispatch(getIndices(`${searchKey}*`));
315+
await dispatch(getAliases(`${searchKey}*`));
316+
const osState = getState().opensearch;
317+
const partialMatchedIndices = osState.indices;
318+
const partialMatchedAliases = osState.aliases;
319+
if (partialMatchedAliases.length || partialMatchedIndices.length) {
320+
return {
321+
indices: partialMatchedIndices,
322+
aliases: partialMatchedAliases,
323+
};
324+
}
326325
}
327-
}
328-
};
326+
};
329327

330328
export default reducer;

server/routes/opensearch.ts

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* GitHub history for details.
1010
*/
1111

12-
1312
import { get } from 'lodash';
1413
import { SearchResponse } from '../models/interfaces';
1514
import {

utils/constants.ts

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* GitHub history for details.
1010
*/
1111

12-
1312
export const BASE_NODE_API_PATH = '/api/anomaly_detectors';
1413

1514
export const AD_NODE_API = Object.freeze({

0 commit comments

Comments
 (0)