Skip to content

Commit fec7b41

Browse files
committed
fix: Anomaly detection features count locked at 5
Signed-off-by: Du Tran <[email protected]>
1 parent eeaedc3 commit fec7b41

File tree

6 files changed

+130
-5
lines changed

6 files changed

+130
-5
lines changed

public/pages/ConfigureModel/components/Features/Features.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,34 @@ import { FieldArray, FieldArrayRenderProps, FormikProps } from 'formik';
2323

2424
import { get } from 'lodash';
2525
import React, { Fragment, useEffect } from 'react';
26+
import { useDispatch, useSelector } from 'react-redux';
2627
import ContentPanel from '../../../../components/ContentPanel/ContentPanel';
2728
import { Detector } from '../../../../models/interfaces';
28-
import { initialFeatureValue } from '../../utils/helpers';
29-
import { MAX_FEATURE_NUM, BASE_DOCS_LINK } from '../../../../utils/constants';
29+
import { initialFeatureValue, getMaxFeatureLimit } from '../../utils/helpers';
30+
import { BASE_DOCS_LINK } from '../../../../utils/constants';
3031
import { FeatureAccordion } from '../FeatureAccordion';
32+
import { getClustersSetting } from '../../../../redux/reducers/opensearch';
33+
import { AppState } from '../../../../redux/reducers';
3134

3235
interface FeaturesProps {
3336
detector: Detector | undefined;
3437
formikProps: FormikProps<any>;
3538
}
3639

