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_metrics,
|
||||||
get_notes,
|
get_notes,
|
||||||
get_picks,
|
get_picks,
|
||||||
|
get_project_activity,
|
||||||
get_stats,
|
get_stats,
|
||||||
init_db,
|
init_db,
|
||||||
log_event,
|
log_event,
|
||||||
@@ -600,6 +601,24 @@ async def dashboard(request: Request):
|
|||||||
repo_info["starred"] = full_name in starred_names
|
repo_info["starred"] = full_name in starred_names
|
||||||
for repo_info in inactive:
|
for repo_info in inactive:
|
||||||
repo_info["starred"] = repo_info["full_name"] in starred_names
|
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:
|
for repo_info in active:
|
||||||
repo_info["home_tab"] = "active"
|
repo_info["home_tab"] = "active"
|
||||||
for repo_info in inactive:
|
for repo_info in inactive:
|
||||||
@@ -2305,13 +2324,17 @@ async def api_capture(request: Request):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/capture", response_class=HTMLResponse)
|
@app.get("/capture", response_class=HTMLResponse)
|
||||||
async def capture_page(request: Request):
|
async def capture_page(request: Request, repo: str = ""):
|
||||||
"""Quick-capture page: form, status and usage instructions."""
|
"""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()
|
settings = get_settings()
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"capture.html",
|
"capture.html",
|
||||||
capture_repo=settings.capture_repo,
|
capture_repo=repo.strip() or settings.capture_repo,
|
||||||
api_enabled=bool(settings.capture_token),
|
api_enabled=bool(settings.capture_token),
|
||||||
ntfy_topic=settings.capture_ntfy_topic,
|
ntfy_topic=settings.capture_ntfy_topic,
|
||||||
ntfy_url=settings.ntfy_url,
|
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}
|
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:
|
def get_metrics(repo_full_name: str | None = None, window_days: int = 30) -> dict:
|
||||||
"""Throughput and cycle time computed from the journal.
|
"""Throughput and cycle time computed from the journal.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user