-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
chore(spans-migration): add post and put validation for EAP extrapolation mode #102970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
2d0a52e
f14a7d7
0842aae
b037625
6ae1833
ca57cb3
4fcd9d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -49,6 +49,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||
| from sentry.models.rulesnooze import RuleSnooze | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.sentry_apps.services.app import app_service | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.sentry_apps.utils.errors import SentryAppBaseError | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.snuba.dataset import Dataset | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.snuba.models import ExtrapolationMode | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.users.services.user.service import user_service | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.workflow_engine.migration_helpers.alert_rule import dual_delete_migrated_alert_rule | ||||||||||||||||||||||||||||||||||||||||||||||
| from sentry.workflow_engine.models import Detector | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -102,6 +104,25 @@ def fetch_alert_rule( | |||||||||||||||||||||||||||||||||||||||||||||
| return Response(serialized_rule) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def _is_invalid_extrapolation_mode(old_extrapolation_mode, new_extrapolation_mode) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||
| new_extrapolation_mode == ExtrapolationMode.SERVER_WEIGHTED.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| and old_extrapolation_mode != ExtrapolationMode.SERVER_WEIGHTED.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||
| new_extrapolation_mode == ExtrapolationMode.NONE.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| and old_extrapolation_mode != ExtrapolationMode.NONE.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||
| new_extrapolation_mode == ExtrapolationMode.CLIENT_AND_SERVER_WEIGHTED.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| or new_extrapolation_mode == ExtrapolationMode.UNKNOWN.name.lower() | ||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| if ( | |
| new_extrapolation_mode == ExtrapolationMode.SERVER_WEIGHTED.name.lower() | |
| and old_extrapolation_mode != ExtrapolationMode.SERVER_WEIGHTED.name.lower() | |
| ): | |
| return True | |
| if ( | |
| new_extrapolation_mode == ExtrapolationMode.NONE.name.lower() | |
| and old_extrapolation_mode != ExtrapolationMode.NONE.name.lower() | |
| ): | |
| return True | |
| if ( | |
| new_extrapolation_mode == ExtrapolationMode.CLIENT_AND_SERVER_WEIGHTED.name.lower() | |
| or new_extrapolation_mode == ExtrapolationMode.UNKNOWN.name.lower() | |
| ): | |
| return False | |
| return False | |
| if ( | |
| new_extrapolation_mode == ExtrapolationMode.SERVER_WEIGHTED.name.lower() | |
| and old_extrapolation_mode != ExtrapolationMode.SERVER_WEIGHTED.name.lower() | |
| ): | |
| return True | |
| return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Extrapolation Mode Validation Bypass
The validation checks data.get("dataset") from the request instead of checking the actual alert rule's dataset (alert_rule.snuba_query.dataset). This allows bypassing the extrapolation mode validation during partial updates by omitting the dataset field, enabling users to set server_weighted on EAP alerts.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,7 @@ | |
| from sentry.sentry_apps.services.app import app_service | ||
| from sentry.sentry_apps.utils.errors import SentryAppBaseError | ||
| from sentry.snuba.dataset import Dataset | ||
| from sentry.snuba.models import ExtrapolationMode | ||
| from sentry.uptime.types import ( | ||
| DATA_SOURCE_UPTIME_SUBSCRIPTION, | ||
| GROUP_TYPE_UPTIME_DOMAIN_CHECK_FAILURE, | ||
|
|
@@ -100,6 +101,9 @@ def create_metric_alert( | |
| "Creation of transaction-based alerts is disabled, as we migrate to the span dataset. Create span-based alerts (dataset: events_analytics_platform) with the is_transaction:true filter instead." | ||
| ) | ||
|
|
||
| if data.get("extrapolation_mode") == ExtrapolationMode.SERVER_WEIGHTED.name.lower(): | ||
| raise ValidationError("server_weighted extrapolation mode is not supported for new alerts.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Extrapolation Validation: Type Mismatch BugThe validation compares |
||
|
|
||
| if project: | ||
| data["projects"] = [project.slug] | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,7 +11,12 @@ | |||||
| from sentry.seer.anomaly_detection.store_data_workflow_engine import send_new_detector_data | ||||||
| from sentry.snuba.dataset import Dataset | ||||||
| from sentry.snuba.metrics.extraction import should_use_on_demand_metrics | ||||||
| from sentry.snuba.models import QuerySubscription, SnubaQuery, SnubaQueryEventType | ||||||
| from sentry.snuba.models import ( | ||||||
| ExtrapolationMode, | ||||||
| QuerySubscription, | ||||||
| SnubaQuery, | ||||||
| SnubaQueryEventType, | ||||||
| ) | ||||||
| from sentry.snuba.snuba_query_validator import SnubaQueryValidator | ||||||
| from sentry.snuba.subscriptions import update_snuba_query | ||||||
| from sentry.tasks.relay import schedule_invalidate_project_config | ||||||
|
|
@@ -162,6 +167,12 @@ def _validate_transaction_dataset_deprecation(self, dataset: Dataset) -> None: | |||||
| "Creation of transaction-based alerts is disabled, as we migrate to the span dataset. Create span-based alerts (dataset: events_analytics_platform) with the is_transaction:true filter instead." | ||||||
| ) | ||||||
|
|
||||||
| def _validate_extrapolation_mode(self, extrapolation_mode: str) -> None: | ||||||
| if extrapolation_mode == ExtrapolationMode.SERVER_WEIGHTED.value: | ||||||
| raise serializers.ValidationError( | ||||||
| "server_weighted extrapolation mode is not supported for new alerts." | ||||||
| ) | ||||||
|
|
||||||
| def get_quota(self) -> DetectorQuota: | ||||||
| organization = self.context.get("organization") | ||||||
| request = self.context.get("request") | ||||||
|
|
@@ -201,6 +212,31 @@ def is_editing_transaction_dataset( | |||||
| return True | ||||||
| return False | ||||||
|
|
||||||
| def is_invalid_extrapolation_mode( | ||||||
| self, snuba_query: SnubaQuery, data_source: SnubaQueryDataSourceType | ||||||
| ) -> bool: | ||||||
| if data_source.get("dataset") == Dataset.EventsAnalyticsPlatform: | ||||||
| new_extrapolation_mode = data_source.get( | ||||||
| "extrapolation_mode", snuba_query.extrapolation_mode | ||||||
| ) | ||||||
| old_extrapolation_mode = snuba_query.extrapolation_mode | ||||||
| if ( | ||||||
| new_extrapolation_mode == ExtrapolationMode.SERVER_WEIGHTED.value | ||||||
| and old_extrapolation_mode != ExtrapolationMode.SERVER_WEIGHTED.value | ||||||
| ): | ||||||
| return True | ||||||
| if ( | ||||||
| new_extrapolation_mode == ExtrapolationMode.NONE.value | ||||||
| and old_extrapolation_mode != ExtrapolationMode.NONE.value | ||||||
| ): | ||||||
| return True | ||||||
| if ( | ||||||
| new_extrapolation_mode == ExtrapolationMode.CLIENT_AND_SERVER_WEIGHTED.value | ||||||
| or new_extrapolation_mode == ExtrapolationMode.UNKNOWN.value | ||||||
| ): | ||||||
| return False | ||||||
| return False | ||||||
|
|
||||||
| def update_data_source(self, instance: Detector, data_source: SnubaQueryDataSourceType): | ||||||
| try: | ||||||
| source_instance = DataSource.objects.get(detector=instance) | ||||||
|
|
@@ -224,6 +260,9 @@ def update_data_source(self, instance: Detector, data_source: SnubaQueryDataSour | |||||
| "Updates to transaction-based alerts is disabled, as we migrate to the span dataset. Create span-based alerts (dataset: events_analytics_platform) with the is_transaction:true filter instead." | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Type mismatch in 🔍 Detailed AnalysisA type mismatch occurs in the validation logic for 💡 Suggested FixEnsure 🤖 Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||||||
| ) | ||||||
|
|
||||||
| if self.is_invalid_extrapolation_mode(snuba_query, data_source): | ||||||
| raise serializers.ValidationError("Invalid extrapolation mode for this alert type.") | ||||||
|
||||||
| raise serializers.ValidationError("Invalid extrapolation mode for this alert type.") | |
| raise serializers.ValidationError("Invalid extrapolation mode for this detector type.") |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |||
| ) | ||||
| from sentry.snuba.dataset import Dataset | ||||
| from sentry.snuba.models import ( | ||||
| ExtrapolationMode, | ||||
| QuerySubscription, | ||||
| QuerySubscriptionDataSourceHandler, | ||||
| SnubaQuery, | ||||
|
|
@@ -617,3 +618,74 @@ def test_transaction_dataset_deprecation_generic_metrics_update(self) -> None: | |||
| with_feature("organizations:discover-saved-queries-deprecation"), | ||||
| ): | ||||
| update_validator.save() | ||||
|
|
||||
| def test_invalid_extrapolation_mode_create(self) -> None: | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should a valid extrapolation mode be added to any of the existing tests? This isn't live yet but I have an open PR with a simple valid update test:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have a default in place if there is no explicit extrapolation mode passed so it's not necessary to be passed in 😌 |
||||
| data = { | ||||
| **self.valid_data, | ||||
| "dataSources": [ | ||||
| { | ||||
| "queryType": SnubaQuery.Type.PERFORMANCE.value, | ||||
| "dataset": Dataset.EventsAnalyticsPlatform.value, | ||||
| "query": "test query", | ||||
| "aggregate": "count()", | ||||
| "timeWindow": 3600, | ||||
| "environment": self.environment.name, | ||||
| "eventTypes": [SnubaQueryEventType.EventType.TRACE_ITEM_SPAN.name.lower()], | ||||
| "extrapolation_mode": ExtrapolationMode.SERVER_WEIGHTED.value, | ||||
| }, | ||||
| ], | ||||
| } | ||||
|
|
||||
| validator = MetricIssueDetectorValidator(data=data, context=self.context) | ||||
| assert validator.is_valid(), validator.errors | ||||
| with self.assertRaisesMessage( | ||||
| ValidationError, | ||||
| expected_message="server_weighted extrapolation mode is not supported for new alerts.", | ||||
| ): | ||||
| validator.save() | ||||
|
|
||||
| def test_invalid_extrapolation_mode_update(self) -> None: | ||||
| data = { | ||||
| **self.valid_data, | ||||
| "dataSources": [ | ||||
| { | ||||
| "queryType": SnubaQuery.Type.PERFORMANCE.value, | ||||
| "dataset": Dataset.EventsAnalyticsPlatform.value, | ||||
| "query": "test query", | ||||
| "aggregate": "count()", | ||||
| "timeWindow": 3600, | ||||
| "environment": self.environment.name, | ||||
| "eventTypes": [SnubaQueryEventType.EventType.TRACE_ITEM_SPAN.name.lower()], | ||||
| "extrapolation_mode": ExtrapolationMode.CLIENT_AND_SERVER_WEIGHTED.value, | ||||
| }, | ||||
| ], | ||||
| } | ||||
|
|
||||
| validator = MetricIssueDetectorValidator(data=data, context=self.context) | ||||
| assert validator.is_valid(), validator.errors | ||||
|
|
||||
| detector = validator.save() | ||||
|
|
||||
| update_data = { | ||||
| "dataSources": [ | ||||
| { | ||||
| "queryType": SnubaQuery.Type.PERFORMANCE.value, | ||||
| "dataset": Dataset.EventsAnalyticsPlatform.value, | ||||
| "query": "updated query", | ||||
| "aggregate": "count()", | ||||
| "timeWindow": 3600, | ||||
| "environment": self.environment.name, | ||||
| "eventTypes": [SnubaQueryEventType.EventType.TRACE_ITEM_SPAN.name.lower()], | ||||
| "extrapolation_mode": ExtrapolationMode.SERVER_WEIGHTED.value, | ||||
| } | ||||
| ], | ||||
| } | ||||
| update_validator = MetricIssueDetectorValidator( | ||||
| instance=detector, data=update_data, context=self.context, partial=True | ||||
| ) | ||||
| assert update_validator.is_valid(), update_validator.errors | ||||
| with self.assertRaisesMessage( | ||||
| ValidationError, | ||||
| expected_message="Invalid extrapolation mode for this alert type.", | ||||
| ): | ||||
| update_validator.save() | ||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you could combine this and the one in
metric_issue_detector.pysince they're doing the same thingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooop yes good point