Internal Documentation internal
TalkIDE internal documentation

Admin (Mirek, role platform_admin) třídí issue přes REST API — mění status, nastavuje severity=CRITICAL, přidává resolution_note a linkuje duplikáty. Mara ani jiní agenti k tomuto endpointu nemají přístup.

  • Pouze platform_admin role může volat PATCH /api/issues/{id}.
  • severity=CRITICAL může nastavit výhradně admin — Mara nikdy (403 při pokusu viz UC-09001).
  • Status transitions jsou řízené — viz stavový diagram níže.
  • Phase 1: admin operuje přes REST (curl / httpie / Mirekův vlastní script). Studio UI přijde v Phase 2 (#82).

Status state machine

                       ┌─────────────────────────────────────┐
                       │                                     │
         report_issue  ▼                                     │
         ──────────► OPEN ──────────► TRIAGED ──────────► IN_PROGRESS
                                        │                    │
                                        ├────────────────────┤
                                        │                    │
                                        ▼                    ▼
                                    RESOLVED            RESOLVED
                                    WONTFIX             WONTFIX
                                    DUPLICATE           DUPLICATE
ZDoPodmínky
OPENTRIAGEDAdmin potvrdil a ohodnotil issue
OPENWONTFIXAdmin rozhodl nepravdivý nebo nevhodný report
OPENDUPLICATEAdmin linkoval parent_issue_id; resolution_note doporučen
TRIAGEDIN_PROGRESSAdmin začal řešit
TRIAGEDRESOLVEDRychlé opravy bez explicitního IN_PROGRESS
TRIAGEDWONTFIXPřehodnocení po triage
TRIAGEDDUPLICATEPozdní detekce duplikátu
IN_PROGRESSRESOLVEDOprava dokončena; resolution_note povinný
IN_PROGRESSWONTFIXRozhodnutí during implementation
RESOLVEDOPENReopening — chyba se opakovala nebo fix selhala
WONTFIXOPENPřehodnocení — issue bude řešen
DUPLICATEOPENParent issue zrušen; tento se stane samostatným

Přechody RESOLVED → *, WONTFIX → *, DUPLICATE → * (kromě zpět na OPEN) jsou zakázány — uzavřený issue se jen reopenuje na OPEN.


Flow 1 — Admin třídí nový OPEN issue

sequenceDiagram
    actor Admin as Admin (Mirek)

    Admin->>+BE: PATCH /api/issues/{id}
    Note over BE: Authorization: Bearer {adminJWT}<br/>{ status: "TRIAGED", severity: "CRITICAL" }

    BE->>BE: ověř Admin JWT (platform_admin role)
    alt chybí role platform_admin
        BE-->>Admin: 403 Forbidden
    end

    BE->>DB: SELECT issues WHERE id = ? FOR UPDATE

    alt issue neexistuje
        BE-->>Admin: 404 Not Found
    end

    BE->>BE: validuj status transition (OPEN → TRIAGED je povoleno)
    alt nepovolená transition
        BE-->>Admin: 422 Unprocessable Entity (INVALID_TRANSITION)
    end

    BE->>DB: UPDATE issues SET status=TRIAGED, severity=CRITICAL, triaged_at=now()<br/>WHERE id = ?

    BE-->>-Admin: 200 OK { id, status, severity, triaged_at }

Flow 2 — Admin označuje issue jako DUPLICATE

sequenceDiagram
    actor Admin as Admin (Mirek)

    Admin->>+BE: PATCH /api/issues/{id}
    Note over BE: { status: "DUPLICATE", parent_issue_id: "uuid-parent",<br/>  resolution_note: "Stejný jako #parent-id" }

    BE->>BE: ověř Admin JWT

    BE->>DB: SELECT issues WHERE id = ? FOR UPDATE
    BE->>DB: SELECT issues WHERE id = parent_issue_id (ověř existenci parenta)

    alt parent neexistuje
        BE-->>Admin: 422 Unprocessable Entity (PARENT_ISSUE_NOT_FOUND)
    end

    alt parent je sám DUPLICATE (chain)
        BE-->>Admin: 422 Unprocessable Entity (DUPLICATE_CHAIN_NOT_ALLOWED)
    end

    BE->>DB: UPDATE issues SET status=DUPLICATE, parent_issue_id=?,<br/>resolution_note=?, resolved_by_user_id={admin}, resolved_at=now()<br/>WHERE id = ?

    BE-->>-Admin: 200 OK { id, status, parent_issue_id, resolution_note }

Flow 3 — Admin resolves issue

sequenceDiagram
    actor Admin as Admin (Mirek)

    Admin->>+BE: PATCH /api/issues/{id}
    Note over BE: { status: "RESOLVED",<br/>  resolution_note: "Opraveno v deployi 2026-05-14, registry scope fixed." }

    BE->>BE: ověř Admin JWT

    BE->>DB: SELECT issues WHERE id = ? FOR UPDATE

    BE->>BE: validuj: status IN (TRIAGED, IN_PROGRESS) → RESOLVED povoleno
    BE->>BE: validuj: resolution_note povinný pro RESOLVED

    alt resolution_note chybí
        BE-->>Admin: 400 Bad Request (RESOLUTION_NOTE_REQUIRED)
    end

    BE->>DB: UPDATE issues SET status=RESOLVED, resolution_note=?,<br/>resolved_by_user_id={admin}, resolved_at=now()<br/>WHERE id = ?

    BE-->>-Admin: 200 OK { id, status, resolved_at, resolution_note }

REST API

PATCH /api/issues/{id}

Autentizace: Admin JWT s rolí platform_admin. Jakýkoli jiný JWT → 403 Forbidden.

Request (všechna pole optional — jen ta přítomná se uplatní):

{
  "status": "TRIAGED",
  "severity": "CRITICAL",
  "resolution_note": "...",
  "parent_issue_id": "uuid-of-parent"
}

200 OK:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "TRIAGED",
  "severity": "CRITICAL",
  "triaged_at": "2026-05-13T16:00:00Z",
  "resolved_at": null,
  "resolved_by_user_id": null,
  "resolution_note": null,
  "parent_issue_id": null
}

