Commit af27c54b authored by Kyle Mann's avatar Kyle Mann Committed by Mark Florian

Migrate vulnerability modal to GlModal

- Migrated vulnerability modal to use GlModal
- Refactored Vuex actions to let the modal be opened from a Vue
  component rather than the actions themselves
Co-authored-by: default avatarPaul Gascou-Vaillancourt <paul.gascvail@gmail.com>
Co-authored-by: default avatarMark Florian <mflorian@gitlab.com>
parent 3ae06d3d
<script>
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import { GlTooltipDirective, GlResizeObserverDirective } from '@gitlab/ui';
import { mapActions } from 'vuex';
......@@ -25,10 +26,11 @@ export default {
this.updateTooltipTitle();
},
methods: {
...mapActions(['openModal']),
...mapActions(['setModalData']),
handleIssueClick() {
const { issue, status, openModal } = this;
openModal({ issue, status });
const { issue, status, setModalData } = this;
setModalData({ issue, status });
this.$root.$emit('bv::show::modal', VULNERABILITY_MODAL_ID);
},
updateTooltipTitle() {
// Only show the tooltip if the text is truncated with an ellipsis.
......
......@@ -45,7 +45,6 @@ export default {
...mapActions('vulnerabilities', [
'deselectAllVulnerabilities',
'fetchVulnerabilities',
'openModal',
'selectAllVulnerabilities',
]),
fetchPage(page) {
......@@ -107,7 +106,6 @@ export default {
v-for="vulnerability in vulnerabilities"
:key="vulnerability.id"
:vulnerability="vulnerability"
@openModal="openModal({ vulnerability })"
/>
<slot v-if="showEmptyState" name="empty-state">
......
......@@ -10,6 +10,7 @@ import {
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
import getPrimaryIdentifier from 'ee/vue_shared/security_reports/store/utils/get_primary_identifier';
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import VulnerabilityActionButtons from './vulnerability_action_buttons.vue';
import VulnerabilityIssueLink from './vulnerability_issue_link.vue';
import { DASHBOARD_TYPES } from '../store/constants';
......@@ -88,13 +89,21 @@ export default {
},
},
methods: {
...mapActions('vulnerabilities', ['openModal', 'selectVulnerability', 'deselectVulnerability']),
...mapActions('vulnerabilities', [
'setModalData',
'selectVulnerability',
'deselectVulnerability',
]),
toggleVulnerability() {
if (this.isSelected) {
return this.deselectVulnerability(this.vulnerability);
}
return this.selectVulnerability(this.vulnerability);
},
openModal(payload) {
this.setModalData(payload);
this.$root.$emit('bv::show::modal', VULNERABILITY_MODAL_ID);
},
},
};
</script>
......
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import { vulnerabilityModalMixin } from 'ee/vue_shared/security_reports/mixins/vulnerability_modal_mixin';
import Filters from './filters.vue';
import SecurityDashboardLayout from './security_dashboard_layout.vue';
import SecurityDashboardTable from './security_dashboard_table.vue';
......@@ -16,6 +17,7 @@ export default {
FuzzingArtifactsDownload,
LoadingError,
},
mixins: [vulnerabilityModalMixin('vulnerabilities')],
props: {
vulnerabilitiesEndpoint: {
type: String,
......@@ -69,19 +71,15 @@ export default {
},
methods: {
...mapActions('vulnerabilities', [
'addDismissalComment',
'deleteDismissalComment',
'closeDismissalCommentBox',
'createIssue',
'createMergeRequest',
'dismissVulnerability',
'fetchVulnerabilities',
'openDismissalCommentBox',
'setPipelineId',
'setVulnerabilitiesEndpoint',
'showDismissalDeleteButtons',
'hideDismissalDeleteButtons',
'undoDismiss',
'downloadPatch',
]),
...mapActions('pipelineJobs', ['fetchPipelineJobs']),
......@@ -126,17 +124,17 @@ export default {
:is-creating-issue="isCreatingIssue"
:is-dismissing-vulnerability="isDismissingVulnerability"
:is-creating-merge-request="isCreatingMergeRequest"
@addDismissalComment="addDismissalComment({ vulnerability, comment: $event })"
@addDismissalComment="handleAddDismissalComment({ vulnerability, comment: $event })"
@editVulnerabilityDismissalComment="openDismissalCommentBox"
@showDismissalDeleteButtons="showDismissalDeleteButtons"
@hideDismissalDeleteButtons="hideDismissalDeleteButtons"
@deleteDismissalComment="deleteDismissalComment({ vulnerability })"
@deleteDismissalComment="handleDeleteDismissalComment({ vulnerability })"
@closeDismissalCommentBox="closeDismissalCommentBox"
@createMergeRequest="createMergeRequest({ vulnerability })"
@createNewIssue="createIssue({ vulnerability })"
@dismissVulnerability="dismissVulnerability({ vulnerability, comment: $event })"
@dismissVulnerability="handleDismissVulnerability({ vulnerability, comment: $event })"
@openDismissalCommentBox="openDismissalCommentBox"
@revertDismissVulnerability="undoDismiss({ vulnerability })"
@revertDismissVulnerability="handleRevertDismissVulnerability({ vulnerability })"
@downloadPatch="downloadPatch({ vulnerability })"
/>
</template>
......
<script>
import { mapActions, mapState } from 'vuex';
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import { s__ } from '~/locale';
export default {
......@@ -37,10 +38,10 @@ export default {
},
methods: {
...mapActions('vulnerabilities', [
'openModal',
'setModalData',
'createIssue',
'dismissVulnerability',
'undoDismiss',
'revertDismissVulnerability',
]),
handleCreateIssue() {
const { vulnerability } = this;
......@@ -52,13 +53,17 @@ export default {
},
handleUndoDismiss() {
const { vulnerability } = this;
this.undoDismiss({ vulnerability, flashError: true });
this.revertDismissVulnerability({ vulnerability, flashError: true });
},
openModal(payload) {
this.setModalData(payload);
this.$root.$emit('bv::show::modal', VULNERABILITY_MODAL_ID);
},
},
i18n: {
moreInfo: s__('SecurityReports|More info'),
createIssue: s__('SecurityReports|Create issue'),
undoDismiss: s__('SecurityReports|Undo dismiss'),
revertDismissVulnerability: s__('SecurityReports|Undo dismiss'),
dismissVulnerability: s__('SecurityReports|Dismiss vulnerability'),
},
};
......
import $ from 'jquery';
import _ from 'lodash';
import download from '~/lib/utils/downloader';
import axios from '~/lib/utils/axios_utils';
......@@ -22,8 +21,6 @@ import * as types from './mutation_types';
* https://gitlab.com/gitlab-org/gitlab/issues/8519
*/
const hideModal = () => $('#modal-mrwidget-security-issue').modal('hide');
export const setPipelineId = ({ commit }, id) => commit(types.SET_PIPELINE_ID, id);
export const setSourceBranch = ({ commit }, ref) => commit(types.SET_SOURCE_BRANCH, ref);
......@@ -77,9 +74,7 @@ export const receiveVulnerabilitiesError = ({ commit }, errorCode) => {
commit(types.RECEIVE_VULNERABILITIES_ERROR, errorCode);
};
export const openModal = ({ commit }, payload = {}) => {
$('#modal-mrwidget-security-issue').modal('show');
export const setModalData = ({ commit }, payload = {}) => {
commit(types.SET_MODAL_DATA, payload);
};
......@@ -224,7 +219,7 @@ export const dismissVulnerability = (
text: s__('SecurityReports|Undo dismiss'),
onClick: (e, toastObject) => {
if (vulnerability.dismissal_feedback) {
dispatch('undoDismiss', { vulnerability })
dispatch('revertDismissVulnerability', { vulnerability })
.then(() => dispatch('fetchVulnerabilities', { page }))
.catch(() => {});
toastObject.goAway(0);
......@@ -234,7 +229,7 @@ export const dismissVulnerability = (
}
: {};
axios
return axios
.post(vulnerability.create_vulnerability_feedback_dismissal_path, {
vulnerability_feedback: {
category: vulnerability.report_type,
......@@ -272,7 +267,6 @@ export const requestDismissVulnerability = ({ commit }) => {
export const receiveDismissVulnerabilitySuccess = ({ commit }, payload) => {
commit(types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS, payload);
hideModal();
};
export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) => {
......@@ -302,7 +296,7 @@ export const addDismissalComment = ({ dispatch }, { vulnerability, comment }) =>
vulnerabilityName: vulnerability.name,
});
axios
return axios
.patch(url, {
project_id: dismissal_feedback.project_id,
id: dismissal_feedback.id,
......@@ -327,7 +321,7 @@ export const deleteDismissalComment = ({ dispatch }, { vulnerability }) => {
vulnerabilityName: vulnerability.name,
});
axios
return axios
.patch(url, {
project_id: dismissal_feedback.project_id,
comment: '',
......@@ -349,7 +343,6 @@ export const requestAddDismissalComment = ({ commit }) => {
export const receiveAddDismissalCommentSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_ADD_DISMISSAL_COMMENT_SUCCESS, payload);
hideModal();
};
export const receiveAddDismissalCommentError = ({ commit }) => {
......@@ -362,7 +355,6 @@ export const requestDeleteDismissalComment = ({ commit }) => {
export const receiveDeleteDismissalCommentSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_DELETE_DISMISSAL_COMMENT_SUCCESS, payload);
hideModal();
};
export const receiveDeleteDismissalCommentError = ({ commit }) => {
......@@ -377,7 +369,7 @@ export const hideDismissalDeleteButtons = ({ commit }) => {
commit(types.HIDE_DISMISSAL_DELETE_BUTTONS);
};
export const undoDismiss = ({ dispatch }, { vulnerability, flashError }) => {
export const revertDismissVulnerability = ({ dispatch }, { vulnerability, flashError }) => {
const { destroy_vulnerability_feedback_dismissal_path } = vulnerability.dismissal_feedback;
dispatch('requestUndoDismiss');
......@@ -398,7 +390,6 @@ export const requestUndoDismiss = ({ commit }) => {
export const receiveUndoDismissSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_REVERT_DISMISSAL_SUCCESS, payload);
hideModal();
};
export const receiveUndoDismissError = ({ commit }, { flashError }) => {
......@@ -423,7 +414,6 @@ export const downloadPatch = ({ state }) => {
*/
const { vulnerability } = state.modal;
download({ fileData: vulnerability.remediations[0].diff, fileName: `remediation.patch` });
$('#modal-mrwidget-security-issue').modal('hide');
};
export const createMergeRequest = ({ state, dispatch }, { vulnerability, flashError }) => {
......
......@@ -7,3 +7,5 @@ export const SEVERITY_TOOLTIP_TITLE_MAP = {
`SecurityReports|The rating "unknown" indicates that the underlying scanner doesn’t contain or provide a severity rating.`,
),
};
export const VULNERABILITY_MODAL_ID = 'modal-mrwidget-security-issue';
......@@ -7,17 +7,19 @@ import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_re
import ModalFooter from 'ee/vue_shared/security_reports/components/modal_footer.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card_vuex.vue';
import VulnerabilityDetails from 'ee/vue_shared/security_reports/components/vulnerability_details.vue';
import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
import { VULNERABILITY_MODAL_ID } from './constants';
export default {
VULNERABILITY_MODAL_ID,
components: {
DismissalNote,
DismissalCommentBoxToggle,
DismissalCommentModalFooter,
IssueNote,
MergeRequestNote,
Modal: DeprecatedModal2,
GlModal,
ModalFooter,
SolutionCard,
VulnerabilityDetails,
......@@ -183,15 +185,20 @@ export default {
clearDismissalError() {
this.dismissalCommentErrorMessage = '';
},
close() {
this.$refs.modal.close();
},
},
};
</script>
<template>
<modal
id="modal-mrwidget-security-issue"
:header-title-text="modal.title"
<gl-modal
ref="modal"
:modal-id="$options.VULNERABILITY_MODAL_ID"
:title="modal.title"
data-qa-selector="vulnerability_modal_content"
class="modal-security-report-dast"
v-bind="$attrs"
>
<slot>
<vulnerability-details :vulnerability="vulnerability" class="js-vulnerability-details" />
......@@ -244,7 +251,7 @@ export default {
<div v-if="modal.error" class="alert alert-danger">{{ modal.error }}</div>
</slot>
<template #footer>
<template #modal-footer>
<dismissal-comment-modal-footer
v-if="modal.isCommentingOnDismissal"
:is-dismissed="vulnerability.isDismissed"
......@@ -274,7 +281,8 @@ export default {
@openDismissalCommentBox="$emit('openDismissalCommentBox')"
@revertDismissVulnerability="$emit('revertDismissVulnerability')"
@downloadPatch="$emit('downloadPatch')"
@cancel="close"
/>
</template>
</modal>
</gl-modal>
</template>
......@@ -100,7 +100,7 @@ export default {
<template>
<div>
<gl-button data-dismiss="modal" :disabled="disabled">
<gl-button :disabled="disabled" @click="$emit('cancel')">
{{ __('Cancel') }}
</gl-button>
......
......@@ -4,8 +4,8 @@
* Scanning, Secret Scanning) body text
* [severity-badge] [name] in [link]:[line]
*/
import ModalOpenName from 'ee/reports/components/modal_open_name.vue';
import ReportLink from '~/reports/components/report_link.vue';
import ModalOpenName from '~/reports/components/modal_open_name.vue';
import SeverityBadge from './severity_badge.vue';
export default {
......
......@@ -4,9 +4,10 @@ import { once } from 'lodash';
import { componentNames } from 'ee/reports/components/issue_body';
import { GlButton, GlSprintf, GlLink, GlModalDirective } from '@gitlab/ui';
import FuzzingArtifactsDownload from 'ee/security_dashboard/components/fuzzing_artifacts_download.vue';
import ArtifactDownload from 'ee/vue_shared/security_reports/components/artifact_download.vue';
import ArtifactDownload from './components/artifact_download.vue';
import { LOADING } from '~/reports/constants';
import { securityReportTypeEnumToReportType } from './constants';
import { vulnerabilityModalMixin } from './mixins/vulnerability_modal_mixin';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ReportSection from '~/reports/components/report_section.vue';
import SummaryRow from '~/reports/components/summary_row.vue';
......@@ -49,7 +50,7 @@ export default {
directives: {
'gl-modal': GlModalDirective,
},
mixins: [securityReportsMixin, glFeatureFlagsMixin()],
mixins: [securityReportsMixin, vulnerabilityModalMixin(), glFeatureFlagsMixin()],
apollo: {
dastSummary: {
query: securityReportSummaryQuery,
......@@ -411,15 +412,11 @@ export default {
'setCreateVulnerabilityFeedbackMergeRequestPath',
'setCreateVulnerabilityFeedbackDismissalPath',
'setPipelineId',
'dismissVulnerability',
'revertDismissVulnerability',
'createNewIssue',
'createMergeRequest',
'openDismissalCommentBox',
'closeDismissalCommentBox',
'downloadPatch',
'addDismissalComment',
'deleteDismissalComment',
'showDismissalDeleteButtons',
'hideDismissalDeleteButtons',
'fetchContainerScanningDiff',
......@@ -700,13 +697,13 @@ export default {
@closeDismissalCommentBox="closeDismissalCommentBox()"
@createMergeRequest="createMergeRequest"
@createNewIssue="createNewIssue"
@dismissVulnerability="dismissVulnerability"
@dismissVulnerability="handleDismissVulnerability"
@openDismissalCommentBox="openDismissalCommentBox()"
@editVulnerabilityDismissalComment="openDismissalCommentBox()"
@revertDismissVulnerability="revertDismissVulnerability"
@revertDismissVulnerability="handleRevertDismissVulnerability"
@downloadPatch="downloadPatch"
@addDismissalComment="addDismissalComment({ comment: $event })"
@deleteDismissalComment="deleteDismissalComment"
@addDismissalComment="handleAddDismissalComment({ comment: $event })"
@deleteDismissalComment="handleDeleteDismissalComment"
@showDismissalDeleteButtons="showDismissalDeleteButtons"
@hideDismissalDeleteButtons="hideDismissalDeleteButtons"
/>
......
import { mapActions } from 'vuex';
import { VULNERABILITY_MODAL_ID } from '../components/constants';
export const vulnerabilityModalMixin = (storeModule) => {
const actions = [
'dismissVulnerability',
'addDismissalComment',
'deleteDismissalComment',
'revertDismissVulnerability',
];
const mapActionsArgs = storeModule ? [storeModule, actions] : [actions];
return {
methods: {
...mapActions(...mapActionsArgs),
handleDismissVulnerability(payload) {
return this.dismissVulnerability(payload).then(this.hideModal);
},
handleAddDismissalComment(payload) {
return this.addDismissalComment(payload).then(this.hideModal);
},
handleDeleteDismissalComment(payload) {
return this.deleteDismissalComment(payload).then(this.hideModal);
},
handleRevertDismissVulnerability(payload) {
return this.revertDismissVulnerability(payload).then(this.hideModal);
},
hideModal() {
this.$root.$emit('bv::hide::modal', VULNERABILITY_MODAL_ID);
},
},
};
};
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import download from '~/lib/utils/downloader';
import pollUntilComplete from '~/lib/utils/poll_until_complete';
......@@ -22,8 +21,6 @@ import * as types from './mutation_types';
* https://gitlab.com/gitlab-org/gitlab/issues/8519
*/
const hideModal = () => $('#modal-mrwidget-security-issue').modal('hide');
export const setHeadBlobPath = ({ commit }, blobPath) => commit(types.SET_HEAD_BLOB_PATH, blobPath);
export const setBaseBlobPath = ({ commit }, blobPath) => commit(types.SET_BASE_BLOB_PATH, blobPath);
......@@ -175,12 +172,6 @@ export const fetchCoverageFuzzingDiff = ({ state, dispatch }) => {
export const updateCoverageFuzzingIssue = ({ commit }, issue) =>
commit(types.UPDATE_COVERAGE_FUZZING_ISSUE, issue);
export const openModal = ({ dispatch }, payload) => {
dispatch('setModalData', payload);
$('#modal-mrwidget-security-issue').modal('show');
};
export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload);
export const requestDismissVulnerability = ({ commit }) =>
commit(types.REQUEST_DISMISS_VULNERABILITY);
......@@ -196,7 +187,7 @@ export const dismissVulnerability = ({ state, dispatch }, comment) => {
vulnerabilityName: state.modal.vulnerability.name,
});
axios
return axios
.post(state.createVulnerabilityFeedbackDismissalPath, {
vulnerability_feedback: {
category: state.modal.vulnerability.category,
......@@ -217,7 +208,6 @@ export const dismissVulnerability = ({ state, dispatch }, comment) => {
dispatch('closeDismissalCommentBox');
dispatch('receiveDismissVulnerability', updatedIssue);
hideModal();
toast(toastMsg);
})
.catch(() => {
......@@ -246,7 +236,7 @@ export const addDismissalComment = ({ state, dispatch }, { comment }) => {
vulnerabilityName: vulnerability.name,
});
axios
return axios
.patch(url, {
project_id: dismissalFeedback.project_id,
id: dismissalFeedback.id,
......@@ -275,7 +265,7 @@ export const deleteDismissalComment = ({ state, dispatch }) => {
vulnerabilityName: vulnerability.name,
});
axios
return axios
.patch(url, {
project_id: dismissalFeedback.project_id,
comment: '',
......@@ -299,7 +289,6 @@ export const requestDeleteDismissalComment = ({ commit }) => {
export const receiveDeleteDismissalCommentSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_DELETE_DISMISSAL_COMMENT_SUCCESS, payload);
hideModal();
};
export const receiveDeleteDismissalCommentError = ({ commit }, error) => {
......@@ -312,7 +301,6 @@ export const requestAddDismissalComment = ({ commit }) => {
export const receiveAddDismissalCommentSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_ADD_DISMISSAL_COMMENT_SUCCESS, payload);
hideModal();
};
export const receiveAddDismissalCommentError = ({ commit }, error) => {
......@@ -322,7 +310,7 @@ export const receiveAddDismissalCommentError = ({ commit }, error) => {
export const revertDismissVulnerability = ({ state, dispatch }) => {
dispatch('requestDismissVulnerability');
axios
return axios
.delete(
state.modal.vulnerability.dismissalFeedback.destroy_vulnerability_feedback_dismissal_path,
)
......@@ -335,8 +323,6 @@ export const revertDismissVulnerability = ({ state, dispatch }) => {
};
dispatch('receiveDismissVulnerability', updatedIssue);
hideModal();
})
.catch(() =>
dispatch(
......@@ -426,7 +412,6 @@ export const downloadPatch = ({ state }) => {
*/
const { vulnerability } = state.modal;
download({ fileData: vulnerability.remediations[0].diff, fileName: 'remediation.patch' });
$('#modal-mrwidget-security-issue').modal('hide');
};
export const requestCreateMergeRequest = ({ commit }) => {
......
import Vue from 'vue';
import Vuex from 'vuex';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import component from '~/reports/components/modal_open_name.vue';
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import component from 'ee/reports/components/modal_open_name.vue';
Vue.use(Vuex);
......@@ -11,7 +12,7 @@ describe('Modal open name', () => {
const store = new Vuex.Store({
actions: {
openModal: () => {},
setModalData: () => {},
},
state: {},
mutations: {},
......@@ -37,11 +38,13 @@ describe('Modal open name', () => {
expect(vm.$el.textContent.trim()).toEqual('Issue');
});
it('calls openModal actions when button is clicked', () => {
jest.spyOn(vm, 'openModal').mockImplementation(() => {});
it('calls setModalData actions and opens modal when button is clicked', () => {
jest.spyOn(vm, 'setModalData').mockImplementation(() => {});
jest.spyOn(vm.$root, '$emit');
vm.$el.click();
expect(vm.openModal).toHaveBeenCalled();
expect(vm.setModalData).toHaveBeenCalled();
expect(vm.$root.$emit).toHaveBeenCalledWith('bv::show::modal', VULNERABILITY_MODAL_ID);
});
});
......@@ -2,6 +2,7 @@ import { GlFormCheckbox } from '@gitlab/ui';
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import SecurityDashboardTableRow from 'ee/security_dashboard/components/security_dashboard_table_row.vue';
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import createStore from 'ee/security_dashboard/store';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import { trimText } from 'helpers/text_helper';
......@@ -104,15 +105,20 @@ describe('Security Dashboard Table Row', () => {
expect(findContent(1).text()).toContain(vulnerability.location.file);
});
it('should fire the openModal action when clicked', () => {
it('should fire the setModalData action and open the modal when clicked', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
jest.spyOn(wrapper.vm.$root, '$emit');
const el = wrapper.find({ ref: 'vulnerability-title' });
el.trigger('click');
expect(store.dispatch).toHaveBeenCalledWith('vulnerabilities/openModal', {
expect(store.dispatch).toHaveBeenCalledWith('vulnerabilities/setModalData', {
vulnerability,
});
expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith(
'bv::show::modal',
VULNERABILITY_MODAL_ID,
);
});
});
......
......@@ -103,14 +103,14 @@ describe('Security Dashboard component', () => {
${'createNewIssue'} | ${undefined} | ${'vulnerabilities/createIssue'} | ${{ vulnerability: 'bar' }}
${'dismissVulnerability'} | ${'bar'} | ${'vulnerabilities/dismissVulnerability'} | ${{ comment: 'bar', vulnerability: 'bar' }}
${'openDismissalCommentBox'} | ${undefined} | ${'vulnerabilities/openDismissalCommentBox'} | ${undefined}
${'revertDismissVulnerability'} | ${undefined} | ${'vulnerabilities/undoDismiss'} | ${{ vulnerability: 'bar' }}
${'revertDismissVulnerability'} | ${undefined} | ${'vulnerabilities/revertDismissVulnerability'} | ${{ vulnerability: 'bar' }}
${'downloadPatch'} | ${undefined} | ${'vulnerabilities/downloadPatch'} | ${{ vulnerability: 'bar' }}
`(
'dispatches the "$expectedDispatchedAction" action when the modal emits a "$emittedModalEvent" event',
({ emittedModalEvent, eventPayload, expectedDispatchedAction, expectedActionPayload }) => {
store.state.vulnerabilities.modal.vulnerability = 'bar';
jest.spyOn(store, 'dispatch').mockImplementation();
jest.spyOn(store, 'dispatch').mockImplementation(() => Promise.resolve());
wrapper.find(IssueModal).vm.$emit(emittedModalEvent, eventPayload);
expect(store.dispatch).toHaveBeenCalledWith(
......
......@@ -3,6 +3,7 @@ import component from 'ee/security_dashboard/components/vulnerability_action_but
import createStore from 'ee/security_dashboard/store';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { VULNERABILITY_MODAL_ID } from 'ee/vue_shared/security_reports/components/constants';
import { resetStore } from '../helpers';
import mockDataVulnerabilities from '../store/modules/vulnerabilities/data/mock_data_vulnerabilities';
......@@ -51,12 +52,15 @@ describe('Security Dashboard Action Buttons', () => {
expect(button).not.toBeNull();
});
it('should emit an `openModal` event when clicked', () => {
it('should emit an `setModalData` event and open the modal when clicked', () => {
jest.spyOn(vm.$root, '$emit');
button.click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/openModal', {
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/setModalData', {
vulnerability: mockDataVulnerabilities[0],
});
expect(vm.$root.$emit).toHaveBeenCalledWith('bv::show::modal', VULNERABILITY_MODAL_ID);
});
});
......
......@@ -222,7 +222,7 @@ describe('vulnerabilities actions', () => {
});
});
describe('openModal', () => {
describe('setModalData', () => {
let state;
beforeEach(() => {
......@@ -232,7 +232,7 @@ describe('openModal', () => {
it('should commit the SET_MODAL_DATA mutation', () => {
const vulnerability = mockDataVulnerabilities[0];
return testAction(actions.openModal, { vulnerability }, state, [
return testAction(actions.setModalData, { vulnerability }, state, [
{
type: types.SET_MODAL_DATA,
payload: { vulnerability },
......@@ -1044,7 +1044,7 @@ describe('hideDismissalDeleteButtons', () => {
});
describe('revert vulnerability dismissal', () => {
describe('undoDismiss', () => {
describe('revertDismissVulnerability', () => {
const vulnerability = mockDataVulnerabilities[2];
const url = vulnerability.dismissal_feedback.destroy_vulnerability_feedback_dismissal_path;
let mock;
......@@ -1064,7 +1064,7 @@ describe('revert vulnerability dismissal', () => {
it('should dispatch the request and success actions', () => {
return testAction(
actions.undoDismiss,
actions.revertDismissVulnerability,
{ vulnerability },
{},
[],
......@@ -1085,7 +1085,7 @@ describe('revert vulnerability dismissal', () => {
const flashError = false;
return testAction(
actions.undoDismiss,
actions.revertDismissVulnerability,
{ vulnerability, flashError },
{},
[],
......
import { mount, shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import Vue from 'vue';
import IssueNote from 'ee/vue_shared/security_reports/components/issue_note.vue';
import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_request_note.vue';
......@@ -8,9 +9,14 @@ import createState from 'ee/vue_shared/security_reports/store/state';
describe('Security Reports modal', () => {
let wrapper;
let modal;
const mountComponent = (propsData, mountFn = shallowMount) => {
wrapper = mountFn(component, {
attrs: {
static: true,
visible: true,
},
propsData: {
isCreatingIssue: false,
isDismissingVulnerability: false,
......@@ -18,6 +24,7 @@ describe('Security Reports modal', () => {
...propsData,
},
});
modal = wrapper.find(GlModal);
};
describe('with permissions', () => {
......@@ -37,13 +44,13 @@ describe('Security Reports modal', () => {
});
it('renders dismissal author and associated pipeline', () => {
expect(wrapper.text().trim()).toContain('John Smith');
expect(wrapper.text().trim()).toContain('@jsmith');
expect(wrapper.text().trim()).toContain('#123');
expect(modal.text().trim()).toContain('John Smith');
expect(modal.text().trim()).toContain('@jsmith');
expect(modal.text().trim()).toContain('#123');
});
it('renders the dismissal comment placeholder', () => {
expect(wrapper.find('.js-comment-placeholder')).not.toBeNull();
expect(modal.find('.js-comment-placeholder')).not.toBeNull();
});
});
......@@ -61,9 +68,9 @@ describe('Security Reports modal', () => {
});
it('renders dismissal author and hides associated pipeline', () => {
expect(wrapper.text().trim()).toContain('John Smith');
expect(wrapper.text().trim()).toContain('@jsmith');
expect(wrapper.text().trim()).not.toContain('#123');
expect(modal.text().trim()).toContain('John Smith');
expect(modal.text().trim()).toContain('@jsmith');
expect(modal.text().trim()).not.toContain('#123');
});
});
......@@ -137,7 +144,7 @@ describe('Security Reports modal', () => {
});
it('renders title', () => {
expect(wrapper.text()).toContain('Arbitrary file existence disclosure in Action Pack');
expect(modal.text()).toContain('Arbitrary file existence disclosure in Action Pack');
});
});
......@@ -303,7 +310,7 @@ describe('Security Reports modal', () => {
});
describe('Solution Card', () => {
it('is rendered if the vulnerability has a solution', () => {
it('is rendered if the vulnerability has a solution', async () => {
const propsData = {
modal: createState().modal,
};
......@@ -311,15 +318,16 @@ describe('Security Reports modal', () => {
const solution = 'Upgrade to XYZ';
propsData.modal.vulnerability.solution = solution;
mountComponent(propsData, mount);
await wrapper.vm.$nextTick();
const solutionCard = wrapper.find(SolutionCard);
const solutionCard = modal.find(SolutionCard);
expect(solutionCard.exists()).toBe(true);
expect(solutionCard.text()).toContain(solution);
expect(wrapper.find('hr').exists()).toBe(false);
expect(modal.find('hr').exists()).toBe(false);
});
it('is rendered if the vulnerability has a remediation', () => {
it('is rendered if the vulnerability has a remediation', async () => {
const propsData = {
modal: createState().modal,
};
......@@ -327,6 +335,7 @@ describe('Security Reports modal', () => {
const diff = 'foo';
propsData.modal.vulnerability.remediations = [{ summary, diff }];
mountComponent(propsData, mount);
await wrapper.vm.$nextTick();
const solutionCard = wrapper.find(SolutionCard);
......@@ -336,11 +345,12 @@ describe('Security Reports modal', () => {
expect(wrapper.find('hr').exists()).toBe(false);
});
it('is rendered if the vulnerability has neither a remediation nor a solution', () => {
it('is rendered if the vulnerability has neither a remediation nor a solution', async () => {
const propsData = {
modal: createState().modal,
};
mountComponent(propsData, mount);
await wrapper.vm.$nextTick();
const solutionCard = wrapper.find(SolutionCard);
......
......@@ -368,11 +368,11 @@ describe('Grouped security reports app', () => {
wrapper.vm.$el.querySelector('[aria-label="Vulnerability Name"]').click();
return Vue.nextTick().then(() => {
expect(wrapper.vm.$el.querySelector('.modal-title').textContent.trim()).toEqual(
expect(document.querySelector('.modal-title').textContent.trim()).toEqual(
mockFindings[0].name,
);
expect(wrapper.vm.$el.querySelector('.modal-body').textContent).toContain(
expect(document.querySelector('.modal-body').textContent).toContain(
mockFindings[0].solution,
);
});
......
......@@ -9,7 +9,6 @@ import {
requestDastDiff,
requestDependencyScanningDiff,
requestCoverageFuzzingDiff,
openModal,
setModalData,
requestDismissVulnerability,
receiveDismissVulnerability,
......@@ -272,24 +271,6 @@ describe('security reports actions', () => {
});
});
describe('openModal', () => {
it('dispatches setModalData action', (done) => {
testAction(
openModal,
{ issue: { id: 1 }, status: 'failed' },
mockedState,
[],
[
{
type: 'setModalData',
payload: { issue: { id: 1 }, status: 'failed' },
},
],
done,
);
});
});
describe('setModalData', () => {
it('commits set issue modal data', (done) => {
testAction(
......
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