fix: positionnement des cartes selon les labels et mise à jour après édition
- 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:
+34
-8
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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)""",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 ──
|
||||
|
||||
Reference in New Issue
Block a user