Commit 96358b71 authored by Savas Vedova's avatar Savas Vedova

Merge branch '346067-training-vulnerability-modal' into 'master'

Add security training UI to vulnerability modal

See merge request gitlab-org/gitlab!80193
parents 01c5f177 f903717c
......@@ -3,10 +3,17 @@ import { GlFriendlyWrap, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui
import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
import FalsePositiveAlert from 'ee/vulnerabilities/components/false_positive_alert.vue';
import GenericReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue';
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import {
SUPPORTING_MESSAGE_TYPES,
VULNERABILITY_TRAINING_HEADING,
} from 'ee/vulnerabilities/constants';
import {
convertObjectPropsToCamelCase,
convertArrayOfObjectsToCamelCase,
} from '~/lib/utils/common_utils';
import { s__, sprintf } from '~/locale';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import VulnerabilityTraining from 'ee/vulnerabilities/components/vulnerability_training.vue';
import getFileLocation from '../store/utils/get_file_location';
import { bodyWithFallBack } from './helpers';
import SeverityBadge from './severity_badge.vue';
......@@ -23,11 +30,17 @@ export default {
GlLink,
GlBadge,
FalsePositiveAlert,
VulnerabilityTraining,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
props: { vulnerability: { type: Object, required: true } },
data() {
return {
showTraining: false,
};
},
computed: {
url() {
return this.vulnerability.request?.url || getFileLocation(this.vulnLocation);
......@@ -141,6 +154,9 @@ export default {
hasRecordedResponse() {
return Boolean(this.constructedRecordedResponse);
},
camelCaseFormattedIdentifiers() {
return convertArrayOfObjectsToCamelCase(this.identifiers);
},
},
methods: {
getHeadersAsCodeBlockLines(headers) {
......@@ -175,6 +191,12 @@ export default {
? [`${method} ${url}\n`, headerLines, '\n\n', bodyWithFallBack(body)].join('')
: '';
},
handleShowTraining(showVulnerabilityTraining) {
this.showTraining = showVulnerabilityTraining;
},
},
i18n: {
VULNERABILITY_TRAINING_HEADING,
},
};
</script>
......@@ -309,5 +331,13 @@ export default {
class="gl-mt-4"
:details="vulnerability.details"
/>
<div v-if="identifiers" v-show="showTraining">
<vulnerability-detail :label="$options.i18n.VULNERABILITY_TRAINING_HEADING.title">
<vulnerability-training
:identifiers="camelCaseFormattedIdentifiers"
@show-vulnerability-training="handleShowTraining"
/>
</vulnerability-detail>
</div>
</div>
</template>
......@@ -65,10 +65,10 @@ export default {
},
computed: {
showVulnerabilityTraining() {
return (
return Boolean(
this.glFeatures.secureVulnerabilityTraining &&
this.enabledSecurityTrainingProviders?.length &&
this.identifiers?.length
this.identifiers?.length,
);
},
enabledSecurityTrainingProviders() {
......@@ -84,6 +84,12 @@ export default {
},
},
watch: {
showVulnerabilityTraining: {
immediate: true,
handler(showVulnerabilityTraining) {
this.$emit('show-vulnerability-training', showVulnerabilityTraining);
},
},
supportedIdentifier: {
immediate: true,
handler(supportedIdentifier) {
......
......@@ -11,6 +11,7 @@ module EE
before_action do
push_frontend_feature_flag(:pipeline_security_dashboard_graphql, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_code_quality_full_report, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:secure_vulnerability_training, project, default_enabled: :yaml)
end
feature_category :license_compliance, [:licenses]
......
......@@ -200,5 +200,17 @@ key2: value2
<!---->
<!---->
<div
style="display: none;"
>
<vulnerability-detail-stub
label="Training"
>
<vulnerability-training-stub
identifiers="[object Object],[object Object]"
/>
</vulnerability-detail-stub>
</div>
</div>
`;
......@@ -9,7 +9,7 @@ import GenericReportSection from 'ee/vulnerabilities/components/generic_report/r
import { SUPPORTING_MESSAGE_TYPES } from 'ee/vulnerabilities/constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'helpers/test_constants';
import VulnerabilityTraining from 'ee/vulnerabilities/components/vulnerability_training.vue';
import { mockFindings } from '../mock_data';
function makeVulnerability(changes = {}) {
......@@ -23,6 +23,9 @@ describe('VulnerabilityDetails component', () => {
wrapper = mountExtended(VulnerabilityDetails, {
propsData: { vulnerability },
provide,
stubs: {
VulnerabilityTraining: true,
},
});
};
......@@ -135,6 +138,17 @@ describe('VulnerabilityDetails component', () => {
);
});
it('renders vulnerability training', () => {
const identifiers = [{ externalType: 'cwe' }, { externalType: 'cve' }];
const vulnerability = makeVulnerability({ identifiers });
componentFactory(vulnerability);
expect(wrapper.findComponent(VulnerabilityTraining).props()).toMatchObject({
identifiers,
});
});
describe('does not render XSS links', () => {
// eslint-disable-next-line no-script-url
const badUrl = 'javascript:alert("")';
......
......@@ -86,6 +86,9 @@ describe('Grouped security reports app', () => {
const createWrapper = (propsData, options, provide) => {
wrapper = mount(GroupedSecurityReportsApp, {
propsData,
stubs: {
VulnerabilityTraining: true,
},
mocks: {
$apollo: {
queries: {
......
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
import * as Sentry from '@sentry/browser';
......@@ -110,6 +110,17 @@ describe('VulnerabilityTraining component', () => {
expect(wrapper.html()).toBeFalsy();
});
it('watches showVulnerabilityTraining and emits change', async () => {
createApolloProvider();
createComponent();
await waitForQueryToBeLoaded();
await nextTick();
// Note: the event emits twice - the second time is when the query is loaded
expect(wrapper.emitted('show-vulnerability-training')).toEqual([[false], [true]]);
});
});
describe('with title slot', () => {
......
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