实现了 GitLab 全功能集成,包含 Webhook 处理、API 客户端、消息收发、会话管理、安全校验、限流器等完整模块,支持 Issues、Merge Requests、流水线等事件监听与交互。
628 lines
23 KiB
Python
628 lines
23 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from yuxi.channel.message.models import (
|
|
GroupContext,
|
|
MessageType,
|
|
PeerInfo,
|
|
UnifiedMessage,
|
|
)
|
|
from yuxi.channel.routing.models import PeerKind
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GitLabMonitor:
|
|
def parse_webhook_to_unified(
|
|
self,
|
|
payload: dict,
|
|
account_id: str,
|
|
event_type: str,
|
|
) -> UnifiedMessage | None:
|
|
object_kind = payload.get("object_kind", "")
|
|
|
|
if object_kind == "note":
|
|
return self._parse_note_event(payload, account_id, event_type)
|
|
elif object_kind == "issue":
|
|
return self._parse_issue_event(payload, account_id)
|
|
elif object_kind == "merge_request":
|
|
return self._parse_mr_event(payload, account_id)
|
|
elif object_kind == "pipeline":
|
|
return self._parse_pipeline_event(payload, account_id)
|
|
elif object_kind == "deployment":
|
|
return self._parse_deployment_event(payload, account_id)
|
|
elif object_kind == "build":
|
|
return self._parse_job_event(payload, account_id)
|
|
elif object_kind == "work_item":
|
|
return self._parse_work_item_event(payload, account_id)
|
|
elif event_type == "Push Hook":
|
|
return self._parse_push_event(payload, account_id)
|
|
elif event_type == "Tag Push Hook":
|
|
return self._parse_tag_event(payload, account_id)
|
|
elif event_type == "Release Hook":
|
|
return self._parse_release_event(payload, account_id)
|
|
else:
|
|
logger.debug("GitLab monitor: unhandled object_kind=%s event_type=%s", object_kind, event_type)
|
|
return None
|
|
|
|
def _parse_note_event(self, payload: dict, account_id: str, event_type: str) -> UnifiedMessage | None:
|
|
attrs = payload.get("object_attributes", {})
|
|
user = payload.get("user", {})
|
|
project = payload.get("project", {})
|
|
noteable_type = attrs.get("noteable_type", "Issue")
|
|
|
|
note_id = attrs.get("id", 0)
|
|
body = attrs.get("note", "")
|
|
note_url = attrs.get("url", "")
|
|
action = attrs.get("action", "create")
|
|
|
|
noteable = (
|
|
payload.get("issue")
|
|
if noteable_type == "Issue"
|
|
else payload.get("merge_request", {})
|
|
if noteable_type == "MergeRequest"
|
|
else payload.get("commit")
|
|
if noteable_type == "Commit"
|
|
else payload.get("snippet", {})
|
|
if noteable_type == "Snippet"
|
|
else {}
|
|
)
|
|
noteable_iid = noteable.get("iid", 0) if noteable else 0
|
|
noteable_title = noteable.get("title", "") if noteable else ""
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(user.get("id", "")),
|
|
display_name=user.get("name", user.get("username", "")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
group = GroupContext(
|
|
id=f"{project_id}:{noteable_type}:{noteable_iid}",
|
|
kind=f"gitlab_{noteable_type.lower()}",
|
|
name=f"{project_path}#{noteable_iid} — {noteable_title}",
|
|
)
|
|
|
|
timestamp = None
|
|
created_at = attrs.get("created_at")
|
|
if created_at:
|
|
try:
|
|
timestamp = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
|
|
except ValueError:
|
|
pass
|
|
|
|
if action == "update":
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:note:{note_id}:update",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=f"\u270f\ufe0f 编辑了评论 (ID: {note_id})",
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.EVENT,
|
|
timestamp=timestamp,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"noteable_type": noteable_type,
|
|
"noteable_iid": noteable_iid,
|
|
"note_id": note_id,
|
|
"action": "update",
|
|
"body": body[:500] if body else "",
|
|
},
|
|
)
|
|
|
|
is_diff_note = attrs.get("position") is not None
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:note:{note_id}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=body,
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.TEXT,
|
|
timestamp=timestamp,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"noteable_type": noteable_type,
|
|
"noteable_iid": noteable_iid,
|
|
"note_id": note_id,
|
|
"note_url": note_url,
|
|
"is_diff_note": is_diff_note,
|
|
"discussion_id": attrs.get("discussion_id", ""),
|
|
"system": attrs.get("system", False),
|
|
},
|
|
)
|
|
|
|
def _parse_issue_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
attrs = payload.get("object_attributes", {})
|
|
user = payload.get("user", {})
|
|
project = payload.get("project", {})
|
|
|
|
action = attrs.get("action", "")
|
|
issue_iid = attrs.get("iid", 0)
|
|
title = attrs.get("title", "")
|
|
description = attrs.get("description", "")
|
|
state = attrs.get("state", "")
|
|
|
|
action_text_map = {
|
|
"open": f"\U0001f4dd 创建了 Issue: **{title}**",
|
|
"close": f"\u2705 关闭了 Issue: **{title}**",
|
|
"reopen": f"\U0001f504 重新打开了 Issue: **{title}**",
|
|
"update": f"\u270f\ufe0f 更新了 Issue: **{title}**",
|
|
}
|
|
text = action_text_map.get(action, f"Issue 事件: {action} — **{title}**")
|
|
if description:
|
|
text += f"\n\n{description[:500]}"
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(user.get("id", "")),
|
|
display_name=user.get("name", user.get("username", "")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
group = GroupContext(
|
|
id=f"{project_id}:Issue:{issue_iid}",
|
|
kind="gitlab_issue",
|
|
name=f"{project_path}#{issue_iid} — {title}",
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:issue:{attrs.get('id', 0)}:{action}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"issue_iid": issue_iid,
|
|
"action": action,
|
|
"state": state,
|
|
"labels": attrs.get("labels", []),
|
|
"url": attrs.get("url", ""),
|
|
},
|
|
)
|
|
|
|
def _parse_mr_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
attrs = payload.get("object_attributes", {})
|
|
user = payload.get("user", {})
|
|
project = payload.get("project", {})
|
|
|
|
action = attrs.get("action", "")
|
|
mr_iid = attrs.get("iid", 0)
|
|
title = attrs.get("title", "")
|
|
source_branch = attrs.get("source_branch", "")
|
|
target_branch = attrs.get("target_branch", "")
|
|
|
|
action_text_map = {
|
|
"open": f"\U0001f500 创建了 MR: **{title}**\n`{source_branch}` → `{target_branch}`",
|
|
"merge": f"\U0001f389 合并了 MR: **{title}**",
|
|
"close": f"\u274c 关闭了 MR: **{title}**",
|
|
"reopen": f"\U0001f504 重新打开了 MR: **{title}**",
|
|
"update": f"\u270f\ufe0f 更新了 MR: **{title}**",
|
|
"approved": f"\U0001f44d 所有审批已通过 MR: **{title}**",
|
|
"approval": f"\u2705 审批 MR: **{title}**",
|
|
"unapproved": f"\U0001f44e 取消审批了 MR: **{title}**",
|
|
}
|
|
text = action_text_map.get(action, f"MR 事件: {action} — **{title}**")
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(user.get("id", "")),
|
|
display_name=user.get("name", user.get("username", "")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
group = GroupContext(
|
|
id=f"{project_id}:MergeRequest:{mr_iid}",
|
|
kind="gitlab_mr",
|
|
name=f"{project_path}!{mr_iid} — {title}",
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:mr:{attrs.get('id', 0)}:{action}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"mr_iid": mr_iid,
|
|
"action": action,
|
|
"source_branch": source_branch,
|
|
"target_branch": target_branch,
|
|
"url": attrs.get("url", ""),
|
|
},
|
|
)
|
|
|
|
def _parse_pipeline_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
attrs = payload.get("object_attributes", {})
|
|
user = payload.get("user", {})
|
|
project = payload.get("project", {})
|
|
commit = payload.get("commit", {})
|
|
|
|
status = attrs.get("status", "")
|
|
pipeline_id = attrs.get("iid", 0)
|
|
ref = attrs.get("ref", "")
|
|
duration = attrs.get("duration", 0)
|
|
|
|
status_emoji = {
|
|
"success": "\u2705",
|
|
"failed": "\u274c",
|
|
"canceled": "\u23f9\ufe0f",
|
|
"skipped": "\u23ed\ufe0f",
|
|
"running": "\U0001f504",
|
|
"pending": "\u23f3",
|
|
"manual": "\u270b",
|
|
}
|
|
emoji = status_emoji.get(status, "\u2139\ufe0f")
|
|
|
|
commit_msg = commit.get("message", "")
|
|
commit_msg_short = commit_msg.split("\n")[0][:100] if commit_msg else ""
|
|
|
|
if status == "manual":
|
|
text = (
|
|
f"{emoji} Pipeline **#{pipeline_id}** 等待手动触发 on `{ref}`\n"
|
|
f"Commit: {commit.get('id', '')[:8]} — {commit_msg_short}\n"
|
|
f"\u2705 需要手动审批才能继续执行"
|
|
)
|
|
else:
|
|
text = (
|
|
f"{emoji} Pipeline **#{pipeline_id}** {status} on `{ref}`\n"
|
|
f"Commit: {commit.get('id', '')[:8]} — {commit_msg_short}\n"
|
|
f"Duration: {duration}s"
|
|
)
|
|
|
|
failed_jobs = []
|
|
for build in payload.get("builds", []):
|
|
if build.get("status") == "failed":
|
|
failed_jobs.append(f" - `{build.get('name')}` ({build.get('stage')})")
|
|
|
|
if failed_jobs:
|
|
text += "\n\n\u274c Failed Jobs:\n" + "\n".join(failed_jobs[:10])
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(user.get("id", "")),
|
|
display_name=user.get("name", user.get("username", "CI")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
group = GroupContext(
|
|
id=f"{project_id}:Pipeline:{ref}",
|
|
kind="gitlab_pipeline",
|
|
name=f"{project_path} Pipeline on `{ref}`",
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:pipeline:{attrs.get('id', 0)}:{status}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"pipeline_id": pipeline_id,
|
|
"status": status,
|
|
"ref": ref,
|
|
"commit_sha": commit.get("id", ""),
|
|
"duration": duration,
|
|
"url": attrs.get("url", ""),
|
|
},
|
|
)
|
|
|
|
def _parse_deployment_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
status = payload.get("status", "")
|
|
environment = payload.get("environment", "")
|
|
env_url = payload.get("environment_external_url", "")
|
|
project = payload.get("project", {})
|
|
commit = payload.get("commit", {})
|
|
short_sha = payload.get("short_sha", commit.get("id", "")[:8])
|
|
|
|
text = (
|
|
f"\U0001f680 Deployment **{status}** to `{environment}`\n"
|
|
f"Commit: {short_sha} — {commit.get('message', '').split(chr(10))[0][:100]}"
|
|
)
|
|
if env_url:
|
|
text += f"\nURL: {env_url}"
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(payload.get("user", {}).get("id", "")),
|
|
display_name=payload.get("user", {}).get("name", "Deployment"),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:deployment:{payload.get('deployment_id', 0)}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project.get("id", 0),
|
|
"environment": environment,
|
|
"status": status,
|
|
"short_sha": short_sha,
|
|
},
|
|
)
|
|
|
|
def _parse_job_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
build_status = payload.get("build_status", "")
|
|
build_name = payload.get("build_name", "")
|
|
build_stage = payload.get("build_stage", "")
|
|
pipeline_id = payload.get("pipeline_id", 0)
|
|
failure_reason = payload.get("build_failure_reason", "")
|
|
|
|
if build_status not in ("failed", "success"):
|
|
return None
|
|
|
|
status_emoji = "\u274c" if build_status == "failed" else "\u2705"
|
|
|
|
text = f"{status_emoji} Job `{build_name}` ({build_stage}) **{build_status}**\nPipeline: #{pipeline_id}"
|
|
if failure_reason:
|
|
text += f"\nReason: {failure_reason}"
|
|
|
|
project = payload.get("project", {})
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(payload.get("user", {}).get("id", "")),
|
|
display_name=payload.get("user", {}).get("name", "Job"),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:job:{payload.get('build_id', 0)}:{build_status}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project.get("id", 0),
|
|
"build_name": build_name,
|
|
"build_stage": build_stage,
|
|
"build_status": build_status,
|
|
"pipeline_id": pipeline_id,
|
|
},
|
|
)
|
|
|
|
def _parse_push_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
project = payload.get("project", {})
|
|
ref = payload.get("ref", "")
|
|
branch = ref.replace("refs/heads/", "") if ref.startswith("refs/heads/") else ref
|
|
|
|
commits = payload.get("commits", [])
|
|
total_commits = payload.get("total_commits_count", len(commits))
|
|
before = payload.get("before", "")
|
|
after = payload.get("after", "")
|
|
|
|
if before == "0000000000000000000000000000000000000000":
|
|
action_text = f"\U0001f4cc 新建分支 `{branch}`"
|
|
elif after == "0000000000000000000000000000000000000000":
|
|
action_text = f"\U0001f5d1\ufe0f 删除分支 `{branch}`"
|
|
else:
|
|
action_text = f"\U0001f4e4 Push {total_commits} commit(s) to `{branch}`"
|
|
|
|
text = action_text
|
|
commit_summaries = []
|
|
for c in commits[:5]:
|
|
msg = c.get("message", "").split("\n")[0][:80]
|
|
author = c.get("author", {}).get("name", "unknown")
|
|
sha = c.get("id", "")[:8]
|
|
commit_summaries.append(f" `{sha}` {msg} — {author}")
|
|
if commit_summaries:
|
|
text += "\n" + "\n".join(commit_summaries)
|
|
if total_commits > 5:
|
|
text += f"\n ... and {total_commits - 5} more"
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(payload.get("user_id", "")),
|
|
display_name=payload.get("user_username", payload.get("user_name", "Push")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
group = GroupContext(
|
|
id=f"{project_id}:Push:{branch}",
|
|
kind="gitlab_push",
|
|
name=f"{project_path} — `{branch}`",
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:push:{after[:16]}:{branch}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
group=group,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"branch": branch,
|
|
"total_commits": total_commits,
|
|
"before": before,
|
|
"after": after,
|
|
},
|
|
)
|
|
|
|
def _parse_tag_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
project = payload.get("project", {})
|
|
ref = payload.get("ref", "")
|
|
tag_name = ref.replace("refs/tags/", "") if ref.startswith("refs/tags/") else ref
|
|
before = payload.get("before", "")
|
|
after = payload.get("after", "")
|
|
|
|
if before == "0000000000000000000000000000000000000000":
|
|
text = f"\U0001f3f7\ufe0f 创建标签: `{tag_name}`"
|
|
elif after == "0000000000000000000000000000000000000000":
|
|
text = f"\U0001f5d1\ufe0f 删除标签: `{tag_name}`"
|
|
else:
|
|
text = f"\U0001f3f7\ufe0f 标签更新: `{tag_name}`"
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(payload.get("user_id", "")),
|
|
display_name=payload.get("user_username", payload.get("user_name", "Tag")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:tag:{tag_name}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project.get("id", 0),
|
|
"project_path": project.get("path_with_namespace", ""),
|
|
"tag": tag_name,
|
|
"before": before,
|
|
"after": after,
|
|
},
|
|
)
|
|
|
|
def _parse_release_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
project = payload.get("project", {})
|
|
tag = payload.get("tag", "")
|
|
name = payload.get("name", tag)
|
|
action = payload.get("action", "create")
|
|
description = payload.get("description", "")
|
|
|
|
action_map = {
|
|
"create": f"\U0001f680 发布: **{name}** (`{tag}`)",
|
|
"update": f"\u270f\ufe0f 更新发布: **{name}** (`{tag}`)",
|
|
"delete": f"\U0001f5d1\ufe0f 删除发布: **{name}** (`{tag}`)",
|
|
}
|
|
text = action_map.get(action, f"Release {action}: **{name}** (`{tag}`)")
|
|
if description:
|
|
text += f"\n\n{description[:500]}"
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:release:{tag}:{action}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=PeerInfo(kind=PeerKind.DIRECT, id="0", display_name="Release", is_bot=False, is_self=False),
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project.get("id", 0),
|
|
"project_path": project.get("path_with_namespace", ""),
|
|
"tag": tag,
|
|
"name": name,
|
|
"action": action,
|
|
"url": payload.get("url", ""),
|
|
},
|
|
)
|
|
|
|
def _parse_work_item_event(self, payload: dict, account_id: str) -> UnifiedMessage | None:
|
|
attrs = payload.get("object_attributes", {})
|
|
user = payload.get("user", {})
|
|
project = payload.get("project", {})
|
|
|
|
work_item_type = attrs.get("work_item_type", payload.get("object_kind", "work_item"))
|
|
action = attrs.get("action", "")
|
|
iid = attrs.get("iid", 0)
|
|
title = attrs.get("title", "")
|
|
|
|
type_label_map = {
|
|
"Issue": "#Issue",
|
|
"Task": "\u2611\ufe0f Task",
|
|
"Incident": "\U0001f6a8 Incident",
|
|
"Epic": "\U0001f4d6 Epic",
|
|
"OKR": "\U0001f3af OKR",
|
|
}
|
|
type_label = type_label_map.get(work_item_type, work_item_type)
|
|
|
|
action_map = {
|
|
"open": f"\U0001f4dd {type_label}: **{title}**",
|
|
"close": f"\u2705 {type_label}: **{title}**",
|
|
"reopen": f"\U0001f504 {type_label}: **{title}**",
|
|
"update": f"\u270f\ufe0f {type_label}: **{title}**",
|
|
}
|
|
text = action_map.get(action, f"{type_label} 事件: {action} — **{title}**")
|
|
|
|
sender = PeerInfo(
|
|
kind=PeerKind.DIRECT,
|
|
id=str(user.get("id", "")),
|
|
display_name=user.get("name", user.get("username", "")),
|
|
is_bot=False,
|
|
is_self=False,
|
|
)
|
|
|
|
project_id = project.get("id", 0)
|
|
project_path = project.get("path_with_namespace", "")
|
|
|
|
return UnifiedMessage(
|
|
msg_id=f"gitlab:work_item:{attrs.get('id', 0)}:{action}",
|
|
channel_type="gitlab",
|
|
account_id=account_id,
|
|
content=text,
|
|
sender=sender,
|
|
group=GroupContext(
|
|
id=f"{project_id}:WorkItem:{iid}",
|
|
kind=f"gitlab_work_item_{work_item_type.lower()}",
|
|
name=f"{project_path} — {title}",
|
|
),
|
|
message_type=MessageType.EVENT,
|
|
raw_payload=payload,
|
|
metadata={
|
|
"project_id": project_id,
|
|
"project_path": project_path,
|
|
"work_item_type": work_item_type,
|
|
"iid": iid,
|
|
"action": action,
|
|
"url": attrs.get("url", ""),
|
|
},
|
|
)
|