Commit a0b1d35e authored by Phil Hughes's avatar Phil Hughes

Merge branch '301003-section-identifiers' into 'master'

Add identifiers section

See merge request gitlab-org/gitlab!77438
parents 15f43627 406eb072
......@@ -2,6 +2,7 @@
import { GlForm } from '@gitlab/ui';
import { s__ } from '~/locale';
import SectionDetails from './section_details.vue';
import SectionIdentifiers from './section_identifiers.vue';
import SectionName from './section_name.vue';
import SectionSolution from './section_solution.vue';
......@@ -10,6 +11,7 @@ export default {
components: {
GlForm,
SectionDetails,
SectionIdentifiers,
SectionName,
SectionSolution,
},
......@@ -21,6 +23,7 @@ export default {
severity: '',
status: '',
detectionMethod: '',
identifiers: [],
},
};
},
......@@ -51,6 +54,7 @@ export default {
<gl-form class="gl-p-4 gl-w-85p" @submit.prevent>
<section-name @change="updateFormValues" />
<section-details @change="updateFormValues" />
<section-identifiers @change="updateFormValues" />
<section-solution @change="updateFormValues" />
</gl-form>
</div>
......
<script>
import { GlFormGroup, GlFormInput, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlFormGroup,
GlFormInput,
GlButton,
},
id: 0,
data() {
return {
identifiers: [{ identifierCode: '', identifierUrl: '', id: this.$options.id }],
};
},
methods: {
emitChanges() {
this.$emit('change', { identifiers: this.identifiers });
},
addIdentifier() {
this.$options.id += 1;
this.identifiers.push({ identifierCode: '', identifierUrl: '', id: this.$options.id });
},
removeIdentifier(id) {
this.identifiers = this.identifiers.filter((i) => i.id !== id);
},
},
i18n: {
title: s__('Vulnerability|Identifiers'),
description: s__(
'Vulnerability|Enter the associated CVE or CWE entries for this vulnerability.',
),
identifierCode: s__('Vulnerability|Identifier code'),
identifierUrl: s__('Vulnerability|Identifier URL'),
addIdentifier: s__('Vulnerability|Add another identifier'),
removeIdentifierRow: s__('Vulnerability|Remove identifier row'),
},
};
</script>
<template>
<div>
<header class="gl-pt-4 gl-my-6 gl-border-t-gray-100 gl-border-t-solid gl-border-t-1">
<h3 class="gl-mt-0 gl-mb-3">
{{ $options.i18n.title }}
</h3>
<p data-testid="section-description">
{{ $options.i18n.description }}
</p>
</header>
<div
v-for="identifier in identifiers"
:key="identifier.id"
data-testid="identifier-row"
class="gl-display-flex gl-mb-6"
>
<gl-form-group
:label="$options.i18n.identifierCode"
:label-for="`form-identifier-code-${identifier.id}`"
class="gl-mr-6 gl-mb-0"
>
<gl-form-input
:id="`form-identifier-code-${identifier.id}`"
v-model="identifier.identifierCode"
type="text"
@change="emitChanges"
/>
</gl-form-group>
<gl-form-group
:label="$options.i18n.identifierUrl"
:label-for="`form-identifier-url-${identifier.id}`"
class="gl-flex-grow-1 gl-mb-0"
>
<gl-form-input
:id="`form-identifier-url-${identifier.id}`"
v-model="identifier.identifierUrl"
type="text"
@change="emitChanges"
/>
</gl-form-group>
<gl-button
v-if="identifier.id > 0"
class="gl-align-self-end gl-ml-4 gl-shadow-none!"
icon="remove"
:aria-label="$options.i18n.removeIdentifierRow"
@click="removeIdentifier(identifier.id)"
/>
<!--
The first row does not contain a remove button and this creates
a misalignment. This button is here as a placeholder to align the rows,
it's not visible to the user but it occupies the space hence aligns the
rows properly.
-->
<gl-button
v-else
class="gl-align-self-end gl-ml-4 gl-shadow-none gl-visibility-hidden"
icon="remove"
/>
</div>
<gl-button
data-testid="add-identifier-row"
category="secondary"
variant="confirm"
@click="addIdentifier"
>{{ $options.i18n.addIdentifier }}</gl-button
>
</div>
</template>
......@@ -2,6 +2,7 @@ import { GlForm } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NewVulnerability from 'ee/vulnerabilities/components/new_vulnerability/new_vulnerability.vue';
import SectionName from 'ee/vulnerabilities/components/new_vulnerability/section_name.vue';
import SectionIdentifiers from 'ee/vulnerabilities/components/new_vulnerability/section_identifiers.vue';
import SectionDetails from 'ee/vulnerabilities/components/new_vulnerability/section_details.vue';
import SectionSolution from 'ee/vulnerabilities/components/new_vulnerability/section_solution.vue';
......@@ -11,6 +12,7 @@ describe('New vulnerability component', () => {
const findSectionName = () => wrapper.findComponent(SectionName);
const findSectionDetails = () => wrapper.findComponent(SectionDetails);
const findSectionSolution = () => wrapper.findComponent(SectionSolution);
const findSectionIdentifiers = () => wrapper.findComponent(SectionIdentifiers);
const createWrapper = () => {
return shallowMountExtended(NewVulnerability);
......@@ -38,10 +40,11 @@ describe('New vulnerability component', () => {
});
it.each`
section | selector | fields
${'Name and Description'} | ${findSectionName} | ${{ vulnerabilityName: 'CVE 2050', vulnerabilityDesc: 'Password leak' }}
${'Details'} | ${findSectionDetails} | ${{ severity: 'low', detectionMethod: 2, status: 'confirmed' }}
${'Solution'} | ${findSectionSolution} | ${{ solution: 'This is the solution of the vulnerability.' }}
section | selector | fields
${'Name and Description'} | ${findSectionName} | ${{ vulnerabilityName: 'CVE 2050', vulnerabilityDesc: 'Password leak' }}
${'Details'} | ${findSectionDetails} | ${{ severity: 'low', detectionMethod: 2, status: 'confirmed' }}
${'Identifiers'} | ${findSectionIdentifiers} | ${{ identifiers: [{ identifierCode: 'CWE-94', IdentifierUrl: 'https://cwe.mitre.org/data/definitions/94.html' }] }}
${'Solution'} | ${findSectionSolution} | ${{ solution: 'This is the solution of the vulnerability.' }}
`('mounts the section $section and reacts on the change event', ({ selector, fields }) => {
const section = selector();
expect(section.exists()).toBe(true);
......
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import SectionIdentifiers from 'ee/vulnerabilities/components/new_vulnerability/section_identifiers.vue';
describe('New vulnerability - Section Identifiers', () => {
let wrapper;
const createWrapper = () => {
return mountExtended(SectionIdentifiers);
};
beforeEach(() => {
wrapper = createWrapper();
});
afterEach(() => {
wrapper.destroy();
});
const findIdentifierRows = () => wrapper.findAllByTestId('identifier-row');
describe.each`
labelText
${'Identifier code'}
${'Identifier URL'}
`('for input $labelText', ({ labelText }) => {
it(`displays the input with the correct label: "${labelText}"`, () => {
expect(wrapper.findByLabelText(labelText).exists()).toBe(true);
});
it('emits change even when input changes', () => {
wrapper.findByLabelText(labelText).trigger('change');
expect(wrapper.emitted('change')[0][0]).toEqual({
identifiers: [{ identifierCode: '', identifierUrl: '', id: 0 }],
});
});
});
it('adds and removes identifier rows', async () => {
expect(findIdentifierRows()).toHaveLength(1);
wrapper.findByTestId('add-identifier-row').trigger('click');
await nextTick();
expect(findIdentifierRows()).toHaveLength(2);
wrapper.findByLabelText('Remove identifier row').trigger('click');
await nextTick();
expect(findIdentifierRows()).toHaveLength(1);
expect(wrapper.findByLabelText('Remove identifier row').exists()).toBe(false);
});
});
......@@ -39554,6 +39554,9 @@ msgstr ""
msgid "Vulnerability|Actual received response is the one received when this fault was detected"
msgstr ""
msgid "Vulnerability|Add another identifier"
msgstr ""
msgid "Vulnerability|Additional Info"
msgstr ""
......@@ -39596,6 +39599,9 @@ msgstr ""
msgid "Vulnerability|Download"
msgstr ""
msgid "Vulnerability|Enter the associated CVE or CWE entries for this vulnerability."
msgstr ""
msgid "Vulnerability|Evidence"
msgstr ""
......@@ -39614,6 +39620,12 @@ msgstr ""
msgid "Vulnerability|Identifier"
msgstr ""
msgid "Vulnerability|Identifier URL"
msgstr ""
msgid "Vulnerability|Identifier code"
msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
......@@ -39638,6 +39650,9 @@ msgstr ""
msgid "Vulnerability|Project"
msgstr ""
msgid "Vulnerability|Remove identifier row"
msgstr ""
msgid "Vulnerability|Reproduction Assets"
msgstr ""
......
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