400 Bad Request (resolution_note chybí při RESOLVED/WONTFIX):

{
  "code": "RESOLUTION_NOTE_REQUIRED",
  "message": "resolution_note is required when resolving or closing an issue"
}

403 Forbidden (chybí platform_admin role):

{
  "code": "FORBIDDEN",
  "message": "Only platform_admin can triage issues"
}

404 Not Found:

{
  "code": "NOT_FOUND",
  "message": "Issue not found"
}

422 Unprocessable Entity (nepovolená status transition):

{
  "code": "INVALID_TRANSITION",
  "message": "Cannot transition from RESOLVED to IN_PROGRESS"
}

422 Unprocessable Entity (parent_issue_id neexistuje):

{
  "code": "PARENT_ISSUE_NOT_FOUND",
  "message": "Parent issue not found"
}

422 Unprocessable Entity (chain duplikátů):

{
  "code": "DUPLICATE_CHAIN_NOT_ALLOWED",
  "message": "Parent issue is already a DUPLICATE — use its parent instead"
}

Backend

Validace

PoleConstraintsPoznámka
JWT roleplatform_adminJakýkoli jiný JWT → 403
statusnullable, in(OPEN, TRIAGED, IN_PROGRESS, RESOLVED, WONTFIX, DUPLICATE)Validovat přechodovou tabulku (viz state machine)
severitynullable, in(LOW, MEDIUM, HIGH, CRITICAL)CRITICAL povoleno pro admin — toto je záměrné; Mara blokována na 403 v UC-09001
resolution_notepovinný pokud status ∈ {RESOLVED, WONTFIX, DUPLICATE}max 5000 chars
parent_issue_idUUID, povinný pokud status=DUPLICATEParent musí existovat a nesmí být sám DUPLICATE
Transition enforcementviz state machineNepovolená transition → 422
Timestamp fieldsserver-side autotriaged_at=now() při přechodu na TRIAGED; resolved_at=now() při RESOLVED/WONTFIX/DUPLICATE
resolved_by_user_idautomaticky z admin JWT subjektuNastavit při RESOLVED/WONTFIX/DUPLICATE

Povinnost resolution_note

Cílový statusresolution_note povinný?
TRIAGEDNe
IN_PROGRESSNe
RESOLVEDAno
WONTFIXAno
DUPLICATEDoporučen, ale nepovinný (parent_issue_id postačí)
OPEN (reopen)Ne

Test Cases

