Commit 58b82adb authored by Mark Florian's avatar Mark Florian Committed by Olena Horal-Koretska

Show CE security MR widget for non-Ultimate users

This makes the CE security MR widget visible for Starter and Premium
users, and also Free tier users on `.com`.

Addresses https://gitlab.com/gitlab-org/gitlab/-/issues/273205, part of
https://gitlab.com/groups/gitlab-org/-/epics/4394.
parent f1bdac24
...@@ -129,7 +129,7 @@ class MergeRequestWidgetEntity < Grape::Entity ...@@ -129,7 +129,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end end
expose :security_reports_docs_path do |merge_request| expose :security_reports_docs_path do |merge_request|
help_page_path('user/application_security/sast/index.md', anchor: 'reports-json-format') help_page_path('user/application_security/index.md', anchor: 'viewing-security-scan-information-in-merge-requests')
end end
private private
......
...@@ -123,6 +123,24 @@ latest versions of the scanning tools without having to do anything. There are s ...@@ -123,6 +123,24 @@ latest versions of the scanning tools without having to do anything. There are s
with this approach, however, and there is a with this approach, however, and there is a
[plan to resolve them](https://gitlab.com/gitlab-org/gitlab/-/issues/9725). [plan to resolve them](https://gitlab.com/gitlab-org/gitlab/-/issues/9725).
## Viewing security scan information in merge requests **(CORE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4393) in GitLab Core 13.5.
> - Made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/273205) in 13.6.
> - It's [deployed behind a feature flag](../feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It can be enabled or disabled for a single project.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-the-basic-security-widget). **(CORE ONLY)**
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
Merge requests which have run security scans let you know that the generated
reports are available to download.
![Security widget](img/security_widget_v13_6.png)
## Interacting with the vulnerabilities ## Interacting with the vulnerabilities
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.8. > Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.8.
...@@ -594,3 +612,28 @@ Analyzer results are displayed in the [job logs](../../ci/pipelines/#expand-and- ...@@ -594,3 +612,28 @@ Analyzer results are displayed in the [job logs](../../ci/pipelines/#expand-and-
[Merge Request widget](sast/index.md#overview) [Merge Request widget](sast/index.md#overview)
or [Security Dashboard](security_dashboard/index.md). or [Security Dashboard](security_dashboard/index.md).
There is [an open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235772) in which changes to this behavior are being discussed. There is [an open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/235772) in which changes to this behavior are being discussed.
### Enable or disable the basic security widget **(CORE ONLY)**
The basic security widget is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../feature_flags.md)
can opt to disable it.
To enable it:
```ruby
# For the instance
Feature.enable(:core_security_mr_widget)
# For a single project
Feature.enable(:core_security_mr_widget, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:core_security_mr_widget)
# For a single project
Feature.disable(:core_security_mr_widget, Project.find(<project id>))
```
<script> <script>
import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue';
import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue'; import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue';
import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin'; import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
import { componentNames } from 'ee/reports/components/issue_body'; import { componentNames } from 'ee/reports/components/issue_body';
...@@ -22,7 +21,8 @@ export default { ...@@ -22,7 +21,8 @@ export default {
MrWidgetGeoSecondaryNode, MrWidgetGeoSecondaryNode,
MrWidgetPolicyViolation, MrWidgetPolicyViolation,
BlockingMergeRequestsReport, BlockingMergeRequestsReport,
GroupedSecurityReportsApp, GroupedSecurityReportsApp: () =>
import('ee/vue_shared/security_reports/grouped_security_reports_app.vue'),
GroupedMetricsReportsApp, GroupedMetricsReportsApp,
ReportSection, ReportSection,
}, },
...@@ -88,9 +88,13 @@ export default { ...@@ -88,9 +88,13 @@ export default {
return Boolean(loadPerformance.head_path && loadPerformance.base_path); return Boolean(loadPerformance.head_path && loadPerformance.base_path);
}, },
shouldRenderSecurityReport() { shouldRenderBaseSecurityReport() {
return !this.mr.canReadVulnerabilities && this.shouldRenderSecurityReport;
},
shouldRenderExtendedSecurityReport() {
const { enabledReports } = this.mr; const { enabledReports } = this.mr;
return ( return (
this.mr.canReadVulnerabilities &&
enabledReports && enabledReports &&
this.$options.securityReportTypes.some(reportType => enabledReports[reportType]) this.$options.securityReportTypes.some(reportType => enabledReports[reportType])
); );
...@@ -306,8 +310,15 @@ export default { ...@@ -306,8 +310,15 @@ export default {
:endpoint="mr.metricsReportsPath" :endpoint="mr.metricsReportsPath"
class="js-metrics-reports-container" class="js-metrics-reports-container"
/> />
<security-reports-app
v-if="shouldRenderBaseSecurityReport"
:pipeline-id="mr.pipeline.id"
:project-id="mr.targetProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
/>
<grouped-security-reports-app <grouped-security-reports-app
v-if="shouldRenderSecurityReport" v-else-if="shouldRenderExtendedSecurityReport"
:head-blob-path="mr.headBlobPath" :head-blob-path="mr.headBlobPath"
:source-branch="mr.sourceBranch" :source-branch="mr.sourceBranch"
:target-branch="mr.targetBranch" :target-branch="mr.targetBranch"
......
...@@ -13,6 +13,7 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -13,6 +13,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.coverageFuzzingHelp = data.coverage_fuzzing_help_path; this.coverageFuzzingHelp = data.coverage_fuzzing_help_path;
this.secretScanningHelp = data.secret_scanning_help_path; this.secretScanningHelp = data.secret_scanning_help_path;
this.dependencyScanningHelp = data.dependency_scanning_help_path; this.dependencyScanningHelp = data.dependency_scanning_help_path;
this.canReadVulnerabilities = data.can_read_vulnerabilities;
this.vulnerabilityFeedbackPath = data.vulnerability_feedback_path; this.vulnerabilityFeedbackPath = data.vulnerability_feedback_path;
this.canReadVulnerabilityFeedback = data.can_read_vulnerability_feedback; this.canReadVulnerabilityFeedback = data.can_read_vulnerability_feedback;
this.vulnerabilityFeedbackHelpPath = data.vulnerability_feedback_help_path; this.vulnerabilityFeedbackHelpPath = data.vulnerability_feedback_help_path;
......
...@@ -65,6 +65,10 @@ module EE ...@@ -65,6 +65,10 @@ module EE
merge_request.head_pipeline.iid merge_request.head_pipeline.iid
end end
expose :can_read_vulnerabilities do |merge_request|
can?(current_user, :read_vulnerability, merge_request.project)
end
expose :can_read_vulnerability_feedback do |merge_request| expose :can_read_vulnerability_feedback do |merge_request|
can?(current_user, :read_vulnerability_feedback, merge_request.project) can?(current_user, :read_vulnerability_feedback, merge_request.project)
end end
......
---
title: Show basic security scan information in merge requests for non-Ultimate users
merge_request: 46458
author:
type: added
...@@ -19,6 +19,7 @@ import mockData, { ...@@ -19,6 +19,7 @@ import mockData, {
headBrowserPerformance, headBrowserPerformance,
baseLoadPerformance, baseLoadPerformance,
headLoadPerformance, headLoadPerformance,
pipelineJobs,
} from './mock_data'; } from './mock_data';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants'; import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
...@@ -74,7 +75,8 @@ describe('ee merge request widget options', () => { ...@@ -74,7 +75,8 @@ describe('ee merge request widget options', () => {
const findBrowserPerformanceWidget = () => vm.$el.querySelector('.js-browser-performance-widget'); const findBrowserPerformanceWidget = () => vm.$el.querySelector('.js-browser-performance-widget');
const findLoadPerformanceWidget = () => vm.$el.querySelector('.js-load-performance-widget'); const findLoadPerformanceWidget = () => vm.$el.querySelector('.js-load-performance-widget');
const findSecurityWidget = () => vm.$el.querySelector('.js-security-widget'); const findExtendedSecurityWidget = () => vm.$el.querySelector('.js-security-widget');
const findBaseSecurityWidget = () => vm.$el.querySelector('[data-testid="security-mr-widget"]');
const setBrowserPerformance = (data = {}) => { const setBrowserPerformance = (data = {}) => {
const browserPerformance = { ...DEFAULT_BROWSER_PERFORMANCE, ...data }; const browserPerformance = { ...DEFAULT_BROWSER_PERFORMANCE, ...data };
...@@ -105,15 +107,18 @@ describe('ee merge request widget options', () => { ...@@ -105,15 +107,18 @@ describe('ee merge request widget options', () => {
}); });
describe('when it is loading', () => { describe('when it is loading', () => {
it('should render loading indicator', () => { beforeEach(() => {
mock = new MockAdapter(axios, { delayResponse: 1 });
mock.onGet(SAST_DIFF_ENDPOINT).reply(200, sastDiffSuccessMock); mock.onGet(SAST_DIFF_ENDPOINT).reply(200, sastDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []); mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
vm.loading = false; vm.loading = false;
});
it('should render loading indicator', () => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(SAST_SELECTOR) .querySelector(SAST_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('SAST is loading'); ).toContain('SAST is loading');
...@@ -131,7 +136,7 @@ describe('ee merge request widget options', () => { ...@@ -131,7 +136,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${SAST_SELECTOR} .report-block-list-issue-description`, `${SAST_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -153,7 +158,7 @@ describe('ee merge request widget options', () => { ...@@ -153,7 +158,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${SAST_SELECTOR} .report-block-list-issue-description`, `${SAST_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
).trim(), ).trim(),
...@@ -173,9 +178,9 @@ describe('ee merge request widget options', () => { ...@@ -173,9 +178,9 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect(trimText(findSecurityWidget().querySelector(SAST_SELECTOR).textContent)).toContain( expect(
'SAST: Loading resulted in an error', trimText(findExtendedSecurityWidget().querySelector(SAST_SELECTOR).textContent),
); ).toContain('SAST: Loading resulted in an error');
done(); done();
}); });
}); });
...@@ -197,14 +202,19 @@ describe('ee merge request widget options', () => { ...@@ -197,14 +202,19 @@ describe('ee merge request widget options', () => {
}); });
describe('when it is loading', () => { describe('when it is loading', () => {
it('should render loading indicator', () => { beforeEach(() => {
mock = new MockAdapter(axios, { delayResponse: 1 });
mock.onGet(DEPENDENCY_SCANNING_ENDPOINT).reply(200, dependencyScanningDiffSuccessMock); mock.onGet(DEPENDENCY_SCANNING_ENDPOINT).reply(200, dependencyScanningDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []); mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('should render loading indicator', () => {
expect( expect(
trimText(findSecurityWidget().querySelector(DEPENDENCY_SCANNING_SELECTOR).textContent), trimText(
findExtendedSecurityWidget().querySelector(DEPENDENCY_SCANNING_SELECTOR).textContent,
),
).toContain('Dependency scanning is loading'); ).toContain('Dependency scanning is loading');
}); });
}); });
...@@ -221,7 +231,7 @@ describe('ee merge request widget options', () => { ...@@ -221,7 +231,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`, `${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -247,7 +257,7 @@ describe('ee merge request widget options', () => { ...@@ -247,7 +257,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`, `${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -269,7 +279,7 @@ describe('ee merge request widget options', () => { ...@@ -269,7 +279,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`, `${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -289,7 +299,9 @@ describe('ee merge request widget options', () => { ...@@ -289,7 +299,9 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText(findSecurityWidget().querySelector(DEPENDENCY_SCANNING_SELECTOR).textContent), trimText(
findExtendedSecurityWidget().querySelector(DEPENDENCY_SCANNING_SELECTOR).textContent,
),
).toContain('Dependency scanning: Loading resulted in an error'); ).toContain('Dependency scanning: Loading resulted in an error');
done(); done();
}); });
...@@ -615,14 +627,19 @@ describe('ee merge request widget options', () => { ...@@ -615,14 +627,19 @@ describe('ee merge request widget options', () => {
}); });
describe('when it is loading', () => { describe('when it is loading', () => {
it('should render loading indicator', () => { beforeEach(() => {
mock = new MockAdapter(axios, { delayResponse: 1 });
mock.onGet(CONTAINER_SCANNING_ENDPOINT).reply(200, containerScanningDiffSuccessMock); mock.onGet(CONTAINER_SCANNING_ENDPOINT).reply(200, containerScanningDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []); mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('should render loading indicator', () => {
expect( expect(
trimText(findSecurityWidget().querySelector(CONTAINER_SCANNING_SELECTOR).textContent), trimText(
findExtendedSecurityWidget().querySelector(CONTAINER_SCANNING_SELECTOR).textContent,
),
).toContain('Container scanning is loading'); ).toContain('Container scanning is loading');
}); });
}); });
...@@ -639,7 +656,7 @@ describe('ee merge request widget options', () => { ...@@ -639,7 +656,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${CONTAINER_SCANNING_SELECTOR} .report-block-list-issue-description`, `${CONTAINER_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -660,7 +677,7 @@ describe('ee merge request widget options', () => { ...@@ -660,7 +677,7 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(CONTAINER_SCANNING_SELECTOR) .querySelector(CONTAINER_SCANNING_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('Container scanning: Loading resulted in an error'); ).toContain('Container scanning: Loading resulted in an error');
...@@ -685,14 +702,17 @@ describe('ee merge request widget options', () => { ...@@ -685,14 +702,17 @@ describe('ee merge request widget options', () => {
}); });
describe('when it is loading', () => { describe('when it is loading', () => {
it('should render loading indicator', () => { beforeEach(() => {
mock = new MockAdapter(axios, { delayResponse: 1 });
mock.onGet(DAST_ENDPOINT).reply(200, dastDiffSuccessMock); mock.onGet(DAST_ENDPOINT).reply(200, dastDiffSuccessMock);
mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []); mock.onGet(VULNERABILITY_FEEDBACK_ENDPOINT).reply(200, []);
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('should render loading indicator', () => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(DAST_SELECTOR) .querySelector(DAST_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('DAST is loading'); ).toContain('DAST is loading');
...@@ -710,7 +730,7 @@ describe('ee merge request widget options', () => { ...@@ -710,7 +730,7 @@ describe('ee merge request widget options', () => {
it('should render provided data', done => { it('should render provided data', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(`${DAST_SELECTOR} .report-block-list-issue-description`) .querySelector(`${DAST_SELECTOR} .report-block-list-issue-description`)
.textContent.trim(), .textContent.trim(),
).toEqual('DAST detected 1 critical severity vulnerability.'); ).toEqual('DAST detected 1 critical severity vulnerability.');
...@@ -730,7 +750,7 @@ describe('ee merge request widget options', () => { ...@@ -730,7 +750,7 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(DAST_SELECTOR) .querySelector(DAST_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('DAST: Loading resulted in an error'); ).toContain('DAST: Loading resulted in an error');
...@@ -769,7 +789,7 @@ describe('ee merge request widget options', () => { ...@@ -769,7 +789,7 @@ describe('ee merge request widget options', () => {
vm = mountWithFeatureFlag(); vm = mountWithFeatureFlag();
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(COVERAGE_FUZZING_SELECTOR) .querySelector(COVERAGE_FUZZING_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('Coverage fuzzing is loading'); ).toContain('Coverage fuzzing is loading');
...@@ -786,7 +806,7 @@ describe('ee merge request widget options', () => { ...@@ -786,7 +806,7 @@ describe('ee merge request widget options', () => {
it('should render provided data', done => { it('should render provided data', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(`${COVERAGE_FUZZING_SELECTOR} .report-block-list-issue-description`) .querySelector(`${COVERAGE_FUZZING_SELECTOR} .report-block-list-issue-description`)
.textContent.trim(), .textContent.trim(),
).toEqual('Coverage fuzzing detected 1 critical and 1 high severity vulnerabilities.'); ).toEqual('Coverage fuzzing detected 1 critical and 1 high severity vulnerabilities.');
...@@ -805,7 +825,7 @@ describe('ee merge request widget options', () => { ...@@ -805,7 +825,7 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(COVERAGE_FUZZING_SELECTOR) .querySelector(COVERAGE_FUZZING_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('Coverage fuzzing: Loading resulted in an error'); ).toContain('Coverage fuzzing: Loading resulted in an error');
...@@ -841,7 +861,9 @@ describe('ee merge request widget options', () => { ...@@ -841,7 +861,9 @@ describe('ee merge request widget options', () => {
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
expect( expect(
trimText(findSecurityWidget().querySelector(SECRET_SCANNING_SELECTOR).textContent), trimText(
findExtendedSecurityWidget().querySelector(SECRET_SCANNING_SELECTOR).textContent,
),
).toContain('Secret scanning is loading'); ).toContain('Secret scanning is loading');
}); });
}); });
...@@ -858,7 +880,7 @@ describe('ee merge request widget options', () => { ...@@ -858,7 +880,7 @@ describe('ee merge request widget options', () => {
setImmediate(() => { setImmediate(() => {
expect( expect(
trimText( trimText(
findSecurityWidget().querySelector( findExtendedSecurityWidget().querySelector(
`${SECRET_SCANNING_SELECTOR} .report-block-list-issue-description`, `${SECRET_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
...@@ -879,7 +901,7 @@ describe('ee merge request widget options', () => { ...@@ -879,7 +901,7 @@ describe('ee merge request widget options', () => {
it('should render error indicator', done => { it('should render error indicator', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findSecurityWidget() findExtendedSecurityWidget()
.querySelector(SECRET_SCANNING_SELECTOR) .querySelector(SECRET_SCANNING_SELECTOR)
.textContent.trim(), .textContent.trim(),
).toContain('Secret scanning: Loading resulted in an error'); ).toContain('Secret scanning: Loading resulted in an error');
...@@ -921,6 +943,37 @@ describe('ee merge request widget options', () => { ...@@ -921,6 +943,37 @@ describe('ee merge request widget options', () => {
}); });
}); });
describe('CE security report', () => {
const PIPELINE_JOBS_ENDPOINT = `/api/undefined/projects/${mockData.target_project_id}/pipelines/${mockData.pipeline.id}/jobs`;
describe.each`
context | canReadVulnerabilities | hasPipeline | featureFlag | shouldRender
${'user cannot read vulnerabilities'} | ${false} | ${true} | ${true} | ${true}
${'user can read vulnerabilities'} | ${true} | ${true} | ${true} | ${false}
${'no pipeline'} | ${false} | ${false} | ${true} | ${false}
${'the feature flag is disabled'} | ${false} | ${true} | ${false} | ${false}
`('given $context', ({ canReadVulnerabilities, hasPipeline, featureFlag, shouldRender }) => {
beforeEach(() => {
gl.mrWidgetData = {
...mockData,
can_read_vulnerabilities: canReadVulnerabilities,
pipeline: hasPipeline ? mockData.pipeline : undefined,
};
gon.features = { coreSecurityMrWidget: featureFlag };
mock.onGet(PIPELINE_JOBS_ENDPOINT).replyOnce(200, pipelineJobs);
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
return waitForPromises();
});
it(`${shouldRender ? 'renders' : 'does not render'} the CE security report`, () => {
expect(findBaseSecurityWidget()).toEqual(shouldRender ? expect.any(HTMLElement) : null);
});
});
});
describe('computed', () => { describe('computed', () => {
describe('shouldRenderApprovals', () => { describe('shouldRenderApprovals', () => {
it('should return false when in empty state', () => { it('should return false when in empty state', () => {
...@@ -1097,8 +1150,26 @@ describe('ee merge request widget options', () => { ...@@ -1097,8 +1150,26 @@ describe('ee merge request widget options', () => {
vm = mountComponent(Component, { mrData: gl.mrWidgetData }); vm = mountComponent(Component, { mrData: gl.mrWidgetData });
expect(findSecurityWidget()).toBe(null); expect(findExtendedSecurityWidget()).toBe(null);
}); });
}); });
}); });
describe('given the user cannot read vulnerabilites', () => {
beforeEach(() => {
gl.mrWidgetData = {
...mockData,
can_read_vulnerabilities: false,
enabled_reports: {
sast: true,
},
};
vm = mountComponent(Component, { mrData: gl.mrWidgetData });
});
it('does not render the EE security report', () => {
expect(findExtendedSecurityWidget()).toBe(null);
});
});
}); });
...@@ -2,6 +2,7 @@ import mockData, { mockStore } from 'jest/vue_mr_widget/mock_data'; ...@@ -2,6 +2,7 @@ import mockData, { mockStore } from 'jest/vue_mr_widget/mock_data';
export default { export default {
...mockData, ...mockData,
can_read_vulnerabilities: true,
vulnerability_feedback_help_path: '/help/user/application_security/index', vulnerability_feedback_help_path: '/help/user/application_security/index',
enabled_reports: { enabled_reports: {
sast: false, sast: false,
...@@ -126,3 +127,14 @@ export const codequalityParsedIssues = [ ...@@ -126,3 +127,14 @@ export const codequalityParsedIssues = [
]; ];
export { mockStore }; export { mockStore };
// TODO: Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/249544
export const pipelineJobs = [
{
artifacts: [
{
file_type: 'sast',
},
],
},
];
...@@ -246,6 +246,28 @@ RSpec.describe MergeRequestWidgetEntity do ...@@ -246,6 +246,28 @@ RSpec.describe MergeRequestWidgetEntity do
expect(subject.as_json).to include(:create_vulnerability_feedback_dismissal_path) expect(subject.as_json).to include(:create_vulnerability_feedback_dismissal_path)
end end
describe '#can_read_vulnerabilities' do
context 'when security dashboard feature is available' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'is set to true' do
expect(subject.as_json[:can_read_vulnerabilities]).to eq(true)
end
end
context 'when security dashboard feature is not available' do
before do
stub_licensed_features(security_dashboard: false)
end
it 'is set to false' do
expect(subject.as_json[:can_read_vulnerabilities]).to eq(false)
end
end
end
describe '#can_read_vulnerability_feedback' do describe '#can_read_vulnerability_feedback' do
context 'when user has permissions to read vulnerability feedback' do context 'when user has permissions to read vulnerability feedback' do
before do before do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment