Skip to content

Commit

Permalink
Merge branch 'main' into refactor-desc-usage
Browse files Browse the repository at this point in the history
  • Loading branch information
talboren authored Apr 19, 2024
2 parents 0f39193 + 0f9637b commit 72f8287
Show file tree
Hide file tree
Showing 45 changed files with 827 additions and 175 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ Workflow triggers can either be executed manually when an alert is activated or
<p align="center">
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/newrelic-icon.png?raw=true"/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/appdynamics-icon.png?raw=true"/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/datadog-icon.png?raw=true"/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img width=32 height=32 src="https://github.com/keephq/keep/blob/main/keep-ui/public/icons/cloudwatch-icon.png?raw=true"/>
Expand Down
3 changes: 2 additions & 1 deletion docs/overview/enrichment/extraction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Handling a variety of alert formats and extracting relevant information can be c
1. **Rule Definition**: Users create extraction rules specifying the regex patterns to apply to certain alert attributes.
2. **Attribute Specification**: Each rule defines which attribute of the alert should be examined by the regex.
3. **Data Extraction**: When an alert is received, the system applies the regex to the specified attribute. If the pattern matches, named groups within the regex define new attributes to be extracted and added to the alert.
4. **Alert Enrichment**: Extracted values are added to the alert, enhancing its data with additional attributes for improved analysis.
4. **First Match Enforcement**: The extraction process is designed to stop after the first successful match. Once a rule successfully applies and enriches the alert, no further rules are processed. This ensures efficiency and prevents overlapping or redundant data extraction.
5. **Alert Enrichment**: Extracted values are added to the alert, enhancing its data with additional attributes for improved analysis.

## Practical Example

Expand Down
30 changes: 30 additions & 0 deletions docs/providers/documentation/appdynamics-provider.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: "AppDynamics"
sidebarTitle: "AppDynamics Provider"
description: "AppDynamics provider allows you to get AppDynamics `alerts/actions` via webhook installation"
---

## Authentication Parameters
The AppDynamics provider requires the following authentication parameter:

- `AppDynamics Username`: Required. This is your AppDynamics account username.
- `AppDynamics Password`: This is the password associated with your AppDynamics Username.
- `AppDynamics Account Name`: This is your account's name.
- `App Id`: The Id of the Application in which you would like to install the webhook.
- `Host`: This is the hostname of the AppDynamics instance you wish to connect to. It identifies the AppDynamics server that the API will interact with.

## Connecting with the Provider

