diff --git a/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx b/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx index 23de5e62..81cc3d1c 100644 --- a/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx +++ b/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx @@ -45,6 +45,7 @@ import type { VulnEventDTO, } from "@/types/api/api"; import { RequirementsLevel } from "@/types/api/api"; +import { getVexRulesSectionLabel } from "@/utils/vexRuleHelpers"; import { getIntegrationNameFromRepositoryIdOrExternalProviderId } from "@/utils/view"; import { InformationCircleIcon, @@ -919,7 +920,9 @@ const Index: FunctionComponent = () => { )} - Applied Rules ({vexRulesData?.length || 0}) + {vuln + ? `${getVexRulesSectionLabel(vexRulesData ?? [], vuln)} (${vexRulesData?.length ?? 0})` + : `Matching Rules (${vexRulesData?.length ?? 0})`} {vexRulesData && vexRulesData.length > 0 && (
diff --git a/src/utils/vexRuleHelpers.test.ts b/src/utils/vexRuleHelpers.test.ts new file mode 100644 index 00000000..8a66ea3b --- /dev/null +++ b/src/utils/vexRuleHelpers.test.ts @@ -0,0 +1,81 @@ +import type { DetailedDependencyVulnDTO, VexRule } from "@/types/api/api"; +import { + getVexRulesSectionLabel, + isVexRuleAppliedToVuln, +} from "./vexRuleHelpers"; + +const baseVuln = { + state: "open", +} as DetailedDependencyVulnDTO; + +const falsePositiveRule = { + id: "rule-1", + eventType: "falsePositive", +} as VexRule; + +const acceptedRule = { + id: "rule-2", + eventType: "accepted", +} as VexRule; + +describe("isVexRuleAppliedToVuln", () => { + it("returns true when false positive rule matches vuln state", () => { + expect( + isVexRuleAppliedToVuln(falsePositiveRule, { + ...baseVuln, + state: "falsePositive", + }), + ).toBe(true); + }); + + it("returns false when vuln was reopened (state is open)", () => { + expect( + isVexRuleAppliedToVuln(falsePositiveRule, { + ...baseVuln, + state: "open", + }), + ).toBe(false); + }); + + it("returns true when accepted rule matches vuln state", () => { + expect( + isVexRuleAppliedToVuln(acceptedRule, { + ...baseVuln, + state: "accepted", + }), + ).toBe(true); + }); +}); + +describe("getVexRulesSectionLabel", () => { + it('shows "Matching Rules" when rule is not currently applied (reopened vuln)', () => { + expect( + getVexRulesSectionLabel([falsePositiveRule], { + ...baseVuln, + state: "open", + }), + ).toBe("Matching Rules"); + }); + + it('shows "Applied Rules" when false positive rule is applied', () => { + expect( + getVexRulesSectionLabel([falsePositiveRule], { + ...baseVuln, + state: "falsePositive", + }), + ).toBe("Applied Rules"); + }); + + it('shows "Matching Rules" when only some rules are applied', () => { + expect( + getVexRulesSectionLabel([falsePositiveRule, acceptedRule], { + ...baseVuln, + state: "falsePositive", + }), + ).toBe("Matching Rules"); + }); + + it('shows "Matching Rules" when there are no rules', () => { + expect(getVexRulesSectionLabel([], baseVuln)).toBe("Matching Rules"); + }); +}); diff --git a/src/utils/vexRuleHelpers.ts b/src/utils/vexRuleHelpers.ts new file mode 100644 index 00000000..c2145383 --- /dev/null +++ b/src/utils/vexRuleHelpers.ts @@ -0,0 +1,27 @@ +import type { DetailedDependencyVulnDTO, VexRule } from "@/types/api/api"; + +export function isVexRuleAppliedToVuln( + rule: VexRule, + vuln: DetailedDependencyVulnDTO, +): boolean { + if (rule.eventType === "falsePositive") { + return vuln.state === "falsePositive"; + } + if (rule.eventType === "accepted") { + return vuln.state === "accepted"; + } + return false; +} + +export function getVexRulesSectionLabel( + rules: VexRule[], + vuln: DetailedDependencyVulnDTO, +): string { + if (rules.length === 0) { + return "Matching Rules"; + } + const allApplied = rules.every((rule) => + isVexRuleAppliedToVuln(rule, vuln), + ); + return allApplied ? "Applied Rules" : "Matching Rules"; +}