Commit ddefc08d authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '214595-refresh-needed-for-state-changes' into 'master'

Fetch discussions on vulnerability state change

See merge request gitlab-org/gitlab!30244
parents 6fc49556 288af839
...@@ -33,7 +33,7 @@ function createFooterApp() { ...@@ -33,7 +33,7 @@ function createFooterApp() {
return false; return false;
} }
const { vulnerabilityFeedbackHelpPath, hasMr } = el.dataset; const { vulnerabilityFeedbackHelpPath, hasMr, discussionsUrl } = el.dataset;
const vulnerability = JSON.parse(el.dataset.vulnerabilityJson); const vulnerability = JSON.parse(el.dataset.vulnerabilityJson);
const finding = JSON.parse(el.dataset.findingJson); const finding = JSON.parse(el.dataset.findingJson);
const { issue_feedback: feedback, remediation, solution } = finding; const { issue_feedback: feedback, remediation, solution } = finding;
...@@ -42,6 +42,7 @@ function createFooterApp() { ...@@ -42,6 +42,7 @@ function createFooterApp() {
); );
const props = { const props = {
discussionsUrl,
solutionInfo: { solutionInfo: {
solution, solution,
remediation, remediation,
......
...@@ -2,15 +2,19 @@ ...@@ -2,15 +2,19 @@
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue'; import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue'; import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import HistoryEntry from './history_entry.vue'; import HistoryEntry from './history_entry.vue';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
export default { export default {
name: 'VulnerabilityFooter', name: 'VulnerabilityFooter',
components: { IssueNote, SolutionCard, HistoryEntry }, components: { IssueNote, SolutionCard, HistoryEntry },
props: { props: {
discussionsUrl: {
type: String,
required: true,
},
feedback: { feedback: {
type: Object, type: Object,
required: false, required: false,
...@@ -40,20 +44,26 @@ export default { ...@@ -40,20 +44,26 @@ export default {
}, },
created() { created() {
// window.location.pathname is the URL without the protocol or hash/querystring this.fetchDiscussions();
// i.e. http://server/url?query=string#note_123 -> /server/url
axios VulnerabilitiesEventBus.$on('VULNERABILITY_STATE_CHANGE', this.fetchDiscussions);
.get(joinPaths(window.location.pathname, 'discussions')) },
.then(({ data }) => {
this.discussions = data; methods: {
}) fetchDiscussions() {
.catch(() => { axios
createFlash( .get(this.discussionsUrl)
s__( .then(({ data }) => {
'VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later.', this.discussions = data;
), })
); .catch(() => {
}); createFlash(
s__(
'VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later.',
),
);
});
},
}, },
}; };
</script> </script>
......
...@@ -10,6 +10,7 @@ import ResolutionAlert from './resolution_alert.vue'; ...@@ -10,6 +10,7 @@ import ResolutionAlert from './resolution_alert.vue';
import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from './vulnerability_state_dropdown.vue';
import StatusDescription from './status_description.vue'; import StatusDescription from './status_description.vue';
import { VULNERABILITY_STATE_OBJECTS } from '../constants'; import { VULNERABILITY_STATE_OBJECTS } from '../constants';
import VulnerabilitiesEventBus from './vulnerabilities_event_bus';
export default { export default {
name: 'VulnerabilityHeader', name: 'VulnerabilityHeader',
...@@ -110,6 +111,7 @@ export default { ...@@ -110,6 +111,7 @@ export default {
}) })
.finally(() => { .finally(() => {
this.isLoadingVulnerability = false; this.isLoadingVulnerability = false;
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGE');
}); });
}, },
createIssue() { createIssue() {
......
// TODO: Replace with mitt implementation for Vue3 once it has been verified
// (https://gitlab.com/gitlab-org/gitlab/-/issues/215672)
import Vue from 'vue';
export default new Vue();
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import VulnerabilityFooter from 'ee/vulnerabilities/components/footer.vue'; import VulnerabilityFooter from 'ee/vulnerabilities/components/footer.vue';
import HistoryEntry from 'ee/vulnerabilities/components/history_entry.vue'; import HistoryEntry from 'ee/vulnerabilities/components/history_entry.vue';
import VulnerabilitiesEventBus from 'ee/vulnerabilities/components/vulnerabilities_event_bus';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue'; import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue'; import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
...@@ -15,6 +16,7 @@ describe('Vulnerability Footer', () => { ...@@ -15,6 +16,7 @@ describe('Vulnerability Footer', () => {
let wrapper; let wrapper;
const minimumProps = { const minimumProps = {
discussionsUrl: `/discussions`,
solutionInfo: { solutionInfo: {
hasDownload: false, hasDownload: false,
hasMr: false, hasMr: false,
...@@ -63,9 +65,19 @@ describe('Vulnerability Footer', () => { ...@@ -63,9 +65,19 @@ describe('Vulnerability Footer', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
mockAxios.reset(); mockAxios.reset();
}); });
describe('vulnerabilities event bus listener', () => {
it('calls the discussion url on vulnerabilities event bus emit of VULNERABILITY_STATE_CHANGE', () => {
createWrapper();
jest.spyOn(axios, 'get');
VulnerabilitiesEventBus.$emit('VULNERABILITY_STATE_CHANGE');
expect(axios.get).toHaveBeenCalledTimes(1);
});
});
describe('solution card', () => { describe('solution card', () => {
it('does show solution card when there is one', () => { it('does show solution card when there is one', () => {
createWrapper({ ...minimumProps, solutionInfo: solutionInfoProp }); createWrapper({ ...minimumProps, solutionInfo: solutionInfoProp });
......
...@@ -10,6 +10,7 @@ import Header from 'ee/vulnerabilities/components/header.vue'; ...@@ -10,6 +10,7 @@ import Header from 'ee/vulnerabilities/components/header.vue';
import StatusDescription from 'ee/vulnerabilities/components/status_description.vue'; import StatusDescription from 'ee/vulnerabilities/components/status_description.vue';
import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue'; import ResolutionAlert from 'ee/vulnerabilities/components/resolution_alert.vue';
import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue'; import VulnerabilityStateDropdown from 'ee/vulnerabilities/components/vulnerability_state_dropdown.vue';
import VulnerabilitiesEventBus from 'ee/vulnerabilities/components/vulnerabilities_event_bus';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants'; import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
const vulnerabilityStateEntries = Object.entries(VULNERABILITY_STATE_OBJECTS); const vulnerabilityStateEntries = Object.entries(VULNERABILITY_STATE_OBJECTS);
...@@ -80,6 +81,7 @@ describe('Vulnerability Header', () => { ...@@ -80,6 +81,7 @@ describe('Vulnerability Header', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
mockAxios.reset(); mockAxios.reset();
createFlash.mockReset(); createFlash.mockReset();
}); });
...@@ -102,6 +104,36 @@ describe('Vulnerability Header', () => { ...@@ -102,6 +104,36 @@ describe('Vulnerability Header', () => {
}); });
}); });
it('when the vulnerability state dropdown emits a change event, the state badge updates', () => {
const newState = 'dismiss';
mockAxios.onPost().reply(201, { state: newState });
expect(findBadge().text()).not.toBe(newState);
const dropdown = wrapper.find(VulnerabilityStateDropdown);
dropdown.vm.$emit('change');
return waitForPromises().then(() => {
expect(findBadge().text()).toBe(newState);
});
});
it('when the vulnerability state dropdown emits a change event, the vulnerabilities event bus event is emitted with the proper event', () => {
const newState = 'dismiss';
jest.spyOn(VulnerabilitiesEventBus, '$emit');
mockAxios.onPost().reply(201, { state: newState });
expect(findBadge().text()).not.toBe(newState);
const dropdown = wrapper.find(VulnerabilityStateDropdown);
dropdown.vm.$emit('change');
return waitForPromises().then(() => {
expect(VulnerabilitiesEventBus.$emit).toHaveBeenCalledTimes(1);
expect(VulnerabilitiesEventBus.$emit).toHaveBeenCalledWith('VULNERABILITY_STATE_CHANGE');
});
});
it('when the vulnerability state changes but the API call fails, an error message is displayed', () => { it('when the vulnerability state changes but the API call fails, an error message is displayed', () => {
const dropdown = wrapper.find(VulnerabilityStateDropdown); const dropdown = wrapper.find(VulnerabilityStateDropdown);
mockAxios.onPost().reply(400); mockAxios.onPost().reply(400);
......
import Vue from 'vue';
import vulnerabilitiesEventBus from 'ee/vulnerabilities/components/vulnerabilities_event_bus';
describe('Vulnerabilities event bus', () => {
it('default exports a vue instance', () => {
expect(vulnerabilitiesEventBus instanceof Vue).toBe(true);
});
});
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