From 7b2fa01b0a56c482d8347bdd7a17446d38984411 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 8 Jul 2026 11:27:56 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20positionnement=20des=20cartes=20selon=20?= =?UTF-8?q?les=20labels=20et=20mise=20=C3=A0=20jour=20apr=C3=A8s=20=C3=A9d?= =?UTF-8?q?ition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create_issue: utilise _issue_column() au lieu de hardcoder 'Backlog' - update_issue_api: recalcule la colonne après changement de labels - update_issue_api: utilise l'endpoint PUT /labels au lieu de PATCH (Gitea ignore labels dans PATCH) - webhooks: utilise _issue_column() pour 'opened'/'reopened' - saveIssueEdit: rafraîchit le board (HTMX) après édition au lieu de rouvrir la modale - update_issue_labels: corrigé pour utiliser PUT /issues/{id}/labels (liste d'entiers) --- app/routers/api.py | 42 +++++++++++++++++++++++++++++------- app/routers/webhooks.py | 8 ++++--- app/services/gitea_client.py | 15 ++++++++++--- app/templates/board.html | 10 +++++++-- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/app/routers/api.py b/app/routers/api.py index a514b58..c403e45 100644 --- a/app/routers/api.py +++ b/app/routers/api.py @@ -10,6 +10,7 @@ from fastapi import APIRouter, HTTPException, Query, Request from app.config import settings from app.db import get_conn +from app.routers.board import _issue_column from app.services.gitea_client import gitea logger = logging.getLogger(__name__) @@ -266,16 +267,18 @@ async def create_issue( labels=label_ids, milestone=milestone_id, assignee=assignee, ) - # Add card to local board + # Add card to local board — determine column from label mapping with get_conn() as conn: - board = conn.execute( - "SELECT id FROM boards WHERE project_owner=? AND project_name=?", + board_row = conn.execute( + "SELECT id, columns_json FROM boards WHERE project_owner=? AND project_name=?", (owner, repo), ).fetchone() - if board: + if board_row: + columns = json.loads(board_row["columns_json"]) + col = _issue_column(issue, columns, board_row["id"]) conn.execute( - "INSERT INTO cards (board_id, gitea_issue_id, column_name, position) VALUES (?, ?, 'Backlog', 0)", - (board["id"], issue["number"]), + "INSERT INTO cards (board_id, gitea_issue_id, column_name, position) VALUES (?, ?, ?, 0)", + (board_row["id"], issue["number"], col), ) conn.commit() @@ -300,6 +303,7 @@ async def update_issue_api( raise HTTPException(status_code=429, detail="Rate limit exceeded") kwargs = {} + label_ids = None if title: kwargs["title"] = title if body: @@ -307,13 +311,35 @@ async def update_issue_api( if state: kwargs["state"] = state if labels: - kwargs["labels"] = labels.split(",") + label_ids = [int(l) for l in labels.split(",") if l.strip().isdigit()] if milestone and milestone.strip().isdigit(): kwargs["milestone"] = int(milestone) if assignee: kwargs["assignees"] = [assignee] - issue = await gitea.update_issue(owner, repo, issue_id, **kwargs) + if kwargs: + await gitea.update_issue(owner, repo, issue_id, **kwargs) + if label_ids is not None: + await gitea.update_issue_labels(owner, repo, issue_id, label_ids) + + # Re-fetch issue to get complete updated state for column recalculation + issue = await gitea.get_issue(owner, repo, issue_id) + + # Recalculate card column based on updated labels + with get_conn() as conn: + board_row = conn.execute( + "SELECT id, columns_json FROM boards WHERE project_owner=? AND project_name=?", + (owner, repo), + ).fetchone() + if board_row: + columns = json.loads(board_row["columns_json"]) + col = _issue_column(issue, columns, board_row["id"]) + conn.execute( + "UPDATE cards SET column_name=?, updated_at=CURRENT_TIMESTAMP WHERE board_id=? AND gitea_issue_id=?", + (col, board_row["id"], issue_id), + ) + conn.commit() + return {"status": "ok", "issue": issue} diff --git a/app/routers/webhooks.py b/app/routers/webhooks.py index b5c9441..813819a 100644 --- a/app/routers/webhooks.py +++ b/app/routers/webhooks.py @@ -10,6 +10,7 @@ from fastapi import APIRouter, Request, HTTPException from app.config import settings from app.db import get_conn +from app.routers.board import _issue_column logger = logging.getLogger(__name__) router = APIRouter(tags=["webhooks"], prefix="/api/webhook") @@ -70,7 +71,7 @@ async def _handle_issue_event(payload: dict): with get_conn() as conn: board = conn.execute( - "SELECT id FROM boards WHERE project_owner=? AND project_name=?", + "SELECT id, columns_json FROM boards WHERE project_owner=? AND project_name=?", (owner, repo_name), ).fetchone() @@ -79,8 +80,9 @@ async def _handle_issue_event(payload: dict): board_id = board["id"] if action in ("opened", "reopened"): - # Add card to default column - column = "Backlog" + # Determine column from label mapping (fallback to first column) + columns = json.loads(board["columns_json"]) + column = _issue_column(issue, columns, board_id) conn.execute( """INSERT OR IGNORE INTO cards (board_id, gitea_issue_id, column_name, position) VALUES (?, ?, ?, 0)""", diff --git a/app/services/gitea_client.py b/app/services/gitea_client.py index e891f87..649435a 100644 --- a/app/services/gitea_client.py +++ b/app/services/gitea_client.py @@ -152,9 +152,18 @@ class GiteaClient: return resp.json() async def update_issue_labels( - self, owner: str, repo: str, issue_id: int, labels: list[str] - ) -> dict: - return await self.update_issue(owner, repo, issue_id, labels=labels) + self, owner: str, repo: str, issue_id: int, labels: list[int] + ) -> list[dict]: + """Replace all labels on an issue (PUT endpoint).""" + self._invalidate_issue_cache(owner, repo) + async with httpx.AsyncClient(timeout=10) as client: + resp = await client.put( + f"{self._base}/repos/{owner}/{repo}/issues/{issue_id}/labels", + headers=self._headers, + json={"labels": labels}, + ) + resp.raise_for_status() + return resp.json() async def close_issue(self, owner: str, repo: str, issue_id: int) -> dict: return await self.update_issue(owner, repo, issue_id, state="closed") diff --git a/app/templates/board.html b/app/templates/board.html index d5b5e16..cf16f4b 100644 --- a/app/templates/board.html +++ b/app/templates/board.html @@ -348,10 +348,16 @@ document.querySelectorAll('#card-modal-content input[name="edit-labels"]:checked').forEach(cb => checkedLabels.push(cb.value)); const params = new URLSearchParams({title, body, assignee}); if (checkedLabels.length) params.set('labels', checkedLabels.join(',')); - await fetch(`/api/issues/${owner}/${repo}/${issueId}?${params}`, { + const resp = await fetch(`/api/issues/${owner}/${repo}/${issueId}?${params}`, { method: 'PATCH', headers: {'X-CSRF-Token': getCSRFToken()} }); - openCardDetail(owner, repo, issueId); + if (resp.ok) { + closeModal(); + // Refresh board columns to reposition card + htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'}); + } else { + alert('Erreur lors de la mise à jour'); + } } // ── Helpers ──