feat(heureka): add Mitigate Manually action to vulnerability rows#1703
feat(heureka): add Mitigate Manually action to vulnerability rows#1703hodanoori wants to merge 14 commits into
Conversation
Add a new "Mitigate Manually" popup action to both the active and remediated vulnerability tabs. Opens a modal (same shape as False Positive) that creates a remediation with type=mitigation. Revert works automatically via the existing RemediationHistoryPanel flow. Signed-off-by: Hoda Noori <hoda.noori@sap.com>
Signed-off-by: Hoda Noori <hoda.noori@sap.com>
🦋 Changeset detectedLatest commit: 0b916e1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Signed-off-by: Hoda Noori <hoda.noori@sap.com>
There was a problem hiding this comment.
Pull request overview
Adds first-class UI support in Heureka’s Image Details vulnerabilities list for creating Manual Mitigation remediations from both the Active and Remediated tabs, including optimistic React Query cache updates and user-facing success banners.
Changes:
- Introduces
MitigateManuallyModalto create remediations withtype: RemediationTypeValues.Mitigation. - Adds a “Mitigate Manually” action to row popup menus in both Active (
IssuesDataRow) and Remediated (RemediatedIssueDataRow) tables. - Threads a new success callback through the Active tab rendering chain and updates remediated-tab success messaging to include Mitigation.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/heureka/src/components/Service/ImageDetails/MitigateManuallyModal/index.tsx | New modal component for creating mitigation remediations (description/user/expiration + submit). |
| apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/RemediatedIssuesDataRows/RemediatedIssueDataRow/index.tsx | Adds mitigation action + modal in the Remediated tab, reusing existing remediation creation handler. |
| apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/IssuesDataRows/IssuesDataRows.test.tsx | Updates tests to pass the newly required onMitigateManuallySuccess prop. |
| apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/IssuesDataRows/IssuesDataRow/index.tsx | Adds mitigation action + modal in the Active tab, plus optimistic remediations cache update and success callback. |
| apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/IssuesDataRows/index.tsx | Threads onMitigateManuallySuccess down to IssuesDataRow. |
| apps/heureka/src/components/Service/ImageDetails/ImageIssuesList/index.tsx | Wires mitigation success banner for Active tab and extends remediated success labeling to include Mitigation. |
Comments suppressed due to low confidence (1)
apps/heureka/src/components/Service/ImageDetails/MitigateManuallyModal/index.tsx:115
- MitigateManuallyModal is a new remediation flow but currently has no dedicated Vitest coverage (unlike RiskAcceptanceModal). Consider adding tests that assert: (1) required-field validation (description/userId/expirationDate), (2) onConfirm is called with type=Mitigation and the expected fields (remediatedBy from session vs manual entry, expirationDate ISO), and (3) API errors returned from onConfirm are rendered in the Message banner.
const handleConfirm = async () => {
if (!descriptionTrimmed) {
setDescriptionError("Description is required")
return
}
if (!remediatedBy) {
setUserIdError("User ID is required")
return
}
if (!expirationDate) {
setExpirationDateError("Expiration date is required")
return
}
setDescriptionError("")
setUserIdError("")
setExpirationDateError("")
setIsSubmitting(true)
try {
const severityValue = severity ? toSeverityValue(severity) : undefined
const input: RemediationInput = {
type: RemediationTypeValues.Mitigation,
vulnerability,
service,
image,
description: descriptionTrimmed,
...(remediatedBy && { remediatedBy }),
...(severityValue !== undefined && { severity: severityValue }),
expirationDate: expirationDate.toISOString(),
}
…ngle factory Signed-off-by: Hoda Noori <hoda.noori@sap.com>
Signed-off-by: Hoda Noori <hoda.noori@sap.com>
…icated modal code Signed-off-by: Hoda Noori <hoda.noori@sap.com>
Cover rendering, validation, interactions, source-ticket behaviour, and smoke-test all three wrapper components (FalsePositiveModal, RiskAcceptanceModal, MitigateManuallyModal). Signed-off-by: Hoda Noori <hoda.noori@sap.com>
Replace queryClient.fetchQuery (network round-trip) with queryClient.getQueryData (synchronous read). The panel already holds a fresh cache entry (staleTime: 0 on open), so we read it directly to find any remediations created in other sessions and patch the broad cache. This ensures the CVE stays in the Remediated tab when other remediations still exist, while avoiding the prior regression where the deleted entry briefly reappeared due to stale server responses. Signed-off-by: Hoda Noori <hoda.noori@sap.com>
ArtieReus
left a comment
There was a problem hiding this comment.
great job! thanks for adding tests!
…lict Kept the refactored thin-wrapper version of RiskAcceptanceModal (which delegates to RemediationModal) over the old standalone implementation that was still on main. Signed-off-by: Hoda Noori <hoda.noori@sap.com>
…d as url field Add `sourceTicketRequired` prop to RemediationModal that: - Shows the source ticket field as required - Blocks confirm button until the field is filled - Validates on submit with an inline error message - Sends the ticket as the `url` field in RemediationInput (not embedded in description, which is the existing optional-ticket behaviour) RiskAcceptanceModal switches from `showSourceTicket` to `sourceTicketRequired` so the Jira ticket is treated the same as the other required fields (description, user ID, expiration date). Signed-off-by: Hoda Noori <hoda.noori@sap.com>
Signed-off-by: Hoda Noori <hoda.noori@sap.com>
| const [userIdError, setUserIdError] = useState<string>("") | ||
| const [sourceTicketError, setSourceTicketError] = useState<string>("") | ||
| const [expirationDateError, setExpirationDateError] = useState<string>("") | ||
| const [apiError, setApiError] = useState<string | null>(null) |
There was a problem hiding this comment.
[Optional] I think we can reduce number of useStates by grouping related fields like
const [form, setForm] = useState({
description: "",
manualUserId: "",
sourceTicket: "",
expirationDate: null as Date | null,
})
const [errors, setErrors] = useState({
description: "",
userId: "",
sourceTicket: "",
expirationDate: "",
})
Summary
Adds a Mitigate Manually action to both the Active and Remediated vulnerability tabs, completing the remediation epic. Users can now mark a CVE as manually mitigated directly from the popup menu on any vulnerability row. The CVE moves to the Remediated tab immediately (optimistic cache update), and can be reverted via the Remediation History Panel.
Changes Made
MitigateManuallyModalcomponent (mirrorsFalsePositiveModal, submitstype: RemediationTypeValues.Mitigation)IssuesDataRow(Active tab) withhandleMitigateManuallyConfirmhandlerRemediatedIssueDataRow(Remediated tab) reusing existinghandleRemediationConfirmonMitigateManuallySuccesscallback throughIssuesDataRows→VulnerabilitiesTabContent→ImageIssuesListhandleRemediatedTabRemediationSuccessto produce correct label forMitigationtypeRelated Issues
Screenshots (if applicable)
Testing Instructions
pnpm ipnpm TASKChecklist
PR Manifesto
Review the PR Manifesto for best practises.