diff --git a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
index b77147d3..d62ccb04 100644
--- a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
+++ b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
@@ -24,7 +24,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
import './styles.scss';
-import { Field, FieldArray, FieldProps } from 'formik';
+import { Field, FieldProps } from 'formik';
import {
required,
isInvalid,
diff --git a/public/pages/ConfigureModel/components/SuppressionRules/SuppressionRules.tsx b/public/pages/ConfigureModel/components/SuppressionRules/SuppressionRules.tsx
index c9a4649e..848a9270 100644
--- a/public/pages/ConfigureModel/components/SuppressionRules/SuppressionRules.tsx
+++ b/public/pages/ConfigureModel/components/SuppressionRules/SuppressionRules.tsx
@@ -13,19 +13,12 @@ import {
EuiFlexItem,
EuiFlexGroup,
EuiText,
- EuiLink,
- EuiTitle,
EuiCompressedFieldNumber,
EuiSpacer,
EuiCompressedSelect,
EuiButtonIcon,
- EuiCompressedFieldText,
EuiToolTip,
- OuiTextColor,
- EuiTextColor,
EuiSelect,
- EuiCheckbox,
- EuiFieldNumber,
EuiButtonEmpty,
} from '@elastic/eui';
import { Field, FieldProps, FieldArray } from 'formik';
@@ -34,8 +27,6 @@ import ContentPanel from '../../../../components/ContentPanel/ContentPanel';
import { BASE_DOCS_LINK } from '../../../../utils/constants';
import {
isInvalid,
- getError,
- validatePositiveInteger,
validatePositiveDecimal,
} from '../../../../utils/utils';
import { FormattedFormRow } from '../../../../components/FormattedFormRow/FormattedFormRow';
@@ -72,8 +63,8 @@ export function SuppressionRules(props: SuppressionRulesProps) {
? 'relative threshold'
: fieldKey;
return typeof fieldError === 'string'
- ? `${friendlyFieldName} ${fieldError.toLowerCase()}`
- : String(fieldError || '');
+ ? `${friendlyFieldName} ${fieldError.toLowerCase()}`
+ : String(fieldError || '');
}
}
}
@@ -315,23 +306,69 @@ export function SuppressionRules(props: SuppressionRulesProps) {
- {({ field }: FieldProps) => (
-
-
-
- )}
+ {({ field }: FieldProps) => {
+ const currentRules =
+ form.values.suppressionRules?.[
+ props.featureIndex
+ ] || [];
+
+ // Check if there's a directionRule = true and get its "aboveBelow" value
+ const directionRule = currentRules.find(
+ (rule) => rule.directionRule === true
+ );
+
+ let options = [
+ {
+ value: 'above',
+ text: 'above the expected value',
+ disabled: false,
+ },
+ {
+ value: 'below',
+ text: 'below the expected value',
+ disabled: false,
+ },
+ ];
+
+ let tooltipContent =
+ 'Select above or below expected value';
+
+ // Modify options based on the directionRule logic
+ if (directionRule) {
+ options = options.map((option) => ({
+ ...option,
+ disabled:
+ directionRule.aboveBelow !==
+ option.value,
+ }));
+
+ if (
+ field.value !==
+ directionRule.aboveBelow
+ ) {
+ form.setFieldValue(
+ `suppressionRules.${props.featureIndex}[${index}].aboveBelow`,
+ directionRule.aboveBelow
+ );
+ }
+ if (directionRule?.aboveBelow) {
+ const directionText =
+ directionRule.aboveBelow === 'above'
+ ? 'exceeds'
+ : 'drops below';
+ tooltipContent = `Base criteria includes anomalies where the actual value ${directionText} the expected value. Rules can only be made in this direction.`;
+ }
+ }
+
+ return (
+
+
+
+ );
+ }}
diff --git a/public/pages/ConfigureModel/utils/helpers.ts b/public/pages/ConfigureModel/utils/helpers.ts
index 7551d5a7..965b7fa1 100644
--- a/public/pages/ConfigureModel/utils/helpers.ts
+++ b/public/pages/ConfigureModel/utils/helpers.ts
@@ -479,7 +479,6 @@ export const getSuppressionRulesArray = (detector: Detector): string[] => {
return `Ignore anomalies for feature "${featureName}" when actual value is below the expected value`;
}
-
let value = condition.value;
const isPercentage =
thresholdType === ThresholdType.ACTUAL_OVER_EXPECTED_RATIO ||
@@ -558,15 +557,14 @@ export const getSuppressionRulesArrayForFeature = (
export const formikToRules = (
formikValues?: RuleFormikValues[]
): Rule[] | undefined => {
-
if (!formikValues || formikValues.length === 0) {
return undefined; // Return undefined for undefined or empty input
}
- // Flatten the nested array of suppressionRule by feature and filter out null entries
- const flattenedSuppressionFormikValues = formikValues.flatMap((nestedArray) =>
- nestedArray || [] // If null, replace with an empty array
- );
+ // Flatten the nested array of suppressionRule by feature and filter out null entries
+ const flattenedSuppressionFormikValues = formikValues.flatMap(
+ (nestedArray) => nestedArray || [] // If null, replace with an empty array
+ );
return flattenedSuppressionFormikValues.map((formikValue) => {
const conditions: Condition[] = [];
@@ -592,12 +590,14 @@ export const formikToRules = (
}
};
-
-
if (formikValue.directionRule) {
conditions.push({
featureName: formikValue.featureName,
- thresholdType: getThresholdType(formikValue.aboveBelow, true, formikValue.directionRule),
+ thresholdType: getThresholdType(
+ formikValue.aboveBelow,
+ true,
+ formikValue.directionRule
+ ),
operator: undefined,
value: undefined,
});
@@ -647,28 +647,26 @@ export const formikToRules = (
});
};
-// Convert Rule[] to RuleFormikValues[][]
-export const rulesToFormik = (rules?: Rule[]): (RuleFormikValues[] | null)[] => {
+export const rulesToFormik = (
+ rules?: Rule[]
+): (RuleFormikValues[] | null)[] => {
if (!rules || rules.length === 0) {
- return []; // Return empty array for undefined or empty input
+ return [];
}
// Group rules by featureName
const groupedRules: { [featureName: string]: RuleFormikValues[] } = {};
rules.forEach((rule) => {
- // Start with default values for each rule
- const formikValue: RuleFormikValues = {
- featureName: '',
- absoluteThreshold: undefined,
- relativeThreshold: undefined,
- aboveBelow: 'above', // Default to 'above', adjust as needed
- };
-
- // Loop through conditions to populate formikValue
rule.conditions.forEach((condition) => {
- formikValue.featureName = condition.featureName;
+ // Create a new formikValue for each condition
+ const formikValue: RuleFormikValues = {
+ featureName: condition.featureName,
+ absoluteThreshold: undefined,
+ relativeThreshold: undefined,
+ aboveBelow: 'above', // Default to 'above', adjust as needed
+ };
- // Determine the value and type of threshold
+ // Populate formikValue based on threshold type
switch (condition.thresholdType) {
case ThresholdType.ACTUAL_OVER_EXPECTED_MARGIN:
formikValue.absoluteThreshold = condition.value;
@@ -679,13 +677,11 @@ export const rulesToFormik = (rules?: Rule[]): (RuleFormikValues[] | null)[] =>
formikValue.aboveBelow = 'below';
break;
case ThresholdType.ACTUAL_OVER_EXPECTED_RATIO:
- // *100 to convert to percentage
- formikValue.relativeThreshold = (condition.value ?? 1) * 100;
+ formikValue.relativeThreshold = (condition.value ?? 1) * 100; // Convert to percentage
formikValue.aboveBelow = 'above';
break;
case ThresholdType.EXPECTED_OVER_ACTUAL_RATIO:
- // *100 to convert to percentage
- formikValue.relativeThreshold = (condition.value ?? 1) * 100;
+ formikValue.relativeThreshold = (condition.value ?? 1) * 100; // Convert to percentage
formikValue.aboveBelow = 'below';
break;
case ThresholdType.ACTUAL_IS_BELOW_EXPECTED:
@@ -701,21 +697,17 @@ export const rulesToFormik = (rules?: Rule[]): (RuleFormikValues[] | null)[] =>
default:
break;
}
- });
- // Add the rule to the grouped object based on featureName
- if (!groupedRules[formikValue.featureName]) {
- groupedRules[formikValue.featureName] = [];
- }
- groupedRules[formikValue.featureName].push(formikValue);
+ if (!groupedRules[formikValue.featureName]) {
+ groupedRules[formikValue.featureName] = [];
+ }
+ groupedRules[formikValue.featureName].push(formikValue);
+ });
});
- // Convert grouped object into an array of arrays based on featureList index
- const featureList = Object.keys(groupedRules); // Ensure you have a reference to your feature list somewhere
-
+ const featureList = Object.keys(groupedRules);
const finalRules: (RuleFormikValues[] | null)[] = featureList.map(
(featureName) => groupedRules[featureName] || null
);
-
return finalRules;
};
diff --git a/public/pages/DetectorConfig/containers/Features.tsx b/public/pages/DetectorConfig/containers/Features.tsx
index 87847bd6..f4ad0089 100644
--- a/public/pages/DetectorConfig/containers/Features.tsx
+++ b/public/pages/DetectorConfig/containers/Features.tsx
@@ -25,8 +25,8 @@ import {
FEATURE_TYPE,
FeatureAttributes,
} from '../../../models/interfaces';
-import { get, isEmpty, sortBy } from 'lodash';
-import { PLUGIN_NAME, BASE_DOCS_LINK } from '../../../utils/constants';
+import { get, sortBy } from 'lodash';
+import { PLUGIN_NAME } from '../../../utils/constants';
import ContentPanel from '../../../components/ContentPanel/ContentPanel';
import { CodeModal } from '../components/CodeModal/CodeModal';
import { getTitleWithCount } from '../../../utils/utils';
@@ -39,7 +39,6 @@ import {
getSuppressionRulesArrayForFeature,
} from '../../ConfigureModel/utils/helpers';
import { SuppressionRulesModal } from '../../ReviewAndCreate/components/SuppressionRulesModal/SuppressionRulesModal';
-import { Rule } from '../../../models/types';
interface FeaturesProps {
detectorId: string;
diff --git a/public/pages/DetectorConfig/containers/__tests__/Features.test.tsx b/public/pages/DetectorConfig/containers/__tests__/Features.test.tsx
index 4b7a7f28..39b8c163 100644
--- a/public/pages/DetectorConfig/containers/__tests__/Features.test.tsx
+++ b/public/pages/DetectorConfig/containers/__tests__/Features.test.tsx
@@ -28,15 +28,11 @@ import {
} from '../../../../models/types';
import { getRandomDetector } from '../../../../redux/reducers/__tests__/utils';
import {
- Detector,
UiMetaData,
FILTER_TYPES,
- UIFilter,
FEATURE_TYPE,
UiFeature,
FeatureAttributes,
- OPERATORS_MAP,
- UNITS,
} from '../../../../models/interfaces';
import { featureQuery1, featureQuery2 } from './DetectorConfig.test';