Basic implementation of app hooks

Still need to remove the examples and add tests
This commit is contained in:
T'rahk Rokym 2025-04-21 01:12:40 +02:00
parent 49a271a99f
commit 1c1e219037
2 changed files with 147 additions and 2 deletions

View File

@ -46,6 +46,36 @@
</div> </div>
{% endif %} {% endif %}
{% if application_notifications %}
<div id="aa-dashboard-panel-admin-application-notifications" class="col-12 mb-3">
<div class="card">
<div class="card-body">
{% translate "Application Notifications" as widget_title %}
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
<div>
<ul class="list-group">
{% for notif in application_notifications %}
<li class="list-group-item">
{% if 'open' in notif.state %}
<span class="badge bg-success me-2">{% translate "Open" %}</span>
{% else %}
<span class="badge bg-danger me-2">{% translate "Closed" %}</span>
{% endif %}
<span class="badge bg-info me-2">{{ notif.app_name }}</span>
<a href="{{ notif.web_url }}{{ notif.url }}" target="_blank">#{{ notif.iid }}{{ notif.number }} {{ notif.title }}</a>
</li>
{% endfor %}
</ul>
{# TODO maybe add some disclaimer that those are managed by application devs? #}
</div>
</div>
</div>
</div>
{% endif %}
<div class="col-12 mb-3"> <div class="col-12 mb-3">
<div class="card"> <div class="card">
<div class="card-body row"> <div class="card-body row">

View File

@ -1,4 +1,7 @@
import logging import logging
from dataclasses import dataclass
from enum import Enum, auto
from urllib.parse import quote_plus
import requests import requests
from packaging.version import InvalidVersion, Version as Pep440Version from packaging.version import InvalidVersion, Version as Pep440Version
@ -7,10 +10,11 @@ from django import template
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from allianceauth import __version__ from allianceauth import __version__, hooks
from allianceauth.authentication.task_statistics.counters import ( from allianceauth.authentication.task_statistics.counters import (
dashboard_results, dashboard_results,
) )
from allianceauth.hooks import get_hooks
register = template.Library() register = template.Library()
@ -32,6 +36,63 @@ GITLAB_AUTH_ANNOUNCEMENT_ISSUES_URL = (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RepositoryKind(Enum):
"""What kind of repository is being used"""
GITLAB = auto()
GITHUB = auto()
@dataclass
class AppAnnouncementHook:
"""Hook for an application to send GitHub/GitLab issues as announcements"""
app_name: str
repository_namespace: str
repository_kind: RepositoryKind
label: str = "announcement"
def get_announcement_list(self) -> list:
"""
Checks the application repository to find issues with the `Announcement` tag and return their title and link to
be displayed.
"""
match self.repository_kind:
case RepositoryKind.GITHUB:
announcement_list = self._get_github_announcement_list()
case RepositoryKind.GITLAB:
announcement_list = self._get_gitlab_announcement_list()
case _:
return []
for announcement in announcement_list:
announcement["app_name"] = self.app_name
return announcement_list
def _get_github_announcement_list(self) -> list:
"""
Return the issue list for a GitHub repository
Will filter if the `pull_request` attribute is present
"""
raw_list = _fetch_list_from_github(
f"https://api.github.com/repos/{self.repository_namespace}/issues"
f"?labels={self.label}&state=all"
)
return [element for element in raw_list if not element.get("pull_request")]
def _get_gitlab_announcement_list(self) -> list:
"""Return the issues list for a GitLab repository"""
return _fetch_list_from_gitlab(
f"https://gitlab.com/api/v4/projects/{quote_plus(self.repository_namespace)}/issues"
f"?labels={self.label}")
@hooks.register("app_announcement_hook")
def test_hook():
return AppAnnouncementHook("test GitHub app", "r0kym/allianceauth-example-plugin", RepositoryKind.GITLAB)
@hooks.register("app_announcement_hook")
def test_hook_2():
return AppAnnouncementHook("test GitHub app", "r0kym/test", RepositoryKind.GITHUB)
@register.simple_tag() @register.simple_tag()
def decimal_widthratio(this_value, max_value, max_width) -> str: def decimal_widthratio(this_value, max_value, max_width) -> str:
@ -80,17 +141,36 @@ def _current_notifications() -> dict:
_fetch_notification_issues_from_gitlab, _fetch_notification_issues_from_gitlab,
NOTIFICATION_CACHE_TIME NOTIFICATION_CACHE_TIME
) )
app_notifications = []
hooks = get_hooks("app_announcement_hook")
items = [fn() for fn in hooks]
for hook in items:
app_notifications.extend(hook.get_announcement_list())
"""
app_notifications.extend(cache.get_or_set(
f"{hook.app_name}_notification_issues",
hook.get_announcement_list,
NOTIFICATION_CACHE_TIME,
))
"""
except requests.HTTPError: except requests.HTTPError:
logger.warning('Error while getting gitlab notifications', exc_info=True) logger.warning('Error while getting gitlab notifications', exc_info=True)
top_notifications = [] top_notifications = []
application_notifications = []
else: else:
if notifications: if notifications:
top_notifications = notifications[:5] top_notifications = notifications[:5]
else: else:
top_notifications = [] top_notifications = []
if app_notifications:
application_notifications = app_notifications[:10]
else:
application_notifications = []
response = { response = {
'notifications': top_notifications, 'notifications': top_notifications,
'application_notifications': application_notifications,
} }
return response return response
@ -123,7 +203,7 @@ def _current_version_summary() -> dict:
has_current_beta = \ has_current_beta = \
current_version <= latest_beta_version \ current_version <= latest_beta_version \
and latest_patch_version <= latest_beta_version \ and latest_patch_version <= latest_beta_version \
if latest_beta_version else False if latest_beta_version else False
response = { response = {
'latest_patch': has_latest_patch, 'latest_patch': has_latest_patch,
@ -199,3 +279,38 @@ def _fetch_list_from_gitlab(url: str, max_pages: int = MAX_PAGES) -> list:
break break
return result return result
def _fetch_list_from_github(url: str, max_pages: int = MAX_PAGES) -> list:
"""returns a list from the GitHub API. Supports paging"""
# TODO actual paging
result = []
for page in range(1, max_pages+1):
try:
request = requests.get(
url,
params={'page': page},
headers={
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
}
)
request.raise_for_status()
except requests.exceptions.RequestException as e:
error_str = str(e)
logger.warning(
f'Unable to fetch from GitHub API. Error: {error_str}',
exc_info=True,
)
return result
result += request.json()
if 'link' in request.headers and 'rel=\"next\"' in request.headers['link']:
continue
break
return result