Commit 648ff75a authored by Daniel Tian's avatar Daniel Tian Committed by Savas Vedova

Change "Detected" to "Needs triage" on vulnerability report and details

parent 73845e96
...@@ -18,7 +18,7 @@ export default { ...@@ -18,7 +18,7 @@ export default {
computed: { computed: {
dropdownPlaceholderText() { dropdownPlaceholderText() {
return this.selectedKey return this.selectedKey
? this.$options.states[this.selectedKey].displayName ? this.$options.states[this.selectedKey].dropdownText
: this.$options.i18n.defaultPlaceholder; : this.$options.i18n.defaultPlaceholder;
}, },
}, },
...@@ -46,8 +46,8 @@ export default { ...@@ -46,8 +46,8 @@ export default {
is-check-item is-check-item
@click="setSelectedKey(state)" @click="setSelectedKey(state)"
> >
<div class="gl-font-weight-bold">{{ state.displayName }}</div> <div class="gl-font-weight-bold">{{ state.dropdownText }}</div>
<div>{{ state.description }}</div> <div>{{ state.dropdownDescription }}</div>
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</template> </template>
...@@ -11,7 +11,12 @@ import download from '~/lib/utils/downloader'; ...@@ -11,7 +11,12 @@ import download from '~/lib/utils/downloader';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import UsersCache from '~/lib/utils/users_cache'; import UsersCache from '~/lib/utils/users_cache';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { VULNERABILITY_STATE_OBJECTS, FEEDBACK_TYPES, HEADER_ACTION_BUTTONS } from '../constants'; import {
VULNERABILITY_STATES,
VULNERABILITY_STATE_OBJECTS,
FEEDBACK_TYPES,
HEADER_ACTION_BUTTONS,
} from '../constants';
import { normalizeGraphQLVulnerability } from '../helpers'; import { normalizeGraphQLVulnerability } from '../helpers';
import ResolutionAlert from './resolution_alert.vue'; import ResolutionAlert from './resolution_alert.vue';
import StatusDescription from './status_description.vue'; import StatusDescription from './status_description.vue';
...@@ -53,6 +58,9 @@ export default { ...@@ -53,6 +58,9 @@ export default {
}, },
computed: { computed: {
stateName() {
return VULNERABILITY_STATES[this.vulnerability.state];
},
stateVariant() { stateVariant() {
return this.$options.badgeVariants[this.vulnerability.state] || 'neutral'; return this.$options.badgeVariants[this.vulnerability.state] || 'neutral';
}, },
...@@ -223,8 +231,8 @@ export default { ...@@ -223,8 +231,8 @@ export default {
data-testid="vulnerability-detail-body" data-testid="vulnerability-detail-body"
> >
<gl-loading-icon v-if="isLoadingVulnerability" size="sm" class="mr-2" /> <gl-loading-icon v-if="isLoadingVulnerability" size="sm" class="mr-2" />
<gl-badge v-else class="gl-mr-4 text-capitalize" :variant="stateVariant"> <gl-badge v-else class="gl-mr-4" :variant="stateVariant">
{{ vulnerability.state }} {{ stateName }}
</gl-badge> </gl-badge>
<status-description <status-description
......
...@@ -24,6 +24,9 @@ export default { ...@@ -24,6 +24,9 @@ export default {
initialStateItem() { initialStateItem() {
return VULNERABILITY_STATE_OBJECTS[this.initialState]; return VULNERABILITY_STATE_OBJECTS[this.initialState];
}, },
buttonText() {
return this.initialStateItem?.buttonText;
},
}, },
watch: { watch: {
...@@ -37,16 +40,13 @@ export default { ...@@ -37,16 +40,13 @@ export default {
changeSelectedState(newState) { changeSelectedState(newState) {
this.selected = newState; this.selected = newState;
}, },
closeDropdown() { closeDropdown() {
this.$refs.dropdown.hide(); this.$refs.dropdown.hide();
}, },
// Reset the selected dropdown item to what was passed in by the parent. // Reset the selected dropdown item to what was passed in by the parent.
resetDropdown() { resetDropdown() {
this.selected = this.initialStateItem; this.selected = this.initialStateItem;
}, },
saveState(selectedState) { saveState(selectedState) {
this.$emit('change', selectedState); this.$emit('change', selectedState);
this.closeDropdown(); this.closeDropdown();
...@@ -59,14 +59,14 @@ export default { ...@@ -59,14 +59,14 @@ export default {
<gl-dropdown <gl-dropdown
ref="dropdown" ref="dropdown"
menu-class="gl-p-0 dropdown-extended-height" menu-class="gl-p-0 dropdown-extended-height"
toggle-class="text-capitalize" :text="buttonText"
:text="initialState"
:right="true" :right="true"
@hide="resetDropdown" @hide="resetDropdown"
> >
<li <li
v-for="stateItem in $options.states" v-for="stateItem in $options.states"
:key="stateItem.action" :key="stateItem.action"
:data-testid="stateItem.state"
class="py-3 px-2 dropdown-item cursor-pointer border-bottom" class="py-3 px-2 dropdown-item cursor-pointer border-bottom"
:class="{ selected: selected === stateItem }" :class="{ selected: selected === stateItem }"
@click="changeSelectedState(stateItem)" @click="changeSelectedState(stateItem)"
...@@ -78,9 +78,9 @@ export default { ...@@ -78,9 +78,9 @@ export default {
name="status_success_borderless" name="status_success_borderless"
:size="24" :size="24"
/> />
<div class="pl-4 font-weight-bold">{{ stateItem.displayName }}</div> <div class="pl-4 font-weight-bold">{{ stateItem.dropdownText }}</div>
</div> </div>
<div class="pl-4">{{ stateItem.description }}</div> <div class="pl-4">{{ stateItem.dropdownDescription }}</div>
</li> </li>
<template #footer> <template #footer>
......
...@@ -6,19 +6,27 @@ import { ...@@ -6,19 +6,27 @@ import {
const falsePositiveMessage = s__('VulnerabilityManagement|Will not fix or a false-positive'); const falsePositiveMessage = s__('VulnerabilityManagement|Will not fix or a false-positive');
export const VULNERABILITY_STATES = {
detected: s__('VulnerabilityStatusTypes|Needs triage'),
confirmed: s__('VulnerabilityStatusTypes|Confirmed'),
dismissed: s__('VulnerabilityStatusTypes|Dismissed'),
resolved: s__('VulnerabilityStatusTypes|Resolved'),
};
export const VULNERABILITY_STATE_OBJECTS = { export const VULNERABILITY_STATE_OBJECTS = {
detected: { detected: {
action: 'revert', action: 'revert',
state: 'detected', state: 'detected',
statusBoxStyle: 'expired', buttonText: VULNERABILITY_STATES.detected,
displayName: s__('VulnerabilityManagement|Detected'), dropdownText: s__('VulnerabilityManagement|Needs triage'),
description: s__('VulnerabilityManagement|Needs triage'), dropdownDescription: s__('VulnerabilityManagement|Requires assessment'),
}, },
dismissed: { dismissed: {
action: 'dismiss', action: 'dismiss',
state: 'dismissed', state: 'dismissed',
displayName: __('Dismiss'), buttonText: VULNERABILITY_STATES.dismissed,
description: falsePositiveMessage, dropdownText: __('Dismiss'),
dropdownDescription: falsePositiveMessage,
payload: { payload: {
comment: falsePositiveMessage, comment: falsePositiveMessage,
}, },
...@@ -26,24 +34,19 @@ export const VULNERABILITY_STATE_OBJECTS = { ...@@ -26,24 +34,19 @@ export const VULNERABILITY_STATE_OBJECTS = {
confirmed: { confirmed: {
action: 'confirm', action: 'confirm',
state: 'confirmed', state: 'confirmed',
displayName: __('Confirm'), buttonText: VULNERABILITY_STATES.confirmed,
description: s__('VulnerabilityManagement|A true-positive and will fix'), dropdownText: __('Confirm'),
dropdownDescription: s__('VulnerabilityManagement|A true-positive and will fix'),
}, },
resolved: { resolved: {
action: 'resolve', action: 'resolve',
state: 'resolved', state: 'resolved',
displayName: __('Resolve'), buttonText: VULNERABILITY_STATES.resolved,
description: s__('VulnerabilityManagement|Verified as fixed or mitigated'), dropdownText: __('Resolve'),
dropdownDescription: s__('VulnerabilityManagement|Verified as fixed or mitigated'),
}, },
}; };
export const VULNERABILITY_STATES = {
detected: s__('VulnerabilityStatusTypes|Detected'),
confirmed: s__('VulnerabilityStatusTypes|Confirmed'),
dismissed: s__('VulnerabilityStatusTypes|Dismissed'),
resolved: s__('VulnerabilityStatusTypes|Resolved'),
};
export const HEADER_ACTION_BUTTONS = { export const HEADER_ACTION_BUTTONS = {
mergeRequestCreation: { mergeRequestCreation: {
name: s__('ciReport|Resolve with merge request'), name: s__('ciReport|Resolve with merge request'),
......
...@@ -24,7 +24,6 @@ describe('Status Dropdown component', () => { ...@@ -24,7 +24,6 @@ describe('Status Dropdown component', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('renders the correct placeholder', () => { it('renders the correct placeholder', () => {
...@@ -38,7 +37,7 @@ describe('Status Dropdown component', () => { ...@@ -38,7 +37,7 @@ describe('Status Dropdown component', () => {
it(`renders ${state}`, () => { it(`renders ${state}`, () => {
expect(findDropdownItems().at(index).text()).toBe( expect(findDropdownItems().at(index).text()).toBe(
`${status.displayName} ${status.description}`, `${status.dropdownText} ${status.dropdownDescription}`,
); );
}); });
......
...@@ -9,7 +9,11 @@ import Header from 'ee/vulnerabilities/components/header.vue'; ...@@ -9,7 +9,11 @@ import Header from 'ee/vulnerabilities/components/header.vue';
import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue'; import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue';
import StatusDescription from 'ee/vulnerabilities/components/status_description.vue'; import StatusDescription from 'ee/vulnerabilities/components/status_description.vue';
import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue';
import { FEEDBACK_TYPES, VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants'; import {
FEEDBACK_TYPES,
VULNERABILITY_STATE_OBJECTS,
VULNERABILITY_STATES,
} from 'ee/vulnerabilities/constants';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import UsersMockHelper from 'helpers/user_mock_data_helper'; import UsersMockHelper from 'helpers/user_mock_data_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
...@@ -305,7 +309,7 @@ describe('Vulnerability Header', () => { ...@@ -305,7 +309,7 @@ describe('Vulnerability Header', () => {
createWrapper({ vulnerability: { state } }); createWrapper({ vulnerability: { state } });
expect(findBadge().props('variant')).toBe(variant); expect(findBadge().props('variant')).toBe(variant);
expect(findBadge().text()).toBe(state); expect(findBadge().text()).toBe(VULNERABILITY_STATES[state]);
}, },
); );
}); });
......
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants'; import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
const vulnerabilityStateEntries = Object.entries(VULNERABILITY_STATE_OBJECTS); const vulnerabilityStateObjects = Object.values(VULNERABILITY_STATE_OBJECTS);
describe('Vulnerability state dropdown component', () => { describe('Vulnerability state dropdown component', () => {
let wrapper; let wrapper;
const createWrapper = (initialState = vulnerabilityStateEntries[0][0]) => { const createWrapper = (initialState = vulnerabilityStateObjects[0].state) => {
// Create a dropdown that by default has the first vulnerability state selected. // Create a dropdown that by default has the first vulnerability state selected.
wrapper = shallowMount(VulnerabilityStateDropdown, { wrapper = shallowMountExtended(VulnerabilityStateDropdown, {
propsData: { initialState }, propsData: { initialState },
stubs: { stubs: { GlDropdown },
GlDropdown,
},
}); });
// Mock out this function, it's called by some methods in the component. // Mock out this function, it's called by some methods in the component.
wrapper.vm.$refs.dropdown.hide = jest.fn(); wrapper.vm.$refs.dropdown.hide = jest.fn();
}; };
// isSelected is designed to work with both single VueWrapper or WrapperArray const isSelected = (item) => item.find('.selected-icon').exists(); // Item is selected if there's a checkmark icon.
const isSelected = (items) =>
Boolean((items.wrappers ?? [items]).find((w) => w.find('.selected-icon').exists()));
const isDisabled = (item) => item.attributes('disabled') === 'true'; const isDisabled = (item) => item.attributes('disabled') === 'true';
const dropdownItems = () => wrapper.findAll('.dropdown-item'); const dropdownItems = () => wrapper.findAll('.dropdown-item');
const firstUnselectedItem = () => wrapper.find('.dropdown-item:not(.selected)'); const firstUnselectedItem = () => wrapper.find('.dropdown-item:not(.selected)');
...@@ -31,45 +27,41 @@ describe('Vulnerability state dropdown component', () => { ...@@ -31,45 +27,41 @@ describe('Vulnerability state dropdown component', () => {
const saveButton = () => wrapper.find({ ref: 'save-button' }); const saveButton = () => wrapper.find({ ref: 'save-button' });
const cancelButton = () => wrapper.find({ ref: 'cancel-button' }); const cancelButton = () => wrapper.find({ ref: 'cancel-button' });
const innerDropdown = () => wrapper.find({ ref: 'dropdown' }); const innerDropdown = () => wrapper.find({ ref: 'dropdown' });
const dropdownItemFor = (state) => wrapper.findByTestId(state);
const dropdownItemFor = (stateEntry) =>
dropdownItems().wrappers.find((x) => {
const text = x.text();
return text.includes(stateEntry.displayName) && text.includes(stateEntry.description);
});
afterEach(() => wrapper.destroy()); afterEach(() => wrapper.destroy());
describe('tests that need to manually create the wrapper', () => { describe('tests that need to manually create the wrapper', () => {
it.each(vulnerabilityStateEntries)( it.each(vulnerabilityStateObjects)(
'dropdown is created with the %s state already selected', 'dropdown is created with the %s state already selected',
(stateString, stateObject) => { ({ state }) => {
createWrapper(stateString); createWrapper(state);
expect(isSelected(dropdownItemFor(stateObject))).toBe(true); // Check that the dropdown item is selected. expect(isSelected(dropdownItemFor(state))).toBe(true); // Check that the dropdown item is selected.
}, },
); );
it('if an unknown state is passed in, nothing will be selected by default', () => { it('if an unknown state is passed in, nothing will be selected by default', () => {
createWrapper('some unknown state'); createWrapper('some unknown state');
expect(isSelected(dropdownItems())).toBe(false);
dropdownItems().wrappers.forEach((dropdownItem) => {
expect(isSelected(dropdownItem)).toBe(false);
});
}); });
it.each(vulnerabilityStateEntries)( it.each(vulnerabilityStateObjects)(
`when the %s dropdown item is clicked, it's the only one that's selected`, `when the %s dropdown item is clicked, it's the only one that's selected`,
(stateString, stateObject) => { ({ state }) => {
// Start off with an unknown state so we can click through each item and see it change. // Start off with an unknown state so we can click through each item and see it change.
createWrapper('some unknown state'); createWrapper('some unknown state');
const dropdownItem = dropdownItemFor(stateObject); const dropdownItem = dropdownItemFor(state);
dropdownItem.trigger('click'); dropdownItem.trigger('click');
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
// Check that the clicked item is selected. dropdownItems().wrappers.forEach((item) => {
expect(isSelected(dropdownItem)).toBe(true); expect(isSelected(item)).toBe(item.attributes('data-testid') === state);
// Check that the other items aren't selected. });
const otherItems = wrapper.findAll('.dropdown-item:not(.selected)');
expect(isSelected(otherItems)).toBe(false);
}); });
}, },
); );
...@@ -141,13 +133,13 @@ describe('Vulnerability state dropdown component', () => { ...@@ -141,13 +133,13 @@ describe('Vulnerability state dropdown component', () => {
}); });
it('when the parent component changes the state, the dropdown will update its selected and initial item', () => { it('when the parent component changes the state, the dropdown will update its selected and initial item', () => {
const [stateString, stateObject] = vulnerabilityStateEntries[1]; const stateObject = vulnerabilityStateObjects[1];
wrapper.setProps({ initialState: stateString }); // Change the state. wrapper.setProps({ initialState: stateObject.state }); // Change the state.
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(innerDropdown().props('text')).toBe(stateString); // Check that the dropdown button's value matches the initial state. expect(innerDropdown().props('text')).toBe(stateObject.buttonText); // Check that the dropdown button's value matches the initial state.
expect(selectedItem().text()).toMatch(new RegExp(`^${stateObject.action}`, 'i')); // Check that the selected item is the initial state. expect(selectedItem().text()).toMatch(stateObject.dropdownText); // Check that the selected item is the initial state.
expect(isDisabled(saveButton())).toBe(true); // Check that the save button is disabled. expect(isDisabled(saveButton())).toBe(true); // Check that the save button is disabled.
}); });
}); });
......
import { screen, within } from '@testing-library/dom'; import { screen, within } from '@testing-library/dom';
import initVulnerabilities from 'ee/vulnerabilities/vulnerabilities_init'; import initVulnerabilities from 'ee/vulnerabilities/vulnerabilities_init';
import { waitForText } from 'helpers/wait_for_text'; import { waitForText } from 'helpers/wait_for_text';
import { VULNERABILITY_STATES } from 'ee/vulnerabilities/constants';
import { mockIssueLink } from '../test_helpers/mock_data/vulnerabilities_mock_data'; import { mockIssueLink } from '../test_helpers/mock_data/vulnerabilities_mock_data';
import { mockVulnerability } from './mock_data'; import { mockVulnerability } from './mock_data';
...@@ -38,8 +39,9 @@ describe('Vulnerability Report', () => { ...@@ -38,8 +39,9 @@ describe('Vulnerability Report', () => {
it("displays the vulnerability's status", () => { it("displays the vulnerability's status", () => {
const headerBody = screen.getByTestId('vulnerability-detail-body'); const headerBody = screen.getByTestId('vulnerability-detail-body');
const stateName = VULNERABILITY_STATES[mockVulnerability.state];
expect(within(headerBody).getByText(mockVulnerability.state)).toBeInstanceOf(HTMLElement); expect(within(headerBody).getByText(stateName)).toBeInstanceOf(HTMLElement);
}); });
it("displays the vulnerability's severity", () => { it("displays the vulnerability's severity", () => {
......
...@@ -38513,9 +38513,6 @@ msgstr "" ...@@ -38513,9 +38513,6 @@ msgstr ""
msgid "VulnerabilityManagement|Create Jira issue" msgid "VulnerabilityManagement|Create Jira issue"
msgstr "" msgstr ""
msgid "VulnerabilityManagement|Detected"
msgstr ""
msgid "VulnerabilityManagement|Fetching linked Jira issues" msgid "VulnerabilityManagement|Fetching linked Jira issues"
msgstr "" msgstr ""
...@@ -38531,6 +38528,9 @@ msgstr "" ...@@ -38531,6 +38528,9 @@ msgstr ""
msgid "VulnerabilityManagement|Related Jira issues" msgid "VulnerabilityManagement|Related Jira issues"
msgstr "" msgstr ""
msgid "VulnerabilityManagement|Requires assessment"
msgstr ""
msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later." msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
msgstr "" msgstr ""
...@@ -38576,10 +38576,10 @@ msgstr "" ...@@ -38576,10 +38576,10 @@ msgstr ""
msgid "VulnerabilityStatusTypes|Confirmed" msgid "VulnerabilityStatusTypes|Confirmed"
msgstr "" msgstr ""
msgid "VulnerabilityStatusTypes|Detected" msgid "VulnerabilityStatusTypes|Dismissed"
msgstr "" msgstr ""
msgid "VulnerabilityStatusTypes|Dismissed" msgid "VulnerabilityStatusTypes|Needs triage"
msgstr "" msgstr ""
msgid "VulnerabilityStatusTypes|Resolved" msgid "VulnerabilityStatusTypes|Resolved"
......
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