Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions keep/api/routes/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,100 @@ def delete_alert(
return {"status": "ok"}


@router.delete(
"/{fingerprint}",
description="Delete all alert instances by fingerprint (all timestamps)",
)
def delete_all_alert_instances(
fingerprint: str,
restore: bool = False,
authenticated_entity: AuthenticatedEntity = Depends(
IdentityManagerFactory.get_auth_verifier(["delete:alert"])
),
) -> dict[str, str]:
"""
Delete all alert instances (all timestamps) for a given fingerprint.
This endpoint allows users to delete the entire alert history for a fingerprint
in a single request, instead of calling the delete endpoint for each timestamp.
"""
tenant_id = authenticated_entity.tenant_id
user_email = authenticated_entity.email

logger.info(
"Deleting all alert instances",
extra={
"fingerprint": fingerprint,
"restore": restore,
"tenant_id": tenant_id,
},
)

# Get all alerts with this fingerprint to get all timestamps
db_alerts = get_alerts_by_fingerprint(
tenant_id=tenant_id,
fingerprint=fingerprint,
limit=None, # Get all alerts
)

if not db_alerts:
logger.warning(
"No alerts found for fingerprint",
extra={
"fingerprint": fingerprint,
"tenant_id": tenant_id,
},
)
return {"status": "ok", "deleted_count": 0}

# Extract all timestamps (lastReceived) from the alerts
all_timestamps = [alert.timestamp.isoformat() for alert in db_alerts]

# Get existing enrichment to preserve existing deleted timestamps
deleted_last_received = []
assignees_last_receievd = {}

enrichment = get_enrichment(tenant_id, fingerprint)
if enrichment:
deleted_last_received = enrichment.enrichments.get("deletedAt", [])
assignees_last_receievd = enrichment.enrichments.get("assignees", {})

if restore:
# Restore all timestamps - remove all from deleted list
deleted_last_received = []
else:
# Delete all timestamps - add all to deleted list
for timestamp in all_timestamps:
if timestamp not in deleted_last_received:
deleted_last_received.append(timestamp)
# Auto-assign the deleting user to each alert
if timestamp not in assignees_last_receievd:
assignees_last_receievd[timestamp] = user_email

# Update the enrichment with all deleted timestamps
enrichment_bl = EnrichmentsBl(tenant_id)
enrichment_bl.enrich_entity(
fingerprint=fingerprint,
enrichments={
"deletedAt": deleted_last_received,
"assignees": assignees_last_receievd,
},
action_type=ActionType.DELETE_ALERT,
action_description=f"All alert instances deleted by {user_email}",
action_callee=user_email,
)

logger.info(
"Deleted all alert instances successfully",
extra={
"tenant_id": tenant_id,
"restore": restore,
"fingerprint": fingerprint,
"deleted_count": len(all_timestamps),
},
)
return {"status": "ok", "deleted_count": len(all_timestamps)}


@router.post(
"/{fingerprint}/assign/{last_received}", description="Assign alert to user"
)
Expand Down
17 changes: 14 additions & 3 deletions keep/providers/prometheus_provider/prometheus_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,23 @@ def validate_scopes(self) -> dict[str, bool | str]:
validated_scopes["connectivity"] = str(e)
return validated_scopes

def _query(self, query):
def query(self, query: str):
"""
Executes a query against the Prometheus server.
Executes a PromQL query against the Prometheus server.

This method allows executing arbitrary PromQL queries to retrieve
time series data from Prometheus for analysis by AI agents.

Args:
query: A PromQL query string (e.g., "up", "rate(http_requests_total[5m])")

Returns:
list | tuple: list of results or single result if single_row is True
dict: Prometheus query response containing results

Example:
>>> provider = PrometheusProvider(...)
>>> result = provider.query("up{job='prometheus'}")
>>> print(result['data']['result'])
"""
if not query:
raise ValueError("Query is required")
Expand Down
Loading