Skip to content

Commit

Permalink
Update Frontend for Custom Result Index Query and Fix Issues (#772) (#…
Browse files Browse the repository at this point in the history
…774)

* 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>

* change to check alias

Signed-off-by: Kaituo Li <kaituo@amazon.com>

* fix warning msg

Signed-off-by: Kaituo Li <kaituo@amazon.com>

---------

Signed-off-by: Kaituo Li <kaituo@amazon.com>
(cherry picked from commit 6513e1b)

Co-authored-by: Kaituo Li <kaituo@amazon.com>
  • Loading branch information
opensearch-trigger-bot[bot] and kaituo authored Jun 10, 2024
1 parent 14ebd03 commit 87448de
Show file tree
Hide file tree
Showing 11 changed files with 449 additions and 26 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 @@ -89,7 +89,7 @@ function CustomResultIndex(props: CustomResultIndexProps) {
<EuiFlexItem>
<EuiCallOut
data-test-subj="cannotEditResultIndexCallout"
title="You can't change the custom result index after creating the detector. You can manage the result index using the following three settings inside Anomaly Detection plugin or with the Index Management plugin."
title="You can't change the custom result index after creating the detector. You can manage the result index using the following three settings."
color="warning"
iconType="alert"
size="s"
Expand Down
28 changes: 17 additions & 11 deletions public/pages/DetectorDetail/containers/DetectorDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
getDetector,
stopHistoricalDetector,
} from '../../../redux/reducers/ad';
import { getIndices } from '../../../redux/reducers/opensearch';
import { getAliases, getIndices } from '../../../redux/reducers/opensearch';
import { getErrorMessage, Listener } from '../../../utils/utils';
import { darkModeEnabled } from '../../../utils/opensearchDashboardsUtils';
import { BREADCRUMBS, MDS_BREADCRUMBS } from '../../../utils/constants';
Expand All @@ -62,8 +62,8 @@ import {
prettifyErrorMessage,
} from '../../../../server/utils/helpers';
import { DETECTOR_STATE } from '../../../../server/utils/constants';
import { CatIndex } from '../../../../server/models/types';
import { containsIndex } from '../utils/helpers';
import { CatIndex, IndexAlias } from '../../../../server/models/types';
import { containsIndex, containsAlias } from '../utils/helpers';
import { DataSourceViewConfig } from '../../../../../../src/plugins/data_source_management/public';
import {
getDataSourceManagementPlugin,
Expand Down Expand Up @@ -136,29 +136,34 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
const isCatIndicesRequesting = useSelector(
(state: AppState) => state.opensearch.requesting
) as boolean;
const visibleAliases = useSelector(
(state: AppState) => state.opensearch.aliases
) as IndexAlias[];

/*
Determine if the result index is missing based on several conditions:
- If the detector is still loading, the result index is not missing.
- If the result index retrieved from the detector is empty, it is not missing.
- If cat indices are being requested, the result index is not missing.
- If visible indices are empty, it is likely there is an issue retrieving existing indices.
- If visible indices/aliaes are empty, it is likely there is an issue retrieving existing indices.
To be safe, we'd rather not show the error message and consider the result index not missing.
- If the result index is not found in the visible indices, then it is missing.
*/
const resultIndexOrAlias = get(detector, 'resultIndex', '')
const isResultIndexMissing = isLoadingDetector
? false
: isEmpty(get(detector, 'resultIndex', ''))
? false
: isCatIndicesRequesting
? false
: isEmpty(visibleIndices)
: isEmpty(visibleIndices) || isEmpty(visibleAliases)
? false
: !containsIndex(get(detector, 'resultIndex', ''), visibleIndices);
: !containsIndex(resultIndexOrAlias, visibleIndices) && !containsAlias(resultIndexOrAlias, visibleAliases);

// debug message: prints visibleIndices if isResultIndexMissing is true
if (isResultIndexMissing) {
console.log(`isResultIndexMissing is true, visibleIndices: ${visibleIndices}, detector result index: ${get(detector, 'resultIndex', '')}`);
// The JSON.stringify method converts a JavaScript object or value to a JSON string. The optional null parameter is for the replacer function (not used here), and 2 specifies the indentation level for pretty-printing the JSON.
console.log(`isResultIndexMissing is true, visibleIndices: ${JSON.stringify(visibleIndices, null, 2)}, visibleAliases: ${JSON.stringify(visibleAliases, null, 2)}, detector result index: ${resultIndexOrAlias}`);
}

// String to set in the modal if the realtime detector and/or historical analysis
Expand Down Expand Up @@ -197,20 +202,21 @@ export const DetectorDetail = (props: DetectorDetailProps) => {
scroll(0, 0);
}, []);

