diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/status_dropdown.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/status_dropdown.vue index c700948e346c6dbdeb690aec5e1bdc31d43a452c..84d5667fb1d933f82395c4bfdda5bc6f0de0b516 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/status_dropdown.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/status_dropdown.vue @@ -18,7 +18,7 @@ export default { computed: { dropdownPlaceholderText() { return this.selectedKey - ? this.$options.states[this.selectedKey].displayName + ? this.$options.states[this.selectedKey].dropdownText : this.$options.i18n.defaultPlaceholder; }, }, @@ -46,8 +46,8 @@ export default { is-check-item @click="setSelectedKey(state)" > - <div class="gl-font-weight-bold">{{ state.displayName }}</div> - <div>{{ state.description }}</div> + <div class="gl-font-weight-bold">{{ state.dropdownText }}</div> + <div>{{ state.dropdownDescription }}</div> </gl-dropdown-item> </gl-dropdown> </template> diff --git a/ee/app/assets/javascripts/vulnerabilities/components/header.vue b/ee/app/assets/javascripts/vulnerabilities/components/header.vue index 96987cd8480df76b77e39c82dfe6f8132e88d4fe..acdfeba2ec7e08069525672f9dc23316294fbf1d 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/header.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/header.vue @@ -11,7 +11,12 @@ import download from '~/lib/utils/downloader'; import { redirectTo } from '~/lib/utils/url_utility'; import UsersCache from '~/lib/utils/users_cache'; 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 ResolutionAlert from './resolution_alert.vue'; import StatusDescription from './status_description.vue'; @@ -53,6 +58,9 @@ export default { }, computed: { + stateName() { + return VULNERABILITY_STATES[this.vulnerability.state]; + }, stateVariant() { return this.$options.badgeVariants[this.vulnerability.state] || 'neutral'; }, @@ -223,8 +231,8 @@ export default { data-testid="vulnerability-detail-body" > <gl-loading-icon v-if="isLoadingVulnerability" size="sm" class="mr-2" /> - <gl-badge v-else class="gl-mr-4 text-capitalize" :variant="stateVariant"> - {{ vulnerability.state }} + <gl-badge v-else class="gl-mr-4" :variant="stateVariant"> + {{ stateName }} </gl-badge> <status-description diff --git a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_state_dropdown.vue b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_state_dropdown.vue index 5a36f538d4d4b9e367ead1ea32db8fcbc57565b6..6f53b47c4b3670272a1eff0389cf44f1e1371d25 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_state_dropdown.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/vulnerability_state_dropdown.vue @@ -24,6 +24,9 @@ export default { initialStateItem() { return VULNERABILITY_STATE_OBJECTS[this.initialState]; }, + buttonText() { + return this.initialStateItem?.buttonText; + }, }, watch: { @@ -37,16 +40,13 @@ export default { changeSelectedState(newState) { this.selected = newState; }, - closeDropdown() { this.$refs.dropdown.hide(); }, - // Reset the selected dropdown item to what was passed in by the parent. resetDropdown() { this.selected = this.initialStateItem; }, - saveState(selectedState) { this.$emit('change', selectedState); this.closeDropdown(); @@ -59,14 +59,14 @@ export default { <gl-dropdown ref="dropdown" menu-class="gl-p-0 dropdown-extended-height" - toggle-class="text-capitalize" - :text="initialState" + :text="buttonText" :right="true" @hide="resetDropdown" > <li v-for="stateItem in $options.states" :key="stateItem.action" + :data-testid="stateItem.state" class="py-3 px-2 dropdown-item cursor-pointer border-bottom" :class="{ selected: selected === stateItem }" @click="changeSelectedState(stateItem)" @@ -78,9 +78,9 @@ export default { name="status_success_borderless" :size="24" /> - <div class="pl-4 font-weight-bold">{{ stateItem.displayName }}</div> + <div class="pl-4 font-weight-bold">{{ stateItem.dropdownText }}</div> </div> - <div class="pl-4">{{ stateItem.description }}</div> + <div class="pl-4">{{ stateItem.dropdownDescription }}</div> </li> <template #footer> diff --git a/ee/app/assets/javascripts/vulnerabilities/constants.js b/ee/app/assets/javascripts/vulnerabilities/constants.js index ccefb7924c2f2c3a803ca99220e87f69d9848246..a98202e0ad8e63181914392e7d88bbdb7856f812 100644 --- a/ee/app/assets/javascripts/vulnerabilities/constants.js +++ b/ee/app/assets/javascripts/vulnerabilities/constants.js @@ -6,19 +6,27 @@ import { 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 = { detected: { action: 'revert', state: 'detected', - statusBoxStyle: 'expired', - displayName: s__('VulnerabilityManagement|Detected'), - description: s__('VulnerabilityManagement|Needs triage'), + buttonText: VULNERABILITY_STATES.detected, + dropdownText: s__('VulnerabilityManagement|Needs triage'), + dropdownDescription: s__('VulnerabilityManagement|Requires assessment'), }, dismissed: { action: 'dismiss', state: 'dismissed', - displayName: __('Dismiss'), - description: falsePositiveMessage, + buttonText: VULNERABILITY_STATES.dismissed, + dropdownText: __('Dismiss'), + dropdownDescription: falsePositiveMessage, payload: { comment: falsePositiveMessage, }, @@ -26,24 +34,19 @@ export const VULNERABILITY_STATE_OBJECTS = { confirmed: { action: 'confirm', state: 'confirmed', - displayName: __('Confirm'), - description: s__('VulnerabilityManagement|A true-positive and will fix'), + buttonText: VULNERABILITY_STATES.confirmed, + dropdownText: __('Confirm'), + dropdownDescription: s__('VulnerabilityManagement|A true-positive and will fix'), }, resolved: { action: 'resolve', state: 'resolved', - displayName: __('Resolve'), - description: s__('VulnerabilityManagement|Verified as fixed or mitigated'), + buttonText: VULNERABILITY_STATES.resolved, + 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 = { mergeRequestCreation: { name: s__('ciReport|Resolve with merge request'), diff --git a/ee/spec/frontend/security_dashboard/components/shared/status_dropdown_spec.js b/ee/spec/frontend/security_dashboard/components/shared/status_dropdown_spec.js index 0370fd5af6a4c2a5023799ec9f602a9421f566a8..8186d36fb282da2d45c96dab263f251c0a80c862 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/status_dropdown_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/status_dropdown_spec.js @@ -24,7 +24,6 @@ describe('Status Dropdown component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); it('renders the correct placeholder', () => { @@ -38,7 +37,7 @@ describe('Status Dropdown component', () => { it(`renders ${state}`, () => { expect(findDropdownItems().at(index).text()).toBe( - `${status.displayName} ${status.description}`, + `${status.dropdownText} ${status.dropdownDescription}`, ); }); diff --git a/ee/spec/frontend/vulnerabilities/header_spec.js b/ee/spec/frontend/vulnerabilities/header_spec.js index 08b9c30228a8fdf06ee292b6179cceb5a2c8f76a..a6affb1e5e999e3a4821df30e7e28ead6948486e 100644 --- a/ee/spec/frontend/vulnerabilities/header_spec.js +++ b/ee/spec/frontend/vulnerabilities/header_spec.js @@ -9,7 +9,11 @@ import Header from 'ee/vulnerabilities/components/header.vue'; import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue'; import StatusDescription from 'ee/vulnerabilities/components/status_description.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 UsersMockHelper from 'helpers/user_mock_data_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -305,7 +309,7 @@ describe('Vulnerability Header', () => { createWrapper({ vulnerability: { state } }); expect(findBadge().props('variant')).toBe(variant); - expect(findBadge().text()).toBe(state); + expect(findBadge().text()).toBe(VULNERABILITY_STATES[state]); }, ); }); diff --git a/ee/spec/frontend/vulnerabilities/vulnerability_state_dropdown_spec.js b/ee/spec/frontend/vulnerabilities/vulnerability_state_dropdown_spec.js index c157350c2501d1c893f774f59f700f7ef076a9b9..f648ffc4f624730f70b76097760d91cc5df6cdd9 100644 --- a/ee/spec/frontend/vulnerabilities/vulnerability_state_dropdown_spec.js +++ b/ee/spec/frontend/vulnerabilities/vulnerability_state_dropdown_spec.js @@ -1,29 +1,25 @@ 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 { 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', () => { 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. - wrapper = shallowMount(VulnerabilityStateDropdown, { + wrapper = shallowMountExtended(VulnerabilityStateDropdown, { propsData: { initialState }, - stubs: { - GlDropdown, - }, + stubs: { GlDropdown }, }); // Mock out this function, it's called by some methods in the component. wrapper.vm.$refs.dropdown.hide = jest.fn(); }; - // isSelected is designed to work with both single VueWrapper or WrapperArray - const isSelected = (items) => - Boolean((items.wrappers ?? [items]).find((w) => w.find('.selected-icon').exists())); + const isSelected = (item) => item.find('.selected-icon').exists(); // Item is selected if there's a checkmark icon. const isDisabled = (item) => item.attributes('disabled') === 'true'; const dropdownItems = () => wrapper.findAll('.dropdown-item'); const firstUnselectedItem = () => wrapper.find('.dropdown-item:not(.selected)'); @@ -31,45 +27,41 @@ describe('Vulnerability state dropdown component', () => { const saveButton = () => wrapper.find({ ref: 'save-button' }); const cancelButton = () => wrapper.find({ ref: 'cancel-button' }); const innerDropdown = () => wrapper.find({ ref: 'dropdown' }); - - const dropdownItemFor = (stateEntry) => - dropdownItems().wrappers.find((x) => { - const text = x.text(); - return text.includes(stateEntry.displayName) && text.includes(stateEntry.description); - }); + const dropdownItemFor = (state) => wrapper.findByTestId(state); afterEach(() => wrapper.destroy()); describe('tests that need to manually create the wrapper', () => { - it.each(vulnerabilityStateEntries)( + it.each(vulnerabilityStateObjects)( 'dropdown is created with the %s state already selected', - (stateString, stateObject) => { - createWrapper(stateString); + ({ state }) => { + 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', () => { 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`, - (stateString, stateObject) => { + ({ state }) => { // Start off with an unknown state so we can click through each item and see it change. createWrapper('some unknown state'); - const dropdownItem = dropdownItemFor(stateObject); + const dropdownItem = dropdownItemFor(state); dropdownItem.trigger('click'); return wrapper.vm.$nextTick().then(() => { - // Check that the clicked item is selected. - expect(isSelected(dropdownItem)).toBe(true); - // Check that the other items aren't selected. - const otherItems = wrapper.findAll('.dropdown-item:not(.selected)'); - expect(isSelected(otherItems)).toBe(false); + dropdownItems().wrappers.forEach((item) => { + expect(isSelected(item)).toBe(item.attributes('data-testid') === state); + }); }); }, ); @@ -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', () => { - 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(() => { - expect(innerDropdown().props('text')).toBe(stateString); // 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(innerDropdown().props('text')).toBe(stateObject.buttonText); // Check that the dropdown button's value matches 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. }); }); diff --git a/ee/spec/frontend_integration/vulnerabilities/vulnerabilities_init_integration_spec.js b/ee/spec/frontend_integration/vulnerabilities/vulnerabilities_init_integration_spec.js index a8404b0380108e3caf664835ad3a52e1f3eebdef..0f170f47d7d688b7c479efa6bae1330df1a672f6 100644 --- a/ee/spec/frontend_integration/vulnerabilities/vulnerabilities_init_integration_spec.js +++ b/ee/spec/frontend_integration/vulnerabilities/vulnerabilities_init_integration_spec.js @@ -1,6 +1,7 @@ import { screen, within } from '@testing-library/dom'; import initVulnerabilities from 'ee/vulnerabilities/vulnerabilities_init'; 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 { mockVulnerability } from './mock_data'; @@ -38,8 +39,9 @@ describe('Vulnerability Report', () => { it("displays the vulnerability's status", () => { 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", () => { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index cecca0cd98c606f8cb5ee65022e4fde3218a7f18..d2d543d2bd9c8f33e7ca34ea7b36da95e0366232 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -38576,9 +38576,6 @@ msgstr "" msgid "VulnerabilityManagement|Create Jira issue" msgstr "" -msgid "VulnerabilityManagement|Detected" -msgstr "" - msgid "VulnerabilityManagement|Fetching linked Jira issues" msgstr "" @@ -38594,6 +38591,9 @@ msgstr "" msgid "VulnerabilityManagement|Related Jira issues" msgstr "" +msgid "VulnerabilityManagement|Requires assessment" +msgstr "" + msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later." msgstr "" @@ -38639,10 +38639,10 @@ msgstr "" msgid "VulnerabilityStatusTypes|Confirmed" msgstr "" -msgid "VulnerabilityStatusTypes|Detected" +msgid "VulnerabilityStatusTypes|Dismissed" msgstr "" -msgid "VulnerabilityStatusTypes|Dismissed" +msgid "VulnerabilityStatusTypes|Needs triage" msgstr "" msgid "VulnerabilityStatusTypes|Resolved"