diff --git a/.github/workflows/auto-resolve-keep.yml b/.github/workflows/auto-resolve-keep.yml
index 8ec8138e1..a694a5f4f 100644
--- a/.github/workflows/auto-resolve-keep.yml
+++ b/.github/workflows/auto-resolve-keep.yml
@@ -61,7 +61,7 @@ jobs:
url: "https://api.keephq.dev/incidents/${{ steps.set_ids.outputs.final_incident_id }}/enrich"
method: "POST"
customHeaders: '{"X-API-KEY": "${{ secrets.KEEP_API_KEY }}", "Content-Type": "application/json"}'
- data: '{"enrichments":{"incident_title":"${{ github.event.pull_request.title || ''Manual resolution'' }}","incident_url":"${{ github.event.pull_request.html_url || github.server_url }}//${{ github.repository }}/actions/runs/${{ github.run_id }}", "incident_id": "${{ github.run_id }}", "incident_provider": "github"}}'
+ data: '{"enrichments":{"incident_title":"${{ github.event.pull_request.title || ''Manual resolution'' }}","incident_url":"${{ github.event.pull_request.html_url || format(''{0}/{1}/actions/runs/{2}'', github.server_url, github.repository, github.run_id) }}", "incident_id": "${{ github.run_id }}", "incident_provider": "github"}}'
- name: Auto resolve Keep alert
if: |
@@ -72,4 +72,4 @@ jobs:
url: "https://api.keephq.dev/alerts/enrich?dispose_on_new_alert=true"
method: "POST"
customHeaders: '{"Content-Type": "application/json", "X-API-KEY": "${{ secrets.KEEP_API_KEY }}"}'
- data: '{"enrichments":{"status":"${{ inputs.status || ''resolved'' }}","dismissed":false,"dismissUntil":"","note":"${{ github.event.pull_request.title || ''Manual resolution'' }}","ticket_url":"${{ github.event.pull_request.html_url || github.server_url }}//${{ github.repository }}/actions/runs/${{ github.run_id }}"},"fingerprint":"${{ steps.set_ids.outputs.final_alert_fingerprint }}"}'
+ data: '{"enrichments":{"status":"${{ inputs.status || ''resolved'' }}","dismissed":false,"dismissUntil":"","note":"${{ github.event.pull_request.title || ''Manual resolution'' }}","ticket_url":"${{ github.event.pull_request.html_url || format(''{0}/{1}/actions/runs/{2}'', github.server_url, github.repository, github.run_id) }}"},"fingerprint":"${{ steps.set_ids.outputs.final_alert_fingerprint }}"}'
diff --git a/README.md b/README.md
index 59b22dc63..4d9f5067a 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,12 @@
Cilium
+
+
+ ![Checkly](keep-ui/public/icons/checkly-icon.png)
+ Checkly
+
+ |
![CloudWatch](keep-ui/public/icons/cloudwatch-icon.png)
diff --git a/docs/images/checkly-provider_1.png b/docs/images/checkly-provider_1.png
new file mode 100644
index 000000000..f797b2e03
Binary files /dev/null and b/docs/images/checkly-provider_1.png differ
diff --git a/docs/images/checkly-provider_10.png b/docs/images/checkly-provider_10.png
new file mode 100644
index 000000000..0628ccd7b
Binary files /dev/null and b/docs/images/checkly-provider_10.png differ
diff --git a/docs/images/checkly-provider_11.png b/docs/images/checkly-provider_11.png
new file mode 100644
index 000000000..4dfa318c6
Binary files /dev/null and b/docs/images/checkly-provider_11.png differ
diff --git a/docs/images/checkly-provider_12.png b/docs/images/checkly-provider_12.png
new file mode 100644
index 000000000..2fbe5fa68
Binary files /dev/null and b/docs/images/checkly-provider_12.png differ
diff --git a/docs/images/checkly-provider_2.png b/docs/images/checkly-provider_2.png
new file mode 100644
index 000000000..0ac91e04e
Binary files /dev/null and b/docs/images/checkly-provider_2.png differ
diff --git a/docs/images/checkly-provider_3.png b/docs/images/checkly-provider_3.png
new file mode 100644
index 000000000..e974e1fbb
Binary files /dev/null and b/docs/images/checkly-provider_3.png differ
diff --git a/docs/images/checkly-provider_4.png b/docs/images/checkly-provider_4.png
new file mode 100644
index 000000000..58ae31ad1
Binary files /dev/null and b/docs/images/checkly-provider_4.png differ
diff --git a/docs/images/checkly-provider_5.png b/docs/images/checkly-provider_5.png
new file mode 100644
index 000000000..3b3946e92
Binary files /dev/null and b/docs/images/checkly-provider_5.png differ
diff --git a/docs/images/checkly-provider_6.png b/docs/images/checkly-provider_6.png
new file mode 100644
index 000000000..71cc7483a
Binary files /dev/null and b/docs/images/checkly-provider_6.png differ
diff --git a/docs/images/checkly-provider_7.png b/docs/images/checkly-provider_7.png
new file mode 100644
index 000000000..894a5f3c3
Binary files /dev/null and b/docs/images/checkly-provider_7.png differ
diff --git a/docs/images/checkly-provider_8.png b/docs/images/checkly-provider_8.png
new file mode 100644
index 000000000..1f46abf61
Binary files /dev/null and b/docs/images/checkly-provider_8.png differ
diff --git a/docs/images/checkly-provider_9.png b/docs/images/checkly-provider_9.png
new file mode 100644
index 000000000..c9205b53f
Binary files /dev/null and b/docs/images/checkly-provider_9.png differ
diff --git a/docs/mint.json b/docs/mint.json
index 53b41543e..f90c61e29 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -137,6 +137,7 @@
"providers/documentation/bigquery-provider",
"providers/documentation/centreon-provider",
"providers/documentation/checkmk-provider",
+ "providers/documentation/checkly-provider",
"providers/documentation/cilium-provider",
"providers/documentation/clickhouse-provider",
"providers/documentation/cloudwatch-provider",
diff --git a/docs/providers/documentation/checkly-provider.mdx b/docs/providers/documentation/checkly-provider.mdx
new file mode 100644
index 000000000..19a502f3c
--- /dev/null
+++ b/docs/providers/documentation/checkly-provider.mdx
@@ -0,0 +1,124 @@
+---
+title: 'Checkly'
+sidebarTitle: 'Checkly Provider'
+description: 'StatusCake allows you to receive alerts from Checkly using API endpoints as well as webhooks'
+---
+
+## Authentication Parameters
+
+The Checkly provider offers two ways to authenticate:
+
+- `Checkly API Key` - This is the API key created in the User Settings of your Checkly account and is used to authenticate requests to the Checkly API.
+- `Checkly Account ID` - This is the account ID of your Checkly account.
+
+## Connecting Checkly to Keep
+
+1. Open Checkly dashboard and click on your profile picture in the top right corner.
+
+2. Click on `User Settings`.
+
+
+
+
+
+3. Open the `API Keys` tab and click on `Create API Key` to generate a new API key.
+
+
+
+
+
+4. Copy the API key.
+
+5. Open `General` tab under Account Settings and copy the `Account ID`.
+
+
+
+
+
+6. Go to Keep, add Checkly as a provider and enter the API key and Account ID in the respective fields and click on `Connect`.
+
+## Webhooks Integration
+
+1. Open Checkly dashboard and open `Alerts` tab in the left sidebar.
+
+
+
+
+
+2. Click on `Add more channels`
+
+
+
+
+
+3. Select `Webhook` from the list of available channels.
+
+
+
+
+
+4. Enter a name for the webhook, select the method as `POST`
+
+5. Enter [https://api.keephq.dev/alerts/event/checkly](https://api.keephq.dev/alerts/event/checkly) as the URL.
+
+6. Copy the below snippet and paste in the `Body` of Webhook. Refer the screenshot below for reference.
+
+```json
+{
+ "event": "{{ALERT_TITLE}}",
+ "alert_type": "{{ALERT_TYPE}}",
+ "check_name": "{{CHECK_NAME}}",
+ "group_name": "{{GROUP_NAME}}",
+ "check_id": "{{CHECK_ID}}",
+ "check_type": "{{CHECK_TYPE}}",
+ "check_result_id": "{{CHECK_RESULT_ID}}",
+ "check_error_message": "{{CHECK_ERROR_MESSAGE}}",
+ "response_time": "{{RESPONSE_TIME}}",
+ "api_check_response_status_code": "{{API_CHECK_RESPONSE_STATUS_CODE}}",
+ "api_check_response_status_text": "{{API_CHECK_RESPONSE_STATUS_TEXT}}",
+ "run_location": "{{RUN_LOCATION}}",
+ "ssl_days_remaining": "{{SSL_DAYS_REMAINING}}",
+ "ssl_check_domain": "{{SSL_CHECK_DOMAIN}}",
+ "started_at": "{{STARTED_AT}}",
+ "tags": "{{TAGS}}",
+ "link": "{{RESULT_LINK}}",
+ "region": "{{REGION}}",
+ "uuid": "{{$UUID}}"
+}
+```
+
+
+
+
+
+8. Go to Headers tab and add a new header with key as `X-API-KEY` and create a new API key in Keep and paste it as the value and save the webhook.
+
+
+
+
+
+9. Follow the below steps to create a new API key in Keep.
+
+7. Go to Keep dashboard and click on the profile icon in the botton left corner and click `Settings`.
+
+
+
+
+
+8. Select `Users and Access` tab and then select `API Keys` tab and create a new API key.
+
+
+
+
+
+9. Give name and select the role as `webhook` and click on `Create API Key`.
+
+
+
+
+
+10. Use the generated API key in the `X-API-KEY` header of the webhook created in Checkly.
+
+## Useful Links
+
+- [Checkly Website](https://www.checklyhq.com/)
diff --git a/docs/providers/overview.mdx b/docs/providers/overview.mdx
index 5f8b47146..53a16c3c7 100644
--- a/docs/providers/overview.mdx
+++ b/docs/providers/overview.mdx
@@ -84,6 +84,12 @@ By leveraging Keep Providers, users are able to deeply integrate Keep with the t
icon={ }
>
+ }
+>
+
router.push("/alerts/feed")}
/>
);
diff --git a/keep-ui/app/(keep)/incidents/[id]/incident-layout-client.tsx b/keep-ui/app/(keep)/incidents/[id]/incident-layout-client.tsx
index 9a993ac51..3ba696f6e 100644
--- a/keep-ui/app/(keep)/incidents/[id]/incident-layout-client.tsx
+++ b/keep-ui/app/(keep)/incidents/[id]/incident-layout-client.tsx
@@ -44,7 +44,7 @@ export function IncidentLayoutClient({
/>
}
- initialLeftWidth={60}
+ initialLeftWidth={65}
/>
) : (
{children}
diff --git a/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx b/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx
index 0e048775e..7ac903814 100644
--- a/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx
+++ b/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx
@@ -283,9 +283,9 @@ export default function IncidentTimeline({
// TODO: Load data on server side
// Loading state is true if the data is not loaded and there is no error for smoother loading state on initial load
- const alertsLoading = _alertsLoading || (!alerts && !alertsError);
- const auditEventsLoading =
- _auditEventsLoading || (!auditEvents && !auditEventsError);
+ // const alertsLoading = _alertsLoading || (!alerts && !alertsError);
+ // const auditEventsLoading =
+ // _auditEventsLoading || (!auditEvents && !auditEventsError);
const [selectedEvent, setSelectedEvent] = useState(null);
@@ -358,7 +358,7 @@ export default function IncidentTimeline({
return {};
}, [auditEvents, alerts]);
- if (auditEventsLoading || alertsLoading) {
+ if (_auditEventsLoading || _alertsLoading) {
return (
diff --git a/keep-ui/components/ui/EmptyStateCard.tsx b/keep-ui/components/ui/EmptyStateCard.tsx
index 43cb90d84..5559c57da 100644
--- a/keep-ui/components/ui/EmptyStateCard.tsx
+++ b/keep-ui/components/ui/EmptyStateCard.tsx
@@ -10,7 +10,7 @@ export function EmptyStateCard({
}: {
title: string;
description: string;
- buttonText: string;
+ buttonText?: string;
onClick: (e: React.MouseEvent) => void;
className?: string;
}) {
@@ -29,9 +29,11 @@ export function EmptyStateCard({
{description}
-
+ {buttonText && (
+
+ )}
);
diff --git a/keep-ui/components/ui/ResizableColumns.tsx b/keep-ui/components/ui/ResizableColumns.tsx
index 128778e09..8b10b67e1 100644
--- a/keep-ui/components/ui/ResizableColumns.tsx
+++ b/keep-ui/components/ui/ResizableColumns.tsx
@@ -62,7 +62,7 @@ const ResizableColumns = ({
onMouseDown={startDragging}
/>
- {rightChild}
+ {rightChild}
);
};
diff --git a/keep-ui/public/icons/checkly-icon.png b/keep-ui/public/icons/checkly-icon.png
new file mode 100644
index 000000000..563513d4b
Binary files /dev/null and b/keep-ui/public/icons/checkly-icon.png differ
diff --git a/keep/providers/checkly_provider/__init__.py b/keep/providers/checkly_provider/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/keep/providers/checkly_provider/alerts_mock.py b/keep/providers/checkly_provider/alerts_mock.py
new file mode 100644
index 000000000..a5d70e8db
--- /dev/null
+++ b/keep/providers/checkly_provider/alerts_mock.py
@@ -0,0 +1,21 @@
+ALERTS = {
+ "event": "API Check #1 has recovered",
+ "alert_type": "ALERT_RECOVERY",
+ "check_name": "API Check #1",
+ "group_name": "",
+ "check_id": "927a2982-1007-4b81-b383-eae8bf717e61",
+ "check_type": "API",
+ "check_result_id": "a34867c0-9239-421f-92f2-4408bbd05417",
+ "check_error_message": "",
+ "response_time": "258",
+ "api_check_response_status_code": "200",
+ "api_check_response_status_text": "OK",
+ "run_location": "Singapore",
+ "ssl_days_remaining": "",
+ "ssl_check_domain": "",
+ "started_at": "2025-01-26T11:19:40.544Z",
+ "tags": "",
+ "link": "https://app.checklyhq.com/checks/927a2982-1007-4b81-b383-eae8bf717e61/check-sessions/478cacb1-c40f-4675-89d7-a4e3ecaafb7b",
+ "region": "",
+ "uuid": "4583208e-0bca-48c6-8dc8-d14faf6102b3"
+}
diff --git a/keep/providers/checkly_provider/checkly_provider.py b/keep/providers/checkly_provider/checkly_provider.py
new file mode 100644
index 000000000..64f1736a2
--- /dev/null
+++ b/keep/providers/checkly_provider/checkly_provider.py
@@ -0,0 +1,258 @@
+"""
+ChecklyProvider is a class that allows you to receive alerts from Checkly using API endpoints as well as webhooks.
+"""
+
+import dataclasses
+
+import pydantic
+import requests
+
+from keep.api.models.alert import AlertDto, AlertSeverity, AlertStatus
+from keep.contextmanager.contextmanager import ContextManager
+from keep.providers.base.base_provider import BaseProvider
+from keep.providers.models.provider_config import ProviderConfig, ProviderScope
+
+@pydantic.dataclasses.dataclass
+class ChecklyProviderAuthConfig:
+ """
+ ChecklyProviderAuthConfig is a class that allows you to authenticate in Checkly.
+ """
+
+ checklyApiKey: str = dataclasses.field(
+ metadata={
+ "required": True,
+ "description": "Checkly API Key",
+ "sensitive": True,
+ },
+ )
+
+ accountId: str = dataclasses.field(
+ metadata={
+ "required": True,
+ "description": "Checkly Account ID",
+ "sensitive": True,
+ },
+ )
+
+class ChecklyProvider(BaseProvider):
+ """
+ Get alerts from Checkly into Keep.
+ """
+
+ webhook_description = ""
+ webhook_template = ""
+ webhook_markdown = """
+💡 For more details on how to configure Checkly to send alerts to Keep, see the [Keep documentation](https://docs.keephq.dev/providers/documentation/checkly-provider).
+
+To send alerts from Checkly to Keep, Use the following webhook url to configure Checkly send alerts to Keep:
+
+1. In Checkly dashboard open "Alerts" tab.
+2. Click on "Add more channels".
+3. Select "Webhook" from the list.
+4. Enter a name for the webhook, select the method as "POST" and enter the webhook URL as {keep_webhook_api_url}.
+5. Copy the Body template from the [Keep documentation](https://docs.keephq.dev/providers/documentation/checkly-provider) and paste it in the Body field of the webhook.
+6. Add a request header with the key "X-API-KEY" and the value as {api_key}.
+7. Save the webhook.
+ """
+
+ PROVIDER_DISPLAY_NAME = "Checkly"
+ PROVIDER_TAGS = ["alert"]
+ PROVIDER_CATEGORY = ["Monitoring"]
+
+ PROVIDER_SCOPES = [
+ ProviderScope(
+ name="read_alerts",
+ description="Read alerts from Checkly",
+ ),
+ ]
+
+ # Based on the Alert states in Checkly, we map them to the AlertStatus and AlertSeverity in Keep.
+ STATUS_MAP = {
+ "NO_ALERT": AlertStatus.RESOLVED,
+ "ALERT_DEGRADED": AlertStatus.FIRING,
+ "ALERT_FAILURE": AlertStatus.FIRING,
+ "ALERT_DEGRADED_REMAIN": AlertStatus.ACKNOWLEDGED,
+ "ALERT_DEGRADED_RECOVERY": AlertStatus.RESOLVED,
+ "ALERT_DEGRADED_FAILURE": AlertStatus.FIRING,
+ "ALERT_FAILURE_REMAIN": AlertStatus.ACKNOWLEDGED,
+ "ALERT_FAILURE_DEGRADED": AlertStatus.ACKNOWLEDGED,
+ "ALERT_RECOVERY": AlertStatus.RESOLVED
+ }
+
+ SEVERITY_MAP = {
+ "NO_ALERT": AlertSeverity.INFO,
+ "ALERT_DEGRADED": AlertSeverity.WARNING,
+ "ALERT_FAILURE": AlertSeverity.CRITICAL,
+ "ALERT_DEGRADED_REMAIN": AlertSeverity.WARNING,
+ "ALERT_DEGRADED_RECOVERY": AlertSeverity.INFO,
+ "ALERT_DEGRADED_FAILURE": AlertSeverity.HIGH,
+ "ALERT_FAILURE_REMAIN": AlertSeverity.CRITICAL,
+ "ALERT_FAILURE_DEGRADED": AlertSeverity.WARNING,
+ "ALERT_RECOVERY": AlertSeverity.INFO
+ }
+
+ def __init__(
+ self, context_manager: ContextManager, provider_id: str, config: ProviderConfig
+ ):
+ super().__init__(context_manager, provider_id, config)
+
+ def dispose(self):
+ """
+ Dispose the provider.
+ """
+ pass
+
+ def validate_config(self):
+ """
+ Validates required configuration for ilert provider.
+ """
+ self.authentication_config = ChecklyProviderAuthConfig(
+ **self.config.authentication
+ )
+
+ def validate_scopes(self):
+ """
+ Validate scopes for the provider
+ """
+ self.logger.info("Validating Checkly provider scopes")
+ try:
+ response = requests.get(
+ self.__get_url(),
+ headers=self.__get_auth_headers(),
+ )
+
+ if response.status_code != 200:
+ response.raise_for_status()
+
+ self.logger.info("Successfully validated scopes", extra={"response": response.json()})
+
+ return {"read_alerts": True}
+
+ except Exception as e:
+ self.logger.exception("Failed to validate scopes", extra={"error": e})
+ return {"read_alerts": str(e)}
+
+ def _get_alerts(self) -> list[AlertDto]:
+ """
+ Get alerts from Checkly.
+ """
+ self.logger.info("Getting alerts from Checkly")
+ alerts = self.__get_paginated_data()
+ return [
+ AlertDto(
+ id=alert["id"],
+ name=alert["name"],
+ status=ChecklyProvider.STATUS_MAP[alert["alertType"]],
+ severity=ChecklyProvider.SEVERITY_MAP[alert["alertType"]],
+ lastReceivedAt=alert["created_at"],
+ alertType=alert["alertType"],
+ checkId=alert["checkId"],
+ checkType=alert["checkType"],
+ runLocation=alert["runLocation"],
+ responseTime=alert["responseTime"],
+ error=alert["error"],
+ statusCode=alert["statusCode"],
+ created_at=alert["created_at"],
+ startedAt=alert["startedAt"],
+ source=["checkly"]
+ ) for alert in alerts
+ ]
+
+ @staticmethod
+ def _format_alert(
+ event: dict, provider_instance: "BaseProvider" = None
+ ) -> AlertDto | list[AlertDto]:
+ alert = AlertDto(
+ id=event["uuid"],
+ name=event["check_name"],
+ description=event["event"],
+ status=ChecklyProvider.STATUS_MAP[event["alert_type"]],
+ severity=ChecklyProvider.SEVERITY_MAP[event["alert_type"]],
+ lastReceived=event["started_at"],
+ alertType=event["alert_type"],
+ groupName=event["group_name"],
+ checkId=event["check_id"],
+ checkType=event["check_type"],
+ checkResultId=event["check_result_id"],
+ checkErrorMessage=event["check_error_message"],
+ responseTime=event["response_time"],
+ apiCheckResponseStatus=event["api_check_response_status_code"],
+ apiCheckResponseStatusText=event["api_check_response_status_text"],
+ runLocation=event["run_location"],
+ sslDaysRemaining=event["ssl_days_remaining"],
+ sslCheckDomain=event["ssl_check_domain"],
+ startedAt=event["started_at"],
+ tags=event["tags"],
+ url=event["link"],
+ region=event["region"],
+ source=["checkly"]
+ )
+
+ return alert
+
+
+ def __get_auth_headers(self):
+ return {
+ "Authorization": f"Bearer {self.authentication_config.checklyApiKey}",
+ "X-Checkly-Account": self.authentication_config.accountId,
+ "accept": "application/json"
+ }
+
+ def __get_paginated_data(self, query_params: dict = {}) -> list:
+ data = []
+ page = 1
+
+ while True:
+ self.logger.info(f"Getting data from page {page}")
+ query_params["page"] = page
+ try:
+ url = self.__get_url(query_params)
+ headers = self.__get_auth_headers()
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page_data = response.json()
+ if not page_data:
+ break
+ self.logger.info(f"Got {len(page_data)} data from page {page}")
+ data.extend(page_data)
+ page += 1
+ except Exception as e:
+ self.logger.error(f"Error getting data from page {page}: {e}")
+ break
+ return data
+
+ def __get_url(self, query_params: dict = {}):
+ url = "https://api.checklyhq.com/v1/check-alerts"
+ if query_params:
+ url += "?"
+ for key, value in query_params.items():
+ url += f"{key}={value}&"
+ url = url[:-1]
+ return url
+
+if __name__ == "__main__":
+ import logging
+
+ logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
+ context_manager = ContextManager(
+ tenant_id="singletenant",
+ workflow_id="test",
+ )
+
+ import os
+
+ checkly_api_key = os.getenv("CHECKLY_API_KEY")
+ checkly_account_id = os.getenv("CHECKLY_ACCOUNT_ID")
+
+ config = ProviderConfig(
+ description="Checkly Provider",
+ authentication={
+ "checklyApiKey": checkly_api_key,
+ "accountId": checkly_account_id,
+ }
+ )
+
+ provider = ChecklyProvider(context_manager, "checkly", config)
+
+ alerts = provider.get_alerts()
+ print(alerts)
|