// Getting all visible indices. Will re-fetch if changes to the detector (e.g.,
// Getting all visible indices & aliases. Will re-fetch if changes to the detector (e.g.,
// detector starts, result index recreated or user switches tabs to re-fetch detector)
useEffect(() => {
const getInitialIndices = async () => {
const getInitialIndicesAliases = async () => {
try {
await dispatch(getIndices('', dataSourceId));
await dispatch(getAliases('', dataSourceId));
} catch (error) {
console.error(error);
core.notifications.toasts.addDanger('Error getting all indices');
core.notifications.toasts.addDanger('Error getting all indices or aliases');
}
};
// only need to check if indices exist after detector finishes loading
if (!isLoadingDetector) {
getInitialIndices();
getInitialIndicesAliases();
}
}, [detector]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ describe('detector detail', () => {
expect(element).toBeNull();
});

test('the result index is not found in the visible indices', () => {
test('the result index is not found in the visible indices but alias is empty', () => {
const detectorInfo = {
detector: getRandomDetector(true, resultIndex),
hasError: false,
Expand All @@ -236,7 +236,7 @@ describe('detector detail', () => {
const element = screen.queryByTestId('missingResultIndexCallOut');

// Assert that the element is in the document
expect(element).not.toBeNull();
expect(element).toBeNull();
});

test('the result index is found in the visible indices', () => {
Expand Down Expand Up @@ -272,4 +272,81 @@ 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 aliaes', () => {
const detector = getRandomDetector(true, resultIndex);
const resultIndexFull = resultIndex + '-history-2024.06.05-1';

// 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: resultIndexFull},
],
aliases : [
{index: '.opendistro-anomaly-results-history-2024.06.08-1', alias: '.opendistro-anomaly-results'},
{index: resultIndexFull, alias: resultIndex},
{index: '.kibana_1', alias: '.kibana'},
],
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();
});

test('the result index prefix is not found in both visible aliaes and 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' },
],
aliases : [
{index: '.opendistro-anomaly-results-history-2024.06.08-1', alias: '.opendistro-anomaly-results'},
{index: '.kibana_1', alias: '.kibana'},
],
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).not.toBeNull();
});
});
25 changes: 24 additions & 1 deletion public/pages/DetectorDetail/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { DETECTOR_STATE } from '../../../../server/utils/constants';
import { Detector } from '../../../models/interfaces';
import { EuiHealth } from '@elastic/eui';
import moment from 'moment';
import { CatIndex } from '../../../../server/models/types';
import { CatIndex, IndexAlias } from '../../../../server/models/types';

export const getInitFailureMessageAndActionItem = (error: string): object => {
const failureDetails = Object.values(DETECTOR_INIT_FAILURES);
Expand Down Expand Up @@ -142,6 +142,17 @@ export const getDetectorStateDetails = (
);
};

/**
* Checks if any of the given indices contain the specified index.
*
* This function iterates through an array of `CatIndex` objects and checks if the `index` property of any
* `CatIndex` object equals to the specified `index` string. It returns `true` if such an `index` is found,
* otherwise it returns `false`.
*
* @param index - The 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 equals to the specified prefix.
*/
export const containsIndex = (index: string, indices: CatIndex[]) => {
let containsIndex = false;
if (!isEmpty(indices)) {
Expand All @@ -153,3 +164,15 @@ export const containsIndex = (index: string, indices: CatIndex[]) => {
}
return containsIndex;
};

export const containsAlias = (alias: string, aliases: IndexAlias[]) => {
let containsAlias = false;
if (!isEmpty(aliases)) {
aliases.forEach((catAlias: IndexAlias) => {
if (get(catAlias, 'alias', '') == alias) {
containsAlias = true;
}
});
}
return containsAlias;
};
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export const DetectorDefinitionFields = (
}
};

const minAge = get(props, 'detector.resultIndexMinAge', '-');
const minSize = get(props, 'detector.resultIndexMinSize', '-');
const ttl = get(props, 'detector.resultIndexTtl', '-');

return (
<ContentPanel
title="Detector settings"
Expand Down Expand Up @@ -220,19 +224,19 @@ export const DetectorDefinitionFields = (
<EuiFlexItem>
<ConfigCell
title="Custom result index min age"
description={get(props, 'detector.resultIndexMinAge', '-') + ' Days'}
description={minAge === '-' ? minAge : `${minAge} Days`}
/>
</EuiFlexItem>
<EuiFlexItem>
<ConfigCell
title="Custom result index min size"
description={get(props, 'detector.resultIndexMinSize', '-') + ' MB'}
description={minSize == '-' ? minSize : `${minSize} MB`}
/>
</EuiFlexItem>
<EuiFlexItem>
<ConfigCell
title="Custom result index TTL"
description={get(props, 'detector.resultIndexTtl', '-') + ' Days'}
description={ttl == '-' ? ttl : `${ttl} Days`}
/>
</EuiFlexItem>
</EuiFlexGrid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ exports[`<ReviewAndCreate /> spec renders the component, validation loading 1`]
<p
class="enabled"
>
- Days
-
</p>
</div>
</div>
Expand Down Expand Up @@ -458,7 +458,7 @@ exports[`<ReviewAndCreate /> spec renders the component, validation loading 1`]
<p
class="enabled"
>
- MB
-
</p>
</div>
</div>
Expand Down Expand Up @@ -492,7 +492,7 @@ exports[`<ReviewAndCreate /> spec renders the component, validation loading 1`]
<p
class="enabled"
>
- Days
-
</p>
</div>
</div>
Expand Down Expand Up @@ -1518,7 +1518,7 @@ exports[`issue in detector validation issues in feature query 1`] = `
<p
class="enabled"
>
- Days
-
</p>
</div>
</div>
Expand Down Expand Up @@ -1552,7 +1552,7 @@ exports[`issue in detector validation issues in feature query 1`] = `
<p
class="enabled"
>
- MB
-
</p>
</div>
</div>
Expand Down Expand Up @@ -1586,7 +1586,7 @@ exports[`issue in detector validation issues in feature query 1`] = `
<p
class="enabled"
>
- Days
-
</p>
</div>
</div>
Expand Down
Loading

0 comments on commit 87448de

Please sign in to comment.