Skip to content

Commit

Permalink
feat: add preset for incidents
Browse files Browse the repository at this point in the history
Signed-off-by: 35C4n0r <[email protected]>
  • Loading branch information
35C4n0r committed Sep 5, 2024
1 parent 81e7e16 commit 0d08431
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 105 deletions.
3 changes: 3 additions & 0 deletions keep-ui/app/alerts/alert-presets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down
2 changes: 1 addition & 1 deletion keep-ui/utils/hooks/useAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const useAlerts = () => {
options: SWRConfiguration = { revalidateOnFocus: false }
) => {
return useSWR<AlertDto[]>(
() => (session && presetName ? `${apiUrl}/preset/${presetName}/alerts` : null),
() => (session && presetName ? `${apiUrl}/preset/${presetName}/alert` : null),
(url) => fetcher(url, session?.accessToken),
options
);
Expand Down
13 changes: 7 additions & 6 deletions keep-ui/utils/hooks/usePresets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ export const usePresets = () => {
{ revalidateOnFocus: false }
);

const useFetchAllPresets = (options?: SWRConfiguration) => {
const useFetchAllPresets = (entity: string, options?: SWRConfiguration) => {
return useSWR<Preset[]>(
() => (session ? `${apiUrl}/preset` : null),
() => (session ? `${apiUrl}/preset?preset_entity=${entity}` : null),
(url) => fetcher(url, session?.accessToken),
{
...options,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
);
Expand Down
2 changes: 1 addition & 1 deletion keep/alembic.ini
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 3 additions & 1 deletion keep/api/consts.py
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -23,6 +23,7 @@
should_do_noise_now=False,
static=True,
tags=[],
entity=PresetEntityEnum.ALERT
),
"dismissed": PresetDto(
id=StaticPresetsId.DISMISSED_PRESET_ID.value,
Expand All @@ -37,6 +38,7 @@
should_do_noise_now=False,
static=True,
tags=[],
entity=PresetEntityEnum.ALERT
),
}

Expand Down
59 changes: 42 additions & 17 deletions keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]]:
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions keep/api/models/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 11 additions & 3 deletions keep/api/models/db/preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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())

Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0d08431

Please sign in to comment.