get_project_activity(): fermetures/déplacements 7 j et dernier événement par dépôt en une seule requête sur board_events; la page /capture accepte ?repo= pour pré-remplir le dépôt.
This commit is contained in:
+26
-3
@@ -58,6 +58,7 @@ from app.models import (
|
||||
get_metrics,
|
||||
get_notes,
|
||||
get_picks,
|
||||
get_project_activity,
|
||||
get_stats,
|
||||
init_db,
|
||||
log_event,
|
||||
@@ -600,6 +601,24 @@ async def dashboard(request: Request):
|
||||
repo_info["starred"] = full_name in starred_names
|
||||
for repo_info in inactive:
|
||||
repo_info["starred"] = repo_info["full_name"] in starred_names
|
||||
activity_by_repo = get_project_activity()
|
||||
today_utc = datetime.now(timezone.utc).date()
|
||||
for repo_info in active + inactive + archived:
|
||||
act = activity_by_repo.get(repo_info["full_name"])
|
||||
if act is None:
|
||||
repo_info["activity"] = None
|
||||
continue
|
||||
idle_days = None
|
||||
if act["last_at"]:
|
||||
last_dt = _parse_datetime(act["last_at"])
|
||||
if last_dt is not None:
|
||||
idle_days = (today_utc - last_dt.date()).days
|
||||
repo_info["activity"] = {
|
||||
"closed": act["closed"],
|
||||
"moved": act["moved"],
|
||||
"idle_days": idle_days,
|
||||
"last_at": act["last_at"] or "",
|
||||
}
|
||||
for repo_info in active:
|
||||
repo_info["home_tab"] = "active"
|
||||
for repo_info in inactive:
|
||||
@@ -2305,13 +2324,17 @@ async def api_capture(request: Request):
|
||||
|
||||
|
||||
@app.get("/capture", response_class=HTMLResponse)
|
||||
async def capture_page(request: Request):
|
||||
"""Quick-capture page: form, status and usage instructions."""
|
||||
async def capture_page(request: Request, repo: str = ""):
|
||||
"""Quick-capture page: form, status and usage instructions.
|
||||
|
||||
`?repo=Owner/name` pre-fills the target repo (issue #23: the
|
||||
project card's quick actions capture straight into a project).
|
||||
"""
|
||||
settings = get_settings()
|
||||
return render(
|
||||
request,
|
||||
"capture.html",
|
||||
capture_repo=settings.capture_repo,
|
||||
capture_repo=repo.strip() or settings.capture_repo,
|
||||
api_enabled=bool(settings.capture_token),
|
||||
ntfy_topic=settings.capture_ntfy_topic,
|
||||
ntfy_url=settings.ntfy_url,
|
||||
|
||||
@@ -368,6 +368,38 @@ def column_entry_times(repo_full_name: str) -> dict[int, str]:
|
||||
return {row["issue_number"]: row["entered_at"] for row in rows}
|
||||
|
||||
|
||||
def get_project_activity(days: int = 7) -> dict:
|
||||
"""Per-repo journal activity in ONE aggregated pass (issue #19).
|
||||
|
||||
For every repo present in the journal: closures and moves of
|
||||
the last `days` days (journal stamps are UTC strings), plus
|
||||
the most recent event ever seen for that repo.
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
with get_db() as conn:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT repo_full_name,
|
||||
SUM(CASE WHEN event_type = 'closed' AND created_at >= ?
|
||||
THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN event_type = 'moved' AND created_at >= ?
|
||||
THEN 1 ELSE 0 END),
|
||||
MAX(created_at)
|
||||
FROM board_events
|
||||
GROUP BY repo_full_name
|
||||
""",
|
||||
(cutoff, cutoff),
|
||||
).fetchall()
|
||||
return {
|
||||
r[0]: {"closed": r[1] or 0, "moved": r[2] or 0, "last_at": r[3]}
|
||||
for r in rows
|
||||
}
|
||||
|
||||
|
||||
def get_metrics(repo_full_name: str | None = None, window_days: int = 30) -> dict:
|
||||
"""Throughput and cycle time computed from the journal.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user