fix: positionnement des cartes selon les labels et mise à jour après édition
CI / test (push) Failing after 8s
CI / lint (push) Failing after 4s

- 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)
This commit is contained in:
2026-07-08 11:27:56 -04:00
parent 6ca376a675
commit 7b2fa01b0a
4 changed files with 59 additions and 16 deletions
+34 -8
View File
@@ -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}
+5 -3
View File
@@ -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)""",
+12 -3
View File
@@ -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")
+8 -2
View File
@@ -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 ──