From 4f1d2534d868f404ccf6ced4d6e7d4cf89ded3ba Mon Sep 17 00:00:00 2001 From: Tal Date: Thu, 26 Sep 2024 17:17:38 +0300 Subject: [PATCH] fix(mapping): exclude enrichment attributes when using complex matchers (`&&`) (#2012) --- .../app/mapping/create-or-edit-mapping.tsx | 11 ++- keep/api/bl/enrichments_bl.py | 15 ++- keep/api/routes/mapping.py | 7 +- tests/test_enrichments.py | 93 +++++++++++++++++++ 4 files changed, 117 insertions(+), 9 deletions(-) diff --git a/keep-ui/app/mapping/create-or-edit-mapping.tsx b/keep-ui/app/mapping/create-or-edit-mapping.tsx index 870a81a53..8109c8061 100644 --- a/keep-ui/app/mapping/create-or-edit-mapping.tsx +++ b/keep-ui/app/mapping/create-or-edit-mapping.tsx @@ -329,9 +329,14 @@ export default function CreateOrEditMapping({ editRule, editCallback }: Props) { ... ) : ( attributes - .filter( - (attribute) => !selectedLookupAttributes.includes(attribute) - ) + .filter((attribute) => { + return !selectedLookupAttributes.some((lookupAttr) => { + const parts = lookupAttr + .split("&&") + .map((part) => part.trim()); + return parts.includes(attribute); + }); + }) .map((attribute) => ( {attribute} diff --git a/keep/api/bl/enrichments_bl.py b/keep/api/bl/enrichments_bl.py index d8f18691c..27311982b 100644 --- a/keep/api/bl/enrichments_bl.py +++ b/keep/api/bl/enrichments_bl.py @@ -294,11 +294,16 @@ def _check_alert_matches_rule(self, alert: AlertDto, rule: MappingRule) -> bool: for matcher in rule.matchers ): # Extract enrichments from the matched row - enrichments = { - key: value - for key, value in row.items() - if key not in rule.matchers and value is not None - } + enrichments = {} + for key, value in row.items(): + if value is not None: + is_matcher = False + for matcher in rule.matchers: + if key in matcher.replace(" ", "").split("&&"): + is_matcher = True + break + if not is_matcher: + enrichments[key] = value break if enrichments: diff --git a/keep/api/routes/mapping.py b/keep/api/routes/mapping.py index e5cfbc4a9..bb4788af3 100644 --- a/keep/api/routes/mapping.py +++ b/keep/api/routes/mapping.py @@ -38,7 +38,12 @@ def get_rules( attributes = [] if rule_dto.type == "csv": attributes = [ - key for key in rule.rows[0].keys() if key not in rule.matchers + key + for key in rule.rows[0].keys() + if not any( + key in matcher.replace(" ", "").split("&&") + for matcher in rule.matchers + ) ] elif rule_dto.type == "topology": attributes = [ diff --git a/tests/test_enrichments.py b/tests/test_enrichments.py index e75c99a24..b06ae8692 100644 --- a/tests/test_enrichments.py +++ b/tests/test_enrichments.py @@ -617,3 +617,96 @@ def test_topology_mapping_rule_enrichment(mock_session, mock_alert_dto): force=False, audit_enabled=True, ) + + +def test_run_mapping_rules_with_complex_matchers(mock_session, mock_alert_dto): + # Setup a mapping rule with complex matchers + rule = MappingRule( + id=1, + tenant_id="test_tenant", + priority=1, + matchers=["name && severity", "source"], + rows=[ + { + "name": "Test Alert", + "severity": "high", + "service": "high_priority_service", + }, + { + "name": "Test Alert", + "severity": "low", + "service": "low_priority_service", + }, + {"source": "test_source", "service": "source_specific_service"}, + ], + disabled=False, + type="csv", + ) + mock_session.query.return_value.filter.return_value.filter.return_value.order_by.return_value.all.return_value = [ + rule + ] + + enrichment_bl = EnrichmentsBl(tenant_id="test_tenant", db=mock_session) + + # Test case 1: Matches "name && severity" + mock_alert_dto.name = "Test Alert" + mock_alert_dto.severity = "high" + enrichment_bl.run_mapping_rules(mock_alert_dto) + assert mock_alert_dto.service == "high_priority_service" + + # Test case 2: Matches "name && severity" (different severity) + mock_alert_dto.severity = "low" + del mock_alert_dto.service + enrichment_bl.run_mapping_rules(mock_alert_dto) + assert mock_alert_dto.service == "low_priority_service" + + # Test case 3: Matches "source" + mock_alert_dto.name = "Different Alert" + mock_alert_dto.severity = "medium" + mock_alert_dto.source = ["test_source"] + del mock_alert_dto.service + enrichment_bl.run_mapping_rules(mock_alert_dto) + assert mock_alert_dto.service == "source_specific_service" + + # Test case 4: No match + mock_alert_dto.name = "Unmatched Alert" + mock_alert_dto.severity = "medium" + mock_alert_dto.source = ["different_source"] + del mock_alert_dto.service + enrichment_bl.run_mapping_rules(mock_alert_dto) + assert not hasattr(mock_alert_dto, "service") + + +def test_run_mapping_rules_enrichments_filtering(mock_session, mock_alert_dto): + # Setup a mapping rule with complex matchers and multiple enrichment fields + rule = MappingRule( + id=1, + tenant_id="test_tenant", + priority=1, + matchers=["name && severity"], + rows=[ + { + "name": "Test Alert", + "severity": "high", + "service": "high_priority_service", + "team": "on-call", + "priority": "P1", + }, + ], + disabled=False, + type="csv", + ) + mock_session.query.return_value.filter.return_value.filter.return_value.order_by.return_value.all.return_value = [ + rule + ] + + enrichment_bl = EnrichmentsBl(tenant_id="test_tenant", db=mock_session) + + # Test case: Matches "name && severity" and applies multiple enrichments + mock_alert_dto.name = "Test Alert" + mock_alert_dto.severity = "high" + enrichment_bl.run_mapping_rules(mock_alert_dto) + + assert mock_alert_dto.service == "high_priority_service" + assert mock_alert_dto.team == "on-call" + assert mock_alert_dto.priority == "P1"