From 6b69befbbb2f9401ad7babb5187dc93c470897a7 Mon Sep 17 00:00:00 2001 From: Charlie Kroon <ckroon@gitlab.com> Date: Tue, 10 Dec 2024 10:02:18 +0000 Subject: [PATCH] Add Link to the Commit Sha where vulnerability was resolved to Vulnerability Footer --- .../vulnerabilities/components/footer.vue | 36 ++++++-- .../frontend/vulnerabilities/footer_spec.js | 91 ++++++++++++++++++- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/ee/app/assets/javascripts/vulnerabilities/components/footer.vue b/ee/app/assets/javascripts/vulnerabilities/components/footer.vue index 242ee44d52a96..7a2cead84882d 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/footer.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/footer.vue @@ -98,18 +98,34 @@ export default { issueLinksEndpoint() { return Api.buildUrl(Api.vulnerabilityIssueLinksPath).replace(':id', this.vulnerability.id); }, + isRepresentationInfoAvailable() { + return ( + this.glFeatures.vulnerabilityRepresentationInformation && + this.vulnerability.resolvedOnDefaultBranch && + this.vulnerability.representationInformation?.resolvedInCommitShaLink + ); + }, vulnerabilityDetectionData() { - const { pipeline, scanner, detectedAt } = this.vulnerability; + const { pipeline, scanner, detectedAt, representationInformation, resolvedOnDefaultBranch } = + this.vulnerability; - // manually submitted vulnerabilities have no associated pipeline, in that case we don't display the detection data - return pipeline - ? { - state: 'detected', - pipeline, - scanner, - detectedAt, - } - : null; + if (!this.isRepresentationInfoAvailable && !pipeline) { + return null; + } + + return { + state: 'detected', + pipeline, + scanner, + ...(this.isRepresentationInfoAvailable + ? { + representationInformation, + resolvedOnDefaultBranch, + } + : { + detectedAt, + }), + }; }, mergeRequest() { return this.vulnerability.mergeRequestLinks.at(-1); diff --git a/ee/spec/frontend/vulnerabilities/footer_spec.js b/ee/spec/frontend/vulnerabilities/footer_spec.js index 8bcc554ed0689..a8301a6ad59f7 100644 --- a/ee/spec/frontend/vulnerabilities/footer_spec.js +++ b/ee/spec/frontend/vulnerabilities/footer_spec.js @@ -38,6 +38,11 @@ describe('Vulnerability Footer', () => { relatedIssuesHelpPath: 'help/path', pipeline: {}, mergeRequestLinks: [], + representationInformation: { + resolvedInCommitShaLink: 'https://gitlab.com/gitlab-org/gitlab/-/commit/0123456789', + resolvedInCommitSha: '0123456789', + }, + resolvedOnDefaultBranch: false, }; let discussion1; @@ -61,9 +66,17 @@ describe('Vulnerability Footer', () => { }, }); - const createWrapper = ({ properties, queryHandler, mountOptions } = {}) => { + const createWrapper = ({ + properties, + queryHandler, + mountOptions, + vulnerabilityRepresentationFlag = true, + } = {}) => { wrapper = shallowMountExtended(VulnerabilityFooter, { propsData: { vulnerability: { ...vulnerability, ...properties } }, + provide: { + glFeatures: { vulnerabilityRepresentationInformation: vulnerabilityRepresentationFlag }, + }, apolloProvider: createMockApollo([[vulnerabilityDiscussionsQuery, queryHandler]]), ...mountOptions, }); @@ -348,11 +361,81 @@ describe('Vulnerability Footer', () => { }, ); - it('does not show the detection note when the vulnerability has no pipeline (e.g.: was manually created)', () => { - createWrapper({ properties: { pipeline: null } }); + describe('when the pipeline is null (vulnerability has been created manually)', () => { + it('should not show the status description by default', () => { + createWrapper({ properties: { pipeline: null } }); + expect(statusDescription().exists()).toBe(false); + }); + + it('should not show the status description when the vulnerability is resolved on the default branch and there is no representation information', () => { + createWrapper({ + properties: { + pipeline: null, + resolvedOnDefaultBranch: true, + representationInformation: null, + }, + }); + expect(statusDescription().exists()).toBe(false); + }); - expect(detectionNote().exists()).toBe(false); + it('should show the status description when the vulnerability is resolved on the default branch and there is respresentation information', () => { + createWrapper({ + properties: { + pipeline: null, + resolvedOnDefaultBranch: true, + representationInformation: vulnerability.representationInformation, + }, + }); + expect(statusDescription().exists()).toBe(true); + }); }); + + describe('when the vulnerability is resolved on the default branch and there is representation information', () => { + it('should pass the correct props to the detection note', () => { + createWrapper({ + properties: { + resolvedOnDefaultBranch: true, + representationInformation: vulnerability.representationInformation, + }, + }); + + expect(statusDescription().props('vulnerability')).toMatchObject({ + resolvedOnDefaultBranch: true, + representationInformation: vulnerability.representationInformation, + }); + }); + }); + + it.each` + representationInformation | resolvedOnDefaultBranch | vulnerabilityRepresentationFlag | shouldIncludeRepresentationInfo + ${vulnerability.representationInformation} | ${true} | ${true} | ${true} + ${vulnerability.representationInformation} | ${false} | ${true} | ${false} + ${null} | ${true} | ${true} | ${false} + ${null} | ${false} | ${true} | ${false} + ${vulnerability.representationInformation} | ${true} | ${false} | ${false} + ${vulnerability.representationInformation} | ${false} | ${false} | ${false} + ${null} | ${true} | ${false} | ${false} + `( + 'shows representation information: "$shouldIncludeRepresentationInfo" when feature flag is "$vulnerabilityRepresentationFlag", resolvedOnDefaultBranch is "$resolvedOnDefaultBranch" and representationInformation is "$representationInformation"', + ({ + resolvedOnDefaultBranch, + vulnerabilityRepresentationFlag, + representationInformation, + shouldIncludeRepresentationInfo, + }) => { + createWrapper({ + properties: { + resolvedOnDefaultBranch, + representationInformation, + }, + vulnerabilityRepresentationFlag, + }); + + expect(Boolean(statusDescription().props('vulnerability').representationInformation)).toBe( + shouldIncludeRepresentationInfo, + ); + }, + ); }); describe('generic report', () => { -- GitLab