mirror of
https://github.com/langgenius/dify-plugin-sdks.git
synced 2026-07-22 10:25:23 -04:00
189 lines
5.9 KiB
Python
189 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import fnmatch
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from werkzeug import Request
|
|
|
|
from dify_plugin.errors.trigger import EventIgnoreError
|
|
|
|
from .common import ensure_action, load_json_payload, require_mapping
|
|
|
|
|
|
def load_pull_request_payload(
|
|
request: Request,
|
|
*,
|
|
expected_action: str | None = None,
|
|
) -> tuple[Mapping[str, Any], Mapping[str, Any]]:
|
|
"""Load payload and pull request object, enforcing expected action."""
|
|
payload = load_json_payload(request)
|
|
ensure_action(payload, expected_action)
|
|
pull_request = require_mapping(payload, "pull_request")
|
|
return payload, pull_request
|
|
|
|
|
|
def apply_pull_request_common_filters(pull_request: Mapping[str, Any], parameters: Mapping[str, Any]) -> None:
|
|
"""Apply standard pull request filters based on configuration parameters."""
|
|
check_base_branch(pull_request, parameters.get("base_branch"))
|
|
check_head_branch(pull_request, parameters.get("head_branch"))
|
|
check_author(pull_request, parameters.get("author"))
|
|
check_draft_state(pull_request, parameters.get("draft"))
|
|
check_labels(pull_request, parameters.get("label_names"))
|
|
check_reviewers(pull_request, parameters.get("reviewers"))
|
|
check_pr_size_threshold(pull_request, parameters.get("pr_size_threshold"))
|
|
check_changed_files_glob(pull_request, parameters.get("changed_files_glob"))
|
|
|
|
|
|
def check_base_branch(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
branches = _normalize_list(value)
|
|
if not branches:
|
|
return
|
|
|
|
current = pull_request.get("base", {}).get("ref")
|
|
if current not in branches:
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_head_branch(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
branches = _normalize_list(value)
|
|
if not branches:
|
|
return
|
|
|
|
current = pull_request.get("head", {}).get("ref")
|
|
if current not in branches:
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_author(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
authors = _normalize_list(value)
|
|
if not authors:
|
|
return
|
|
|
|
author = pull_request.get("user", {}).get("login")
|
|
if author not in authors:
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_draft_state(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
if value is None:
|
|
return
|
|
|
|
is_draft = bool(pull_request.get("draft"))
|
|
if is_draft != bool(value):
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_labels(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
labels = _normalize_list(value)
|
|
if not labels:
|
|
return
|
|
|
|
current = [label.get("name") for label in pull_request.get("labels", [])]
|
|
if not any(label in current for label in labels):
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_reviewers(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
reviewers = _normalize_list(value)
|
|
if not reviewers:
|
|
return
|
|
|
|
requested: list[str] = []
|
|
for reviewer in pull_request.get("requested_reviewers", []) or []:
|
|
login = reviewer.get("login")
|
|
if login:
|
|
requested.append(login)
|
|
for team in pull_request.get("requested_teams", []) or []:
|
|
slug = team.get("slug")
|
|
if slug:
|
|
requested.append(slug)
|
|
|
|
if not requested or not any(r in requested for r in reviewers):
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_merged_state(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
if value is None:
|
|
return
|
|
|
|
is_merged = bool(pull_request.get("merged"))
|
|
if is_merged != bool(value):
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_pr_size_threshold(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
"""Filter by PR size: additions+deletions must be <= threshold.
|
|
|
|
If threshold cannot be parsed or counts are missing, this filter is ignored.
|
|
"""
|
|
if value in (None, ""):
|
|
return
|
|
|
|
try:
|
|
threshold = int(str(value).strip())
|
|
except ValueError:
|
|
return
|
|
|
|
additions = pull_request.get("additions")
|
|
deletions = pull_request.get("deletions")
|
|
if isinstance(additions, int) and isinstance(deletions, int):
|
|
total = additions + deletions
|
|
if total > threshold:
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def check_changed_files_glob(pull_request: Mapping[str, Any], value: Any) -> None:
|
|
"""Filter by changed file patterns.
|
|
|
|
This requires file paths to be present in the payload. If not present,
|
|
the filter is skipped gracefully.
|
|
Supported payload keys: 'files' (list[str]) or 'changed_files_detail' (list[Mapping] with 'filename').
|
|
Multiple patterns can be provided (comma-separated).
|
|
"""
|
|
patterns = _normalize_list(value)
|
|
if not patterns:
|
|
return
|
|
|
|
file_paths: list[str] = []
|
|
raw_files = pull_request.get("files")
|
|
if isinstance(raw_files, list):
|
|
for item in raw_files:
|
|
if isinstance(item, str):
|
|
file_paths.append(item)
|
|
elif isinstance(item, Mapping) and isinstance(item.get("filename"), str):
|
|
file_paths.append(item.get("filename"))
|
|
|
|
if not file_paths:
|
|
details = pull_request.get("changed_files_detail")
|
|
if isinstance(details, list):
|
|
for entry in details:
|
|
if isinstance(entry, Mapping) and isinstance(entry.get("filename"), str):
|
|
file_paths.append(entry.get("filename"))
|
|
|
|
if not file_paths:
|
|
# Cannot evaluate; skip filter
|
|
return
|
|
|
|
matched = False
|
|
for path in file_paths:
|
|
for pattern in patterns:
|
|
if fnmatch.fnmatch(path, pattern):
|
|
matched = True
|
|
break
|
|
if matched:
|
|
break
|
|
|
|
if not matched:
|
|
raise EventIgnoreError()
|
|
|
|
|
|
def _normalize_list(raw: Any) -> list[str]:
|
|
if raw is None:
|
|
return []
|
|
|
|
if isinstance(raw, (list, tuple)):
|
|
return [str(item).strip() for item in raw if str(item).strip()]
|
|
|
|
return [item.strip() for item in str(raw).split(",") if item.strip()]
|