GIVENWHENTHEN
Admin JWT, issue ve stavu OPENPATCH /api/issues/{id} s { status: "TRIAGED", severity: "CRITICAL" }200 OK; status=TRIAGED, severity=CRITICAL, triaged_at nastaven; non-admin by dostal 403
Admin JWT, issue ve stavu TRIAGEDPATCH /api/issues/{id} s { status: "IN_PROGRESS" }200 OK; status=IN_PROGRESS
Admin JWT, issue IN_PROGRESS, chybí resolution_notePATCH /api/issues/{id} s { status: "RESOLVED" }400 Bad Request, code=RESOLUTION_NOTE_REQUIRED
Admin JWT, issue IN_PROGRESS, platná resolution_notePATCH /api/issues/{id} s { status: "RESOLVED", resolution_note: "..." }200 OK; status=RESOLVED, resolved_at nastaven, resolved_by_user_id = admin ID
Admin JWT, pokus o RESOLVED → IN_PROGRESSPATCH /api/issues/{id} s { status: "IN_PROGRESS" }422 Unprocessable Entity, code=INVALID_TRANSITION
Admin JWT, status=DUPLICATE, platný parent_issue_idPATCH /api/issues/{id} s { status: "DUPLICATE", parent_issue_id: "..." }200 OK; parent_issue_id nastaven; resolved_at nastaven
Admin JWT, status=DUPLICATE, parent_issue_id je sám DUPLICATEPATCH /api/issues/{id}422, code=DUPLICATE_CHAIN_NOT_ALLOWED
Admin JWT, status=DUPLICATE, neexistující parent_issue_idPATCH /api/issues/{id}422, code=PARENT_ISSUE_NOT_FOUND
Non-admin JWT (agent nebo user JWT)PATCH /api/issues/{id} s libovolným payload403 Forbidden, code=FORBIDDEN
Issue neexistujePATCH /api/issues/{neexistujici-id}404 Not Found
Admin JWT, WONTFIX bez resolution_notePATCH /api/issues/{id} s { status: "WONTFIX" }400 Bad Request, code=RESOLUTION_NOTE_REQUIRED
Admin JWT, pouze severity update bez status změnyPATCH /api/issues/{id} s { severity: "HIGH" }200 OK; pouze severity aktualizována; status beze změny

Poznámky k implementaci

Partial update (PATCH sémantika)

PATCH /api/issues/{id} je partial update — přítomné fieldy se uplatní, chybějící se nezmění. BE musí rozlišit “field chybí v requestu” od “field má null hodnotu”. Doporučeno: explicitní nullable wrapper nebo přítomnost klíče v JSON body.

Timestamps server-side

Admin nenastavuje triaged_at, resolved_at explicitně — server je nastavuje při příslušném status přechodu:

  • triaged_at = now() při prvním přechodu do TRIAGED
  • resolved_at = now() při přechodu do RESOLVED, WONTFIX, nebo DUPLICATE
  • triaged_at se nemění při dalších přechodech

Reopen (→ OPEN)

Při reopen se resolved_at a resolved_by_user_id nulují (SET NULL). triaged_at zůstane (audit trail). Admin musí znovu projít TRIAGED → IN_PROGRESS → RESOLVED cyklus.

Concurrent triage

PATCH používá SELECT ... FOR UPDATE — zabraňuje race condition při paralelních admin aktualizacích (i když v Phase 1 je admin jen Mirek, dobrá praxe).


