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.config import settings
|
||||||
from app.db import get_conn
|
from app.db import get_conn
|
||||||
|
from app.routers.board import _issue_column
|
||||||
from app.services.gitea_client import gitea
|
from app.services.gitea_client import gitea
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -266,16 +267,18 @@ async def create_issue(
|
|||||||
labels=label_ids, milestone=milestone_id, assignee=assignee,
|
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:
|
with get_conn() as conn:
|
||||||
board = conn.execute(
|
board_row = 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),
|
(owner, repo),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if board:
|
if board_row:
|
||||||
|
columns = json.loads(board_row["columns_json"])
|
||||||
|
col = _issue_column(issue, columns, board_row["id"])
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO cards (board_id, gitea_issue_id, column_name, position) VALUES (?, ?, 'Backlog', 0)",
|
"INSERT INTO cards (board_id, gitea_issue_id, column_name, position) VALUES (?, ?, ?, 0)",
|
||||||
(board["id"], issue["number"]),
|
(board_row["id"], issue["number"], col),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
@@ -300,6 +303,7 @@ async def update_issue_api(
|
|||||||
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
label_ids = None
|
||||||
if title:
|
if title:
|
||||||
kwargs["title"] = title
|
kwargs["title"] = title
|
||||||
if body:
|
if body:
|
||||||
@@ -307,13 +311,35 @@ async def update_issue_api(
|
|||||||
if state:
|
if state:
|
||||||
kwargs["state"] = state
|
kwargs["state"] = state
|
||||||
if labels:
|
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():
|
if milestone and milestone.strip().isdigit():
|
||||||
kwargs["milestone"] = int(milestone)
|
kwargs["milestone"] = int(milestone)
|
||||||
if assignee:
|
if assignee:
|
||||||
kwargs["assignees"] = [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}
|
return {"status": "ok", "issue": issue}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from fastapi import APIRouter, Request, HTTPException
|
|||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.db import get_conn
|
from app.db import get_conn
|
||||||
|
from app.routers.board import _issue_column
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(tags=["webhooks"], prefix="/api/webhook")
|
router = APIRouter(tags=["webhooks"], prefix="/api/webhook")
|
||||||
@@ -70,7 +71,7 @@ async def _handle_issue_event(payload: dict):
|
|||||||
|
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
board = conn.execute(
|
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),
|
(owner, repo_name),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
@@ -79,8 +80,9 @@ async def _handle_issue_event(payload: dict):
|
|||||||
board_id = board["id"]
|
board_id = board["id"]
|
||||||
|
|
||||||
if action in ("opened", "reopened"):
|
if action in ("opened", "reopened"):
|
||||||
# Add card to default column
|
# Determine column from label mapping (fallback to first column)
|
||||||
column = "Backlog"
|
columns = json.loads(board["columns_json"])
|
||||||
|
column = _issue_column(issue, columns, board_id)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT OR IGNORE INTO cards (board_id, gitea_issue_id, column_name, position)
|
"""INSERT OR IGNORE INTO cards (board_id, gitea_issue_id, column_name, position)
|
||||||
VALUES (?, ?, ?, 0)""",
|
VALUES (?, ?, ?, 0)""",
|
||||||
|
|||||||
@@ -152,9 +152,18 @@ class GiteaClient:
|
|||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
async def update_issue_labels(
|
async def update_issue_labels(
|
||||||
self, owner: str, repo: str, issue_id: int, labels: list[str]
|
self, owner: str, repo: str, issue_id: int, labels: list[int]
|
||||||
) -> dict:
|
) -> list[dict]:
|
||||||
return await self.update_issue(owner, repo, issue_id, labels=labels)
|
"""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:
|
async def close_issue(self, owner: str, repo: str, issue_id: int) -> dict:
|
||||||
return await self.update_issue(owner, repo, issue_id, state="closed")
|
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));
|
document.querySelectorAll('#card-modal-content input[name="edit-labels"]:checked').forEach(cb => checkedLabels.push(cb.value));
|
||||||
const params = new URLSearchParams({title, body, assignee});
|
const params = new URLSearchParams({title, body, assignee});
|
||||||
if (checkedLabels.length) params.set('labels', checkedLabels.join(','));
|
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()}
|
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 ──
|
// ── Helpers ──
|
||||||
|
|||||||
Reference in New Issue
Block a user