Skip to content

Commit

Permalink
feat: quickchart (#1709)
Browse files Browse the repository at this point in the history
  • Loading branch information
talboren authored Aug 25, 2024
1 parent fb3e4b9 commit ceae5b2
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 24 deletions.
Binary file added docs/images/chart_example_1.webp
Binary file not shown.
Binary file added docs/images/chart_example_2.webp
Binary file not shown.
1 change: 1 addition & 0 deletions docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"providers/documentation/grafana-provider",
"providers/documentation/http-provider",
"providers/documentation/ilert-provider",
"providers/documentation/quickchart-provider",
"providers/documentation/incidentio-provider",
"providers/documentation/jira-provider",
"providers/documentation/kibana-provider",
Expand Down
71 changes: 71 additions & 0 deletions docs/providers/documentation/quickchart-provider.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: "QuickChart Provider"
sidebarTitle: "QuickChart Provider"
description: "The QuickChart provider enables the generation of chart images through a simple and open API, allowing visualization of alert trends and counts. It supports both anonymous usage and authenticated access with an API key for enhanced functionality."
---

# QuickChart Provider

## Overview

The QuickChart provider allows for the generation of two types of charts based on alert data within Keep's platform:

1. A line chart that shows the trend of a specific fingerprint alert over time.
2. A radial gauge chart displaying the total number of alerts Keep received for this fingerprint.

These charts can be used in various reports, dashboards, or alert summaries to provide visual insights into alert activity and trends.

## Inputs

- `fingerprint`: The unique identifier of the alert whose trend you want to visualize. This is required.
- `status`: (Optional) The status of alerts to filter by (e.g., firing, resolved). Defaults to all statuses.
- `chartConfig`: (Optional) Custom chart configuration settings in JSON format. Default settings will be used if not provided.

## Outputs

The output is a JSON object that includes URLs to the generated chart images:

- `chart_url`: URL of the trend chart image.

<Frame
width="100"
height="200">
<img height="10" src="/images/chart_example_1.webp" />
</Frame>

- `counter_url`: URL of the total alerts gauge chart image.

<Frame
width="100"
height="200">
<img height="10" src="/images/chart_example_2.webp" />
</Frame>

## Authentication Parameters

- `api_key`: (Optional) QuickChart API Key. The provider can be used without an API key, but for more advanced usage, such as generating more complex charts or handling higher request volumes, an API key is recommended.

## Connecting with the Provider

### Using QuickChart without an API Key

The QuickChart provider can generate charts without the need for an API key. However, this usage is limited to basic functionality and lower request limits.

### Using QuickChart with an API Key

To unlock more advanced features and higher usage limits, you can use a QuickChart API key. Here's how to obtain one:

1. Visit [QuickChart](https://quickchart.io/).
2. Sign up for a free account to get started.
3. Navigate to your account settings to find your API key.

Once you have your API key, add it to the provider configuration in Keep.

## Notes

This provider is designed to offer flexible chart generation capabilities within Keep, enhancing how you visualize alert data and trends. It is ideal for users who want to quickly integrate visual representations of alert activity into their workflows.

## Useful Links

- [QuickChart API Documentation](https://quickchart.io/documentation/)
- [QuickChart Website](https://quickchart.io/)
Binary file added keep-ui/public/icons/quickchart-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 0 additions & 23 deletions keep/api/routes/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ def get_all_alerts(
@router.get("/{fingerprint}/history", description="Get alert history")
def get_alert_history(
fingerprint: str,
provider_id: str | None = None,
provider_type: str | None = None,
authenticated_entity: AuthenticatedEntity = Depends(AuthVerifier(["read:alert"])),
) -> list[AlertDto]:
logger.info(
Expand All @@ -97,27 +95,6 @@ def get_alert_history(
)
enriched_alerts_dto = convert_db_alerts_to_dto_alerts(db_alerts)

if provider_id is not None and provider_type is not None:
try:
installed_provider = ProvidersFactory.get_installed_provider(
tenant_id=authenticated_entity.tenant_id,
provider_id=provider_id,
provider_type=provider_type,
)
pulled_alerts_history = installed_provider.get_alerts_by_fingerprint(
tenant_id=authenticated_entity.tenant_id
).get(fingerprint, [])
enriched_alerts_dto.extend(pulled_alerts_history)
except Exception:
logger.warning(
"Failed to pull alerts history from installed provider",
extra={
"provider_id": provider_id,
"provider_type": provider_type,
"tenant_id": authenticated_entity.tenant_id,
},
)

logger.info(
"Fetched alert history",
extra={
Expand Down
Empty file.
210 changes: 210 additions & 0 deletions keep/providers/quickchart_provider/quickchart_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# builtins
import dataclasses
import datetime
from collections import defaultdict

import pydantic

# third-parties
from quickchart import QuickChart

# internals
from keep.api.core.db import get_alerts_by_fingerprint
from keep.api.utils.enrichment_helpers import convert_db_alerts_to_dto_alerts
from keep.contextmanager.contextmanager import ContextManager
from keep.providers.base.base_provider import BaseProvider
from keep.providers.models.provider_config import ProviderConfig
from keep.providers.providers_factory import ProvidersFactory


def get_date_key(date: datetime.datetime, time_unit: str) -> str:
if isinstance(date, str):
date = datetime.datetime.fromisoformat(date)
if time_unit == "Minutes":
return f"{date.hour}:{date.minute}:{date.second}"
elif time_unit == "Hours":
return f"{date.hour}:{date.minute}"
else:
return f"{date.day}/{date.month}/{date.year}"


@pydantic.dataclasses.dataclass
class QuickchartProviderAuthConfig:
api_key: str = dataclasses.field(
metadata={
"required": False,
"description": "Quickchart API Key",
"sensitive": True,
},
default=None,
)


class QuickchartProvider(BaseProvider):
def __init__(
self, context_manager: ContextManager, provider_id: str, config: ProviderConfig
):
super().__init__(context_manager, provider_id, config)

def validate_config(self):
self.authentication_config = QuickchartProviderAuthConfig(
**self.config.authentication
)

def dispose(self):
pass

def _notify(
self,
fingerprint: str,
status: str | None = None,
chartConfig: dict | None = None,
) -> dict:
db_alerts = get_alerts_by_fingerprint(
tenant_id=self.context_manager.tenant_id,
fingerprint=fingerprint,
limit=False,
status=status,
)
alerts = convert_db_alerts_to_dto_alerts(db_alerts)

if not alerts:
self.logger.warning(
"No alerts found for this fingerprint",
extra={
"tenant_id": self.context_manager.tenant_id,
"fingerprint": fingerprint,
},
)
return {"chart_url": ""}

min_last_received = min(
datetime.datetime.fromisoformat(alert.lastReceived) for alert in alerts
)
max_last_received = max(
datetime.datetime.fromisoformat(alert.lastReceived) for alert in alerts
)

title = f"First: {str(min_last_received)} | Last: {str(max_last_received)} | Total: {len(alerts)}"

time_difference = (
max_last_received - min_last_received
).total_seconds() * 1000 # Convert to milliseconds
time_unit = "Days"
if time_difference < 3600000:
time_unit = "Minutes"
elif time_difference < 86400000:
time_unit = "Hours"

categories_by_status = []
raw_chart_data = defaultdict(dict)

for alert in reversed(alerts):
date_key = get_date_key(alert.lastReceived, time_unit)
status = alert.status
if date_key not in raw_chart_data:
raw_chart_data[date_key][status] = 1
else:
raw_chart_data[date_key][status] = (
raw_chart_data[date_key].get(status, 0) + 1
)

if status not in categories_by_status:
categories_by_status.append(status)

chart_data = [{"date": key, **value} for key, value in raw_chart_data.items()]

# Generate chart using QuickChart
return self.generate_chart_image(
chart_data, categories_by_status, len(alerts), title, chartConfig
)

def __get_total_alerts_gaugae(self, counter: int):
qc = QuickChart()
if self.authentication_config.api_key:
qc.key = self.authentication_config.api_key
qc.width = 500
qc.height = 300
qc.config = {
"type": "radialGauge",
"data": {"datasets": [{"data": [counter]}]},
"options": {
"centerArea": {"fontSize": 25, "fontWeight": "bold"},
},
}
chart_url = qc.get_short_url()
return chart_url

def generate_chart_image(
self,
chart_data,
categories_by_status,
total_alerts: int,
title: str,
config: dict | None = None,
) -> dict:
qc = QuickChart()

if self.authentication_config.api_key:
qc.key = self.authentication_config.api_key

qc.width = 800
qc.height = 400
qc.config = config or {
"type": "line",
"data": {
"labels": [data["date"] for data in chart_data],
"datasets": [
{
"fill": False,
"label": category,
"lineTension": 0.4,
"borderWidth": 3,
"data": [data.get(category, 0) for data in chart_data],
}
for category in categories_by_status
],
},
"options": {
"title": {
"display": True,
"position": "top",
"fontSize": 14,
"padding": 10,
"text": title,
},
"scales": {
"xAxes": [{"type": "category"}],
"yAxes": [{"ticks": {"beginAtZero": True}}],
},
},
}
chart_url = qc.get_short_url()

counter_url = self.__get_total_alerts_gaugae(total_alerts)

return {"chart_url": chart_url, "counter_url": counter_url}


if __name__ == "__main__":
import logging

logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
context_manager = ContextManager(
tenant_id="keep",
workflow_id="test",
)
config = {
"description": "",
"authentication": {},
}
provider = ProvidersFactory.get_provider(
context_manager,
provider_id="quickchart",
provider_type="quickchart",
provider_config=config,
)
result = provider.notify(
fingerprint="5bcafb4ea94749f36871a2e1169d5252ecfb1c589d7464bd8bf863cdeb76b864"
)
print(result)
15 changes: 14 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ alembic = "^1.13.2"
numpy = "^2.0.0"
pandas = "^2.2.2"
networkx = "^3.3"
quickchart-io = "^2.0.0"
[tool.poetry.group.dev.dependencies]
pre-commit = "^3.0.4"
pre-commit-hooks = "^4.4.0"
Expand Down

0 comments on commit ceae5b2

Please sign in to comment.