Skip to content
代码片段 群组 项目
提交 b622939b 编辑于 作者: Dheeraj Joshi's avatar Dheeraj Joshi 提交者: Himanshu Kapoor
浏览文件

Add toggle for continuous vulnerability scans

This add the feature to enable continuous vulnerability
scans in Security Configuration page.

This change is behind a feature flag.
上级 087d10da
No related branches found
No related tags found
无相关合并请求
显示 293 个添加0 个删除
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue';
import { import {
REPORT_TYPE_SAST, REPORT_TYPE_SAST,
...@@ -210,6 +211,7 @@ export const securityFeatures = [ ...@@ -210,6 +211,7 @@ export const securityFeatures = [
configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH, configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH,
type: REPORT_TYPE_DEPENDENCY_SCANNING, type: REPORT_TYPE_DEPENDENCY_SCANNING,
anchor: 'dependency-scanning', anchor: 'dependency-scanning',
slotComponent: ContinuousVulnerabilityScan,
}, },
{ {
name: CONTAINER_SCANNING_NAME, name: CONTAINER_SCANNING_NAME,
......
<script>
import { GlBadge, GlIcon, GlToggle, GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
import ProjectSetContinuousVulnerabilityScanning from '~/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export default {
name: 'ContinuousVulnerabilityscan',
components: { GlBadge, GlIcon, GlToggle, GlLink, GlSprintf, GlAlert },
mixins: [glFeatureFlagsMixin()],
inject: ['continuousVulnerabilityScansEnabled', 'projectFullPath'],
i18n: {
badgeLabel: __('Experiment'),
title: s__('CVS|Continuous Vulnerability Scan'),
description: s__(
'CVS|Detect vulnerabilities outside a pipeline as new data is added to the GitLab Advisory Database.',
),
learnMore: __('Learn more'),
testingAgreementMessage: s__(
'CVS|By enabling this feature, you accept the %{linkStart}Testing Terms of Use%{linkEnd}',
),
},
props: {
feature: {
type: Object,
required: true,
},
},
data() {
return {
toggleValue: this.continuousVulnerabilityScansEnabled,
errorMessage: '',
isAlertDismissed: false,
};
},
computed: {
isFeatureConfigured() {
return this.feature.available && this.feature.configured;
},
shouldShowAlert() {
return this.errorMessage && !this.isAlertDismissed;
},
},
methods: {
reportError(error) {
this.errorMessage = error;
this.isAlertDismissed = false;
},
async toggleCVS(checked) {
try {
const { data } = await this.$apollo.mutate({
mutation: ProjectSetContinuousVulnerabilityScanning,
variables: {
input: {
projectPath: this.projectFullPath,
enable: checked,
},
},
});
const { errors } = data.projectSetContinuousVulnerabilityScanning;
if (errors.length > 0) {
this.reportError(errors[0].message);
}
if (data.projectSetContinuousVulnerabilityScanning !== null) {
this.toggleValue = checked;
}
} catch (error) {
this.reportError(error);
}
},
},
CVSHelpPagePath: helpPagePath(
'user/application_security/continuous_vulnerability_scanning/index',
),
experimentHelpPagePath: helpPagePath('policy/experiment-beta-support', { anchor: 'experiment' }),
};
</script>
<template>
<div v-if="glFeatures.dependencyScanningOnAdvisoryIngestion">
<h4 class="gl-font-base gl-m-0 gl-mt-6">
{{ $options.i18n.title }}
<gl-badge
ref="badge"
:href="$options.experimentHelpPagePath"
target="_blank"
size="sm"
variant="neutral"
class="gl-cursor-pointer"
>{{ $options.i18n.badgeLabel }}</gl-badge
>
</h4>
<gl-alert
v-if="shouldShowAlert"
class="gl-mb-5 gl-mt-2"
variant="danger"
@dismiss="isAlertDismissed = true"
>{{ errorMessage }}</gl-alert
>
<gl-toggle
class="gl-mt-5"
:disabled="!isFeatureConfigured"
:value="toggleValue"
:label="s__('CVS|Toggle CVS')"
label-position="hidden"
@change="toggleCVS"
/>
<p class="gl-mb-0 gl-mt-5">
{{ $options.i18n.description }}
<gl-link :href="$options.CVSHelpPagePath" target="_blank">{{
$options.i18n.learnMore
}}</gl-link>
<br />
<gl-sprintf :message="$options.i18n.testingAgreementMessage">
<template #link="{ content }">
<gl-link href="https://about.gitlab.com/handbook/legal/testing-agreement" target="_blank">
{{ content }} <gl-icon name="external-link" />
</gl-link>
</template>
</gl-sprintf>
</p>
</div>
</template>
...@@ -73,6 +73,9 @@ export default { ...@@ -73,6 +73,9 @@ export default {
hasSecondary() { hasSecondary() {
return Boolean(this.feature.secondary); return Boolean(this.feature.secondary);
}, },
hasSlotComponent() {
return Boolean(this.feature.slotComponent);
},
// This condition is a temporary hack to not display any wrong information // This condition is a temporary hack to not display any wrong information
// until this BE Bug is fixed: https://gitlab.com/gitlab-org/gitlab/-/issues/350307. // until this BE Bug is fixed: https://gitlab.com/gitlab-org/gitlab/-/issues/350307.
// More Information: https://gitlab.com/gitlab-org/gitlab/-/issues/350307#note_825447417 // More Information: https://gitlab.com/gitlab-org/gitlab/-/issues/350307#note_825447417
...@@ -215,5 +218,9 @@ export default { ...@@ -215,5 +218,9 @@ export default {
{{ $options.i18n.configurationGuide }} {{ $options.i18n.configurationGuide }}
</gl-button> </gl-button>
</div> </div>
<div v-if="hasSlotComponent">
<component :is="feature.slotComponent" :feature="feature" />
</div>
</gl-card> </gl-card>
</template> </template>
...@@ -26,6 +26,7 @@ export const initSecurityConfiguration = (el) => { ...@@ -26,6 +26,7 @@ export const initSecurityConfiguration = (el) => {
autoDevopsHelpPagePath, autoDevopsHelpPagePath,
autoDevopsPath, autoDevopsPath,
vulnerabilityTrainingDocsPath, vulnerabilityTrainingDocsPath,
continuousVulnerabilityScansEnabled,
} = el.dataset; } = el.dataset;
const { augmentedSecurityFeatures } = augmentFeatures( const { augmentedSecurityFeatures } = augmentFeatures(
...@@ -43,6 +44,7 @@ export const initSecurityConfiguration = (el) => { ...@@ -43,6 +44,7 @@ export const initSecurityConfiguration = (el) => {
autoDevopsHelpPagePath, autoDevopsHelpPagePath,
autoDevopsPath, autoDevopsPath,
vulnerabilityTrainingDocsPath, vulnerabilityTrainingDocsPath,
continuousVulnerabilityScansEnabled,
}, },
render(createElement) { render(createElement) {
return createElement(SecurityConfigurationApp, { return createElement(SecurityConfigurationApp, {
......
...@@ -14,6 +14,7 @@ module ConfigurationController ...@@ -14,6 +14,7 @@ module ConfigurationController
before_action only: [:show] do before_action only: [:show] do
push_frontend_feature_flag(:security_auto_fix, project) push_frontend_feature_flag(:security_auto_fix, project)
push_frontend_feature_flag(:dependency_scanning_on_advisory_ingestion, project)
end end
before_action only: [:auto_fix] do before_action only: [:auto_fix] do
......
...@@ -9172,6 +9172,18 @@ msgstr "" ...@@ -9172,6 +9172,18 @@ msgstr ""
msgid "CVE|Why Request a CVE ID?" msgid "CVE|Why Request a CVE ID?"
msgstr "" msgstr ""
   
msgid "CVS|By enabling this feature, you accept the %{linkStart}Testing Terms of Use%{linkEnd}"
msgstr ""
msgid "CVS|Continuous Vulnerability Scan"
msgstr ""
msgid "CVS|Detect vulnerabilities outside a pipeline as new data is added to the GitLab Advisory Database."
msgstr ""
msgid "CVS|Toggle CVS"
msgstr ""
msgid "Cadence is not automated" msgid "Cadence is not automated"
msgstr "" msgstr ""
   
import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlToggle } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import ProjectSetContinuousVulnerabilityScanning from '~/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql';
import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
Vue.use(VueApollo);
const setCVSMockResponse = {
data: {
projectSetContinuousVulnerabilityScanning: {
continuousVulnerabilityScanningEnabled: true,
errors: [],
},
},
};
const defaultProvide = {
continuousVulnerabilityScansEnabled: true,
projectFullPath: 'project/full/path',
};
describe('ContinuousVulnerabilityScan', () => {
let wrapper;
let apolloProvider;
let requestHandlers;
const createComponent = (options) => {
requestHandlers = {
setCVSMutationHandler: jest.fn().mockResolvedValue(setCVSMockResponse),
};
apolloProvider = createMockApollo([
[ProjectSetContinuousVulnerabilityScanning, requestHandlers.setCVSMutationHandler],
]);
wrapper = shallowMount(ContinuousVulnerabilityScan, {
propsData: {
feature: {
available: true,
configured: true,
},
},
provide: {
glFeatures: {
dependencyScanningOnAdvisoryIngestion: true,
},
...defaultProvide,
},
apolloProvider,
...options,
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
apolloProvider = null;
});
const findBadge = () => wrapper.findComponent(GlBadge);
const findToggle = () => wrapper.findComponent(GlToggle);
it('renders the component', () => {
expect(wrapper.exists()).toBe(true);
});
it('renders the correct title', () => {
expect(wrapper.text()).toContain('Continuous Vulnerability Scan');
});
it('renders the badge and toggle component with correct values', () => {
expect(findBadge().exists()).toBe(true);
expect(findBadge().text()).toBe('Experiment');
expect(findToggle().exists()).toBe(true);
expect(findToggle().props('value')).toBe(defaultProvide.continuousVulnerabilityScansEnabled);
});
it('should disable toggle when feature is not configured', () => {
createComponent({
propsData: {
feature: {
available: true,
configured: false,
},
},
});
expect(findToggle().props('disabled')).toBe(true);
});
it('calls mutation on toggle change with correct payload', () => {
findToggle().vm.$emit('change', true);
expect(requestHandlers.setCVSMutationHandler).toHaveBeenCalledWith({
input: {
projectPath: 'project/full/path',
enable: true,
},
});
});
describe('when feature flag is disabled', () => {
beforeEach(() => {
createComponent({
provide: {
glFeatures: {
dependencyScanningOnAdvisoryIngestion: false,
},
...defaultProvide,
},
});
});
it('should not render toggle and badge', () => {
expect(findToggle().exists()).toBe(false);
expect(findBadge().exists()).toBe(false);
});
});
});
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { securityFeatures } from '~/security_configuration/components/constants'; import { securityFeatures } from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue'; import FeatureCard from '~/security_configuration/components/feature_card.vue';
...@@ -13,6 +14,10 @@ import { ...@@ -13,6 +14,10 @@ import {
import { manageViaMRErrorMessage } from '../constants'; import { manageViaMRErrorMessage } from '../constants';
import { makeFeature } from './utils'; import { makeFeature } from './utils';
const MockComponent = Vue.component('MockComponent', {
render: (createElement) => createElement('span'),
});
describe('FeatureCard component', () => { describe('FeatureCard component', () => {
let feature; let feature;
let wrapper; let wrapper;
...@@ -389,4 +394,17 @@ describe('FeatureCard component', () => { ...@@ -389,4 +394,17 @@ describe('FeatureCard component', () => {
}); });
}); });
}); });
describe('when a slot component is passed', () => {
beforeEach(() => {
feature = makeFeature({
slotComponent: MockComponent,
});
createComponent({ feature });
});
it('renders the component properly', () => {
expect(wrapper.findComponent(MockComponent).exists()).toBe(true);
});
});
}); });
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册