From 0b749118c20653ba26270b3af84b673b02524d4d Mon Sep 17 00:00:00 2001 From: Ram Senthamarai Date: Thu, 12 Sep 2024 14:52:06 -0700 Subject: [PATCH] chore(dynamic alert thresholds) Delete alert data endpoint --- src/seer/anomaly_detection/accessors.py | 20 ++++++++++++++++++- .../anomaly_detection/anomaly_detection.py | 17 ++++++++++++++++ src/seer/anomaly_detection/models/external.py | 11 ++++++++++ src/seer/app.py | 18 +++++++++++++++++ src/seer/db.py | 2 +- 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/seer/anomaly_detection/accessors.py b/src/seer/anomaly_detection/accessors.py index 6256c53c7..52f3edda8 100644 --- a/src/seer/anomaly_detection/accessors.py +++ b/src/seer/anomaly_detection/accessors.py @@ -17,6 +17,7 @@ ) from seer.anomaly_detection.models.external import AnomalyDetectionConfig, TimeSeriesPoint from seer.db import DbDynamicAlert, DbDynamicAlertTimeSeries, Session +from seer.exceptions import ClientError logger = logging.getLogger(__name__) @@ -49,6 +50,10 @@ def save_timepoint( ): return NotImplemented + @abc.abstractmethod + def delete_alert_data(self, external_alert_id: int): + return NotImplemented + class DbAlertDataAccessor(AlertDataAccessor): @@ -179,7 +184,7 @@ def save_timepoint( .one_or_none() ) if existing is None: - raise Exception(f"Alert with id {external_alert_id} not found") + raise ClientError(f"Alert with id {external_alert_id} not found") new_record = DbDynamicAlertTimeSeries( dynamic_alert_id=existing.id, @@ -191,3 +196,16 @@ def save_timepoint( ) session.add(new_record) session.commit() + + @sentry_sdk.trace + def delete_alert_data(self, external_alert_id: int): + with Session() as session: + existing = ( + session.query(DbDynamicAlert) + .filter_by(external_alert_id=external_alert_id) + .one_or_none() + ) + if existing is None: + raise ClientError(f"Alert with id {external_alert_id} not found") + session.delete(existing) + session.commit() diff --git a/src/seer/anomaly_detection/anomaly_detection.py b/src/seer/anomaly_detection/anomaly_detection.py index 73d708c96..670c5dcc1 100644 --- a/src/seer/anomaly_detection/anomaly_detection.py +++ b/src/seer/anomaly_detection/anomaly_detection.py @@ -15,6 +15,8 @@ AlertInSeer, Anomaly, AnomalyDetectionConfig, + DeleteAlertDataRequest, + DeleteAlertDataResponse, DetectAnomaliesRequest, DetectAnomaliesResponse, StoreDataRequest, @@ -233,6 +235,7 @@ def detect_anomalies(self, request: DetectAnomaliesRequest) -> DetectAnomaliesRe sentry_sdk.set_tag("ad_mode", mode) if isinstance(request.context, AlertInSeer): + sentry_sdk.set_tag("alert_id", request.context.id) ts, anomalies = self._online_detect(request.context, request.config) elif isinstance(request.context, TimeSeriesWithHistory): ts, anomalies = self._combo_detect(request.context, request.config) @@ -287,3 +290,17 @@ def store_data( anomaly_algo_data={"window_size": anomalies.window_size}, ) return StoreDataResponse(success=True) + + @inject + def delete_alert_data( + self, request: DeleteAlertDataRequest, alert_data_accessor: AlertDataAccessor = injected + ) -> DeleteAlertDataResponse: + """ + Main entry point for deleting data related to an alert. + + Parameters: + request: DeleteAlertDataRequest + Alert to clear + """ + alert_data_accessor.delete_alert_data(external_alert_id=request.alert.id) + return DeleteAlertDataResponse(success=True) diff --git a/src/seer/anomaly_detection/models/external.py b/src/seer/anomaly_detection/models/external.py index c32753228..258829a89 100644 --- a/src/seer/anomaly_detection/models/external.py +++ b/src/seer/anomaly_detection/models/external.py @@ -88,3 +88,14 @@ class StoreDataRequest(BaseModel): class StoreDataResponse(BaseModel): success: bool message: Optional[str] = Field(None) + + +class DeleteAlertDataRequest(BaseModel): + organization_id: int + project_id: int + alert: AlertInSeer + + +class DeleteAlertDataResponse(BaseModel): + success: bool + message: Optional[str] = Field(None) diff --git a/src/seer/app.py b/src/seer/app.py index 9ed0a45f4..62fdc5d6a 100644 --- a/src/seer/app.py +++ b/src/seer/app.py @@ -8,6 +8,8 @@ from sentry_sdk.integrations.logging import LoggingIntegration from seer.anomaly_detection.models.external import ( + DeleteAlertDataRequest, + DeleteAlertDataResponse, DetectAnomaliesRequest, DetectAnomaliesResponse, StoreDataRequest, @@ -265,6 +267,7 @@ def detect_anomalies_endpoint(data: DetectAnomaliesRequest) -> DetectAnomaliesRe def store_data_endpoint(data: StoreDataRequest) -> StoreDataResponse: sentry_sdk.set_tag("organization_id", data.organization_id) sentry_sdk.set_tag("project_id", data.project_id) + sentry_sdk.set_tag("alert_id", data.alert.id) try: response = anomaly_detection().store_data(data) except ClientError as e: @@ -272,6 +275,21 @@ def store_data_endpoint(data: StoreDataRequest) -> StoreDataResponse: return response +@json_api(blueprint, "/v1/anomaly-detection/delete-alert-data") +@sentry_sdk.trace +def delete_alert__data_endpoint( + data: DeleteAlertDataRequest, +) -> DeleteAlertDataResponse: + sentry_sdk.set_tag("organization_id", data.organization_id) + sentry_sdk.set_tag("project_id", data.project_id) + sentry_sdk.set_tag("alert_id", data.alert.id) + try: + response = anomaly_detection().delete_alert_data(data) + except ClientError as e: + response = DeleteAlertDataResponse(success=False, message=str(e)) + return response + + @blueprint.route("/health/live", methods=["GET"]) def health_check(): from seer.inference_models import models_loading_status diff --git a/src/seer/db.py b/src/seer/db.py index a66ecddf8..c5c5867d1 100644 --- a/src/seer/db.py +++ b/src/seer/db.py @@ -278,7 +278,7 @@ class DbDynamicAlert(Base): config: Mapped[dict] = mapped_column(JSON, nullable=False) anomaly_algo_data: Mapped[dict] = mapped_column(JSON, nullable=False) created_at: Mapped[datetime.datetime] = mapped_column( - DateTime, nullable=False, default=datetime.datetime.utcnow + DateTime, nullable=False, default=lambda: datetime.datetime.now(datetime.datetime.UTC) ) timeseries: Mapped[List["DbDynamicAlertTimeSeries"]] = relationship( "DbDynamicAlertTimeSeries",