Skip to content

Commit

Permalink
feat: add discord webhook helpers
Browse files Browse the repository at this point in the history
Closes #1027
  • Loading branch information
MarceloRobert committed Mar 7, 2025
1 parent 0c1bbe8 commit 92ee98d
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 0 deletions.
65 changes: 65 additions & 0 deletions backend/kernelCI_app/helpers/discordWebhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from typing import Any, Optional, TypedDict

import requests
import os

from kernelCI_app.helpers.logger import log_message

# For more information on discord webhook structure, visit
# https://discord.com/developers/docs/resources/webhook#execute-webhook

AVATAR_URL = "https://avatars.githubusercontent.com/u/11725450?s=200&v=4"
WEBHOOK_NAME = "KernelCI Dashboard Notifications"


class DiscordImage(TypedDict):
url: str
width: Optional[int]
height: Optional[int]


class DiscordEmbed(TypedDict):
title: str
description: Optional[str]
url: Optional[str]
image: Optional[DiscordImage]


def send_discord_notification(
*,
content: Optional[str] = None,
embeds: Optional[list[DiscordEmbed]] = None,
avatar_url: Optional[str] = AVATAR_URL,
webhook_name: Optional[str] = WEBHOOK_NAME,
) -> None:
url = os.getenv("DISCORD_WEBHOOK_URL")
if not url:
log_message("DISCORD_WEBHOOK_URL environment variable is not set.")
return

if not content and not embeds:
log_message(
"Either content or embeds must be set in order to send notifications."
)
return

if embeds is not None and len(embeds) > 10:
log_message("The embed list can contain at most 10 elements.")
return

data: dict[str, Any] = {
"avatar_url": avatar_url,
"username": webhook_name,
}
if content is not None:
data["content"] = content
if embeds is not None:
data["embeds"] = embeds

try:
result = requests.post(url=url, json=data)
result.raise_for_status()
except requests.HTTPError as e:
log_message(e)

return
19 changes: 19 additions & 0 deletions backend/kernelCI_app/helpers/logger.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
from django.http import HttpRequest
from datetime import datetime


# For logging that we care about, we create a function so we can easily use
# a more sophisticated logging library later.
def log_message(message: str) -> None:
print(message)


def create_endpoint_notification(*, message: str, request: HttpRequest) -> str:
return (
message
+ "\n\nEndpoint:\n"
+ request.build_absolute_uri()
+ (
("\nBody:\n```json\n" + request.body.decode("utf-8") + "```")
if request.body
else ""
)
+ "\nAccessed in: "
+ datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
10 changes: 10 additions & 0 deletions backend/kernelCI_app/views/buildTestsView.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from http import HTTPStatus
from kernelCI_app.helpers.discordWebhook import send_discord_notification
from kernelCI_app.helpers.logger import create_endpoint_notification
from kernelCI_app.typeModels.buildDetails import BuildTestsResponse
from kernelCI_app.models import Tests
from drf_spectacular.utils import extend_schema
Expand All @@ -18,6 +20,7 @@ def get(self, request, build_id: str) -> Response:
"start_time",
"environment_compatible",
"environment_misc",
"build__valid",
)

if not result:
Expand All @@ -26,6 +29,13 @@ def get(self, request, build_id: str) -> Response:
status=HTTPStatus.OK,
)

if result[0]["build__valid"] is False:
notification = create_endpoint_notification(
message="Found tests for a failed build.",
request=request,
)
send_discord_notification(content=notification)

try:
valid_response = BuildTestsResponse(result)
except ValidationError as e:
Expand Down
7 changes: 7 additions & 0 deletions backend/kernelCI_app/views/treeDetailsView.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from kernelCI_app.helpers.commonDetails import PossibleTabs
from kernelCI_app.helpers.discordWebhook import send_discord_notification
from kernelCI_app.helpers.filters import (
FilterParams,
)
from drf_spectacular.utils import extend_schema
from pydantic import ValidationError
from kernelCI_app.helpers.errorHandling import create_api_error_response
from kernelCI_app.helpers.logger import create_endpoint_notification
from kernelCI_app.helpers.treeDetails import (
call_based_on_compatible_and_misc_platform,
decide_if_is_boot_filtered_out,
Expand Down Expand Up @@ -194,6 +196,11 @@ def get(self, request, commit_hash: str | None):
self.filters = FilterParams(request)

if len(rows) == 0:
notification = create_endpoint_notification(
message="Found tree with no builds, boots or tests.",
request=request,
)
send_discord_notification(content=notification)
return create_api_error_response(
error_message="Tree not found", status_code=HTTPStatus.OK
)
Expand Down

0 comments on commit 92ee98d

Please sign in to comment.