Obtain AppDynamics Username and Password:
1. Ensure you have a AppDynamics account with the necessary [permissions](https://docs.appdynamics.com/accounts/en/cisco-appdynamics-on-premises-user-management/roles-and-permissions). The basic permissions required are `Account Owner` or `Administrator`. Alternatively you can create an account (instructions)[https://docs.appdynamics.com/accounts/en/global-account-administration/access-management/manage-user-accounts]
2. Find your account name [here](https://accounts.appdynamics.com/overview).
3. Determine the Host [here](https://accounts.appdynamics.com/overview).
4. Get the appId of the Appdynamics instance in which you wish to install the webhook into.


## Useful Links

- [AppDynamics HTTP Action Templates](https://docs.appdynamics.com/appd/24.x/24.3/en/extend-cisco-appdynamics/cisco-appdynamics-apis/configuration-import-and-export-api#id-.ConfigurationImportandExportAPIv24.2-ImportHTTPActionTemplatesintoanAccount)
- [AppDynamics Permissions and Roles](https://docs.appdynamics.com/accounts/en/cisco-appdynamics-on-premises-user-management/roles-and-permissions)
- [AppDynamics User Accounts](https://docs.appdynamics.com/accounts/en/global-account-administration/access-management/manage-user-accounts)

16 changes: 8 additions & 8 deletions docs/providers/documentation/ilert-provider.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "ILert"
sidebarTitle: "ILert Provider"
description: "ILert provider allows you to create, update, and resolve incidents in ILert for effective incident management and response."
title: "ilert"
sidebarTitle: "ilert Provider"
description: "ilert provider allows you to create, update, and resolve incidents in ILert for effective incident management and response."
---

## Inputs
Expand All @@ -24,9 +24,9 @@ The `ilert_token` is required for connecting to the ILert provider. This should

### API Token

To obtain the ILert API token, follow these steps:
To obtain the ilert API token, follow these steps:

1. Log in to your ILert account.
1. Log in to your ilert account.
2. Navigate to the "API Tokens" section under your user profile or account settings.
3. Generate a new API token.
4. Make sure "Read Permission" and "Write Permission" are checked.
Expand All @@ -36,13 +36,13 @@ Ensure you have the necessary permissions assigned to the token for creating and

## Scopes

ILert integration does not require specific scopes to be set for API token as permissions are managed directly within ILert's platform.
ilert integration does not require specific scopes to be set for API token as permissions are managed directly within ilert's platform.

## Notes

_No information yet, feel free to contribute it using the "Edit this page" link at the bottom of the page_

## Useful Links

- [ILert API Documentation](https://api.ilert.com/api-docs/)
- [ILert Incident Management](https://www.ilert.com/incident-management/)
- [ilert API Documentation](https://api.ilert.com/api-docs/)
- [ilert Alerting](https://www.ilert.com/product/reliable-actionable-alerting)
17 changes: 12 additions & 5 deletions keep-ui/app/alerts/alerts-rules-builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ const sanitizeCELIntoJS = (celExpression: string): string => {

// this pattern is far from robust
const variablePattern = /[a-zA-Z$_][0-9a-zA-Z$_]*/;
const jsReservedWords = new Set([
"break", "case", "catch", "class", "const", "continue", "debugger",
"default", "delete", "do", "else", "export", "extends", "finally",
"for", "function", "if", "import", "in", "instanceof", "new", "return",
"super", "switch", "this", "throw", "try", "typeof", "var", "void",
"while", "with", "yield"
]);

export const evalWithContext = (context: AlertDto, celExpression: string) => {
try {
Expand All @@ -187,10 +194,12 @@ export const evalWithContext = (context: AlertDto, celExpression: string) => {
}

const jsExpression = sanitizeCELIntoJS(celExpression);
const variables = (
let variables = (
getAllMatches(variablePattern, jsExpression) ?? []
).filter((variable) => variable !== "true" && variable !== "false");

// filter reserved words from variables
variables = variables.filter((variable) => !jsReservedWords.has(variable));
const func = new Function(...variables, `return (${jsExpression})`);

const args = variables.map((arg) =>
Expand Down Expand Up @@ -328,10 +337,6 @@ export const AlertsRulesBuilder = ({
// This effect should only run when celRules updates and on initial render
}, [celRules]);

useEffect(() => {
setCELRules(defaultQuery);
}, [defaultQuery]);

// Adjust the height of the textarea based on its content
const adjustTextAreaHeight = () => {
const textArea = textAreaRef.current;
Expand Down Expand Up @@ -363,6 +368,8 @@ export const AlertsRulesBuilder = ({
celQuery.replace(/\s+/g, "") === celRules.replace(/\s+/g, "") ||
celRules === "";
setIsValidCEL(isValidCEL);
// close the menu
setShowSuggestions(false);
if (isValidCEL) {
onApplyFilter();
updateOutputCEL?.(celRules);
Expand Down
2 changes: 1 addition & 1 deletion keep-ui/app/extraction/extractions-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export default function RulesTable({
<TableCell colSpan={columns.length}>
<div className="flex space-x-2 divide-x">
<div className="flex items-center space-x-2">
<span className="font-bold">{" > "}Created At:</span>
<span className="font-bold">Created At:</span>
<span>
{new Date(
row.original.created_at + "Z"
Expand Down
Binary file added keep-ui/public/icons/appdynamics-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 36 additions & 7 deletions keep/api/bl/enrichments.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,15 @@ def run_extraction_rules(self, event: AlertDto | dict) -> AlertDto | dict:
"""
Run the extraction rules for the event
"""
self.logger.info("Running extraction rules for incoming event")
fingerprint = (
event.get("fingerprint")
if isinstance(event, dict)
else getattr(event, "fingerprint", None)
)
self.logger.info(
"Running extraction rules for incoming event",
extra={"tenant_id": self.tenant_id, "fingerprint": fingerprint},
)
rules: list[ExtractionRule] = (
self.db_session.query(ExtractionRule)
.filter(ExtractionRule.tenant_id == self.tenant_id)
Expand Down Expand Up @@ -83,7 +91,11 @@ def run_extraction_rules(self, event: AlertDto | dict) -> AlertDto | dict:
if rule.condition is None or rule.condition == "*" or rule.condition == "":
self.logger.info(
"No condition specified for the rule, enriching...",
extra={"rule_id": rule.id},
extra={
"rule_id": rule.id,
"tenant_id": self.tenant_id,
"fingerprint": fingerprint,
},
)
else:
env = celpy.Environment()
Expand All @@ -110,12 +122,22 @@ def run_extraction_rules(self, event: AlertDto | dict) -> AlertDto | dict:
event.update(match_dict)
self.logger.info(
"Event enriched with extraction rule",
extra={"rule_id": rule.id},
extra={
"rule_id": rule.id,
"tenant_id": self.tenant_id,
"fingerprint": fingerprint,
},
)
# Stop after the first match
break
else:
self.logger.info(
"Regex did not match, skipping extraction",
extra={"rule_id": rule.id},
extra={
"rule_id": rule.id,
"tenant_id": self.tenant_id,
"fingerprint": fingerprint,
},
)

return AlertDto(**event) if is_alert_dto else event
Expand All @@ -126,7 +148,7 @@ def run_mapping_rules(self, alert: AlertDto):
"""
self.logger.info(
"Running mapping rules for incoming alert",
extra={"fingerprint": alert.fingerprint},
extra={"fingerprint": alert.fingerprint, "tenant_id": self.tenant_id},
)
rules: list[MappingRule] = (
self.db_session.query(MappingRule)
Expand Down Expand Up @@ -160,7 +182,10 @@ def run_mapping_rules(self, alert: AlertDto):
):
self.logger.info(
"Alert matched a mapping rule, enriching...",
extra={"fingerprint": alert.fingerprint},
extra={
"fingerprint": alert.fingerprint,
"tenant_id": self.tenant_id,
},
)
# This is where you match the row, add your enrichment logic here
# For example: alert.enrich(row)
Expand All @@ -180,6 +205,10 @@ def run_mapping_rules(self, alert: AlertDto):
self.tenant_id, alert.fingerprint, enrichments, self.db_session
)
self.logger.info(
"Alert enriched", extra={"fingerprint": alert.fingerprint}
"Alert enriched",
extra={
"fingerprint": alert.fingerprint,
"tenant_id": self.tenant_id,
},
)
break
58 changes: 57 additions & 1 deletion keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import logging
import os
import uuid
from datetime import datetime, timedelta, timezone
from typing import List, Tuple
from uuid import uuid4
Expand Down Expand Up @@ -153,6 +154,26 @@ def create_db_and_tables():
f"ALTER TABLE workflowtoalertexecution DROP FOREIGN KEY {constraint_name};"
)
logger.info(f"Dropped constraint {constraint_name}")
# now add the new column
try:
if session.bind.dialect.name == "sqlite":
session.exec("ALTER TABLE workflowtoalertexecution ADD COLUMN event_id VARCHAR(255);")
elif session.bind.dialect.name == "mysql":
session.exec("ALTER TABLE workflowtoalertexecution ADD COLUMN event_id VARCHAR(255);")
elif session.bind.dialect.name == "postgresql":
session.exec("ALTER TABLE workflowtoalertexecution ADD COLUMN event_id TEXT;")
elif session.bind.dialect.name == "mssql":
session.exec("ALTER TABLE workflowtoalertexecution ADD event_id NVARCHAR(255);")
else:
raise ValueError("Unsupported database type")
except Exception as e:
# that's ok
if "Duplicate column name" in str(e):
pass
# else, log
else:
logger.exception("Failed to migrate rule table")
pass
# also add grouping_criteria to the workflow table
logger.info("Migrating Rule table")
try:
Expand Down Expand Up @@ -274,6 +295,7 @@ def create_workflow_execution(
tenant_id: str,
triggered_by: str,
execution_number: int = 1,
event_id: str = None,
fingerprint: str = None,
) -> WorkflowExecution:
with Session(engine) as session:
Expand All @@ -295,6 +317,7 @@ def create_workflow_execution(
workflow_to_alert_execution = WorkflowToAlertExecution(
workflow_execution_id=workflow_execution.id,
alert_fingerprint=fingerprint,
event_id=event_id,
)
session.add(workflow_to_alert_execution)

Expand Down Expand Up @@ -490,6 +513,26 @@ def add_or_update_workflow(
return existing_workflow if existing_workflow else workflow


def get_workflow_to_alert_execution_by_workflow_execution_id(
workflow_execution_id: str
) -> WorkflowToAlertExecution:
"""
Get the WorkflowToAlertExecution entry for a given workflow execution ID.
Args:
workflow_execution_id (str): The workflow execution ID to filter the workflow execution by.
Returns:
WorkflowToAlertExecution: The WorkflowToAlertExecution object.
"""
with Session(engine) as session:
return (
session.query(WorkflowToAlertExecution)
.filter_by(workflow_execution_id=workflow_execution_id)
.first()
)


def get_last_workflow_workflow_to_alert_executions(
session: Session, tenant_id: str
) -> list[WorkflowToAlertExecution]:
Expand Down Expand Up @@ -541,13 +584,14 @@ def get_last_workflow_workflow_to_alert_executions(


def get_last_workflow_execution_by_workflow_id(
workflow_id: str, tenant_id: str
tenant_id: str, workflow_id: str
) -> Optional[WorkflowExecution]:
with Session(engine) as session:
workflow_execution = (
session.query(WorkflowExecution)
.filter(WorkflowExecution.workflow_id == workflow_id)
.filter(WorkflowExecution.tenant_id == tenant_id)
.filter(WorkflowExecution.started >= datetime.now() - timedelta(days=7))
.filter(WorkflowExecution.status == "success")
.order_by(desc(WorkflowExecution.started))
.first()
Expand Down Expand Up @@ -1036,6 +1080,18 @@ def get_alerts_by_fingerprint(tenant_id: str, fingerprint: str, limit=1) -> List
return alerts


def get_alert_by_fingerprint_and_event_id(tenant_id: str, fingerprint: str, event_id: str) -> Alert:
with Session(engine) as session:
alert = (
session.query(Alert)
.filter(Alert.tenant_id == tenant_id)
.filter(Alert.fingerprint == fingerprint)
.filter(Alert.id == uuid.UUID(event_id))
.first()
)
return alert


def get_previous_alert_by_fingerprint(tenant_id: str, fingerprint: str) -> Alert:
# get the previous alert for a given fingerprint
with Session(engine) as session:
Expand Down
5 changes: 4 additions & 1 deletion keep/api/models/db/preset.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from uuid import UUID, uuid4
from typing import Optional
from uuid import UUID, uuid4

from pydantic import BaseModel
from sqlalchemy import UniqueConstraint
from sqlmodel import JSON, Column, Field, SQLModel


class Preset(SQLModel, table=True):
__table_args__ = (UniqueConstraint("tenant_id", "name"),)
# Unique ID for each preset
id: UUID = Field(default_factory=uuid4, primary_key=True)

Expand Down
1 change: 1 addition & 0 deletions keep/api/models/db/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class WorkflowToAlertExecution(SQLModel, table=True):
id: Optional[int] = Field(primary_key=True, default=None)
workflow_execution_id: str = Field(foreign_key="workflowexecution.id")
alert_fingerprint: str
event_id: str | None
workflow_execution: WorkflowExecution = Relationship(
back_populates="workflow_to_alert_execution"
)
Expand Down
Loading

0 comments on commit 72f8287

Please sign in to comment.