From 979d7c3b74d8fdfc5faf2d3d4147fcd529378a4d Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 9 Dec 2025 15:25:43 -0600 Subject: [PATCH] feat(blocks): Add 4 new GitHub webhook trigger blocks (#11588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I want to be able to automate some actions on social media or our sevrver in response to actions from discord ### Changes 🏗️ Add trigger blocks for common GitHub events to enable OSS automation: - GithubReleaseTriggerBlock: Trigger on release events (published, etc.) - GithubStarTriggerBlock: Trigger on star events for milestone celebrations - GithubIssuesTriggerBlock: Trigger on issue events for triage/notifications - GithubDiscussionTriggerBlock: Trigger on discussion events for Q&A sync ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Test Stars - [x] Test Discussions - [x] Test Issues - [x] Test Release 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../example_payloads/discussion.created.json | 108 +++++ .../example_payloads/issues.opened.json | 112 +++++ .../example_payloads/release.published.json | 97 +++++ .../github/example_payloads/star.created.json | 53 +++ .../backend/backend/blocks/github/triggers.py | 388 ++++++++++++++++++ .../frontend/src/tests/pages/build.page.ts | 50 ++- 6 files changed, 799 insertions(+), 9 deletions(-) create mode 100644 autogpt_platform/backend/backend/blocks/github/example_payloads/discussion.created.json create mode 100644 autogpt_platform/backend/backend/blocks/github/example_payloads/issues.opened.json create mode 100644 autogpt_platform/backend/backend/blocks/github/example_payloads/release.published.json create mode 100644 autogpt_platform/backend/backend/blocks/github/example_payloads/star.created.json diff --git a/autogpt_platform/backend/backend/blocks/github/example_payloads/discussion.created.json b/autogpt_platform/backend/backend/blocks/github/example_payloads/discussion.created.json new file mode 100644 index 0000000000..6b0d73dda3 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/github/example_payloads/discussion.created.json @@ -0,0 +1,108 @@ +{ + "action": "created", + "discussion": { + "repository_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "category": { + "id": 12345678, + "node_id": "DIC_kwDOJKSTjM4CXXXX", + "repository_id": 614765452, + "emoji": ":pray:", + "name": "Q&A", + "description": "Ask the community for help", + "created_at": "2023-03-16T09:21:07Z", + "updated_at": "2023-03-16T09:21:07Z", + "slug": "q-a", + "is_answerable": true + }, + "answer_html_url": null, + "answer_chosen_at": null, + "answer_chosen_by": null, + "html_url": "https://github.com/Significant-Gravitas/AutoGPT/discussions/9999", + "id": 5000000001, + "node_id": "D_kwDOJKSTjM4AYYYY", + "number": 9999, + "title": "How do I configure custom blocks?", + "user": { + "login": "curious-user", + "id": 22222222, + "node_id": "MDQ6VXNlcjIyMjIyMjIy", + "avatar_url": "https://avatars.githubusercontent.com/u/22222222?v=4", + "url": "https://api.github.com/users/curious-user", + "html_url": "https://github.com/curious-user", + "type": "User", + "site_admin": false + }, + "state": "open", + "state_reason": null, + "locked": false, + "comments": 0, + "created_at": "2024-12-01T17:00:00Z", + "updated_at": "2024-12-01T17:00:00Z", + "author_association": "NONE", + "active_lock_reason": null, + "body": "## Question\n\nI'm trying to create a custom block for my specific use case. I've read the documentation but I'm not sure how to:\n\n1. Define the input/output schema\n2. Handle authentication\n3. Test my block locally\n\nCan someone point me to examples or provide guidance?\n\n## Environment\n\n- AutoGPT Platform version: latest\n- Python: 3.11", + "reactions": { + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/discussions/9999/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/discussions/9999/timeline" + }, + "repository": { + "id": 614765452, + "node_id": "R_kgDOJKSTjA", + "name": "AutoGPT", + "full_name": "Significant-Gravitas/AutoGPT", + "private": false, + "owner": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "url": "https://api.github.com/users/Significant-Gravitas", + "html_url": "https://github.com/Significant-Gravitas", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/Significant-Gravitas/AutoGPT", + "description": "AutoGPT is the vision of accessible AI for everyone, to use and to build on.", + "fork": false, + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "created_at": "2023-03-16T09:21:07Z", + "updated_at": "2024-12-01T17:00:00Z", + "pushed_at": "2024-12-01T12:00:00Z", + "stargazers_count": 170000, + "watchers_count": 170000, + "language": "Python", + "has_discussions": true, + "forks_count": 45000, + "visibility": "public", + "default_branch": "master" + }, + "organization": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "url": "https://api.github.com/orgs/Significant-Gravitas", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "description": "" + }, + "sender": { + "login": "curious-user", + "id": 22222222, + "node_id": "MDQ6VXNlcjIyMjIyMjIy", + "avatar_url": "https://avatars.githubusercontent.com/u/22222222?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/curious-user", + "html_url": "https://github.com/curious-user", + "type": "User", + "site_admin": false + } +} diff --git a/autogpt_platform/backend/backend/blocks/github/example_payloads/issues.opened.json b/autogpt_platform/backend/backend/blocks/github/example_payloads/issues.opened.json new file mode 100644 index 0000000000..078d5da0be --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/github/example_payloads/issues.opened.json @@ -0,0 +1,112 @@ +{ + "action": "opened", + "issue": { + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345", + "repository_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "labels_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345/labels{/name}", + "comments_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345/comments", + "events_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345/events", + "html_url": "https://github.com/Significant-Gravitas/AutoGPT/issues/12345", + "id": 2000000001, + "node_id": "I_kwDOJKSTjM5wXXXX", + "number": 12345, + "title": "Bug: Application crashes when processing large files", + "user": { + "login": "bug-reporter", + "id": 11111111, + "node_id": "MDQ6VXNlcjExMTExMTEx", + "avatar_url": "https://avatars.githubusercontent.com/u/11111111?v=4", + "url": "https://api.github.com/users/bug-reporter", + "html_url": "https://github.com/bug-reporter", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 5272676214, + "node_id": "LA_kwDOJKSTjM8AAAABOkandg", + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/labels/bug", + "name": "bug", + "color": "d73a4a", + "default": true, + "description": "Something isn't working" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2024-12-01T16:00:00Z", + "updated_at": "2024-12-01T16:00:00Z", + "closed_at": null, + "author_association": "NONE", + "active_lock_reason": null, + "body": "## Description\n\nWhen I try to process a file larger than 100MB, the application crashes with an out of memory error.\n\n## Steps to Reproduce\n\n1. Open the application\n2. Select a file larger than 100MB\n3. Click 'Process'\n4. Application crashes\n\n## Expected Behavior\n\nThe application should handle large files gracefully.\n\n## Environment\n\n- OS: Ubuntu 22.04\n- Python: 3.11\n- AutoGPT Version: 1.0.0", + "reactions": { + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/issues/12345/timeline", + "state_reason": null + }, + "repository": { + "id": 614765452, + "node_id": "R_kgDOJKSTjA", + "name": "AutoGPT", + "full_name": "Significant-Gravitas/AutoGPT", + "private": false, + "owner": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "url": "https://api.github.com/users/Significant-Gravitas", + "html_url": "https://github.com/Significant-Gravitas", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/Significant-Gravitas/AutoGPT", + "description": "AutoGPT is the vision of accessible AI for everyone, to use and to build on.", + "fork": false, + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "created_at": "2023-03-16T09:21:07Z", + "updated_at": "2024-12-01T16:00:00Z", + "pushed_at": "2024-12-01T12:00:00Z", + "stargazers_count": 170000, + "watchers_count": 170000, + "language": "Python", + "forks_count": 45000, + "open_issues_count": 190, + "visibility": "public", + "default_branch": "master" + }, + "organization": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "url": "https://api.github.com/orgs/Significant-Gravitas", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "description": "" + }, + "sender": { + "login": "bug-reporter", + "id": 11111111, + "node_id": "MDQ6VXNlcjExMTExMTEx", + "avatar_url": "https://avatars.githubusercontent.com/u/11111111?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bug-reporter", + "html_url": "https://github.com/bug-reporter", + "type": "User", + "site_admin": false + } +} diff --git a/autogpt_platform/backend/backend/blocks/github/example_payloads/release.published.json b/autogpt_platform/backend/backend/blocks/github/example_payloads/release.published.json new file mode 100644 index 0000000000..eac8461e59 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/github/example_payloads/release.published.json @@ -0,0 +1,97 @@ +{ + "action": "published", + "release": { + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/releases/123456789", + "assets_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/releases/123456789/assets", + "upload_url": "https://uploads.github.com/repos/Significant-Gravitas/AutoGPT/releases/123456789/assets{?name,label}", + "html_url": "https://github.com/Significant-Gravitas/AutoGPT/releases/tag/v1.0.0", + "id": 123456789, + "author": { + "login": "ntindle", + "id": 12345678, + "node_id": "MDQ6VXNlcjEyMzQ1Njc4", + "avatar_url": "https://avatars.githubusercontent.com/u/12345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ntindle", + "html_url": "https://github.com/ntindle", + "type": "User", + "site_admin": false + }, + "node_id": "RE_kwDOJKSTjM4HWwAA", + "tag_name": "v1.0.0", + "target_commitish": "master", + "name": "AutoGPT Platform v1.0.0", + "draft": false, + "prerelease": false, + "created_at": "2024-12-01T10:00:00Z", + "published_at": "2024-12-01T12:00:00Z", + "assets": [ + { + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/releases/assets/987654321", + "id": 987654321, + "node_id": "RA_kwDOJKSTjM4HWwBB", + "name": "autogpt-v1.0.0.zip", + "label": "Release Package", + "content_type": "application/zip", + "state": "uploaded", + "size": 52428800, + "download_count": 0, + "created_at": "2024-12-01T11:30:00Z", + "updated_at": "2024-12-01T11:35:00Z", + "browser_download_url": "https://github.com/Significant-Gravitas/AutoGPT/releases/download/v1.0.0/autogpt-v1.0.0.zip" + } + ], + "tarball_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/tarball/v1.0.0", + "zipball_url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT/zipball/v1.0.0", + "body": "## What's New\n\n- Feature 1: Amazing new capability\n- Feature 2: Performance improvements\n- Bug fixes and stability improvements\n\n## Breaking Changes\n\nNone\n\n## Contributors\n\nThanks to all our contributors!" + }, + "repository": { + "id": 614765452, + "node_id": "R_kgDOJKSTjA", + "name": "AutoGPT", + "full_name": "Significant-Gravitas/AutoGPT", + "private": false, + "owner": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "url": "https://api.github.com/users/Significant-Gravitas", + "html_url": "https://github.com/Significant-Gravitas", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/Significant-Gravitas/AutoGPT", + "description": "AutoGPT is the vision of accessible AI for everyone, to use and to build on.", + "fork": false, + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "created_at": "2023-03-16T09:21:07Z", + "updated_at": "2024-12-01T12:00:00Z", + "pushed_at": "2024-12-01T12:00:00Z", + "stargazers_count": 170000, + "watchers_count": 170000, + "language": "Python", + "forks_count": 45000, + "visibility": "public", + "default_branch": "master" + }, + "organization": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "url": "https://api.github.com/orgs/Significant-Gravitas", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "description": "" + }, + "sender": { + "login": "ntindle", + "id": 12345678, + "node_id": "MDQ6VXNlcjEyMzQ1Njc4", + "avatar_url": "https://avatars.githubusercontent.com/u/12345678?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/ntindle", + "html_url": "https://github.com/ntindle", + "type": "User", + "site_admin": false + } +} diff --git a/autogpt_platform/backend/backend/blocks/github/example_payloads/star.created.json b/autogpt_platform/backend/backend/blocks/github/example_payloads/star.created.json new file mode 100644 index 0000000000..cb2dfd7522 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/github/example_payloads/star.created.json @@ -0,0 +1,53 @@ +{ + "action": "created", + "starred_at": "2024-12-01T15:30:00Z", + "repository": { + "id": 614765452, + "node_id": "R_kgDOJKSTjA", + "name": "AutoGPT", + "full_name": "Significant-Gravitas/AutoGPT", + "private": false, + "owner": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "url": "https://api.github.com/users/Significant-Gravitas", + "html_url": "https://github.com/Significant-Gravitas", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/Significant-Gravitas/AutoGPT", + "description": "AutoGPT is the vision of accessible AI for everyone, to use and to build on.", + "fork": false, + "url": "https://api.github.com/repos/Significant-Gravitas/AutoGPT", + "created_at": "2023-03-16T09:21:07Z", + "updated_at": "2024-12-01T15:30:00Z", + "pushed_at": "2024-12-01T12:00:00Z", + "stargazers_count": 170001, + "watchers_count": 170001, + "language": "Python", + "forks_count": 45000, + "visibility": "public", + "default_branch": "master" + }, + "organization": { + "login": "Significant-Gravitas", + "id": 130738209, + "node_id": "O_kgDOB8roIQ", + "url": "https://api.github.com/orgs/Significant-Gravitas", + "avatar_url": "https://avatars.githubusercontent.com/u/130738209?v=4", + "description": "" + }, + "sender": { + "login": "awesome-contributor", + "id": 98765432, + "node_id": "MDQ6VXNlcjk4NzY1NDMy", + "avatar_url": "https://avatars.githubusercontent.com/u/98765432?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/awesome-contributor", + "html_url": "https://github.com/awesome-contributor", + "type": "User", + "site_admin": false + } +} diff --git a/autogpt_platform/backend/backend/blocks/github/triggers.py b/autogpt_platform/backend/backend/blocks/github/triggers.py index f7215b8f8e..2fc568a468 100644 --- a/autogpt_platform/backend/backend/blocks/github/triggers.py +++ b/autogpt_platform/backend/backend/blocks/github/triggers.py @@ -159,3 +159,391 @@ class GithubPullRequestTriggerBlock(GitHubTriggerBase, Block): # --8<-- [end:GithubTriggerExample] + + +class GithubStarTriggerBlock(GitHubTriggerBase, Block): + """Trigger block for GitHub star events - useful for milestone celebrations.""" + + EXAMPLE_PAYLOAD_FILE = ( + Path(__file__).parent / "example_payloads" / "star.created.json" + ) + + class Input(GitHubTriggerBase.Input): + class EventsFilter(BaseModel): + """ + https://docs.github.com/en/webhooks/webhook-events-and-payloads#star + """ + + created: bool = False + deleted: bool = False + + events: EventsFilter = SchemaField( + title="Events", description="The star events to subscribe to" + ) + + class Output(GitHubTriggerBase.Output): + event: str = SchemaField( + description="The star event that triggered the webhook ('created' or 'deleted')" + ) + starred_at: str = SchemaField( + description="ISO timestamp when the repo was starred (empty if deleted)" + ) + stargazers_count: int = SchemaField( + description="Current number of stars on the repository" + ) + repository_name: str = SchemaField( + description="Full name of the repository (owner/repo)" + ) + repository_url: str = SchemaField(description="URL to the repository") + + def __init__(self): + from backend.integrations.webhooks.github import GithubWebhookType + + example_payload = json.loads( + self.EXAMPLE_PAYLOAD_FILE.read_text(encoding="utf-8") + ) + + super().__init__( + id="551e0a35-100b-49b7-89b8-3031322239b6", + description="This block triggers on GitHub star events. " + "Useful for celebrating milestones (e.g., 1k, 10k stars) or tracking engagement.", + categories={BlockCategory.DEVELOPER_TOOLS, BlockCategory.INPUT}, + input_schema=GithubStarTriggerBlock.Input, + output_schema=GithubStarTriggerBlock.Output, + webhook_config=BlockWebhookConfig( + provider=ProviderName.GITHUB, + webhook_type=GithubWebhookType.REPO, + resource_format="{repo}", + event_filter_input="events", + event_format="star.{event}", + ), + test_input={ + "repo": "Significant-Gravitas/AutoGPT", + "events": {"created": True}, + "credentials": TEST_CREDENTIALS_INPUT, + "payload": example_payload, + }, + test_credentials=TEST_CREDENTIALS, + test_output=[ + ("payload", example_payload), + ("triggered_by_user", example_payload["sender"]), + ("event", example_payload["action"]), + ("starred_at", example_payload.get("starred_at", "")), + ("stargazers_count", example_payload["repository"]["stargazers_count"]), + ("repository_name", example_payload["repository"]["full_name"]), + ("repository_url", example_payload["repository"]["html_url"]), + ], + ) + + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value + yield "event", input_data.payload["action"] + yield "starred_at", input_data.payload.get("starred_at", "") + yield "stargazers_count", input_data.payload["repository"]["stargazers_count"] + yield "repository_name", input_data.payload["repository"]["full_name"] + yield "repository_url", input_data.payload["repository"]["html_url"] + + +class GithubReleaseTriggerBlock(GitHubTriggerBase, Block): + """Trigger block for GitHub release events - ideal for announcing new versions.""" + + EXAMPLE_PAYLOAD_FILE = ( + Path(__file__).parent / "example_payloads" / "release.published.json" + ) + + class Input(GitHubTriggerBase.Input): + class EventsFilter(BaseModel): + """ + https://docs.github.com/en/webhooks/webhook-events-and-payloads#release + """ + + published: bool = False + unpublished: bool = False + created: bool = False + edited: bool = False + deleted: bool = False + prereleased: bool = False + released: bool = False + + events: EventsFilter = SchemaField( + title="Events", description="The release events to subscribe to" + ) + + class Output(GitHubTriggerBase.Output): + event: str = SchemaField( + description="The release event that triggered the webhook (e.g., 'published')" + ) + release: dict = SchemaField(description="The full release object") + release_url: str = SchemaField(description="URL to the release page") + tag_name: str = SchemaField(description="The release tag name (e.g., 'v1.0.0')") + release_name: str = SchemaField(description="Human-readable release name") + body: str = SchemaField(description="Release notes/description") + prerelease: bool = SchemaField(description="Whether this is a prerelease") + draft: bool = SchemaField(description="Whether this is a draft release") + assets: list = SchemaField(description="List of release assets/files") + + def __init__(self): + from backend.integrations.webhooks.github import GithubWebhookType + + example_payload = json.loads( + self.EXAMPLE_PAYLOAD_FILE.read_text(encoding="utf-8") + ) + + super().__init__( + id="2052dd1b-74e1-46ac-9c87-c7a0e057b60b", + description="This block triggers on GitHub release events. " + "Perfect for automating announcements to Discord, Twitter, or other platforms.", + categories={BlockCategory.DEVELOPER_TOOLS, BlockCategory.INPUT}, + input_schema=GithubReleaseTriggerBlock.Input, + output_schema=GithubReleaseTriggerBlock.Output, + webhook_config=BlockWebhookConfig( + provider=ProviderName.GITHUB, + webhook_type=GithubWebhookType.REPO, + resource_format="{repo}", + event_filter_input="events", + event_format="release.{event}", + ), + test_input={ + "repo": "Significant-Gravitas/AutoGPT", + "events": {"published": True}, + "credentials": TEST_CREDENTIALS_INPUT, + "payload": example_payload, + }, + test_credentials=TEST_CREDENTIALS, + test_output=[ + ("payload", example_payload), + ("triggered_by_user", example_payload["sender"]), + ("event", example_payload["action"]), + ("release", example_payload["release"]), + ("release_url", example_payload["release"]["html_url"]), + ("tag_name", example_payload["release"]["tag_name"]), + ("release_name", example_payload["release"]["name"]), + ("body", example_payload["release"]["body"]), + ("prerelease", example_payload["release"]["prerelease"]), + ("draft", example_payload["release"]["draft"]), + ("assets", example_payload["release"]["assets"]), + ], + ) + + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value + release = input_data.payload["release"] + yield "event", input_data.payload["action"] + yield "release", release + yield "release_url", release["html_url"] + yield "tag_name", release["tag_name"] + yield "release_name", release.get("name", "") + yield "body", release.get("body", "") + yield "prerelease", release["prerelease"] + yield "draft", release["draft"] + yield "assets", release["assets"] + + +class GithubIssuesTriggerBlock(GitHubTriggerBase, Block): + """Trigger block for GitHub issues events - great for triage and notifications.""" + + EXAMPLE_PAYLOAD_FILE = ( + Path(__file__).parent / "example_payloads" / "issues.opened.json" + ) + + class Input(GitHubTriggerBase.Input): + class EventsFilter(BaseModel): + """ + https://docs.github.com/en/webhooks/webhook-events-and-payloads#issues + """ + + opened: bool = False + edited: bool = False + deleted: bool = False + closed: bool = False + reopened: bool = False + assigned: bool = False + unassigned: bool = False + labeled: bool = False + unlabeled: bool = False + locked: bool = False + unlocked: bool = False + transferred: bool = False + milestoned: bool = False + demilestoned: bool = False + pinned: bool = False + unpinned: bool = False + + events: EventsFilter = SchemaField( + title="Events", description="The issue events to subscribe to" + ) + + class Output(GitHubTriggerBase.Output): + event: str = SchemaField( + description="The issue event that triggered the webhook (e.g., 'opened')" + ) + number: int = SchemaField(description="The issue number") + issue: dict = SchemaField(description="The full issue object") + issue_url: str = SchemaField(description="URL to the issue") + issue_title: str = SchemaField(description="The issue title") + issue_body: str = SchemaField(description="The issue body/description") + labels: list = SchemaField(description="List of labels on the issue") + assignees: list = SchemaField(description="List of assignees") + state: str = SchemaField(description="Issue state ('open' or 'closed')") + + def __init__(self): + from backend.integrations.webhooks.github import GithubWebhookType + + example_payload = json.loads( + self.EXAMPLE_PAYLOAD_FILE.read_text(encoding="utf-8") + ) + + super().__init__( + id="b2605464-e486-4bf4-aad3-d8a213c8a48a", + description="This block triggers on GitHub issues events. " + "Useful for automated triage, notifications, and welcoming first-time contributors.", + categories={BlockCategory.DEVELOPER_TOOLS, BlockCategory.INPUT}, + input_schema=GithubIssuesTriggerBlock.Input, + output_schema=GithubIssuesTriggerBlock.Output, + webhook_config=BlockWebhookConfig( + provider=ProviderName.GITHUB, + webhook_type=GithubWebhookType.REPO, + resource_format="{repo}", + event_filter_input="events", + event_format="issues.{event}", + ), + test_input={ + "repo": "Significant-Gravitas/AutoGPT", + "events": {"opened": True}, + "credentials": TEST_CREDENTIALS_INPUT, + "payload": example_payload, + }, + test_credentials=TEST_CREDENTIALS, + test_output=[ + ("payload", example_payload), + ("triggered_by_user", example_payload["sender"]), + ("event", example_payload["action"]), + ("number", example_payload["issue"]["number"]), + ("issue", example_payload["issue"]), + ("issue_url", example_payload["issue"]["html_url"]), + ("issue_title", example_payload["issue"]["title"]), + ("issue_body", example_payload["issue"]["body"]), + ("labels", example_payload["issue"]["labels"]), + ("assignees", example_payload["issue"]["assignees"]), + ("state", example_payload["issue"]["state"]), + ], + ) + + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value + issue = input_data.payload["issue"] + yield "event", input_data.payload["action"] + yield "number", issue["number"] + yield "issue", issue + yield "issue_url", issue["html_url"] + yield "issue_title", issue["title"] + yield "issue_body", issue.get("body") or "" + yield "labels", issue["labels"] + yield "assignees", issue["assignees"] + yield "state", issue["state"] + + +class GithubDiscussionTriggerBlock(GitHubTriggerBase, Block): + """Trigger block for GitHub discussion events - perfect for community Q&A sync.""" + + EXAMPLE_PAYLOAD_FILE = ( + Path(__file__).parent / "example_payloads" / "discussion.created.json" + ) + + class Input(GitHubTriggerBase.Input): + class EventsFilter(BaseModel): + """ + https://docs.github.com/en/webhooks/webhook-events-and-payloads#discussion + """ + + created: bool = False + edited: bool = False + deleted: bool = False + answered: bool = False + unanswered: bool = False + labeled: bool = False + unlabeled: bool = False + locked: bool = False + unlocked: bool = False + category_changed: bool = False + transferred: bool = False + pinned: bool = False + unpinned: bool = False + + events: EventsFilter = SchemaField( + title="Events", description="The discussion events to subscribe to" + ) + + class Output(GitHubTriggerBase.Output): + event: str = SchemaField( + description="The discussion event that triggered the webhook" + ) + number: int = SchemaField(description="The discussion number") + discussion: dict = SchemaField(description="The full discussion object") + discussion_url: str = SchemaField(description="URL to the discussion") + title: str = SchemaField(description="The discussion title") + body: str = SchemaField(description="The discussion body") + category: dict = SchemaField(description="The discussion category object") + category_name: str = SchemaField(description="Name of the category") + state: str = SchemaField(description="Discussion state") + + def __init__(self): + from backend.integrations.webhooks.github import GithubWebhookType + + example_payload = json.loads( + self.EXAMPLE_PAYLOAD_FILE.read_text(encoding="utf-8") + ) + + super().__init__( + id="87f847b3-d81a-424e-8e89-acadb5c9d52b", + description="This block triggers on GitHub Discussions events. " + "Great for syncing Q&A to Discord or auto-responding to common questions. " + "Note: Discussions must be enabled on the repository.", + categories={BlockCategory.DEVELOPER_TOOLS, BlockCategory.INPUT}, + input_schema=GithubDiscussionTriggerBlock.Input, + output_schema=GithubDiscussionTriggerBlock.Output, + webhook_config=BlockWebhookConfig( + provider=ProviderName.GITHUB, + webhook_type=GithubWebhookType.REPO, + resource_format="{repo}", + event_filter_input="events", + event_format="discussion.{event}", + ), + test_input={ + "repo": "Significant-Gravitas/AutoGPT", + "events": {"created": True}, + "credentials": TEST_CREDENTIALS_INPUT, + "payload": example_payload, + }, + test_credentials=TEST_CREDENTIALS, + test_output=[ + ("payload", example_payload), + ("triggered_by_user", example_payload["sender"]), + ("event", example_payload["action"]), + ("number", example_payload["discussion"]["number"]), + ("discussion", example_payload["discussion"]), + ("discussion_url", example_payload["discussion"]["html_url"]), + ("title", example_payload["discussion"]["title"]), + ("body", example_payload["discussion"]["body"]), + ("category", example_payload["discussion"]["category"]), + ("category_name", example_payload["discussion"]["category"]["name"]), + ("state", example_payload["discussion"]["state"]), + ], + ) + + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value + discussion = input_data.payload["discussion"] + yield "event", input_data.payload["action"] + yield "number", discussion["number"] + yield "discussion", discussion + yield "discussion_url", discussion["html_url"] + yield "title", discussion["title"] + yield "body", discussion.get("body") or "" + yield "category", discussion["category"] + yield "category_name", discussion["category"]["name"] + yield "state", discussion["state"] diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index 709b0ef3ed..8acc9a8f40 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -472,14 +472,44 @@ export class BuildPage extends BasePage { ); } - async getGithubTriggerBlockDetails(): Promise { - return { - id: "6c60ec01-8128-419e-988f-96a063ee2fea", - name: "Github Trigger", - description: - "This block triggers on pull request events and outputs the event type and payload.", - type: "Standard", - }; + async getGithubTriggerBlockDetails(): Promise { + return [ + { + id: "6c60ec01-8128-419e-988f-96a063ee2fea", + name: "Github Trigger", + description: + "This block triggers on pull request events and outputs the event type and payload.", + type: "Standard", + }, + { + id: "551e0a35-100b-49b7-89b8-3031322239b6", + name: "Github Star Trigger", + description: + "This block triggers on star events and outputs the event type and payload.", + type: "Standard", + }, + { + id: "2052dd1b-74e1-46ac-9c87-c7a0e057b60b", + name: "Github Release Trigger", + description: + "This block triggers on release events and outputs the event type and payload.", + type: "Standard", + }, + { + id: "b2605464-e486-4bf4-aad3-d8a213c8a48a", + name: "Github Issue Trigger", + description: + "This block triggers on issue events and outputs the event type and payload.", + type: "Standard", + }, + { + id: "87f847b3-d81a-424e-8e89-acadb5c9d52b", + name: "Github Discussion Trigger", + description: + "This block triggers on discussion events and outputs the event type and payload.", + type: "Standard", + }, + ]; } async nextTutorialStep(): Promise { @@ -488,7 +518,9 @@ export class BuildPage extends BasePage { } async getBlocksToSkip(): Promise { - return [(await this.getGithubTriggerBlockDetails()).id]; + return [ + (await this.getGithubTriggerBlockDetails()).map((b) => b.id), + ].flat(); } async createDummyAgent() {