Skip to content

Commit

Permalink
feat: add /incidents/{id}/split endpoint (#2459)
Browse files Browse the repository at this point in the history
Signed-off-by: Kirill Chernakov <yakiryous@gmail.com>
  • Loading branch information
Kiryous authored Dec 19, 2024
1 parent 1a51b20 commit 7509998
Show file tree
Hide file tree
Showing 8 changed files with 2,126 additions and 1,831 deletions.
38 changes: 32 additions & 6 deletions keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -3313,7 +3313,6 @@ def get_last_incidents(

return incidents, total_count


def get_incident_by_id(
tenant_id: str,
incident_id: str | UUID,
Expand Down Expand Up @@ -3574,12 +3573,18 @@ def get_future_incidents_by_incident_id(

return query.all(), total_count

def get_int_severity(input_severity: int | str) -> int:
if isinstance(input_severity, int):
return input_severity
else:
return IncidentSeverity(input_severity).order


def get_alerts_data_for_incident(
tenant_id: str,
fingerprints: Optional[List[str]] = None,
session: Optional[Session] = None,
) -> dict:
):
"""
Function to prepare aggregated data for incidents from the given list of alert_ids
Logic is wrapped to the inner function for better usability with an optional database session
Expand Down Expand Up @@ -3634,7 +3639,7 @@ def get_alerts_data_for_incident(
return {
"sources": set(sources),
"services": set(services),
"max_severity": max(severities),
"max_severity": max(severities) if severities else IncidentSeverity.LOW,
"count": len(alerts_data),
}

Expand Down Expand Up @@ -3906,6 +3911,28 @@ def remove_alerts_to_incident_by_incident_id(
)
sources_existed = session.exec(existed_sources_query)

severity_field = get_json_extract_field(session, Alert.event, "severity")
# checking if severities of removed alerts are still presented in alerts
# which still assigned with the incident
updated_severities_query = (
select(severity_field)
.select_from(LastAlert)
.join(
LastAlertToIncident,
and_(
LastAlert.tenant_id == LastAlertToIncident.tenant_id,
LastAlert.fingerprint == LastAlertToIncident.fingerprint,
),
)
.join(Alert, LastAlert.alert_id == Alert.id)
.filter(
LastAlertToIncident.deleted_at == NULL_FOR_DELETED_AT,
LastAlertToIncident.incident_id == incident_id,
)
)
updated_severities_result = session.exec(updated_severities_query)
updated_severities = [get_int_severity(severity) for severity in updated_severities_result]

# Making lists of services and sources to remove from the incident
services_to_remove = [
service
Expand Down Expand Up @@ -3946,7 +3973,7 @@ def remove_alerts_to_incident_by_incident_id(
]

incident.alerts_count -= alerts_data_for_incident["count"]
incident.severity = alerts_data_for_incident["max_severity"].order
incident.severity = max(updated_severities) if updated_severities else IncidentSeverity.LOW.order
incident.start_time = started_at
incident.last_seen_time = last_seen_at

Expand Down Expand Up @@ -4011,7 +4038,7 @@ def merge_incidents_to_id(
)
except OperationalError as e:
logger.error(
f"Error removing alerts to incident {source_incident.id}: {e}"
f"Error removing alerts from incident {source_incident.id}: {e}"
)
try:
add_alerts_to_incident(
Expand All @@ -4031,7 +4058,6 @@ def merge_incidents_to_id(
session.refresh(destination_incident)
return merged_incident_ids, skipped_incident_ids, failed_incident_ids


def get_alerts_count(
tenant_id: str,
) -> int:
Expand Down
5 changes: 2 additions & 3 deletions keep/api/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
# Just a fake random tenant id
SINGLE_TENANT_UUID = "keep"
SINGLE_TENANT_EMAIL = "admin@keephq"
PUSHER_DISABLED = os.environ.get("PUSHER_DISABLED", "false") == "true"


async def extract_generic_body(request: Request) -> dict | bytes | FormData:
"""
Expand All @@ -39,12 +37,13 @@ async def extract_generic_body(request: Request) -> dict | bytes | FormData:


def get_pusher_client() -> Pusher | None:
pusher_disabled = os.environ.get("PUSHER_DISABLED", "false") == "true"
pusher_host = os.environ.get("PUSHER_HOST")
pusher_app_id = os.environ.get("PUSHER_APP_ID")
pusher_app_key = os.environ.get("PUSHER_APP_KEY")
pusher_app_secret = os.environ.get("PUSHER_APP_SECRET")
if (
PUSHER_DISABLED
pusher_disabled
or pusher_app_id is None
or pusher_app_key is None
or pusher_app_secret is None
Expand Down
9 changes: 8 additions & 1 deletion keep/api/models/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,11 +561,18 @@ def to_db_incident(self) -> "Incident":
return db_incident


class SplitIncidentRequestDto(BaseModel):
alert_fingerprints: list[str]
destination_incident_id: UUID

class SplitIncidentResponseDto(BaseModel):
destination_incident_id: UUID
moved_alert_fingerprints: list[str]

class MergeIncidentsRequestDto(BaseModel):
source_incident_ids: list[UUID]
destination_incident_id: UUID


class MergeIncidentsResponseDto(BaseModel):
merged_incident_ids: list[UUID]
skipped_incident_ids: list[UUID]
Expand Down
39 changes: 38 additions & 1 deletion keep/api/routes/incidents.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
IncidentStatusChangeDto,
MergeIncidentsRequestDto,
MergeIncidentsResponseDto,
SplitIncidentRequestDto,
SplitIncidentResponseDto,
)
from keep.api.models.db.alert import AlertActionType, AlertAudit
from keep.api.routes.alerts import _enrich_alert
Expand Down Expand Up @@ -257,6 +259,42 @@ def delete_incident(
incident_bl.delete_incident(incident_id)
return Response(status_code=202)

@router.post(
"/{incident_id}/split",
description="Split incident by incident id",
response_model=SplitIncidentResponseDto,
)
async def split_incident(
incident_id: UUID,
command: SplitIncidentRequestDto,
authenticated_entity: AuthenticatedEntity = Depends(
IdentityManagerFactory.get_auth_verifier(["write:incident"])
),
pusher_client: Pusher | None = Depends(get_pusher_client),
session: Session = Depends(get_session),
) -> SplitIncidentResponseDto:
tenant_id = authenticated_entity.tenant_id
logger.info(
"Splitting incident",
extra={
"incident_id": incident_id,
"tenant_id": tenant_id,
"alert_fingerprints": command.alert_fingerprints,
},
)
incident_bl = IncidentBl(tenant_id, session, pusher_client)
await incident_bl.add_alerts_to_incident(
incident_id=command.destination_incident_id, alert_fingerprints=command.alert_fingerprints
)
incident_bl.delete_alerts_from_incident(
incident_id=incident_id, alert_fingerprints=command.alert_fingerprints
)
return SplitIncidentResponseDto(
destination_incident_id=command.destination_incident_id,
moved_alert_fingerprints=command.alert_fingerprints,
)



@router.post(
"/merge", description="Merge incidents", response_model=MergeIncidentsResponseDto
Expand Down Expand Up @@ -305,7 +343,6 @@ def merge_incidents(
except DestinationIncidentNotFound as e:
raise HTTPException(status_code=400, detail=str(e))


@router.get(
"/{incident_id}/alerts",
description="Get incident alerts by incident incident id",
Expand Down
Loading

0 comments on commit 7509998

Please sign in to comment.