@kernel-sig/console 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +50 -0
- package/LICENSE +127 -0
- package/Makefile +66 -0
- package/README.md +279 -0
- package/backend/.dockerignore +12 -0
- package/backend/Dockerfile +43 -0
- package/backend/alembic/env.py +75 -0
- package/backend/alembic/script.py.mako +25 -0
- package/backend/alembic/versions/0001_initial.py +88 -0
- package/backend/alembic/versions/350e2d9b6553_add_pr_comment_table.py +48 -0
- package/backend/alembic/versions/4f8342e727fe_sig_info_roster_fields_and_branch_.py +39 -0
- package/backend/alembic/versions/5a31ade90136_add_repository_credential_pull_request_.py +293 -0
- package/backend/alembic/versions/87a008142f17_add_classification_attention_and_ai_.py +436 -0
- package/backend/alembic/versions/a1c7d90e4b52_branch_belongs_to_a_repository.py +72 -0
- package/backend/alembic/versions/b41c9d7e5f28_extend_pr_kind_categories.py +43 -0
- package/backend/alembic/versions/c8e4f2a71b93_add_release_kind.py +40 -0
- package/backend/alembic/versions/d1275091dfdc_add_needs_detail_flag_to_pull_request.py +44 -0
- package/backend/alembic/versions/e5b27c9d3a41_downgrade_cve_without_ids.py +54 -0
- package/backend/alembic/versions/ee3159a4eff0_add_sig_meeting_member_and_release_.py +164 -0
- package/backend/alembic/versions/fcf3c186d63b_attention_rule_subscriptions.py +42 -0
- package/backend/alembic.ini +40 -0
- package/backend/app/__init__.py +0 -0
- package/backend/app/api/__init__.py +0 -0
- package/backend/app/api/deps.py +74 -0
- package/backend/app/api/v1/__init__.py +0 -0
- package/backend/app/api/v1/ai.py +386 -0
- package/backend/app/api/v1/analytics.py +48 -0
- package/backend/app/api/v1/attention.py +142 -0
- package/backend/app/api/v1/auth.py +106 -0
- package/backend/app/api/v1/classification.py +192 -0
- package/backend/app/api/v1/health.py +29 -0
- package/backend/app/api/v1/issues.py +143 -0
- package/backend/app/api/v1/pulls.py +388 -0
- package/backend/app/api/v1/repositories.py +273 -0
- package/backend/app/api/v1/router.py +32 -0
- package/backend/app/api/v1/sig.py +565 -0
- package/backend/app/api/v1/users.py +87 -0
- package/backend/app/api/v1/webhooks.py +135 -0
- package/backend/app/core/__init__.py +0 -0
- package/backend/app/core/config.py +72 -0
- package/backend/app/core/crypto.py +74 -0
- package/backend/app/core/db.py +79 -0
- package/backend/app/core/exceptions.py +60 -0
- package/backend/app/core/logging.py +69 -0
- package/backend/app/core/permissions.py +87 -0
- package/backend/app/core/queue.py +57 -0
- package/backend/app/core/security.py +69 -0
- package/backend/app/domain/__init__.py +0 -0
- package/backend/app/domain/attention.py +618 -0
- package/backend/app/domain/classification.py +689 -0
- package/backend/app/domain/meeting.py +408 -0
- package/backend/app/domain/release.py +163 -0
- package/backend/app/domain/review.py +416 -0
- package/backend/app/domain/sig.py +178 -0
- package/backend/app/domain/sig_info.py +201 -0
- package/backend/app/integrations/__init__.py +0 -0
- package/backend/app/integrations/atomgit/__init__.py +0 -0
- package/backend/app/integrations/atomgit/client.py +505 -0
- package/backend/app/integrations/atomgit/models.py +311 -0
- package/backend/app/integrations/llm/__init__.py +0 -0
- package/backend/app/integrations/llm/prompts.py +222 -0
- package/backend/app/integrations/llm/provider.py +326 -0
- package/backend/app/main.py +242 -0
- package/backend/app/middleware/__init__.py +0 -0
- package/backend/app/middleware/audit.py +129 -0
- package/backend/app/middleware/request_context.py +42 -0
- package/backend/app/models/__init__.py +112 -0
- package/backend/app/models/ai.py +214 -0
- package/backend/app/models/attention.py +131 -0
- package/backend/app/models/attention_settings.py +61 -0
- package/backend/app/models/audit.py +53 -0
- package/backend/app/models/base.py +35 -0
- package/backend/app/models/classification.py +144 -0
- package/backend/app/models/credential.py +56 -0
- package/backend/app/models/issue.py +132 -0
- package/backend/app/models/meeting.py +189 -0
- package/backend/app/models/pull_request.py +288 -0
- package/backend/app/models/repository.py +196 -0
- package/backend/app/models/sig.py +194 -0
- package/backend/app/models/user.py +44 -0
- package/backend/app/schemas/__init__.py +0 -0
- package/backend/app/schemas/ai.py +120 -0
- package/backend/app/schemas/attention.py +43 -0
- package/backend/app/schemas/auth.py +26 -0
- package/backend/app/schemas/classification.py +58 -0
- package/backend/app/schemas/common.py +43 -0
- package/backend/app/schemas/pull_request.py +214 -0
- package/backend/app/schemas/repository.py +94 -0
- package/backend/app/schemas/sig.py +246 -0
- package/backend/app/schemas/user.py +79 -0
- package/backend/app/services/__init__.py +0 -0
- package/backend/app/services/ai_service.py +569 -0
- package/backend/app/services/analytics_service.py +390 -0
- package/backend/app/services/attention_queue.py +451 -0
- package/backend/app/services/attention_service.py +432 -0
- package/backend/app/services/auth_service.py +74 -0
- package/backend/app/services/classification_service.py +658 -0
- package/backend/app/services/credential_service.py +105 -0
- package/backend/app/services/pull_query.py +273 -0
- package/backend/app/services/release_service.py +446 -0
- package/backend/app/services/repository_service.py +132 -0
- package/backend/app/services/sig_service.py +385 -0
- package/backend/app/services/sync_service.py +752 -0
- package/backend/app/services/user_service.py +68 -0
- package/backend/app/worker.py +389 -0
- package/backend/entrypoint.sh +10 -0
- package/backend/pyproject.toml +68 -0
- package/backend/tests/test_analysis_api.py +154 -0
- package/backend/tests/test_atomgit_client.py +360 -0
- package/backend/tests/test_atomgit_models.py +257 -0
- package/backend/tests/test_attention.py +291 -0
- package/backend/tests/test_audit_middleware.py +135 -0
- package/backend/tests/test_classification.py +498 -0
- package/backend/tests/test_config.py +41 -0
- package/backend/tests/test_crypto.py +68 -0
- package/backend/tests/test_exceptions.py +62 -0
- package/backend/tests/test_health.py +63 -0
- package/backend/tests/test_llm_provider.py +320 -0
- package/backend/tests/test_meeting_domain.py +169 -0
- package/backend/tests/test_permissions.py +69 -0
- package/backend/tests/test_pull_query_wiring.py +66 -0
- package/backend/tests/test_pull_schemas.py +82 -0
- package/backend/tests/test_release_domain.py +82 -0
- package/backend/tests/test_review_parser.py +301 -0
- package/backend/tests/test_schemas_user.py +97 -0
- package/backend/tests/test_security.py +92 -0
- package/cli/index.js +338 -0
- package/compose.yaml +105 -0
- package/frontend/.dockerignore +4 -0
- package/frontend/Dockerfile +27 -0
- package/frontend/index.html +14 -0
- package/frontend/nginx.conf +47 -0
- package/frontend/package-lock.json +5020 -0
- package/frontend/package.json +35 -0
- package/frontend/src/api/ai.ts +124 -0
- package/frontend/src/api/analytics.ts +66 -0
- package/frontend/src/api/attention.ts +181 -0
- package/frontend/src/api/auth.ts +74 -0
- package/frontend/src/api/classification.ts +170 -0
- package/frontend/src/api/issues.ts +90 -0
- package/frontend/src/api/pulls.ts +233 -0
- package/frontend/src/api/repositories.ts +95 -0
- package/frontend/src/api/sig.ts +232 -0
- package/frontend/src/app/antd-theme.ts +94 -0
- package/frontend/src/app/providers.tsx +59 -0
- package/frontend/src/app/router.tsx +411 -0
- package/frontend/src/app/search.ts +30 -0
- package/frontend/src/components/ClassificationBadge.tsx +58 -0
- package/frontend/src/components/GateBadge.tsx +13 -0
- package/frontend/src/components/SeverityBadge.tsx +20 -0
- package/frontend/src/components/layout/AppShell.tsx +16 -0
- package/frontend/src/components/layout/AuthLayout.tsx +32 -0
- package/frontend/src/components/layout/Sidebar.tsx +223 -0
- package/frontend/src/components/layout/TopBar.tsx +47 -0
- package/frontend/src/components/pulls/DiscussionTimeline.tsx +145 -0
- package/frontend/src/components/pulls/FacetRail.tsx +199 -0
- package/frontend/src/components/pulls/LabelChips.tsx +87 -0
- package/frontend/src/components/ui/alert.tsx +30 -0
- package/frontend/src/components/ui/badge.tsx +53 -0
- package/frontend/src/components/ui/button.tsx +51 -0
- package/frontend/src/components/ui/card.tsx +64 -0
- package/frontend/src/components/ui/chart-theme.ts +65 -0
- package/frontend/src/components/ui/data-table.tsx +39 -0
- package/frontend/src/components/ui/echart.tsx +70 -0
- package/frontend/src/components/ui/empty-state.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +39 -0
- package/frontend/src/components/ui/lazy-chart.tsx +21 -0
- package/frontend/src/components/ui/skeleton.tsx +19 -0
- package/frontend/src/hooks/use-current-repository.ts +44 -0
- package/frontend/src/hooks/use-current-user.ts +38 -0
- package/frontend/src/lib/api-client.ts +93 -0
- package/frontend/src/lib/css-color.ts +60 -0
- package/frontend/src/lib/utils.ts +45 -0
- package/frontend/src/main.tsx +22 -0
- package/frontend/src/pages/attention/AttentionQueuePage.tsx +316 -0
- package/frontend/src/pages/attention/RuleSettingsPanel.tsx +177 -0
- package/frontend/src/pages/branches/BranchDetailPage.tsx +632 -0
- package/frontend/src/pages/branches/BranchListPage.tsx +308 -0
- package/frontend/src/pages/dashboard/DashboardPage.tsx +657 -0
- package/frontend/src/pages/issues/IssueListPage.tsx +284 -0
- package/frontend/src/pages/login/LoginPage.tsx +95 -0
- package/frontend/src/pages/meetings/MeetingDetailPage.tsx +403 -0
- package/frontend/src/pages/meetings/MeetingListPage.tsx +264 -0
- package/frontend/src/pages/members/MembersPage.tsx +534 -0
- package/frontend/src/pages/pulls/PullDetailPage.tsx +833 -0
- package/frontend/src/pages/pulls/PullListPage.tsx +399 -0
- package/frontend/src/pages/settings/AiSettingsPage.tsx +449 -0
- package/frontend/src/pages/settings/SettingsPage.tsx +321 -0
- package/frontend/src/pages/setup/SetupPage.tsx +144 -0
- package/frontend/src/styles/globals.css +147 -0
- package/frontend/tsconfig.json +22 -0
- package/frontend/vite.config.ts +57 -0
- package/package.json +48 -0
- package/scripts/e2e-auth-flow.py +131 -0
- package/scripts/e2e-pr-detail.py +128 -0
- package/scripts/e2e-verify.py +773 -0
- package/scripts/verify-ai-pipeline.py +616 -0
- package/scripts/verify-analysis-pipeline.py +303 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""``sig-info.yaml`` 解析。
|
|
2
|
+
|
|
3
|
+
这是 openEuler 各 SIG 的**权威元数据文件**,机器可读,位于
|
|
4
|
+
``openeuler/community`` 的 ``sig/Kernel/sig-info.yaml``。成员名单、
|
|
5
|
+
仓库清单、分支负责人都在这里,由社区工具链维护。
|
|
6
|
+
|
|
7
|
+
此前用的是 ``README.md`` 的 Maintainer 列表加 ``committers.md`` 的模块表,
|
|
8
|
+
两者都是给人看的文档:README 只有 4 位维护者的展示名与 gitee handle,
|
|
9
|
+
committers.md 是按内核路径划分的分工表。合起来只有 101 人,且**没有邮箱、
|
|
10
|
+
没有组织、没有 atomgit 账号**(实测 14% 的人只有 gitee 账号)。
|
|
11
|
+
``sig-info.yaml`` 有 133 人且字段完整。
|
|
12
|
+
|
|
13
|
+
两份材料不冲突,但用途不同,因此都保留:
|
|
14
|
+
- ``sig-info.yaml`` → 成员是谁、管哪些仓库、负责哪些分支
|
|
15
|
+
- ``committers.md`` → 改动了哪个内核路径该找谁(yaml 里没有路径信息)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
# 角色优先级。一个人可以同时是维护者与某仓库的 committer,
|
|
25
|
+
# 取最高的那个作为主角色,全部角色另存。
|
|
26
|
+
ROLE_RANK = {"maintainer": 3, "committer": 2, "contributor": 1}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class RosterEntry:
|
|
31
|
+
"""名单里的一个人。"""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
atomgit_id: str | None = None
|
|
35
|
+
gitee_id: str | None = None
|
|
36
|
+
email: str | None = None
|
|
37
|
+
organization: str | None = None
|
|
38
|
+
|
|
39
|
+
role: str = "contributor"
|
|
40
|
+
"""最高角色。"""
|
|
41
|
+
|
|
42
|
+
roles: list[str] = field(default_factory=list)
|
|
43
|
+
repositories: list[str] = field(default_factory=list)
|
|
44
|
+
"""作为 committer / contributor 参与的仓库。"""
|
|
45
|
+
|
|
46
|
+
admin_repositories: list[str] = field(default_factory=list)
|
|
47
|
+
kept_branches: list[str] = field(default_factory=list)
|
|
48
|
+
"""作为 keeper 负责的分支。"""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def key(self) -> str:
|
|
52
|
+
"""去重键。优先用 atomgit 账号 —— 那是在本平台能匹配到 PR 作者的字段。"""
|
|
53
|
+
return self.atomgit_id or self.gitee_id or self.name
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class BranchKeeper:
|
|
58
|
+
repo: str
|
|
59
|
+
branch: str
|
|
60
|
+
keepers: list[str] = field(default_factory=list)
|
|
61
|
+
"""keeper 的 atomgit/gitee 账号。yaml 里只有 id,姓名要回名单里查。"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class SIGInfo:
|
|
66
|
+
name: str
|
|
67
|
+
description: str | None = None
|
|
68
|
+
mailing_list: str | None = None
|
|
69
|
+
roster: list[RosterEntry] = field(default_factory=list)
|
|
70
|
+
repositories: list[str] = field(default_factory=list)
|
|
71
|
+
branch_keepers: list[BranchKeeper] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _person(raw: object) -> RosterEntry | None:
|
|
75
|
+
if not isinstance(raw, dict):
|
|
76
|
+
return None
|
|
77
|
+
name = str(raw.get("name") or "").strip()
|
|
78
|
+
atomgit = str(raw.get("atomgit_id") or "").strip() or None
|
|
79
|
+
gitee = str(raw.get("gitee_id") or "").strip() or None
|
|
80
|
+
if not name and not atomgit and not gitee:
|
|
81
|
+
return None
|
|
82
|
+
return RosterEntry(
|
|
83
|
+
name=name or (atomgit or gitee or ""),
|
|
84
|
+
atomgit_id=atomgit,
|
|
85
|
+
gitee_id=gitee,
|
|
86
|
+
email=str(raw.get("email") or "").strip() or None,
|
|
87
|
+
organization=str(raw.get("organization") or "").strip() or None,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _as_list(value: object) -> list:
|
|
92
|
+
if value is None:
|
|
93
|
+
return []
|
|
94
|
+
return value if isinstance(value, list) else [value]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_sig_info(text: str) -> SIGInfo | None:
|
|
98
|
+
"""解析 sig-info.yaml。结构不合预期时返回 None,不抛异常。
|
|
99
|
+
|
|
100
|
+
yaml 是上游维护的,字段增删由他们决定;解析失败只该让这轮同步跳过,
|
|
101
|
+
不该让整个名单同步任务报红 —— 名单是低频数据,下轮再试即可。
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
payload = yaml.safe_load(text)
|
|
105
|
+
except yaml.YAMLError:
|
|
106
|
+
return None
|
|
107
|
+
if not isinstance(payload, dict):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
info = SIGInfo(
|
|
111
|
+
name=str(payload.get("name") or "Kernel"),
|
|
112
|
+
description=str(payload.get("description") or "") or None,
|
|
113
|
+
mailing_list=str(payload.get("mailing_list") or "") or None,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
entries: dict[str, RosterEntry] = {}
|
|
117
|
+
|
|
118
|
+
def merge(entry: RosterEntry | None, role: str) -> RosterEntry | None:
|
|
119
|
+
if entry is None:
|
|
120
|
+
return None
|
|
121
|
+
existing = entries.get(entry.key)
|
|
122
|
+
if existing is None:
|
|
123
|
+
entries[entry.key] = entry
|
|
124
|
+
existing = entry
|
|
125
|
+
# 补全字段:同一个人在不同段落出现时可能只写了 id
|
|
126
|
+
for attr in ("name", "atomgit_id", "gitee_id", "email", "organization"):
|
|
127
|
+
if not getattr(existing, attr) and getattr(entry, attr):
|
|
128
|
+
setattr(existing, attr, getattr(entry, attr))
|
|
129
|
+
if role not in existing.roles:
|
|
130
|
+
existing.roles.append(role)
|
|
131
|
+
if ROLE_RANK.get(role, 0) > ROLE_RANK.get(existing.role, 0):
|
|
132
|
+
existing.role = role
|
|
133
|
+
return existing
|
|
134
|
+
|
|
135
|
+
for raw in _as_list(payload.get("maintainers")):
|
|
136
|
+
merge(_person(raw), "maintainer")
|
|
137
|
+
|
|
138
|
+
for repo_entry in _as_list(payload.get("repositories")):
|
|
139
|
+
if not isinstance(repo_entry, dict):
|
|
140
|
+
continue
|
|
141
|
+
repo = repo_entry.get("repo")
|
|
142
|
+
# repo 是仓库名列表(同一份配置同时管源码仓与构建仓)
|
|
143
|
+
repos = [str(item) for item in _as_list(repo) if isinstance(item, str)]
|
|
144
|
+
info.repositories.extend(repos)
|
|
145
|
+
|
|
146
|
+
for raw in _as_list(repo_entry.get("repo_admin")):
|
|
147
|
+
entry = merge(_person(raw), "committer")
|
|
148
|
+
if entry is not None:
|
|
149
|
+
entry.admin_repositories.extend(repos)
|
|
150
|
+
for raw in _as_list(repo_entry.get("committers")):
|
|
151
|
+
entry = merge(_person(raw), "committer")
|
|
152
|
+
if entry is not None:
|
|
153
|
+
entry.repositories.extend(repos)
|
|
154
|
+
for raw in _as_list(repo_entry.get("contributors")):
|
|
155
|
+
entry = merge(_person(raw), "contributor")
|
|
156
|
+
if entry is not None:
|
|
157
|
+
entry.repositories.extend(repos)
|
|
158
|
+
|
|
159
|
+
for raw_branch in _as_list(payload.get("branches")):
|
|
160
|
+
if not isinstance(raw_branch, dict):
|
|
161
|
+
continue
|
|
162
|
+
# keeper 只有 id,没有姓名与邮箱 —— 姓名要回名单里查,
|
|
163
|
+
# 因此这里先存账号,解析结束后统一补名
|
|
164
|
+
keeper_logins = []
|
|
165
|
+
for raw in _as_list(raw_branch.get("keeper")):
|
|
166
|
+
person = _person(raw)
|
|
167
|
+
if person is not None:
|
|
168
|
+
keeper_logins.append(person.key)
|
|
169
|
+
for target in _as_list(raw_branch.get("repo_branch")):
|
|
170
|
+
if not isinstance(target, dict):
|
|
171
|
+
continue
|
|
172
|
+
branch = str(target.get("branch") or "").strip()
|
|
173
|
+
repo = str(target.get("repo") or "").strip()
|
|
174
|
+
if branch:
|
|
175
|
+
info.branch_keepers.append(
|
|
176
|
+
BranchKeeper(repo=repo, branch=branch, keepers=list(keeper_logins))
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# keeper 若不在 committer 名单里,也要作为成员出现 —— 能管分支的人
|
|
180
|
+
# 不列在成员页上会让人以为名单漏了
|
|
181
|
+
for branch in info.branch_keepers:
|
|
182
|
+
for login in branch.keepers:
|
|
183
|
+
if login not in entries:
|
|
184
|
+
entries[login] = RosterEntry(name=login, role="committer", roles=["committer"])
|
|
185
|
+
if login.startswith("http") or "." in login:
|
|
186
|
+
entries[login].gitee_id = login
|
|
187
|
+
else:
|
|
188
|
+
entries[login].atomgit_id = login
|
|
189
|
+
|
|
190
|
+
for entry in entries.values():
|
|
191
|
+
for branch in info.branch_keepers:
|
|
192
|
+
if entry.key in branch.keepers and branch.branch not in entry.kept_branches:
|
|
193
|
+
entry.kept_branches.append(branch.branch)
|
|
194
|
+
entry.repositories = sorted(set(entry.repositories))
|
|
195
|
+
entry.admin_repositories = sorted(set(entry.admin_repositories))
|
|
196
|
+
|
|
197
|
+
info.roster = sorted(
|
|
198
|
+
entries.values(), key=lambda item: (-ROLE_RANK.get(item.role, 0), item.name)
|
|
199
|
+
)
|
|
200
|
+
info.repositories = sorted(set(info.repositories))
|
|
201
|
+
return info
|
|
File without changes
|
|
File without changes
|