Frontend (Phase 2 — #82)

Phase 1: admin operoval REST-only (curl / httpie). Phase 2 přidává kompletní FE detail view.

Routing

RouteScreenPřístup
/platform-issues/:idPlatformIssueDetailScreen.vueADMIN i USER (USER = read-only; triage panel skryt)

Detail view — layout

Screen je rozdělen na tři zóny (vertikální stack):

┌─────────────────────────────────────────────────────┐
│  Header: Title + Severity badge + Status badge + Kind │
│  Meta row: Reporter icon + email + tenant | timestamps │
├─────────────────────────────────────────────────────┤
│  Description (markdown render, read-only)            │
├─────────────────────────────────────────────────────┤
│  Collapsible context sekce (default collapsed)       │
│  ▸ Conversation snippet                              │
│  ▸ K8s events                                        │
│  ▸ Pod logs                                          │
│  ▸ Project state                                     │
├─────────────────────────────────────────────────────┤
│  Comments timeline (chronologicky ASC)               │
├─────────────────────────────────────────────────────┤
│  Comment box (Markdown, max 10 000 chars)            │
├─────────────────────────────────────────────────────┤
│  Triage akce panel (ADMIN only, skryt pro USER)     │
└─────────────────────────────────────────────────────┘

Header a meta

  • Title: h1, plný text, bez truncation.
  • Severity badge: viz třídy v UC-09002 FE sekci.
  • Status badge: viz třídy v UC-09002 FE sekci.
  • Kind: plain text nebo malý pill BUG / FEATURE.
  • Reporter meta row: reporter_agent ikona + user email + @ tenant_slug, reported_at (relativní čas, absolutní na hover).
  • Timestamps section (kompaktní, font-mono, fg-3):
    • reported_at — vždy přítomný.
    • triaged_at — zobrazit pouze pokud není null.
    • resolved_at — zobrazit pouze pokud není null.

Description

  • Renderovat jako Markdown (použít existující Markdown renderer v projektu, případně marked / markdown-it — zkontrolovat co workspace screen už používá).
  • Read-only pro oba role typy.
  • Pokud description je prázdný → skrýt sekci (nemělo by nastat, BE vyžaduje not_blank).

Collapsible context sekce

Každá ze čtyř sekcí je samostatný <details> / collapse komponent, výchozí stav = closed.

SekceDatový zdrojZobrazení
Conversation snippetcontext_jsonb.conversationSnippet (array zpráv)Chronologický list zpráv; role + content; monospace; max-height 300px se scrollem
K8s eventscontext_jsonb.k8sEvents (array)Tabulka: type, reason, message, firstTime; WARN events zvýraznit žlutě, ERROR červeně
Pod logscontext_jsonb.podLogSnippet (string)<pre> monospace, max-height 400px se scrollem, white-space: pre
Project statecontext_jsonb.projectState (object)Key-value tabulka: tenant_slug, deploy URL (kliknutelný link), last_build_sha

Pokud je sekce v context_jsonb null nebo chybí (best-effort enrichment), zobrazit inline notice: t('issues.context.notAvailable') = “Not available — context enrichment may have failed”.

Žádná z sekcí se nenačítá lazy — data jsou součástí GET /api/issues/{id} response.

Comments timeline

  • Chronologické řazení ASC (created_at).
  • Každý komentář: avatar / ikona podle author_agent (MARA=robot, USER=user, ADMIN=shield/wrench) + author email nebo “Mara” / “Admin” + relativní čas + markdown-rendered body.
  • Systémové auto-komentáře (dedup, author_agent=MARA, author_user_id = reportující user) vizuálně odlišit lehčí barvou pozadí nebo dashed border.
  • Pokud žádné komentáře → zobrazit t('issues.comments.empty') = “No comments yet”.

Comment box

  • Dostupný pro oba role (ADMIN i USER).
  • Prostý <textarea> — žádný WYSIWYG editor. Placeholder: t('issues.comments.placeholder') = “Add a comment (Markdown supported)”.
  • Max 10 000 znaků (shodné s BE limitem). Counter zobrazit při > 9 000 znaků.
  • Submit = tlačítko t('issues.comments.submit') = “Comment”.
  • Po úspěšném POST /api/issues/{id}/comments → přidat nový komentář do timeline bez full page reload (optimistic update nebo refetch comments).
  • Loading state: tlačítko disabled + spinner.
  • Error state: inline error pod textarea.
  • Prázdné body → Submit tlačítko disabled (FE guard shodný s BE).

Triage akce panel (ADMIN only)

Panel je skryt pro USER roli. Pro ADMIN zobrazit jako kartu / panel oddělený od comment sekce.

Triage panel zobrazuje dostupné přechody dle aktuálního stavu issue (jen ty povolené dle state machine):

Přechod OPEN → TRIAGED:

  • Tlačítko “Take ownership” (inline, bez extra inputu).
  • Po kliknutí: PATCH /api/issues/{id} s { status: "TRIAGED" }.
  • Toast: t('issues.triage.toastTriaged') = “Issue triaged”.

Přechod TRIAGED → IN_PROGRESS:

  • Tlačítko “Start working on it” (inline, bez extra inputu).
  • Po kliknutí: PATCH /api/issues/{id} s { status: "IN_PROGRESS" }.

Přechod IN_PROGRESS → RESOLVED nebo TRIAGED → RESOLVED:

  • Tlačítko “Mark as resolved” → inline form rozbalí se pod tlačítkem (ne modal).
  • Inline form: <textarea> s labelem t('issues.triage.resolutionNote') = “Resolution note (required)”, min 10 znaků.
  • Submit: PATCH /api/issues/{id} s { status: "RESOLVED", resolution_note: "..." }.
  • Po úspěchu: inline form se skryje, status badge se aktualizuje.

Přechod * → WONTFIX:

  • Tlačítko “Won’t fix” → inline form shodný s RESOLVED (resolution_note povinný).
  • Submit: PATCH /api/issues/{id} s { status: "WONTFIX", resolution_note: "..." }.

Přechod * → DUPLICATE of #N:

  • Tlačítko “Mark as duplicate” → inline form: number input t('issues.triage.parentIssueId') = “Duplicate of issue ID (UUID)” + optional <textarea> pro poznámku.
  • Submit: PATCH /api/issues/{id} s { status: "DUPLICATE", parent_issue_id: "...", resolution_note?: "..." }.
  • Validace: UUID formát (BE vrátí 422 pokud neexistuje — zobrazit inline error).

Přechod RESOLVED/WONTFIX/DUPLICATE → OPEN (reopen):

  • Tlačítko “Reopen” (červeně zvýrazněno) → confirmation inline: “Reopen this issue?” + tlačítka Confirm / Cancel.
  • Submit: PATCH /api/issues/{id} s { status: "OPEN" }.

Severity upgrade (ADMIN only):

  • Dropdown Severity s volbami LOW / MEDIUM / HIGH / CRITICAL.
  • Změna → okamžitý PATCH /api/issues/{id} s { severity: "..." } (žádný extra submit krok).
  • Podsvítit CRITICAL volbu červeně jako upozornění.

Rozhodnutí: inline form místo modalu — triage akce s povinným resolution_note se realizují inline rozbalitelným formulářem pod tlačítkem, ne modálním dialogem. Důvody:

  1. Triage probíhá v kontextu detailu issue — user vidí description i history zároveň.
  2. Méně kliknutí než modal overlay.
  3. Shodné s UX pattern z KillSwitchBanner.vue (inline confirm-before-action).

i18n klíče (přidat do issues sekce v i18n souboru)

issues.detail.reportedAt      = "Reported" / "Nahlášeno"
issues.detail.triagedAt       = "Triaged" / "Protříděno"
issues.detail.resolvedAt      = "Resolved" / "Vyřešeno"
issues.context.conversation   = "Conversation snippet" / "Snippet konverzace"
issues.context.k8sEvents      = "K8s events" / "K8s události"
issues.context.podLogs        = "Pod logs" / "Logy podu"
issues.context.projectState   = "Project state" / "Stav projektu"
issues.context.notAvailable   = "Not available — context enrichment may have failed" / "Nedostupné — obohacení kontextu mohlo selhat"
issues.comments.empty         = "No comments yet" / "Zatím žádné komentáře"
issues.comments.placeholder   = "Add a comment (Markdown supported)" / "Přidej komentář (Markdown podporován)"
issues.comments.submit        = "Comment" / "Komentovat"
issues.triage.takeOwnership   = "Take ownership" / "Vzít na sebe"
issues.triage.startWork       = "Start working on it" / "Začít řešit"
issues.triage.markResolved    = "Mark as resolved" / "Označit jako vyřešené"
issues.triage.wontFix         = "Won't fix" / "Nebudu řešit"
issues.triage.markDuplicate   = "Mark as duplicate" / "Označit jako duplikát"
issues.triage.reopen          = "Reopen" / "Znovu otevřít"
issues.triage.resolutionNote  = "Resolution note (required)" / "Poznámka k uzavření (povinná)"
issues.triage.parentIssueId   = "Duplicate of issue (UUID)" / "Duplikát issue (UUID)"
issues.triage.confirmReopen   = "Reopen this issue?" / "Znovu otevřít tento issue?"
issues.triage.toastTriaged    = "Issue triaged" / "Issue přidán do triáže"
issues.triage.toastResolved   = "Issue resolved" / "Issue vyřešen"
issues.triage.toastWontFix    = "Marked as won't fix" / "Označeno jako nebudu řešit"
issues.triage.toastDuplicate  = "Marked as duplicate" / "Označeno jako duplikát"
issues.triage.toastReopened   = "Issue reopened" / "Issue znovu otevřen"
issues.triage.toastError      = "Action failed — try again" / "Akce selhala — zkus znovu"

References


Was this page helpful?

Thanks for the feedback.