Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/actions/send-pypi-notification/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Send PyPI Notification
description: Get GitHub avatar and send PyPI update notification to Discord

inputs:
pypi-package:
description: "PyPI package name"
required: true
success:
description: "Whether the publish succeeded ('true' or 'false')"
required: true
toml-updated:
description: "Whether pyproject.toml version was bumped ('true' or 'false')"
required: true
version:
description: "Package version"
required: true
discord-role:
description: "Discord CI/CD role ID"
required: true
discord-webhook-url:
description: "Discord webhook URL"
required: true

runs:
using: composite
steps:
- name: Get user avatar
id: avatar
shell: bash
run: |
AVATAR_URL=$(curl -s "https://api.github.com/users/${{ github.actor }}" | jq -r '.avatar_url')
echo "url=$AVATAR_URL" >> $GITHUB_OUTPUT

- name: Send PyPI notification to Discord
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ inputs.discord-webhook-url }}
run: |
CMD="PYTHONPATH=utils python -m notifications.send_pypi \
--type ${{ inputs.pypi-package }} \
--author ${{ github.actor }} \
--author-icon ${{ steps.avatar.outputs.url }} \
--action-url https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

if [ '${{ inputs.success }}' == 'true' ] && \
[ '${{ inputs.toml-updated }}' == 'true' ]; then
CMD="$CMD --success 1"
fi

CMD="$CMD --version ${{ inputs.version }} \
--cicd-id ${{ inputs.discord-role }}"