3740
export function Features(props: FeaturesProps) {
41+
const dispatch = useDispatch();
42+
const anomalySettings = useSelector(
43+
(state: AppState) => state.opensearch.settings
44+
);
45+
46+
useEffect(() => {
47+
const getSettingResult = async () => {
48+
await dispatch(getClustersSetting());
49+
};
50+
getSettingResult();
51+
}, []);
52+
53+
3854
// If the features list is empty: push a default initial one
3955
useEffect(() => {
4056
if (get(props, 'formikProps.values.featureList', []).length === 0) {
@@ -88,7 +104,7 @@ export function Features(props: FeaturesProps) {
88104
<EuiFlexItem grow={false}>
89105
<EuiSmallButton
90106
data-test-subj="addFeature"
91-
isDisabled={values.featureList.length >= MAX_FEATURE_NUM}
107+
isDisabled={values.featureList.length >= getMaxFeatureLimit(anomalySettings)}
92108
onClick={() => {
93109
push(initialFeatureValue());
94110
}}
@@ -99,7 +115,7 @@ export function Features(props: FeaturesProps) {
99115
<p>
100116
You can add up to{' '}
101117
{Math.max(
102-
MAX_FEATURE_NUM - values.featureList.length,
118+
getMaxFeatureLimit(anomalySettings) - values.featureList.length,
103119
0
104120
)}{' '}
105121
more features.

public/pages/ConfigureModel/utils/helpers.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* GitHub history for details.
1010
*/
1111

12-
import { DATA_TYPES, DEFAULT_SHINGLE_SIZE } from '../../../utils/constants';
12+
import { DATA_TYPES, DEFAULT_SHINGLE_SIZE, MAX_FEATURE_NUM } from '../../../utils/constants';
1313
import {
1414
FEATURE_TYPE,
1515
FeatureAttributes,
@@ -40,6 +40,7 @@ import {
4040
Action,
4141
} from '../../../models/types';
4242
import { SparseDataOptionValue } from './constants';
43+
import { ClusterSetting } from 'server/models/types';
4344

4445
export const getFieldOptions = (
4546
allFields: { [key: string]: string[] },
@@ -711,3 +712,15 @@ export const rulesToFormik = (
711712
);
712713
return finalRules;
713714
};
715+
716+
export function getMaxFeatureLimit(
717+
settings: ClusterSetting[]
718+
) {
719+
if (!settings || settings.length === 0) {
720+
return MAX_FEATURE_NUM;
721+
}
722+
const maxFeatureSetting = settings.find(
723+
(setting) => setting.name === 'max_anomaly_features'
724+
);
725+
return maxFeatureSetting ? parseInt(maxFeatureSetting.value, 10) : MAX_FEATURE_NUM;
726+
}

public/redux/reducers/opensearch.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getPathsPerDataType } from './mapper';
2121
import {
2222
CatIndex,
2323
ClusterInfo,
24+
ClusterSetting,
2425
IndexAlias,
2526
} from '../../../server/models/types';
2627
import { AD_NODE_API } from '../../../utils/constants';
@@ -35,6 +36,7 @@ const BULK = 'opensearch/BULK';
3536
const DELETE_INDEX = 'opensearch/DELETE_INDEX';
3637
const GET_CLUSTERS_INFO = 'opensearch/GET_CLUSTERS_INFO';
3738
const GET_INDICES_AND_ALIASES = 'opensearch/GET_INDICES_AND_ALIASES';
39+
const GET_CLUSTERS_SETTING = 'opensearch/GET_CLUSTERS_SETTING';
3840

3941
export type Mappings = {
4042
[key: string]: any;
@@ -69,6 +71,7 @@ interface OpenSearchState {
6971
searchResult: object;
7072
errorMessage: string;
7173
clusters: ClusterInfo[];
74+
settings: ClusterSetting[];
7275
}
7376

7477
export const initialState: OpenSearchState = {
@@ -299,6 +302,31 @@ const reducer = handleActions<OpenSearchState>(
299302
errorMessage: get(action, 'error.error', action.error),
300303
}),
301304
},
305+
[GET_CLUSTERS_SETTING]: {
306+
REQUEST: (state: OpenSearchState): OpenSearchState => {
307+
return { ...state, requesting: true, errorMessage: '' };
308+
},
309+
SUCCESS: (
310+
state: OpenSearchState,
311+
action: APIResponseAction
312+
): OpenSearchState => {
313+
return {
314+
...state,
315+
requesting: false,
316+
settings: get(action, 'result.response.settings', []),
317+
};
318+
},
319+
FAILURE: (
320+
state: OpenSearchState,
321+
action: APIErrorAction
322+
): OpenSearchState => {
323+
return {
324+
...state,
325+
requesting: false,
326+
errorMessage: get(action, 'error.error', action.error),
327+
}
328+
},
329+
},
302330
},
303331
initialState
304332
);
@@ -326,6 +354,14 @@ export const getClustersInfo = (dataSourceId: string = ''): APIAction => {
326354
};
327355
};
328356

357+
export const getClustersSetting = (): APIAction => {
358+
const baseUrl = `..${AD_NODE_API.GET_CLUSTERS_SETTING}`;
359+
return {
360+
type: GET_CLUSTERS_SETTING,
361+
request: (client: HttpSetup) => client.get(baseUrl),
362+
};
363+
};
364+
329365
export const getIndicesAndAliases = (
330366
searchKey = '',
331367
dataSourceId: string = '',

server/models/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export type ClusterInfo = {
2222
localCluster: boolean;
2323
}
2424

25+
export type ClusterSetting = {
26+
name: string;
27+
value: string;
28+
}
29+
2530
export type IndexAlias = {
2631
index: string[] | string;
2732
alias: string;

server/routes/opensearch.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { SearchResponse } from '../models/interfaces';
1414
import {
1515
CatIndex,
1616
ClusterInfo,
17+
ClusterSetting,
1718
GetAliasesResponse,
1819
GetIndicesResponse,
1920
GetMappingResponse,
@@ -76,6 +77,7 @@ export function registerOpenSearchRoutes(
7677
'/_indices_and_aliases/{dataSourceId}',
7778
opensearchService.getIndicesAndAliases
7879
);
80+
apiRouter.get('/_cluster/settings', opensearchService.getClustersSetting);
7981
}
8082

8183
export default class OpenSearchService {
@@ -607,4 +609,56 @@ export default class OpenSearchService {
607609
});
608610
}
609611
};
612+
613+
getClustersSetting = async (
614+
context: RequestHandlerContext,
615+
request: OpenSearchDashboardsRequest,
616+
opensearchDashboardsResponse: OpenSearchDashboardsResponseFactory
617+
): Promise<IOpenSearchDashboardsResponse<any>> => {
618+
const { dataSourceId = '' } = request.params as { dataSourceId?: string };
619+
try {
620+
const callWithRequest = getClientBasedOnDataSource(
621+
context,
622+
this.dataSourceEnabled,
623+
request,
624+
dataSourceId,
625+
this.client
626+
);
627+
628+
let anomalySettings: ClusterSetting[] = [];
629+
630+
try {
631+
const anomalySettingsResponse = await callWithRequest('transport.request', {
632+
method: 'GET',
633+
path: '/_cluster/settings',
634+
});
635+
636+
if (
637+
anomalySettingsResponse?.persistent?.plugins?.anomaly_detection
638+
) {
639+
const anomalyDetectionSettings = anomalySettingsResponse.persistent.plugins.anomaly_detection;
640+
anomalySettings = Object.keys(anomalyDetectionSettings).map((key) => ({
641+
name: key,
642+
value: anomalyDetectionSettings[key],
643+
}));
644+
} else {
645+
console.warn('Could not get anomaly detection setting');
646+
}
647+
} catch (err) {
648+
console.warn('Could not get anomaly detection setting', err);
649+
}
650+
651+
return opensearchDashboardsResponse.ok({
652+
body: { ok: true, response: { settings: anomalySettings } },
653+
});
654+
} catch (err) {
655+
console.log('Anomaly detector - Unable to get anomaly detection cluster setting', err);
656+
return opensearchDashboardsResponse.ok({
657+
body: {
658+
ok: false,
659+
error: getErrorMessage(err),
660+
},
661+
});
662+
}
663+
};
610664
}

utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const AD_NODE_API = Object.freeze({
2323
CREATE_SAMPLE_DATA: `${BASE_NODE_API_PATH}/create_sample_data`,
2424
GET_CLUSTERS_INFO: `${BASE_NODE_API_PATH}/_remote/info`,
2525
GET_INDICES_AND_ALIASES: `${BASE_NODE_API_PATH}/_indices_and_aliases`,
26+
GET_CLUSTERS_SETTING: `${BASE_NODE_API_PATH}/_cluster/settings`,
2627
});
2728
export const ALERTING_NODE_API = Object.freeze({
2829
_SEARCH: `${BASE_NODE_API_PATH}/monitors/_search`,

0 commit comments

Comments
 (0)