mirror of
https://github.com/hanzoai/insights.git
synced 2026-11-02 14:32:45 +00:00
Both are Django apps, and both pin `label` in apps.py — "stamp" and "review". django_migrations keys a row on (app_label, name), and app_label comes from that pin and not from the import path, so moving the directory does not orphan a single migration. All six stay where they are, untouched. products/stamphog -> products/stamp name = products.stamp.backend products/review_hog -> products/review name = products.review.backend StamphogScene/ -> StampScene/, and stamphogSceneLogic.ts with it 189 files follow. An earlier attempt at this move failed twice for reasons worth recording: `git mv A B` where B exists puts A INSIDE B, and a stray products/stamp/node_modules from an abandoned run was enough to make B exist — the app ended up nested two deep. And `git ls-files` does not list a file moved with plain mv and not yet staged, so a pass over "every tracked file" silently skips it; that is how StampScene.tsx kept importing ./stampSceneLogic after the file beside it had been renamed back. bin/check-imports-are-committed: all first-party imports across 15,843 files resolve. Co-authored-by: Hanzo Dev <[email protected]>
81 lines
3.5 KiB
Python
81 lines
3.5 KiB
Python
"""Resolve a merged PR to a digest ``audience_key``.
|
|
|
|
Global best-effort cascade: repo-declared digest channel (see logic/digest_config.py) ->
|
|
PR author -> GitHub team slug -> "repo:{repository}" fallback. Digest grouping and channel
|
|
routing key off that opaque string alone (see models.DigestChannel).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import structlog
|
|
|
|
from .digest_config import load_repo_digest_config
|
|
from .github_client import StampGitHubClient
|
|
|
|
if TYPE_CHECKING:
|
|
from ..models import StampRepoConfig
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
def _repository_audience_key(repo_config: StampRepoConfig) -> str:
|
|
# The pending distributed owners.yaml resolver (PR #68872, contact.slack) is a channel-
|
|
# resolution clue, not an audience_key source — it slots into logic/channel_resolution.py to
|
|
# correct which Slack channel a "repo:" fallback lands in, not this cascade.
|
|
return f"repo:{repo_config.repository}"
|
|
|
|
|
|
def _author_team_audience_key(repo_config: StampRepoConfig, pr_payload: dict[str, Any]) -> str:
|
|
"""Resolve the PR author's GitHub team, live, via the org's team memberships.
|
|
|
|
Falls back to the repository key (with a warning) whenever the author has no resolvable team —
|
|
missing fields, no team membership, or a failed lookup. Wrapped end-to-end so a flaky GitHub call
|
|
never blocks capture of the merged PR itself.
|
|
"""
|
|
try:
|
|
login = ((pr_payload or {}).get("user") or {}).get("login")
|
|
org = (repo_config.repository or "").split("/", 1)[0]
|
|
if not login or not org:
|
|
logger.warning(
|
|
"stamp_author_team_audience_missing_fields",
|
|
repository=repo_config.repository,
|
|
has_login=bool(login),
|
|
)
|
|
return _repository_audience_key(repo_config)
|
|
|
|
# One GraphQL call per merged PR — merge volume is tiny next to API limits, not worth a cache.
|
|
slugs = StampGitHubClient(repo_config.installation_id).get_user_team_slugs(org, login)
|
|
if not slugs:
|
|
logger.warning("stamp_author_team_audience_no_team", repository=repo_config.repository, login=login)
|
|
return _repository_audience_key(repo_config)
|
|
|
|
chosen, *other_teams = slugs
|
|
if other_teams:
|
|
logger.info(
|
|
"stamp_author_team_audience_multiple_teams",
|
|
login=login,
|
|
chosen=chosen,
|
|
other_teams=other_teams,
|
|
)
|
|
# `chosen` (the raw team slug) becomes the audience_key unchanged, and channel_resolution
|
|
# later matches it against a real Slack channel name for auto-provisioning. This trusts
|
|
# GitHub org team-slug governance; only PUBLIC channels are matched (see channel_resolution).
|
|
return chosen
|
|
except Exception:
|
|
logger.warning(
|
|
"stamp_author_team_audience_resolution_failed", repository=repo_config.repository, exc_info=True
|
|
)
|
|
return _repository_audience_key(repo_config)
|
|
|
|
|
|
def resolve_audience_key(repo_config: StampRepoConfig, pr_payload: dict[str, Any]) -> str:
|
|
"""Map a merged PR to its digest audience_key via the single global cascade."""
|
|
digest_config = load_repo_digest_config(repo_config)
|
|
if digest_config is not None and digest_config.channel:
|
|
# Repo declared a single digest channel for all its PRs, so they group per-repo
|
|
# instead of cascading through the author -> team lookup.
|
|
return _repository_audience_key(repo_config)
|
|
return _author_team_audience_key(repo_config, pr_payload)
|