Feat/bch-1186-promote-to-poam#212
Conversation
Align UI types and components with the API model change that removes free-text poc_name/poc_email fields in favour of the structured primaryOwnerUserId FK. - PoamItem: add primaryOwnerUserId, resourceRequired; remove pocName/pocEmail - CreatePoamItemRequest / UpdatePoamItemRequest: same additions - CCFPoamItemsListView: rename POC column to Owner, display primaryOwnerUserId (UUID display is intentional placeholder pending a user-lookup component) Relates to: BCH-1186 / PR #362 review comment (gusfcarvalho)
Implements the Risk -> POAM promotion journey (BCH-1186 Phase 2) in the UI.
Changes:
- types/poam-items.ts: add 'risk-promotion' PoamSourceType and
PromoteRiskToPoamRequest interface for POST /api/risks/:id/promote-to-poam
- composables/usePoamItems.ts: add usePromoteRiskToPoam composable
- utils/risk-workflow.ts:
- add canPromoteToPoam() helper (true only when status is 'investigating')
- add canMarkMitigatingImplemented() helper (true only when 'mitigating-planned')
- update ALLOWED_RISK_TRANSITIONS to align with API PR #362 fixes:
mitigating-planned can fall back to investigating (failed mitigation)
mitigating-implemented can transition to remediated
- remove BCH-1206 TODO comment (now implemented)
- components/risk/PromoteToPoamModal.vue: new modal with status transition
banner, editable title, deadline, resource fields, and milestone preview
- views/risk/RiskDetailView.vue:
- add 'Promote to POAM' button (violet, visible when investigating)
- add 'Mark Mitigating Implemented' button (teal, visible when mitigating-planned)
- mount PromoteToPoamModal with v-model:visible
- add submitPromoteToPoam() and submitMarkMitigatingImplemented() handlers
- add canPromoteToPoamAction and canMarkMitigatingImplementedAction computed
- reset showPromoteToPoamModal in loadRisk()
Tests:
- risk-workflow.spec.ts: 6 new test cases covering canPromoteToPoam,
canMarkMitigatingImplemented, and the updated transition map
- usePoamItems.spec.ts: 4 new test cases covering usePromoteRiskToPoam
(URL construction, URL encoding, payload forwarding, empty payload)
All 415 tests pass. Zero TypeScript errors. Zero ESLint warnings.
…ateMilestoneTitles - RiskDetailView.vue: replace risk.value.id (does not exist on Risk type) with getRiskIdentifier(risk.value) which reads uuid/riskUuid/id/riskId in priority order — resolves TS error at L2939/2942 - PromoteToPoamModal.vue: add null guard on templateMilestoneTitles before calling .length — prop is optional (string[] | undefined), resolves 'possibly undefined' TS error at L77
There was a problem hiding this comment.
Pull request overview
Implements the frontend flow for promoting an investigating Risk to a CCF POAM item (Risk → POAM), plus adds a follow-on workflow action to mark mitigation as implemented, aligning UI workflow transitions with the updated API behavior.
Changes:
- Adds a new “Promote to POAM” modal + wiring in Risk Detail, and a “Mark Mitigating Implemented” action.
- Introduces a
usePromoteRiskToPoam()composable and corresponding request/type additions for the promotion endpoint. - Updates risk workflow transition rules and extends unit tests around workflow helpers and the new composable.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/risk/RiskDetailView.vue | Adds promote/transition buttons, promotion modal integration, and submit handlers. |
| src/components/risk/PromoteToPoamModal.vue | New modal UI for promotion inputs (title/deadline/resource) and milestone preview. |
| src/composables/usePoamItems.ts | Adds usePromoteRiskToPoam() for POST /api/risks/:id/promote-to-poam. |
| src/types/poam-items.ts | Adds risk-promotion source type + PromoteRiskToPoamRequest, extends POAM item fields. |
| src/utils/risk-workflow.ts | Updates allowed transitions and adds canPromoteToPoam / canMarkMitigatingImplemented. |
| src/utils/risk-workflow.spec.ts | Adds tests for new helpers and updated transition map. |
| src/composables/tests/usePoamItems.spec.ts | Adds tests for promotion composable URL/payload behavior. |
| src/views/poam/CCFPoamItemsListView.vue | Adjusts list column from POC fields to primaryOwnerUserId. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <th class="p-2 border">Title</th> | ||
| <th class="p-2 border">Status</th> | ||
| <th class="p-2 border">Deadline</th> | ||
| <th class="p-2 border">POC</th> | ||
| <th class="p-2 border">Owner</th> | ||
| <th class="p-2 border">Updated</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr v-for="item in items" :key="item.id" class="text-sm"> | ||
| <td class="p-2 border">{{ item.title }}</td> | ||
| <td class="p-2 border">{{ item.status }}</td> | ||
| <td class="p-2 border">{{ item.deadline ?? '-' }}</td> | ||
| <td class="p-2 border"> | ||
| {{ item.pocName ?? '-' }} | ||
| <span v-if="item.pocEmail">({{ item.pocEmail }})</span> | ||
| {{ item.primaryOwnerUserId ?? '-' }} | ||
| </td> |
There was a problem hiding this comment.
This table now labels the column "Owner" but renders primaryOwnerUserId, which will typically be a UUID and not user-friendly. Either resolve the userId to a display name/email (similar to risk owner rendering elsewhere) or rename the column to something explicit like "Owner ID" to avoid confusing users.
| v-model:visible="showPromoteToPoamModal" | ||
| :submitting="promotingToPoam" | ||
| :risk-title="risk?.title" | ||
| :template-milestone-titles="[]" |
There was a problem hiding this comment.
template-milestone-titles is currently always passed as an empty array, which will cause PromoteToPoamModal to render its "No remediation template found" fallback copy even when the template just isn’t wired yet. Consider passing undefined (or a separate loading/unknown state) until the remediation template tasks are actually fetched, so the UI doesn’t present a false negative.
| :template-milestone-titles="[]" | |
| :template-milestone-titles="undefined" |
| No remediation template found for this risk. Milestones can be added | ||
| to the POAM item after promotion. |
There was a problem hiding this comment.
The fallback message shown when templateMilestoneTitles is empty says "No remediation template found for this risk", but callers may pass an empty array simply because milestones aren’t loaded/wired yet. Consider distinguishing between undefined (unknown/not loaded) vs [] (known none), or add an explicit boolean prop so the modal doesn’t make an incorrect claim.
| No remediation template found for this risk. Milestones can be added | |
| to the POAM item after promotion. | |
| Remediation milestones are not currently available. Milestones can be | |
| added to the POAM item after promotion. |
| if (parsed.getTime() <= Date.now()) { | ||
| validationError.value = 'Planned completion date must be in the future.'; | ||
| return; | ||
| } |
There was a problem hiding this comment.
The modal enforces that the planned completion date must be in the future. Elsewhere in the POAM UI, planned completion dates appear to allow today/past dates (overdue is a valid state), so this validation may unnecessarily block legitimate promotions (e.g., when promoting an already-overdue risk). Consider aligning this validation with the API/business rules used for POAM item creation/editing (or downgrade to a warning).
| if (parsed.getTime() <= Date.now()) { | |
| validationError.value = 'Planned completion date must be in the future.'; | |
| return; | |
| } |
- CCFPoamItemsListView.vue: rename 'Owner' column header to 'Owner ID' to accurately reflect that the value is a raw UUID, not a resolved display name — avoids confusing users (Copilot comment #1) - RiskDetailView.vue: pass :template-milestone-titles="undefined" instead of "[]" so the modal correctly represents an unloaded state rather than asserting no template exists (Copilot comment #2) - PromoteToPoamModal.vue: update fallback text from 'No remediation template found for this risk' to 'Remediation milestones are not currently available' — avoids false negative when milestones are simply not yet loaded (Copilot comment #3) - PromoteToPoamModal.vue: remove future-date enforcement on plannedCompletionDate — POAM items legitimately allow past/today dates (overdue is a valid state); aligns with API business rules (Copilot #4) All 415 tests pass. Zero TypeScript errors. Zero ESLint warnings.
Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Summary
This PR implements the UI for the Risk → POAM promotion journey introduced in the API (compliance-framework/api#362). It allows a user to promote a risk that is in investigating status directly to a POAM item, transitioning the risk to mitigating-planned in a single action. It also adds the Mark Mitigating Implemented button for the subsequent lifecycle step.
src/types/poam-items.tsrisk-promotionsource type andPromoteRiskToPoamRequestinterfacesrc/composables/usePoamItems.tsusePromoteRiskToPoam()composable forPOST /api/risks/:id/promote-to-poamsrc/utils/risk-workflow.tscanPromoteToPoam()andcanMarkMitigatingImplemented()helpers; updatedALLOWED_RISK_TRANSITIONSto match API fixes; removed BCH-1206 TODOsrc/components/risk/PromoteToPoamModal.vuesrc/views/risk/RiskDetailView.vueinvestigatingonly), Mark Mitigating Implemented button (teal,mitigating-plannedonly), modal wiring, submit handlerssrc/utils/risk-workflow.spec.tscanPromoteToPoam,canMarkMitigatingImplemented, and updated transitionssrc/composables/__tests__/usePoamItems.spec.tsusePromoteRiskToPoam(URL, URL encoding, payload, empty payload)Tests
src/utils/risk-workflow.spec.tscanPromoteToPoam(all statuses),canMarkMitigatingImplemented(all statuses), updated transition map assertionssrc/composables/__tests__/usePoamItems.spec.tsDependencies
Checklist