diff --git a/README.md b/README.md index 130e77e..080ce71 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ python -m uvicorn main:app --host 0.0.0.0 --port 8080 --reload --env-file .env - `AUTO_ASSIGNED_LABEL` - необязательно, по умолчанию `auto-assigned` - `AUTO_ASSIGN_REVIEWERS_COUNT` - необязательно, по умолчанию `2` (сколько ревьюеров назначать автоматически) - `AUTO_ASSIGN_EXCLUDED_AUTHORS` - необязательно, CSV логинов Gitea; для этих авторов автоназначение и auto-label на `PR opened` не выполняются +- `BACKEND_USERS` - необязательно, CSV логинов Gitea backend-команды; если автор PR входит в этот список, ревьюеры выбираются только из этого же списка +- `FRONTEND_USERS` - необязательно, CSV логинов Gitea frontend-команды; если автор PR входит в этот список, ревьюеры выбираются только из этого же списка - `APPROVALS_REQUIRED_FOR_MERGE` - необязательно, по умолчанию `2` (порог approve для сообщения "можно мерджить") - `NOTIFY_ON_WEEKENDS` - необязательно, по умолчанию `true`; если `false`, бот не отправляет ЛС в субботу и воскресенье (МСК) - `ALLOW_SELF_ASSIGN` - необязательно, по умолчанию `false` (для локального теста можно `true`) @@ -52,6 +54,8 @@ python -m uvicorn main:app --host 0.0.0.0 --port 8080 --reload --env-file .env - `GITEA_COUNT_REPOS` - необязательно, CSV репозиториев `org/repo` для расчета нагрузки (исторический счетчик назначений) - `BOT_DB_PATH` - необязательно, по умолчанию `botreviewer.sqlite3` +Если автор PR не входит ни в `BACKEND_USERS`, ни в `FRONTEND_USERS`, бот использует общий пул доступных пользователей (как раньше). + ## Webhook-эндпоинты - Обновления Telegram: `POST /telegram/webhook` diff --git a/main.py b/main.py index 9419979..933410c 100644 --- a/main.py +++ b/main.py @@ -51,6 +51,16 @@ AUTO_ASSIGN_EXCLUDED_AUTHORS = { for v in os.getenv("AUTO_ASSIGN_EXCLUDED_AUTHORS", "").split(",") if v.strip() } +BACKEND_USERS = { + v.strip().lower() + for v in os.getenv("BACKEND_USERS", "").split(",") + if v.strip() +} +FRONTEND_USERS = { + v.strip().lower() + for v in os.getenv("FRONTEND_USERS", "").split(",") + if v.strip() +} NOTIFY_ON_WEEKENDS = os.getenv("NOTIFY_ON_WEEKENDS", "true").lower() == "true" MSK_TZ = timezone(timedelta(hours=3)) @@ -529,6 +539,23 @@ def choose_reviewer(candidates: list[RegisteredUser]) -> RegisteredUser: return random.choice(candidates) +def get_reviewer_group_for_author(author_login: str) -> tuple[set[str] | None, str]: + author_login_norm = author_login.strip().lower() + if author_login_norm in BACKEND_USERS: + return BACKEND_USERS, "backend" + if author_login_norm in FRONTEND_USERS: + return FRONTEND_USERS, "frontend" + return None, "" + + +def filter_candidates_by_author_group(author_login: str, candidates: list[RegisteredUser]) -> tuple[list[RegisteredUser], str]: + configured_group, group_name = get_reviewer_group_for_author(author_login) + if configured_group is None: + return candidates, "" + filtered = [candidate for candidate in candidates if candidate.gitea_login.strip().lower() in configured_group] + return filtered, group_name + + async def build_reviewer_load_counts(candidates: list[RegisteredUser], current_repo_full_name: str) -> dict[str, int]: _ = current_repo_full_name return { @@ -835,13 +862,20 @@ async def handle_pr_opened(payload: dict[str, Any]) -> None: log_warn("[gitea] Клиент не настроен, автоназначение пропущено.") return - candidates = storage.get_available_candidates( + all_candidates = storage.get_available_candidates( author_login=author_login, today=msk_now().date(), allow_self_assign=ALLOW_SELF_ASSIGN, ) + candidates, assigned_group_name = filter_candidates_by_author_group(author_login, all_candidates) if not candidates: - log_warn(f"[assign] Нет доступных кандидатов для {repo_full_name}#{pr_number}.") + if assigned_group_name: + log_warn( + f"[assign] Нет доступных кандидатов для группы '{assigned_group_name}' " + f"({repo_full_name}#{pr_number}, author={author_login})." + ) + else: + log_warn(f"[assign] Нет доступных кандидатов для {repo_full_name}#{pr_number}.") return selected_reviewers, counts = await choose_reviewers_with_remote_load( @@ -856,7 +890,8 @@ async def handle_pr_opened(payload: dict[str, Any]) -> None: log_info( f"[assign] Автоназначение для {repo_full_name}#{pr_number}. " - f"Кандидаты={len(candidates)}, выбраны={selected_logins}, нагрузки={dict(counts)}" + f"Кандидаты={len(candidates)} из {len(all_candidates)}, " + f"группа={assigned_group_name or 'all'}, выбраны={selected_logins}, нагрузки={dict(counts)}" ) await gitea_client.assign_reviewers(repo_full_name, int(pr_number), selected_logins) await gitea_client.add_label(repo_full_name, int(pr_number), AUTO_ASSIGNED_LABEL)