feat: add task type hierarchy with subtypes (issue/meeting/support/maintenance/research/review/story/test)
This commit is contained in:
@@ -17,6 +17,35 @@ from app.services.activity import log_activity
|
||||
|
||||
router = APIRouter(tags=["Issues"])
|
||||
|
||||
# ---- Type / Subtype validation ----
|
||||
ISSUE_SUBTYPE_MAP = {
|
||||
'meeting': {'conference', 'handover', 'recap'},
|
||||
'support': {'access', 'information'},
|
||||
'issue': {'infrastructure', 'performance', 'regression', 'security', 'user_experience'},
|
||||
'maintenance': {'deploy', 'release'},
|
||||
'review': {'code_review', 'decision_review', 'function_review'},
|
||||
'story': {'feature', 'improvement', 'refactor'},
|
||||
'test': {'regression', 'security', 'smoke', 'stress'},
|
||||
'research': set(),
|
||||
'task': set(),
|
||||
'resolution': set(),
|
||||
}
|
||||
ALLOWED_ISSUE_TYPES = set(ISSUE_SUBTYPE_MAP.keys())
|
||||
|
||||
|
||||
def _validate_issue_type_subtype(issue_type: str | None, issue_subtype: str | None, require_subtype: bool = False):
|
||||
if issue_type is None:
|
||||
raise HTTPException(status_code=400, detail='issue_type is required')
|
||||
if issue_type not in ALLOWED_ISSUE_TYPES:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid issue_type: {issue_type}')
|
||||
allowed = ISSUE_SUBTYPE_MAP.get(issue_type, set())
|
||||
if issue_subtype:
|
||||
if issue_subtype not in allowed:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid issue_subtype for {issue_type}: {issue_subtype}')
|
||||
else:
|
||||
if require_subtype and allowed:
|
||||
raise HTTPException(status_code=400, detail=f'issue_subtype required for type: {issue_type}')
|
||||
|
||||
|
||||
def _notify_user(db, user_id, ntype, title, message=None, entity_type=None, entity_id=None):
|
||||
n = NotificationModel(user_id=user_id, type=ntype, title=title, message=message,
|
||||
@@ -45,7 +74,7 @@ def create_issue(issue: schemas.IssueCreate, bg: BackgroundTasks, db: Session =
|
||||
|
||||
@router.get("/issues")
|
||||
def list_issues(
|
||||
project_id: int = None, issue_status: str = None, issue_type: str = None,
|
||||
project_id: int = None, issue_status: str = None, issue_type: str = None, issue_subtype: str = None,
|
||||
assignee_id: int = None, tag: str = None,
|
||||
sort_by: str = "created_at", sort_order: str = "desc",
|
||||
page: int = 1, page_size: int = 50,
|
||||
@@ -59,6 +88,8 @@ def list_issues(
|
||||
query = query.filter(models.Issue.status == issue_status)
|
||||
if issue_type:
|
||||
query = query.filter(models.Issue.issue_type == issue_type)
|
||||
if issue_subtype:
|
||||
query = query.filter(models.Issue.issue_subtype == issue_subtype)
|
||||
if assignee_id:
|
||||
query = query.filter(models.Issue.assignee_id == assignee_id)
|
||||
if tag:
|
||||
@@ -108,7 +139,7 @@ def update_issue(issue_id: int, issue_update: schemas.IssueUpdate, db: Session =
|
||||
check_project_role(db, current_user.id, issue.project_id, min_role="dev")
|
||||
if not issue:
|
||||
raise HTTPException(status_code=404, detail="Issue not found")
|
||||
for field, value in issue_update.model_dump(exclude_unset=True).items():
|
||||
for field, value in update_data.items():
|
||||
setattr(issue, field, value)
|
||||
db.commit()
|
||||
db.refresh(issue)
|
||||
|
||||
@@ -298,7 +298,7 @@ def export_issues_csv(project_id: int = None, db: Session = Depends(get_db)):
|
||||
"reporter_id", "assignee_id", "milestone_id", "due_date",
|
||||
"tags", "created_at", "updated_at"])
|
||||
for i in issues:
|
||||
writer.writerow([i.id, i.title, i.issue_type, i.status, i.priority, i.project_id,
|
||||
writer.writerow([i.id, i.title, i.issue_type, i.issue_subtype or , i.status, i.priority, i.project_id,
|
||||
i.reporter_id, i.assignee_id, i.milestone_id, i.due_date,
|
||||
i.tags, i.created_at, i.updated_at])
|
||||
output.seek(0)
|
||||
|
||||
Reference in New Issue
Block a user