mirror of
https://github.com/langgenius/dify-plugin-sdks.git
synced 2026-08-25 12:29:32 -04:00
feat: expand GitHub issue trigger events in provider
- Added new event mappings for issue closed, reopened, edited, labeled, unlabeled, assigned, and unassigned in the GithubTrigger class. - Updated the YAML configuration to include the new events for subscription. These changes enhance the GitHub trigger functionality by supporting a broader range of issue events, improving responsiveness to user interactions.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueAssignedEvent(Event):
|
||||
"""
|
||||
GitHub Issue Assigned Event
|
||||
|
||||
This event transforms GitHub issue assigned webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
The payload includes an 'assignee' object with the user who was assigned.
|
||||
"""
|
||||
|
||||
def _check_assigned_to(self, payload: Mapping[str, Any], assigned_to_param: str | None) -> None:
|
||||
"""Check if the assignee matches the allowed users"""
|
||||
if not assigned_to_param:
|
||||
return
|
||||
|
||||
allowed_assignees = [assignee.strip() for assignee in assigned_to_param.split(",") if assignee.strip()]
|
||||
if not allowed_assignees:
|
||||
return
|
||||
|
||||
# The payload contains the assignee who was assigned
|
||||
assignee = payload.get("assignee", {})
|
||||
assignee_login = assignee.get("login", "")
|
||||
|
||||
if assignee_login not in allowed_assignees:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labels(self, issue: Mapping[str, Any], labels_param: str | None) -> None:
|
||||
"""Check if issue has required labels"""
|
||||
if not labels_param:
|
||||
return
|
||||
|
||||
required_labels = [label.strip() for label in labels_param.split(",") if label.strip()]
|
||||
if not required_labels:
|
||||
return
|
||||
|
||||
issue_labels = [label.get("name") for label in issue.get("labels", [])]
|
||||
if not any(label in issue_labels for label in required_labels):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_assigner(self, payload: Mapping[str, Any], assigner_param: str | None) -> None:
|
||||
"""Check if the user who assigned the issue is in allowed list"""
|
||||
if not assigner_param:
|
||||
return
|
||||
|
||||
allowed_assigners = [assigner.strip() for assigner in assigner_param.split(",") if assigner.strip()]
|
||||
if not allowed_assigners:
|
||||
return
|
||||
|
||||
assigner = payload.get("sender", {}).get("login")
|
||||
if assigner not in allowed_assigners:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue assigned webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_assigned_to(payload, parameters.get("assigned_to"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labels(issue, parameters.get("labels"))
|
||||
self._check_assigner(payload, parameters.get("assigner"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,137 @@
|
||||
identity:
|
||||
name: issue_assigned
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Assigned
|
||||
zh_Hans: Issue 分配
|
||||
ja_JP: Issue 割り当て
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when an issue is assigned to a user
|
||||
zh_Hans: 当 issue 被分配给用户时触发
|
||||
ja_JP: issue がユーザーに割り当てられたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when an issue is assigned to a user on a GitHub repository, providing information about the issue, the assignee, repository, and user who made the assignment.
|
||||
zh_Hans: 当在 GitHub 仓库中将 issue 分配给用户时,此触发器会被激活,提供有关 issue、被分配者、仓库和进行分配的用户的信息。
|
||||
ja_JP: issue が GitHub リポジトリでユーザーに割り当てられたときにトリガーがアクティブになり、issue、担当者、リポジトリ、および割り当てを行ったユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: assigned_to
|
||||
label:
|
||||
en_US: Assigned To
|
||||
zh_Hans: 分配给
|
||||
ja_JP: 割り当て先
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if assigned to these specific users (e.g., developer-1, team-member, engineer, comma-separated). Leave empty to trigger for any assignee."
|
||||
zh_Hans: "仅当分配给这些特定用户时触发(例如:developer-1, team-member, engineer,逗号分隔)。留空则对任何被分配者触发。"
|
||||
ja_JP: "これらの特定のユーザーに割り当てられた場合のみトリガー(例: developer-1, team-member, engineer,カンマ区切り)。空の場合は任意の担当者でトリガー。"
|
||||
|
||||
- name: labels
|
||||
label:
|
||||
en_US: Required Labels
|
||||
zh_Hans: 必需标签
|
||||
ja_JP: 必須ラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if issue has these labels (e.g., bug, high-priority, ready-for-dev, comma-separated). Leave empty for no label filtering."
|
||||
zh_Hans: "仅当议题有这些标签时触发(例如:bug, high-priority, ready-for-dev,逗号分隔)。留空则不过滤标签。"
|
||||
ja_JP: "イシューがこれらのラベルを持つ場合のみトリガー(例: bug, high-priority, ready-for-dev,カンマ区切り)。空の場合はラベルフィルタなし。"
|
||||
|
||||
- name: assigner
|
||||
label:
|
||||
en_US: Assigned By
|
||||
zh_Hans: 分配者
|
||||
ja_JP: 割り当てたユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if assigned by these users (e.g., project-manager, team-lead, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户分配时触发(例如:project-manager, team-lead,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによって割り当てられた場合のみトリガー(例: project-manager, team-lead,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Next-Release, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Next-Release,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Next-Release,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[TASK\\].*, .*assigned.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[TASK\\].*, .*assigned.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[TASK\\].*, .*assigned.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
assignee:
|
||||
type: object
|
||||
description: The user who was assigned to the issue
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the issue was assigned
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- assignee
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_assigned.py
|
||||
@@ -0,0 +1,104 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueClosedEvent(Event):
|
||||
"""
|
||||
GitHub Issue Closed Event
|
||||
|
||||
This event transforms GitHub issue closed webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
"""
|
||||
|
||||
def _check_state_reason(self, issue: Mapping[str, Any], state_reason_param: str | None) -> None:
|
||||
"""Check if issue close reason matches allowed reasons"""
|
||||
if not state_reason_param:
|
||||
return
|
||||
|
||||
allowed_reasons = [reason.strip() for reason in state_reason_param.split(",") if reason.strip()]
|
||||
if not allowed_reasons:
|
||||
return
|
||||
|
||||
issue_state_reason = issue.get("state_reason")
|
||||
if not issue_state_reason or issue_state_reason not in allowed_reasons:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labels(self, issue: Mapping[str, Any], labels_param: str | None) -> None:
|
||||
"""Check if issue has required labels"""
|
||||
if not labels_param:
|
||||
return
|
||||
|
||||
required_labels = [label.strip() for label in labels_param.split(",") if label.strip()]
|
||||
if not required_labels:
|
||||
return
|
||||
|
||||
issue_labels = [label.get("name") for label in issue.get("labels", [])]
|
||||
if not any(label in issue_labels for label in required_labels):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_closer(self, payload: Mapping[str, Any], closer_param: str | None) -> None:
|
||||
"""Check if the user who closed the issue is in allowed list"""
|
||||
if not closer_param:
|
||||
return
|
||||
|
||||
allowed_closers = [closer.strip() for closer in closer_param.split(",") if closer.strip()]
|
||||
if not allowed_closers:
|
||||
return
|
||||
|
||||
closer = payload.get("sender", {}).get("login")
|
||||
if closer not in allowed_closers:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue closed webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_state_reason(issue, parameters.get("state_reason"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labels(issue, parameters.get("labels"))
|
||||
self._check_closer(payload, parameters.get("closer"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,129 @@
|
||||
identity:
|
||||
name: issue_closed
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Closed
|
||||
zh_Hans: Issue 关闭
|
||||
ja_JP: Issue クローズ
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when an issue is closed
|
||||
zh_Hans: 当 issue 关闭时触发
|
||||
ja_JP: issue がクローズされたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when an issue is closed on a GitHub repository, providing information about the issue, close reason, repository, and user who closed it.
|
||||
zh_Hans: 当在 GitHub 仓库中关闭 issue 时,此触发器会被激活,提供有关 issue、关闭原因、仓库和关闭它的用户的信息。
|
||||
ja_JP: issue が GitHub リポジトリでクローズされたときにトリガーがアクティブになり、issue、クローズ理由、リポジトリ、および issue をクローズしたユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: state_reason
|
||||
label:
|
||||
en_US: Close Reason
|
||||
zh_Hans: 关闭原因
|
||||
ja_JP: クローズ理由
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for specific close reasons (e.g., completed, not_planned, comma-separated). Leave empty to trigger for all close reasons."
|
||||
zh_Hans: "仅对特定关闭原因触发(例如:completed, not_planned,逗号分隔)。留空则对所有关闭原因触发。"
|
||||
ja_JP: "特定のクローズ理由のみトリガー(例: completed, not_planned,カンマ区切り)。空の場合は全てのクローズ理由でトリガー。"
|
||||
|
||||
- name: labels
|
||||
label:
|
||||
en_US: Required Labels
|
||||
zh_Hans: 必需标签
|
||||
ja_JP: 必須ラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if issue has these labels (e.g., bug, critical, resolved, comma-separated). Leave empty for no label filtering."
|
||||
zh_Hans: "仅当议题有这些标签时触发(例如:bug, critical, resolved,逗号分隔)。留空则不过滤标签。"
|
||||
ja_JP: "イシューがこれらのラベルを持つ場合のみトリガー(例: bug, critical, resolved,カンマ区切り)。空の場合はラベルフィルタなし。"
|
||||
|
||||
- name: closer
|
||||
label:
|
||||
en_US: Closed By
|
||||
zh_Hans: 关闭者
|
||||
ja_JP: クローズしたユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if closed by these users (e.g., john, alice, bot-user, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户关闭时触发(例如:john, alice, bot-user,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによってクローズされた場合のみトリガー(例: john, alice, bot-user,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Sprint-1, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Sprint-1,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Sprint-1,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[BUG\\].*, .*URGENT.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[BUG\\].*, .*URGENT.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[BUG\\].*, .*URGENT.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the issue was closed
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_closed.py
|
||||
@@ -0,0 +1,92 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueEditedEvent(Event):
|
||||
"""
|
||||
GitHub Issue Edited Event
|
||||
|
||||
This event transforms GitHub issue edited webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
The payload includes a 'changes' object showing what was modified.
|
||||
"""
|
||||
|
||||
def _check_changed_fields(self, payload: Mapping[str, Any], changed_fields_param: str | None) -> None:
|
||||
"""Check if the edited fields match the allowed fields"""
|
||||
if not changed_fields_param:
|
||||
return
|
||||
|
||||
allowed_fields = [field.strip() for field in changed_fields_param.split(",") if field.strip()]
|
||||
if not allowed_fields:
|
||||
return
|
||||
|
||||
changes = payload.get("changes", {})
|
||||
if not changes:
|
||||
raise EventIgnoreError()
|
||||
|
||||
# Check if any of the changed fields match the allowed fields
|
||||
changed_field_names = list(changes.keys())
|
||||
if not any(field in changed_field_names for field in allowed_fields):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labels(self, issue: Mapping[str, Any], labels_param: str | None) -> None:
|
||||
"""Check if issue has required labels"""
|
||||
if not labels_param:
|
||||
return
|
||||
|
||||
required_labels = [label.strip() for label in labels_param.split(",") if label.strip()]
|
||||
if not required_labels:
|
||||
return
|
||||
|
||||
issue_labels = [label.get("name") for label in issue.get("labels", [])]
|
||||
if not any(label in issue_labels for label in required_labels):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_editor(self, payload: Mapping[str, Any], editor_param: str | None) -> None:
|
||||
"""Check if the user who edited the issue is in allowed list"""
|
||||
if not editor_param:
|
||||
return
|
||||
|
||||
allowed_editors = [editor.strip() for editor in editor_param.split(",") if editor.strip()]
|
||||
if not allowed_editors:
|
||||
return
|
||||
|
||||
editor = payload.get("sender", {}).get("login")
|
||||
if editor not in allowed_editors:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue edited webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_changed_fields(payload, parameters.get("changed_fields"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labels(issue, parameters.get("labels"))
|
||||
self._check_editor(payload, parameters.get("editor"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,121 @@
|
||||
identity:
|
||||
name: issue_edited
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Edited
|
||||
zh_Hans: Issue 编辑
|
||||
ja_JP: Issue 編集
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when an issue is edited
|
||||
zh_Hans: 当 issue 被编辑时触发
|
||||
ja_JP: issue が編集されたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when an issue is edited on a GitHub repository, providing information about the issue, what was changed, repository, and user who edited it.
|
||||
zh_Hans: 当在 GitHub 仓库中编辑 issue 时,此触发器会被激活,提供有关 issue、更改内容、仓库和编辑它的用户的信息。
|
||||
ja_JP: issue が GitHub リポジトリで編集されたときにトリガーがアクティブになり、issue、変更内容、リポジトリ、および issue を編集したユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: changed_fields
|
||||
label:
|
||||
en_US: Changed Fields
|
||||
zh_Hans: 更改的字段
|
||||
ja_JP: 変更されたフィールド
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if these fields were changed (e.g., title, body, comma-separated). Leave empty to trigger on any field change."
|
||||
zh_Hans: "仅当这些字段被更改时触发(例如:title, body,逗号分隔)。留空则对任何字段更改触发。"
|
||||
ja_JP: "これらのフィールドが変更された場合のみトリガー(例: title, body,カンマ区切り)。空の場合は任意のフィールド変更でトリガー。"
|
||||
|
||||
- name: labels
|
||||
label:
|
||||
en_US: Required Labels
|
||||
zh_Hans: 必需标签
|
||||
ja_JP: 必須ラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if issue has these labels (e.g., documentation, enhancement, needs-review, comma-separated). Leave empty for no label filtering."
|
||||
zh_Hans: "仅当议题有这些标签时触发(例如:documentation, enhancement, needs-review,逗号分隔)。留空则不过滤标签。"
|
||||
ja_JP: "イシューがこれらのラベルを持つ場合のみトリガー(例: documentation, enhancement, needs-review,カンマ区切り)。空の場合はラベルフィルタなし。"
|
||||
|
||||
- name: editor
|
||||
label:
|
||||
en_US: Edited By
|
||||
zh_Hans: 编辑者
|
||||
ja_JP: 編集したユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if edited by these users (e.g., maintainer, contributor, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户编辑时触发(例如:maintainer, contributor,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによって編集された場合のみトリガー(例: maintainer, contributor,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[DOCS\\].*, .*updated.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[DOCS\\].*, .*updated.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[DOCS\\].*, .*updated.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
changes:
|
||||
type: object
|
||||
description: The fields that were changed and their previous values
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the issue was edited
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_edited.py
|
||||
@@ -0,0 +1,94 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueLabeledEvent(Event):
|
||||
"""
|
||||
GitHub Issue Labeled Event
|
||||
|
||||
This event transforms GitHub issue labeled webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
The payload includes a 'label' object with the label that was added.
|
||||
"""
|
||||
|
||||
def _check_added_label(self, payload: Mapping[str, Any], added_label_param: str | None) -> None:
|
||||
"""Check if the added label matches the allowed labels"""
|
||||
if not added_label_param:
|
||||
return
|
||||
|
||||
allowed_labels = [label.strip() for label in added_label_param.split(",") if label.strip()]
|
||||
if not allowed_labels:
|
||||
return
|
||||
|
||||
# The payload contains the label that was added
|
||||
label = payload.get("label", {})
|
||||
label_name = label.get("name", "")
|
||||
|
||||
if label_name not in allowed_labels:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labeler(self, payload: Mapping[str, Any], labeler_param: str | None) -> None:
|
||||
"""Check if the user who added the label is in allowed list"""
|
||||
if not labeler_param:
|
||||
return
|
||||
|
||||
allowed_labelers = [labeler.strip() for labeler in labeler_param.split(",") if labeler.strip()]
|
||||
if not allowed_labelers:
|
||||
return
|
||||
|
||||
labeler = payload.get("sender", {}).get("login")
|
||||
if labeler not in allowed_labelers:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue labeled webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_added_label(payload, parameters.get("added_label"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labeler(payload, parameters.get("labeler"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,126 @@
|
||||
identity:
|
||||
name: issue_labeled
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Labeled
|
||||
zh_Hans: Issue 添加标签
|
||||
ja_JP: Issue ラベル追加
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when a label is added to an issue
|
||||
zh_Hans: 当给 issue 添加标签时触发
|
||||
ja_JP: issue にラベルが追加されたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when a label is added to an issue on a GitHub repository, providing information about the issue, the label that was added, repository, and user who added it.
|
||||
zh_Hans: 当在 GitHub 仓库中给 issue 添加标签时,此触发器会被激活,提供有关 issue、添加的标签、仓库和添加它的用户的信息。
|
||||
ja_JP: issue に GitHub リポジトリでラベルが追加されたときにトリガーがアクティブになり、issue、追加されたラベル、リポジトリ、およびラベルを追加したユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: added_label
|
||||
label:
|
||||
en_US: Added Label
|
||||
zh_Hans: 添加的标签
|
||||
ja_JP: 追加されたラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if these specific labels were added (e.g., critical, priority-high, security, comma-separated). Leave empty to trigger for any label addition."
|
||||
zh_Hans: "仅当添加了这些特定标签时触发(例如:critical, priority-high, security,逗号分隔)。留空则对任何标签添加触发。"
|
||||
ja_JP: "これらの特定のラベルが追加された場合のみトリガー(例: critical, priority-high, security,カンマ区切り)。空の場合は任意のラベル追加でトリガー。"
|
||||
|
||||
- name: labeler
|
||||
label:
|
||||
en_US: Labeled By
|
||||
zh_Hans: 标签添加者
|
||||
ja_JP: ラベルを追加したユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if labeled by these users (e.g., project-manager, triage-bot, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户添加标签时触发(例如:project-manager, triage-bot,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによってラベルが追加された場合のみトリガー(例: project-manager, triage-bot,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Release-Candidate, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Release-Candidate,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Release-Candidate,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[URGENT\\].*, .*security.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[URGENT\\].*, .*security.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[URGENT\\].*, .*security.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
label:
|
||||
type: object
|
||||
description: The label that was added
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- color
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the label was added
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- label
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_labeled.py
|
||||
@@ -0,0 +1,90 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueReopenedEvent(Event):
|
||||
"""
|
||||
GitHub Issue Reopened Event
|
||||
|
||||
This event transforms GitHub issue reopened webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
"""
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labels(self, issue: Mapping[str, Any], labels_param: str | None) -> None:
|
||||
"""Check if issue has required labels"""
|
||||
if not labels_param:
|
||||
return
|
||||
|
||||
required_labels = [label.strip() for label in labels_param.split(",") if label.strip()]
|
||||
if not required_labels:
|
||||
return
|
||||
|
||||
issue_labels = [label.get("name") for label in issue.get("labels", [])]
|
||||
if not any(label in issue_labels for label in required_labels):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_reopener(self, payload: Mapping[str, Any], reopener_param: str | None) -> None:
|
||||
"""Check if the user who reopened the issue is in allowed list"""
|
||||
if not reopener_param:
|
||||
return
|
||||
|
||||
allowed_reopeners = [reopener.strip() for reopener in reopener_param.split(",") if reopener.strip()]
|
||||
if not allowed_reopeners:
|
||||
return
|
||||
|
||||
reopener = payload.get("sender", {}).get("login")
|
||||
if reopener not in allowed_reopeners:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue reopened webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labels(issue, parameters.get("labels"))
|
||||
self._check_reopener(payload, parameters.get("reopener"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,117 @@
|
||||
identity:
|
||||
name: issue_reopened
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Reopened
|
||||
zh_Hans: Issue 重新打开
|
||||
ja_JP: Issue 再オープン
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when a closed issue is reopened
|
||||
zh_Hans: 当已关闭的 issue 重新打开时触发
|
||||
ja_JP: クローズされた issue が再度オープンされたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when a closed issue is reopened on a GitHub repository, providing information about the issue, repository, and user who reopened it.
|
||||
zh_Hans: 当在 GitHub 仓库中重新打开已关闭的 issue 时,此触发器会被激活,提供有关 issue、仓库和重新打开它的用户的信息。
|
||||
ja_JP: クローズされた issue が GitHub リポジトリで再度オープンされたときにトリガーがアクティブになり、issue、リポジトリ、および issue を再オープンしたユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: labels
|
||||
label:
|
||||
en_US: Required Labels
|
||||
zh_Hans: 必需标签
|
||||
ja_JP: 必須ラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if issue has these labels (e.g., bug, needs-attention, regression, comma-separated). Leave empty for no label filtering."
|
||||
zh_Hans: "仅当议题有这些标签时触发(例如:bug, needs-attention, regression,逗号分隔)。留空则不过滤标签。"
|
||||
ja_JP: "イシューがこれらのラベルを持つ場合のみトリガー(例: bug, needs-attention, regression,カンマ区切り)。空の場合はラベルフィルタなし。"
|
||||
|
||||
- name: reopener
|
||||
label:
|
||||
en_US: Reopened By
|
||||
zh_Hans: 重新打开者
|
||||
ja_JP: 再オープンしたユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if reopened by these users (e.g., project-maintainer, team-lead, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户重新打开时触发(例如:project-maintainer, team-lead,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによって再オープンされた場合のみトリガー(例: project-maintainer, team-lead,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Hotfix, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Hotfix,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Hotfix,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[BUG\\].*, .*reopened.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[BUG\\].*, .*reopened.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[BUG\\].*, .*reopened.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the issue was reopened
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_reopened.py
|
||||
@@ -0,0 +1,108 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueUnassignedEvent(Event):
|
||||
"""
|
||||
GitHub Issue Unassigned Event
|
||||
|
||||
This event transforms GitHub issue unassigned webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
The payload includes an 'assignee' object with the user who was unassigned.
|
||||
"""
|
||||
|
||||
def _check_unassigned_from(self, payload: Mapping[str, Any], unassigned_from_param: str | None) -> None:
|
||||
"""Check if the unassignee matches the allowed users"""
|
||||
if not unassigned_from_param:
|
||||
return
|
||||
|
||||
allowed_unassignees = [unassignee.strip() for unassignee in unassigned_from_param.split(",") if unassignee.strip()]
|
||||
if not allowed_unassignees:
|
||||
return
|
||||
|
||||
# The payload contains the assignee who was unassigned
|
||||
assignee = payload.get("assignee", {})
|
||||
assignee_login = assignee.get("login", "")
|
||||
|
||||
if assignee_login not in allowed_unassignees:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_labels(self, issue: Mapping[str, Any], labels_param: str | None) -> None:
|
||||
"""Check if issue has required labels"""
|
||||
if not labels_param:
|
||||
return
|
||||
|
||||
required_labels = [label.strip() for label in labels_param.split(",") if label.strip()]
|
||||
if not required_labels:
|
||||
return
|
||||
|
||||
issue_labels = [label.get("name") for label in issue.get("labels", [])]
|
||||
if not any(label in issue_labels for label in required_labels):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_unassigner(self, payload: Mapping[str, Any], unassigner_param: str | None) -> None:
|
||||
"""Check if the user who unassigned the issue is in allowed list"""
|
||||
if not unassigner_param:
|
||||
return
|
||||
|
||||
allowed_unassigners = [unassigner.strip() for unassigner in unassigner_param.split(",") if unassigner.strip()]
|
||||
if not allowed_unassigners:
|
||||
return
|
||||
|
||||
unassigner = payload.get("sender", {}).get("login")
|
||||
if unassigner not in allowed_unassigners:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue unassigned webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_unassigned_from(payload, parameters.get("unassigned_from"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_labels(issue, parameters.get("labels"))
|
||||
self._check_unassigner(payload, parameters.get("unassigner"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,137 @@
|
||||
identity:
|
||||
name: issue_unassigned
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Unassigned
|
||||
zh_Hans: Issue 取消分配
|
||||
ja_JP: Issue 割り当て解除
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when an issue is unassigned from a user
|
||||
zh_Hans: 当 issue 的分配被取消时触发
|
||||
ja_JP: issue のユーザーへの割り当てが解除されたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when an issue is unassigned from a user on a GitHub repository, providing information about the issue, the user who was unassigned, repository, and user who removed the assignment.
|
||||
zh_Hans: 当在 GitHub 仓库中取消 issue 的用户分配时,此触发器会被激活,提供有关 issue、被取消分配的用户、仓库和移除分配的用户的信息。
|
||||
ja_JP: issue の GitHub リポジトリでのユーザーへの割り当てが解除されたときにトリガーがアクティブになり、issue、割り当てが解除されたユーザー、リポジトリ、および割り当てを解除したユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: unassigned_from
|
||||
label:
|
||||
en_US: Unassigned From
|
||||
zh_Hans: 取消分配自
|
||||
ja_JP: 割り当て解除元
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if unassigned from these specific users (e.g., developer-1, team-member, comma-separated). Leave empty to trigger for any unassigned user."
|
||||
zh_Hans: "仅当从这些特定用户取消分配时触发(例如:developer-1, team-member,逗号分隔)。留空则对任何被取消分配的用户触发。"
|
||||
ja_JP: "これらの特定のユーザーから割り当てが解除された場合のみトリガー(例: developer-1, team-member,カンマ区切り)。空の場合は任意のユーザーでトリガー。"
|
||||
|
||||
- name: labels
|
||||
label:
|
||||
en_US: Required Labels
|
||||
zh_Hans: 必需标签
|
||||
ja_JP: 必須ラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if issue has these labels (e.g., on-hold, blocked, needs-reassignment, comma-separated). Leave empty for no label filtering."
|
||||
zh_Hans: "仅当议题有这些标签时触发(例如:on-hold, blocked, needs-reassignment,逗号分隔)。留空则不过滤标签。"
|
||||
ja_JP: "イシューがこれらのラベルを持つ場合のみトリガー(例: on-hold, blocked, needs-reassignment,カンマ区切り)。空の場合はラベルフィルタなし。"
|
||||
|
||||
- name: unassigner
|
||||
label:
|
||||
en_US: Unassigned By
|
||||
zh_Hans: 取消分配者
|
||||
ja_JP: 割り当て解除したユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if unassigned by these users (e.g., project-manager, admin, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户取消分配时触发(例如:project-manager, admin,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによって割り当てが解除された場合のみトリガー(例: project-manager, admin,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Backlog, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Backlog,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Backlog,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[BLOCKED\\].*, .*unassigned.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[BLOCKED\\].*, .*unassigned.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[BLOCKED\\].*, .*unassigned.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
assignee:
|
||||
type: object
|
||||
description: The user who was unassigned from the issue
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the issue was unassigned
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- assignee
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_unassigned.py
|
||||
@@ -0,0 +1,94 @@
|
||||
from collections.abc import Mapping
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from dify_plugin.errors.trigger import EventIgnoreError
|
||||
from werkzeug import Request
|
||||
|
||||
from dify_plugin.entities.trigger import Variables
|
||||
from dify_plugin.interfaces.trigger import Event
|
||||
|
||||
|
||||
class IssueUnlabeledEvent(Event):
|
||||
"""
|
||||
GitHub Issue Unlabeled Event
|
||||
|
||||
This event transforms GitHub issue unlabeled webhook events and extracts relevant
|
||||
information from the webhook payload to provide as variables to the workflow.
|
||||
The payload includes a 'label' object with the label that was removed.
|
||||
"""
|
||||
|
||||
def _check_removed_label(self, payload: Mapping[str, Any], removed_label_param: str | None) -> None:
|
||||
"""Check if the removed label matches the allowed labels"""
|
||||
if not removed_label_param:
|
||||
return
|
||||
|
||||
allowed_labels = [label.strip() for label in removed_label_param.split(",") if label.strip()]
|
||||
if not allowed_labels:
|
||||
return
|
||||
|
||||
# The payload contains the label that was removed
|
||||
label = payload.get("label", {})
|
||||
label_name = label.get("name", "")
|
||||
|
||||
if label_name not in allowed_labels:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_title_pattern(self, issue: Mapping[str, Any], pattern: str | None) -> None:
|
||||
"""Check if issue title matches the pattern"""
|
||||
if not pattern:
|
||||
return
|
||||
|
||||
title = issue.get("title", "")
|
||||
if not re.match(pattern, title):
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_unlabeler(self, payload: Mapping[str, Any], unlabeler_param: str | None) -> None:
|
||||
"""Check if the user who removed the label is in allowed list"""
|
||||
if not unlabeler_param:
|
||||
return
|
||||
|
||||
allowed_unlabelers = [unlabeler.strip() for unlabeler in unlabeler_param.split(",") if unlabeler.strip()]
|
||||
if not allowed_unlabelers:
|
||||
return
|
||||
|
||||
unlabeler = payload.get("sender", {}).get("login")
|
||||
if unlabeler not in allowed_unlabelers:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _check_milestone(self, issue: Mapping[str, Any], milestone_param: str | None) -> None:
|
||||
"""Check if issue milestone matches allowed milestones"""
|
||||
if not milestone_param:
|
||||
return
|
||||
|
||||
allowed_milestones = [milestone.strip() for milestone in milestone_param.split(",") if milestone.strip()]
|
||||
if not allowed_milestones:
|
||||
return
|
||||
|
||||
milestone = issue.get("milestone")
|
||||
if not milestone:
|
||||
raise EventIgnoreError()
|
||||
|
||||
milestone_title = milestone.get("title")
|
||||
if not milestone_title or milestone_title not in allowed_milestones:
|
||||
raise EventIgnoreError()
|
||||
|
||||
def _on_event(self, request: Request, parameters: Mapping[str, Any]) -> Variables:
|
||||
"""
|
||||
Transform GitHub issue unlabeled webhook event into structured Variables
|
||||
"""
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise ValueError("No payload received")
|
||||
|
||||
issue = payload.get("issue")
|
||||
if not issue:
|
||||
raise ValueError("No issue data in payload")
|
||||
|
||||
# Apply all filters
|
||||
self._check_removed_label(payload, parameters.get("removed_label"))
|
||||
self._check_title_pattern(issue, parameters.get("title_pattern"))
|
||||
self._check_unlabeler(payload, parameters.get("unlabeler"))
|
||||
self._check_milestone(issue, parameters.get("milestone"))
|
||||
|
||||
return Variables(variables={**payload})
|
||||
@@ -0,0 +1,126 @@
|
||||
identity:
|
||||
name: issue_unlabeled
|
||||
author: langgenius
|
||||
label:
|
||||
en_US: Issue Unlabeled
|
||||
zh_Hans: Issue 移除标签
|
||||
ja_JP: Issue ラベル削除
|
||||
|
||||
description:
|
||||
human:
|
||||
en_US: Triggers when a label is removed from an issue
|
||||
zh_Hans: 当从 issue 移除标签时触发
|
||||
ja_JP: issue からラベルが削除されたときにトリガーがアクティブになります。
|
||||
llm:
|
||||
en_US: This trigger activates when a label is removed from an issue on a GitHub repository, providing information about the issue, the label that was removed, repository, and user who removed it.
|
||||
zh_Hans: 当在 GitHub 仓库中从 issue 移除标签时,此触发器会被激活,提供有关 issue、移除的标签、仓库和移除它的用户的信息。
|
||||
ja_JP: issue から GitHub リポジトリでラベルが削除されたときにトリガーがアクティブになり、issue、削除されたラベル、リポジトリ、およびラベルを削除したユーザーに関する情報を提供します。
|
||||
parameters:
|
||||
- name: removed_label
|
||||
label:
|
||||
en_US: Removed Label
|
||||
zh_Hans: 移除的标签
|
||||
ja_JP: 削除されたラベル
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if these specific labels were removed (e.g., in-progress, blocked, needs-review, comma-separated). Leave empty to trigger for any label removal."
|
||||
zh_Hans: "仅当移除了这些特定标签时触发(例如:in-progress, blocked, needs-review,逗号分隔)。留空则对任何标签移除触发。"
|
||||
ja_JP: "これらの特定のラベルが削除された場合のみトリガー(例: in-progress, blocked, needs-review,カンマ区切り)。空の場合は任意のラベル削除でトリガー。"
|
||||
|
||||
- name: unlabeler
|
||||
label:
|
||||
en_US: Unlabeled By
|
||||
zh_Hans: 标签移除者
|
||||
ja_JP: ラベルを削除したユーザー
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if unlabeled by these users (e.g., maintainer, admin, comma-separated). Leave empty to allow any user."
|
||||
zh_Hans: "仅当由这些用户移除标签时触发(例如:maintainer, admin,逗号分隔)。留空则允许任何用户。"
|
||||
ja_JP: "これらのユーザーによってラベルが削除された場合のみトリガー(例: maintainer, admin,カンマ区切り)。空の場合は全てのユーザーを許可。"
|
||||
|
||||
- name: milestone
|
||||
label:
|
||||
en_US: Milestone
|
||||
zh_Hans: 里程碑
|
||||
ja_JP: マイルストーン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger for issues with these milestones (e.g., v1.0, v2.0, Sprint-2, comma-separated). Leave empty for all milestones."
|
||||
zh_Hans: "仅对具有这些里程碑的议题触发(例如:v1.0, v2.0, Sprint-2,逗号分隔)。留空则对所有里程碑触发。"
|
||||
ja_JP: "これらのマイルストーンのイシューのみトリガー(例: v1.0, v2.0, Sprint-2,カンマ区切り)。空の場合は全てのマイルストーンでトリガー。"
|
||||
|
||||
- name: title_pattern
|
||||
label:
|
||||
en_US: Title Pattern
|
||||
zh_Hans: 标题模式
|
||||
ja_JP: タイトルパターン
|
||||
type: string
|
||||
required: false
|
||||
description:
|
||||
en_US: "Only trigger if title matches this regex pattern (e.g., ^\\[FEATURE\\].*, .*completed.*, supports regex). Leave empty for no title filtering."
|
||||
zh_Hans: "仅当标题匹配此正则表达式模式时触发(例如:^\\[FEATURE\\].*, .*completed.*,支持正则表达式)。留空则不过滤标题。"
|
||||
ja_JP: "タイトルがこの正規表現パターンに一致する場合のみトリガー(例: ^\\[FEATURE\\].*, .*completed.*,正規表現対応)。空の場合はタイトルフィルタなし。"
|
||||
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
issue:
|
||||
type: object
|
||||
description: The issue itself
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- number
|
||||
- title
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
- user
|
||||
- html_url
|
||||
|
||||
label:
|
||||
type: object
|
||||
description: The label that was removed
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- color
|
||||
|
||||
repository:
|
||||
type: object
|
||||
description: The repository where the label was removed
|
||||
required:
|
||||
- id
|
||||
- node_id
|
||||
- name
|
||||
- full_name
|
||||
- private
|
||||
- owner
|
||||
- html_url
|
||||
- url
|
||||
- created_at
|
||||
- updated_at
|
||||
- fork
|
||||
- default_branch
|
||||
- visibility
|
||||
|
||||
sender:
|
||||
type: object
|
||||
description: The user who triggered the event
|
||||
required:
|
||||
- id
|
||||
- login
|
||||
- type
|
||||
|
||||
required:
|
||||
- issue
|
||||
- label
|
||||
- repository
|
||||
- sender
|
||||
|
||||
extra:
|
||||
python:
|
||||
source: events/issues/issue_unlabeled.py
|
||||
@@ -33,6 +33,13 @@ class GithubTrigger(Trigger):
|
||||
__TRIGGER_EVENTS_MAPPING: ClassVar[Mapping[str, Mapping[str, list[str]]]] = {
|
||||
"issues": {
|
||||
"opened": ["issue_opened"],
|
||||
"closed": ["issue_closed"],
|
||||
"reopened": ["issue_reopened"],
|
||||
"edited": ["issue_edited"],
|
||||
"labeled": ["issue_labeled"],
|
||||
"unlabeled": ["issue_unlabeled"],
|
||||
"assigned": ["issue_assigned"],
|
||||
"unassigned": ["issue_unassigned"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,13 @@ subscription_constructor:
|
||||
|
||||
events:
|
||||
- events/issues/issue_opened.yaml
|
||||
- events/issues/issue_closed.yaml
|
||||
- events/issues/issue_reopened.yaml
|
||||
- events/issues/issue_edited.yaml
|
||||
- events/issues/issue_labeled.yaml
|
||||
- events/issues/issue_unlabeled.yaml
|
||||
- events/issues/issue_assigned.yaml
|
||||
- events/issues/issue_unassigned.yaml
|
||||
|
||||
identity:
|
||||
author: langgenius
|
||||
|
||||
Reference in New Issue
Block a user