echo "$CMD"
eval "$CMD"
49 changes: 19 additions & 30 deletions .github/workflows/poetry_publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
version: ${{ needs.check-toml-version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install Poetry and Version Plugin
run: pip install "poetry<2" poetry-version-from-file
Expand Down Expand Up @@ -66,35 +66,24 @@ jobs:
notify-discord:
needs: [check-toml-version, poetry-publish]
runs-on: ubuntu-latest
if: always() # Notify on both success and failure
if: always()
steps:
- name: Get User Avatar URL
id: avatar
run: |
AVATAR_URL=$(curl -s https://api.github.com/users/${{ github.actor }} | jq -r '.avatar_url')
echo "avatar_url=$AVATAR_URL" >> $GITHUB_ENV

- name: Send notification to Discord
run: |
CMD="python /scripts/send_update_notification.py \
--type ${{ inputs.pypi_package }} \
--author ${{ github.actor }} \
--author-icon ${{ env.avatar_url }} \
--action-url https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

if [ '${{ needs.poetry-publish.outputs.success }}' == 'true' ] && \
[ '${{ needs.check-toml-version.outputs.uped_toml }}' == 'true' ]; then
CMD="$CMD --success 1"
fi

CMD="$CMD --version ${{ needs.poetry-publish.outputs.version }} \
--cicd-id ${{ secrets.discord_role }}"
- name: Checkout utils repo
uses: actions/checkout@v4
with:
repository: BYU-CS-Course-Ops/utils
path: utils

echo "$CMD"
- name: Install notification dependencies
shell: bash
run: pip install discord-webhook ${{ inputs.extra-packages }}

docker run --rm \
-v ${{ github.workspace }}:/repo \
-w /repo \
-e DISCORD_WEBHOOK_URL=${{ secrets.discord_webhook_url }} \
byucscourseops/send_update_notification:latest \
sh -c "$CMD"
- name: Send PyPI notification
uses: utils/.github/actions/send-pypi-notification
with:
pypi-package: ${{ inputs.pypi_package }}
success: ${{ needs.poetry-publish.outputs.success }}
toml-updated: ${{ needs.check-toml-version.outputs.uped_toml }}
version: ${{ needs.poetry-publish.outputs.version }}
discord-role: ${{ secrets.discord_role }}
discord-webhook-url: ${{ secrets.discord_webhook_url }}
8 changes: 8 additions & 0 deletions notifications/formatting/formatting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def get_course_style(ntype: str) -> dict[str, str]:
return {}


def get_pypi_style(ntype: str) -> dict[str, str]:
styles = _load_styles()
for row in styles.get("PyPi Packages", []):
if row.get("type") == ntype:
return row
return {}


def truncate_error(error: str, max_chars: int = 900) -> str:
if not error:
return "```\nNo error output available.\n```"
Expand Down
33 changes: 33 additions & 0 deletions notifications/formatting/pypi_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from notifications.resources import Notification, Embed, Author, Footer, WebhookMessage
from notifications.formatting.formatting_utils import get_pypi_style, hex_to_int


def format_notification(ntype, author, author_icon, action_url, success, version) -> Notification:
style = get_pypi_style(ntype)

if success:
description = f"Updated to version **`{version}`**\n\n-# [Action Log]({action_url})"
else:
description = f"An **error occurred** while updating {style['display_name']}.\n\n-# [Action Log]({action_url})"

return Notification(
username=style["username"],
messages=[
WebhookMessage(
embeds=[
Embed(
title=style["title"],
description=description,
color=hex_to_int(style["hex_color"]),
timestamp="",
author=Author(name=author, icon_url=author_icon),
footer=Footer(
text=style["footer_text"],
icon_url=style["footer_icon_url"],
),
fields=[],
)
],
)
],
)
8 changes: 8 additions & 0 deletions notifications/formatting/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@
|--------|--------------------------|------------------------------|-----------|-------------------------------------|-------------------------|------------------------------|
| canvas | Canvas Notifications | https://tinyurl.com/ek4ytkan | #F2051D | CS {course_id} - Course Updates | MDXCanvas GitHub Action | https://tinyurl.com/4ky2afzx |
| docker | Gradescope Notifications | https://tinyurl.com/mr2fyjse | #03A1FC | CS {course_id} - Docker Updates | Docker GitHub Action | https://tinyurl.com/4ky2afzx |

# PyPi Packages

| type | username | display_name | title | hex_color | footer_text | footer_icon_url |
|------------------|--------------------------------|------------------|-------------------------|-----------|--------------------------------|------------------------------|
| mdxcanvas | MDXCanvas Notifications | MDXCanvas | MDXCanvas Update | #F56236 | MDXCanvas GitHub Action | https://tinyurl.com/4ky2afzx |
| markdowndata | MarkdownData Notifications | MarkdownData | MarkdownData Update | #13DC56 | MarkdownData GitHub Action | https://tinyurl.com/4ky2afzx |
| byu_pytest_utils | BYU Pytest Utils Notifications | BYU Pytest Utils | BYU Pytest Utils Update | #3498DB | BYU Pytest Utils GitHub Action | https://tinyurl.com/4ky2afzx |
41 changes: 41 additions & 0 deletions notifications/send_pypi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
from argparse import ArgumentParser

from .formatting.pypi_format import format_notification
from .send_notification import send_notification


def main(ntype, author, author_icon, action_url, success=None, version=None, cicd_role_id=None):
webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
if not webhook_url:
raise EnvironmentError("DISCORD_WEBHOOK_URL environment variable is not set.")

notification = format_notification(
ntype=ntype,
author=author,
author_icon=author_icon,
action_url=action_url,
success=success,
version=version,
)

if not success and cicd_role_id and notification.messages:
notification.messages[0].content = f"<@&{cicd_role_id}>"

send_notification(webhook_url, notification)


if __name__ == "__main__":
parser = ArgumentParser(description="Send PyPI update notifications to Discord.")
parser.add_argument("--type", required=True, choices=["mdxcanvas", "markdowndata", "byu_pytest_utils"],
help="Type of notification")
parser.add_argument("--author", required=True, help="Name of the author")
parser.add_argument("--author-icon", required=True, help="URL of the author's icon")
parser.add_argument("--action-url", required=True, help="URL to the GHA")
parser.add_argument("--success", nargs='?', const=None, default=None, help="Bool indicating success or failure")
parser.add_argument("--version", nargs='?', const=None, default=None, help="PyPi version")
parser.add_argument("--cicd-id", nargs='?', const=None, default=None, help="CI/CD Role ID")

args = parser.parse_args()

main(args.type, args.author, args.author_icon, args.action_url, args.success, args.version, args.cicd_id)
21 changes: 0 additions & 21 deletions pypi_updates/build_send_update_notification_docker.sh

This file was deleted.

124 changes: 0 additions & 124 deletions pypi_updates/send_update_notification.py

This file was deleted.

15 changes: 15 additions & 0 deletions tests/test_embed_chunking.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,18 @@ def test_all_fields_preserved_when_within_limits(self):
b.add_field(f"f{i}", f"v{i}")
embed = b.build()
assert len(embed.fields) == 10

def test_typical_pypi_embed_stays_small(self):
b = EmbedBuilder(
title="PyPI Update",
description="A new version has been published.",
color=0x3B82F6,
timestamp="2025-01-01T00:00:00Z",
author=Author(name="PyPI Bot"),
footer=Footer(text="BeanLab Dev Utils"),
)
b.add_field("Package", "my-package")
b.add_field("Version", "1.2.3")
b.add_field("Status", "Published")
embed = b.build()
assert calc_embed_size(embed) < EMBED_CHAR_LIMIT