diff --git a/keep-ui/app/alerts/alert-presets.tsx b/keep-ui/app/alerts/alert-presets.tsx index d2b1b572a..eea80d141 100644 --- a/keep-ui/app/alerts/alert-presets.tsx +++ b/keep-ui/app/alerts/alert-presets.tsx @@ -124,6 +124,9 @@ export default function AlertPresets({ value: sqlQuery, }, ], + + // TODO: add an Enum here + entity: "alert", is_private: isPrivate, is_noisy: isNoisy, tags: selectedTags.map((tag) => ({ diff --git a/keep-ui/utils/hooks/useAlerts.ts b/keep-ui/utils/hooks/useAlerts.ts index 53b343b93..72a1a21ab 100644 --- a/keep-ui/utils/hooks/useAlerts.ts +++ b/keep-ui/utils/hooks/useAlerts.ts @@ -33,7 +33,7 @@ export const useAlerts = () => { options: SWRConfiguration = { revalidateOnFocus: false } ) => { return useSWR( - () => (session && presetName ? `${apiUrl}/preset/${presetName}/alerts` : null), + () => (session && presetName ? `${apiUrl}/preset/${presetName}/alert` : null), (url) => fetcher(url, session?.accessToken), options ); diff --git a/keep-ui/utils/hooks/usePresets.ts b/keep-ui/utils/hooks/usePresets.ts index 3bfe9d4f3..a930be47e 100644 --- a/keep-ui/utils/hooks/usePresets.ts +++ b/keep-ui/utils/hooks/usePresets.ts @@ -101,9 +101,9 @@ export const usePresets = () => { { revalidateOnFocus: false } ); - const useFetchAllPresets = (options?: SWRConfiguration) => { + const useFetchAllPresets = (entity: string, options?: SWRConfiguration) => { return useSWR( - () => (session ? `${apiUrl}/preset` : null), + () => (session ? `${apiUrl}/preset?preset_entity=${entity}` : null), (url) => fetcher(url, session?.accessToken), { ...options, @@ -163,13 +163,14 @@ export const usePresets = () => { setIsLocalStorageReady(true); }; - const useAllPresets = (options?: SWRConfiguration) => { + const useAllPresets = (options?: SWRConfiguration, entity?: string) => { const { data: presets, error, isValidating, mutate, - } = useFetchAllPresets(options); + } = useFetchAllPresets(entity ? entity : "alert", options); + const filteredPresets = presets?.filter( (preset) => !["feed", "deleted", "dismissed", "groups"].includes(preset.name) @@ -182,13 +183,13 @@ export const usePresets = () => { }; }; - const useStaticPresets = (options?: SWRConfiguration) => { + const useStaticPresets = (options?: SWRConfiguration, entity?: string) => { const { data: presets, error, isValidating, mutate, - } = useFetchAllPresets(options); + } = useFetchAllPresets(entity ? entity : "alert", options); const staticPresets = presets?.filter((preset) => ["feed", "deleted", "dismissed", "groups"].includes(preset.name) ); diff --git a/keep/alembic.ini b/keep/alembic.ini index 1714b9bd6..74284710e 100644 --- a/keep/alembic.ini +++ b/keep/alembic.ini @@ -1,6 +1,6 @@ [alembic] # Re-defined in the keep/api/core/db_on_start.py to make it stable while keep is installed as a package -script_location = keep/api/models/db/migrations +script_location = api/models/db/migrations file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(rev)s prepend_sys_path = . output_encoding = utf-8 diff --git a/keep/api/consts.py b/keep/api/consts.py index f1c2bbb72..76aa9132c 100644 --- a/keep/api/consts.py +++ b/keep/api/consts.py @@ -1,6 +1,6 @@ import os -from keep.api.models.db.preset import PresetDto, StaticPresetsId +from keep.api.models.db.preset import PresetDto, StaticPresetsId, PresetEntityEnum RUNNING_IN_CLOUD_RUN = os.environ.get("K_SERVICE") is not None PROVIDER_PULL_INTERVAL_DAYS = int( @@ -23,6 +23,7 @@ should_do_noise_now=False, static=True, tags=[], + entity=PresetEntityEnum.ALERT ), "dismissed": PresetDto( id=StaticPresetsId.DISMISSED_PRESET_ID.value, @@ -37,6 +38,7 @@ should_do_noise_now=False, static=True, tags=[], + entity=PresetEntityEnum.ALERT ), } diff --git a/keep/api/core/db.py b/keep/api/core/db.py index 5866d79b4..5b9be7bf4 100644 --- a/keep/api/core/db.py +++ b/keep/api/core/db.py @@ -19,6 +19,7 @@ from dotenv import find_dotenv, load_dotenv from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from sqlalchemy import and_, desc, null, update +from sqlalchemy.dialects import postgresql from sqlalchemy.exc import IntegrityError, OperationalError from sqlalchemy.orm import joinedload, selectinload, subqueryload from sqlalchemy.sql import expression @@ -1000,10 +1001,10 @@ def query_alerts( if provider_id: query = query.filter(Alert.provider_id == provider_id) - + if skip_alerts_with_null_timestamp: query = query.filter(Alert.timestamp.isnot(None)) - + # Order by timestamp in descending order and limit the results query = query.order_by(Alert.timestamp.desc()).limit(limit) @@ -1674,7 +1675,9 @@ def get_provider_distribution(tenant_id: str) -> dict: return provider_distribution -def get_presets(tenant_id: str, email) -> List[Dict[str, Any]]: +def get_presets( + tenant_id: str, email: str, preset_entity: PresetEntityEnum +) -> List[Preset]: with Session(engine) as session: statement = ( select(Preset) @@ -1685,18 +1688,27 @@ def get_presets(tenant_id: str, email) -> List[Dict[str, Any]]: Preset.created_by == email, ) ) + .where(Preset.entity == preset_entity.value) + ) + print( + statement.compile( + dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True} + ) ) result = session.exec(statement) presets = result.unique().all() return presets -def get_preset_by_name(tenant_id: str, preset_name: str) -> Preset: +def get_preset_by_name( + tenant_id: str, preset_name: str, preset_entity: PresetEntityEnum +) -> Preset: with Session(engine) as session: preset = session.exec( select(Preset) .where(Preset.tenant_id == tenant_id) .where(Preset.name == preset_name) + .where(Preset.entity == preset_entity.value) ).first() return preset @@ -2315,6 +2327,7 @@ def get_incident_unique_fingerprint_count(tenant_id: str, incident_id: str) -> i ) ).scalar() + def remove_alerts_to_incident_by_incident_id( tenant_id: str, incident_id: str | UUID, alert_ids: List[UUID] ) -> Optional[int]: @@ -2470,10 +2483,16 @@ def confirm_predicted_incident_by_id( return incident -def write_pmi_matrix_to_temp_file(tenant_id: str, pmi_matrix: np.array, fingerprints: List, temp_dir: str) -> bool: - np.savez(f'{temp_dir}/pmi_matrix.npz', pmi_matrix=pmi_matrix, fingerprints=fingerprints) + +def write_pmi_matrix_to_temp_file( + tenant_id: str, pmi_matrix: np.array, fingerprints: List, temp_dir: str +) -> bool: + np.savez( + f"{temp_dir}/pmi_matrix.npz", pmi_matrix=pmi_matrix, fingerprints=fingerprints + ) return True + def write_pmi_matrix_to_db(tenant_id: str, pmi_matrix_df: pd.DataFrame) -> bool: # TODO: add handlers for sequential launches with Session(engine) as session: @@ -2545,15 +2564,17 @@ def get_pmi_value( return pmi_entry.pmi if pmi_entry else None + def get_pmi_values_from_temp_file(temp_dir: str) -> Tuple[np.array, Dict[str, int]]: - npzfile = np.load(f'{temp_dir}/pmi_matrix.npz', allow_pickle=True) - pmi_matrix = npzfile['pmi_matrix'] - fingerprints = npzfile['fingerprints'] - + npzfile = np.load(f"{temp_dir}/pmi_matrix.npz", allow_pickle=True) + pmi_matrix = npzfile["pmi_matrix"] + fingerprints = npzfile["fingerprints"] + fingerint2idx = {fingerprint: i for i, fingerprint in enumerate(fingerprints)} - + return pmi_matrix, fingerint2idx + def get_pmi_values( tenant_id: str, fingerprints: List[str] ) -> Dict[Tuple[str, str], Optional[float]]: @@ -2568,23 +2589,27 @@ def get_pmi_values( return pmi_values -def update_incident_summary(tenant_id: str, incident_id: UUID, summary: str) -> Incident: +def update_incident_summary( + tenant_id: str, incident_id: UUID, summary: str +) -> Incident: if not summary: return - + with Session(engine) as session: incident = session.exec( - select(Incident).where(Incident.tenant_id == tenant_id).where(Incident.id == incident_id) + select(Incident) + .where(Incident.tenant_id == tenant_id) + .where(Incident.id == incident_id) ).first() if not incident: - return + return incident.generated_summary = summary session.commit() session.refresh(incident) - - return + + return # Fetch all topology data diff --git a/keep/api/models/alert.py b/keep/api/models/alert.py index dc18f40fd..6f8fc2885 100644 --- a/keep/api/models/alert.py +++ b/keep/api/models/alert.py @@ -388,9 +388,11 @@ class Config: @classmethod def from_db_incident(cls, db_incident): - severity = IncidentSeverity.from_number(db_incident.severity) \ - if isinstance(db_incident.severity, int) \ + severity = ( + IncidentSeverity.from_number(db_incident.severity) + if isinstance(db_incident.severity, int) else db_incident.severity + ) return cls( id=db_incident.id, diff --git a/keep/api/models/db/preset.py b/keep/api/models/db/preset.py index a0a3e893e..e45fbeb37 100644 --- a/keep/api/models/db/preset.py +++ b/keep/api/models/db/preset.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, conint, constr from sqlalchemy import UniqueConstraint -from sqlmodel import JSON, Column, Field, Relationship, SQLModel +from sqlmodel import JSON, Column, Field, Relationship, SQLModel, String class StaticPresetsId(enum.Enum): @@ -14,6 +14,12 @@ class StaticPresetsId(enum.Enum): GROUPS_PRESET_ID = "11111111-1111-1111-1111-111111111114" +class PresetEntityEnum(enum.Enum): + # Entities for which presets can be created + ALERT = "alert" + INCIDENT = "incident" + + def generate_uuid(): return str(uuid4()) @@ -39,13 +45,14 @@ class TagDto(BaseModel): class Preset(SQLModel, table=True): - __table_args__ = (UniqueConstraint("tenant_id", "name"),) + __table_args__ = (UniqueConstraint("tenant_id", "name", "entity"),) id: UUID = Field(default_factory=uuid4, primary_key=True) tenant_id: str = Field(foreign_key="tenant.id", index=True) created_by: Optional[str] = Field(index=True, nullable=False) is_private: Optional[bool] = Field(default=False) is_noisy: Optional[bool] = Field(default=False) name: str = Field(unique=True) + entity: str = Field(default=PresetEntityEnum.ALERT) options: list = Field(sa_column=Column(JSON)) # [{"label": "", "value": ""}] tags: List[Tag] = Relationship( back_populates="presets", @@ -83,9 +90,10 @@ class PresetDto(BaseModel, extra="ignore"): # meaning is_noisy + at least one alert is doing noise should_do_noise_now: Optional[bool] = Field(default=False) # number of alerts - alerts_count: Optional[int] = Field(default=0) + entity_count: Optional[int] = Field(default=0) # static presets static: Optional[bool] = Field(default=False) + entity: PresetEntityEnum tags: List[TagDto] = [] @property diff --git a/keep/api/routes/preset.py b/keep/api/routes/preset.py index 0ed3373e8..0774e765b 100644 --- a/keep/api/routes/preset.py +++ b/keep/api/routes/preset.py @@ -23,7 +23,7 @@ update_provider_last_pull_time, ) from keep.api.core.dependencies import AuthenticatedEntity, AuthVerifier -from keep.api.models.alert import AlertDto +from keep.api.models.alert import AlertDto, IncidentDto from keep.api.models.db.preset import ( Preset, PresetDto, @@ -31,6 +31,7 @@ PresetTagLink, Tag, TagDto, + PresetEntityEnum, ) from keep.api.tasks.process_event_task import process_event from keep.api.tasks.process_topology_task import process_topology @@ -139,16 +140,22 @@ def pull_data_from_providers( description="Get all presets for tenant", ) def get_presets( + preset_entity: PresetEntityEnum, authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier()), ) -> list[PresetDto]: tenant_id = authenticated_entity.tenant_id logger.info("Getting all presets") # both global and private presets - presets = get_presets_db(tenant_id=tenant_id, email=authenticated_entity.email) + presets = get_presets_db( + tenant_id=tenant_id, + email=authenticated_entity.email, + preset_entity=preset_entity, + ) presets_dto = [PresetDto(**preset.to_dict()) for preset in presets] # add static presets - presets_dto.append(STATIC_PRESETS["feed"]) - presets_dto.append(STATIC_PRESETS["dismissed"]) + if preset_entity == PresetEntityEnum.ALERT: + presets_dto.append(STATIC_PRESETS["feed"]) + presets_dto.append(STATIC_PRESETS["dismissed"]) logger.info("Got all presets") # get the number of alerts + noisy alerts for each preset @@ -165,6 +172,7 @@ class CreateOrUpdatePresetDto(BaseModel): is_private: bool = False # if true visible to all users of that tenant is_noisy: bool = False # if true, the preset will be noisy tags: list[TagDto] = [] # tags to assign to the preset + entity: PresetEntityEnum @router.post("", description="Create a preset for tenant") @@ -189,6 +197,7 @@ def create_preset( created_by=created_by, is_private=body.is_private, is_noisy=body.is_noisy, + entity=body.entity.value, ) # Handle tags @@ -327,46 +336,62 @@ def update_preset( @router.get( - "/{preset_name}/alerts", + "/{preset_name}/{preset_entity}", description="Get a preset for tenant", ) -async def get_preset_alerts( +async def get_preset_entities( request: Request, bg_tasks: BackgroundTasks, preset_name: str, + preset_entity: PresetEntityEnum, response: Response, authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier()), -) -> list[AlertDto]: +) -> list[IncidentDto | AlertDto]: # Gathering alerts may take a while and we don't care if it will finish before we return the response. # In the worst case, gathered alerts will be pulled in the next request. - - bg_tasks.add_task( - pull_data_from_providers, - authenticated_entity.tenant_id, - request.state.trace_id, - ) - tenant_id = authenticated_entity.tenant_id - logger.info("Getting preset alerts", extra={"preset_name": preset_name}) - # handle static presets - if preset_name in STATIC_PRESETS: - preset = STATIC_PRESETS[preset_name] - else: - preset = get_preset_by_name_db(tenant_id, preset_name) - # if preset does not exist - if not preset: - raise HTTPException(404, "Preset not found") - if isinstance(preset, Preset): - preset_dto = PresetDto(**preset.to_dict()) - else: - preset_dto = PresetDto(**preset.dict()) + logger.info( + f"Getting preset ${preset_entity.value}", extra={"preset_name": preset_name} + ) search_engine = SearchEngine(tenant_id=tenant_id) - preset_alerts = search_engine.search_alerts(preset_dto.query) - logger.info("Got preset alerts", extra={"preset_name": preset_name}) + if preset_entity == PresetEntityEnum.ALERT: + bg_tasks.add_task( + pull_data_from_providers, + authenticated_entity.tenant_id, + request.state.trace_id, + ) - response.headers["X-search-type"] = str(search_engine.search_mode.value) - return preset_alerts + # handle static presets + if preset_name in STATIC_PRESETS: + preset = STATIC_PRESETS[preset_name] + else: + preset = get_preset_by_name_db( + tenant_id=tenant_id, + preset_name=preset_name, + preset_entity=preset_entity, + ) + # if preset does not exist + if not preset: + raise HTTPException(404, "Preset not found") + if isinstance(preset, Preset): + preset_dto = PresetDto(**preset.to_dict()) + else: + preset_dto = PresetDto(**preset.dict()) + + preset_alerts = search_engine.search_alerts(preset_dto.query) + logger.info("Got preset alerts", extra={"preset_name": preset_name}) + + response.headers["X-search-type"] = str(search_engine.search_mode.value) + return preset_alerts + if preset_entity == PresetEntityEnum.INCIDENT: + preset = get_preset_by_name_db( + tenant_id=tenant_id, preset_name=preset_name, preset_entity=preset_entity + ) + preset_dto = PresetDto(**preset.to_dict()) + preset_incidents = search_engine.search_incidents(preset_dto.query) + logger.info("Got preset incidents", extra={"preset_name": preset_name}) + return preset_incidents class CreatePresetTab(BaseModel): diff --git a/keep/api/tasks/process_event_task.py b/keep/api/tasks/process_event_task.py index 72cf0db0e..1b62afbee 100644 --- a/keep/api/tasks/process_event_task.py +++ b/keep/api/tasks/process_event_task.py @@ -362,7 +362,7 @@ def __handle_formatted_events( if not filtered_alerts: continue presets_do_update.append(preset_dto) - preset_dto.alerts_count = len(filtered_alerts) + preset_dto.entity_count = len(filtered_alerts) # update noisy if preset.is_noisy: firing_filtered_alerts = list( diff --git a/keep/rulesengine/rulesengine.py b/keep/rulesengine/rulesengine.py index 9cdacc1b8..cd2a6a984 100644 --- a/keep/rulesengine/rulesengine.py +++ b/keep/rulesengine/rulesengine.py @@ -37,15 +37,17 @@ def run_rules(self, events: list[AlertDto]) -> list[IncidentDto]: self.logger.info( f"Rule {rule.name} on event {event.id} is relevant" ) - + rule_fingerprint = self._calc_rule_fingerprint(event, rule) - incident = get_incident_for_grouping_rule(self.tenant_id, rule, rule.timeframe, rule_fingerprint) + incident = get_incident_for_grouping_rule( + self.tenant_id, rule, rule.timeframe, rule_fingerprint + ) incident = assign_alert_to_incident( alert_id=event.event_id, incident_id=incident.id, - tenant_id=self.tenant_id + tenant_id=self.tenant_id, ) incidents_dto[incident.id] = IncidentDto.from_db_incident(incident) @@ -151,7 +153,7 @@ def _calc_rule_fingerprint(self, event: AlertDto, rule): return ",".join(rule_fingerprint) @staticmethod - def filter_alerts(alerts: list[AlertDto], cel: str): + def filter_alerts(alerts: list[AlertDto], cel: str) -> list[AlertDto]: """This function filters alerts according to a CEL Args: @@ -162,7 +164,6 @@ def filter_alerts(alerts: list[AlertDto], cel: str): list[AlertDto]: list of alerts that are related to the cel """ logger = logging.getLogger(__name__) - env = celpy.Environment() # tb: temp hack because this function is super slow if cel == STATIC_PRESETS.get("feed", {}).options[0].get("value"): return [ @@ -174,10 +175,7 @@ def filter_alerts(alerts: list[AlertDto], cel: str): if not cel: logger.debug("No CEL expression provided") return alerts - # preprocess the cel expression - cel = preprocess_cel_expression(cel) - ast = env.compile(cel) - prgm = env.program(ast) + runner = RulesEngine.create_cel_runner(cel=cel) filtered_alerts = [] for alert in alerts: payload = alert.dict() @@ -187,34 +185,68 @@ def filter_alerts(alerts: list[AlertDto], cel: str): # payload severity could be the severity itself or the order of the severity, cast it to the order if isinstance(payload["severity"], str): payload["severity"] = AlertSeverity(payload["severity"].lower()).order + result = RulesEngine.execute_cel_query( + runner=runner, payload=payload, cel=cel, logger=logger + ) + if result: + filtered_alerts.append(alert) + return filtered_alerts - activation = celpy.json_to_cel(json.loads(json.dumps(payload, default=str))) - try: - r = prgm.evaluate(activation) - except celpy.evaluation.CELEvalError as e: - # this is ok, it means that the subrule is not relevant for this event - if "no such member" in str(e): - continue - # unknown - elif "no such overload" in str(e): - logger.debug( - f"Type mismtach between operator and operand in the CEL expression {cel} for alert {alert.id}" - ) - continue - elif "found no matching overload" in str(e): - logger.debug( - f"Type mismtach between operator and operand in the CEL expression {cel} for alert {alert.id}" - ) - continue - logger.warning( - f"Failed to evaluate the CEL expression {cel} for alert {alert.id} - {e}" + @staticmethod + def create_cel_runner(cel: str) -> celpy.Runner: + env = celpy.Environment() + cel = preprocess_cel_expression(cel) + ast = env.compile(cel) + return env.program(ast) + + @staticmethod + def execute_cel_query( + runner: celpy.Runner, payload, cel: str, logger: logging.Logger + ): + # TODO needs some explanation, why are we doing this ?? + activation = celpy.json_to_cel(json.loads(json.dumps(payload, default=str))) + try: + r = runner.evaluate(activation) + except celpy.evaluation.CELEvalError as e: + # this is ok, it means that the subrule is not relevant for this event + if "no such member" in str(e): + ... + # unknown + elif "no such overload" in str(e): + logger.debug( + f"Type mismatch between operator and operand in the CEL expression {cel} for alert {payload.id}" ) - continue - except Exception: - logger.exception( - f"Failed to evaluate the CEL expression {cel} for alert {alert.id}" + + elif "found no matching overload" in str(e): + logger.debug( + f"Type mismatch between operator and operand in the CEL expression {cel} for alert {payload.id}" ) - continue - if r: - filtered_alerts.append(alert) - return filtered_alerts + + logger.warning( + f"Failed to evaluate the CEL expression {cel} for alert {payload.id} - {e}" + ) + return None + except Exception: # NOQA + logger.exception( + f"Failed to evaluate the CEL expression {cel} for alert {payload.id}" + ) + return None + if r: + return payload + + @staticmethod + def filter_incidents(incidents: list[IncidentDto], cel: str): + logger = logging.getLogger(__name__) + if not cel: + return incidents + runner = RulesEngine.create_cel_runner(cel) + filtered_incidents = [] + for incident in incidents: + payload = incident.dict() + payload["alert_sources"] = ",".join(payload["alert_sources"]) + result = RulesEngine.execute_cel_query( + runner=runner, payload=payload, cel=cel, logger=logger + ) + if result: + filtered_incidents.append(incident) + return filtered_incidents diff --git a/keep/searchengine/searchengine.py b/keep/searchengine/searchengine.py index 0fcf7fb03..853b3d26e 100644 --- a/keep/searchengine/searchengine.py +++ b/keep/searchengine/searchengine.py @@ -1,11 +1,11 @@ import enum import logging -from keep.api.core.db import get_last_alerts +from keep.api.core.db import get_last_alerts, get_last_incidents from keep.api.core.dependencies import SINGLE_TENANT_UUID from keep.api.core.elastic import ElasticClient from keep.api.core.tenant_configuration import TenantConfiguration -from keep.api.models.alert import AlertDto, AlertStatus +from keep.api.models.alert import AlertDto, AlertStatus, IncidentDto from keep.api.models.db.preset import PresetDto, PresetSearchQuery from keep.api.utils.enrichment_helpers import convert_db_alerts_to_dto_alerts from keep.rulesengine.rulesengine import RulesEngine @@ -92,6 +92,21 @@ def search_alerts_by_cel( self.logger.info("Finished searching alerts by CEL") return filtered_alerts + def search_incidents_by_cel(self, cel_query, limit: int = 1000): + self.logger.info("Searching incidents by CEL") + incidents, total_count = get_last_incidents( + tenant_id=self.tenant_id, + is_confirmed=True, + limit=limit, + ) + + incidents_dto = [] + for incident in incidents: + incidents_dto.append(IncidentDto.from_db_incident(incident)) + filtered_incidents = self.rule_engine.filter_incidents(incidents_dto, cel_query) + self.logger.info("Finished searching alerts by CEL") + return filtered_incidents + def _search_alerts_by_sql( self, sql_query: dict, limit=1000, timeframe: int = 0 ) -> list[AlertDto]: @@ -149,6 +164,13 @@ def search_alerts(self, query: PresetSearchQuery) -> list[AlertDto]: self.logger.info("Finished searching alerts") return filtered_alerts + def search_incidents(self, query: PresetSearchQuery): + self.logger.info("Searching alerts") + filtered_incidents = self.search_incidents_by_cel( + query.cel_query, limit=query.limit + ) + return filtered_incidents + def search_preset_alerts( self, presets: list[PresetDto] ) -> dict[str, list[AlertDto]]: @@ -173,7 +195,7 @@ def search_preset_alerts( filtered_alerts = self.rule_engine.filter_alerts( alerts_dto, preset.cel_query ) - preset.alerts_count = len(filtered_alerts) + preset.entity_count = len(filtered_alerts) # update noisy if preset.is_noisy: firing_filtered_alerts = list( @@ -214,14 +236,14 @@ def search_preset_alerts( elastic_sql_query = f"""select count(*), MAX(CASE WHEN isNoisy = true AND dismissed = false AND deleted = false THEN 1 ELSE 0 END) from "{self.elastic_client.alerts_index}" where {query}""" results = self.elastic_client.run_query(elastic_sql_query) if results: - preset.alerts_count = results["rows"][0][0] + preset.entity_count = results["rows"][0][0] preset.should_do_noise_now = results["rows"][0][1] == 1 else: self.logger.warning( "No results found for preset", extra={"preset_id": preset.id, "preset_name": preset.name}, ) - preset.alerts_count = 0 + preset.entity_count = 0 preset.should_do_noise_now = False except Exception: self.logger.exception( @@ -235,6 +257,12 @@ def search_preset_alerts( ) return presets + def search_preset_incidents(self): + self.logger.info( + "Searching incidents for presets", + extra={"tenant_id": self.tenant_id, "search_mode": self.search_mode}, + ) + def _create_raw_sql(self, sql_template, params): """ Replace placeholders in the SQL template with actual values from the params dictionary.