-
Notifications
You must be signed in to change notification settings - Fork 700
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
297 additions
and
24 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
210 changes: 210 additions & 0 deletions
210
keep/providers/quickchart_provider/quickchart_provider.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters