Commit 9041c27c authored by Sam Beckham's avatar Sam Beckham Committed by Phil Hughes

Add modals and actions for the vulnerabilities in the Group security dashboard

parent c3a5223b
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
import { spriteIcon } from '~/lib/utils/common_utils';
import Tabs from '~/vue_shared/components/tabs/tabs';
import Tab from '~/vue_shared/components/tabs/tab.vue';
import IssueModal from 'ee/vue_shared/security_reports/components/modal.vue';
import SecurityDashboardTable from './security_dashboard_table.vue';
import VulnerabilityCountList from './vulnerability_count_list.vue';
import SvgBlankState from '~/pipelines/components/blank_state.vue';
......@@ -16,6 +18,7 @@ export default {
},
components: {
Icon,
IssueModal,
SecurityDashboardTable,
SvgBlankState,
Tab,
......@@ -46,7 +49,7 @@ export default {
},
computed: {
...mapGetters('vulnerabilities', ['vulnerabilitiesCountByReportType']),
...mapState('vulnerabilities', ['hasError']),
...mapState('vulnerabilities', ['hasError', 'modal']),
sastCount() {
return this.vulnerabilitiesCountByReportType('sast');
},
......@@ -66,7 +69,7 @@ export default {
<span class="vertical-align-middle">${s__(
'Security Reports|Security Dashboard Documentation',
)}</span>
${gl.utils.spriteIcon('external-link', 's16 vertical-align-middle')}
${spriteIcon('external-link', 's16 vertical-align-middle')}
</a>
`,
html: true,
......@@ -83,6 +86,9 @@ export default {
'setVulnerabilitiesCountEndpoint',
'setVulnerabilitiesEndpoint',
'fetchVulnerabilitiesCount',
'createIssue',
'dismissVulnerability',
'undoDismissal',
]),
},
};
......@@ -90,6 +96,7 @@ export default {
<template>
<div>
<div class="flash-container"></div>
<svg-blank-state
v-if="hasError"
:svg-path="errorStateSvgPath"
......@@ -110,7 +117,8 @@ export default {
</span>
<span
v-popover="popoverOptions"
class="text-muted ml-1"
class="text-muted prepend-left-4"
:aria-label="__('help')"
>
<icon
name="question"
......@@ -124,6 +132,14 @@ export default {
/>
</tab>
</tabs>
<issue-modal
:modal="modal"
:can-create-issue-permission="true"
:can-create-feedback-permission="true"
@createNewIssue="createIssue({ vulnerability: modal.vulnerability })"
@dismissIssue="dismissVulnerability({ vulnerability: modal.vulnerability })"
@revertDismissIssue="undoDismissal({ vulnerability: modal.vulnerability })"
/>
</div>
</div>
</template>
<script>
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'SecurityDashboardActionButtons',
components: {
Icon,
},
props: {
vulnerability: {
type: Object,
required: true,
},
},
methods: {
openModal() {
// TODO: Open the modal
},
newIssue() {
this.$store.dispatch('newIssue', this.vulnerability);
},
dismissVulnerability() {
this.$store.dispatch('dismissVulnerability', this.vulnerability);
},
},
};
</script>
<template>
<div>
<button
:aria-label="s__('Reports|More info')"
class="btn btn-secondary js-more-info"
type="button"
@click="openModal()"
>
<icon
name="external-link"
/>
</button>
<button
:aria-label="s__('Reports|New Issue')"
class="btn btn-inverted btn-info js-new-issue"
type="button"
@click="newIssue()"
>
<icon
name="issue-new"
/>
</button>
<button
:aria-label="s__('Reports|Dismiss Vulnerability')"
class="btn btn-inverted btn-remove js-dismiss-vulnerability"
type="button"
@click="dismissVulnerability()"
>
<icon
name="cancel"
/>
</button>
</div>
</template>
......@@ -19,7 +19,7 @@ export default {
this.fetchVulnerabilities();
},
methods: {
...mapActions('vulnerabilities', ['fetchVulnerabilities']),
...mapActions('vulnerabilities', ['fetchVulnerabilities', 'openModal']),
},
};
</script>
......@@ -63,6 +63,7 @@ export default {
v-for="vulnerability in vulnerabilities"
:key="vulnerability.id"
:vulnerability="vulnerability"
@openModal="openModal({ vulnerability })"
/>
<pagination
......
<script>
import { mapActions } from 'vuex';
import { GlSkeletonLoading } from '@gitlab-org/gitlab-ui';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SecurityDashboardActionButtons from './security_dashboard_action_buttons.vue';
import VulnerabilityActionButtons from './vulnerability_action_buttons.vue';
import VulnerabilityIssueLink from './vulnerability_issue_link.vue';
export default {
name: 'SecurityDashboardTableRow',
components: {
SeverityBadge,
SecurityDashboardActionButtons,
GlSkeletonLoading,
VulnerabilityActionButtons,
VulnerabilityIssueLink,
},
props: {
......@@ -31,16 +32,25 @@ export default {
severity() {
return this.vulnerability.severity || ' ';
},
projectNamespace() {
projectFullName() {
const { project } = this.vulnerability;
return project && project.full_name ? project.full_name : null;
return project && project.full_name;
},
isDismissed() {
return this.vulnerability.dismissal_feedback;
return Boolean(this.vulnerability.dismissal_feedback);
},
hasIssue() {
return this.vulnerability.issue_feedback;
return Boolean(this.vulnerability.issue_feedback);
},
canDismissVulnerability() {
return Boolean(this.vulnerability.vulnerability_feedback_url);
},
canCreateIssue() {
return this.canDismissVulnerability && !this.hasIssue;
},
},
methods: {
...mapActions('vulnerabilities', ['openModal']),
},
};
</script>
......@@ -73,8 +83,11 @@ export default {
:lines="2"
/>
<div v-else>
<strike v-if="isDismissed">{{ vulnerability.name }}</strike>
<span v-else>{{ vulnerability.name }}</span>
<span
class="js-vulnerability-info"
:class="{ strikethrough: isDismissed }"
@click="openModal({ vulnerability })"
>{{ vulnerability.name }}</span>
<vulnerability-issue-link
v-if="hasIssue"
:issue="vulnerability.issue_feedback"
......@@ -82,9 +95,9 @@ export default {
/>
<br />
<span
v-if="projectNamespace"
v-if="projectFullName"
class="vulnerability-namespace">
{{ projectNamespace }}
{{ projectFullName }}
</span>
</div>
</div>
......@@ -98,12 +111,10 @@ export default {
{{ s__('Reports|Confidence') }}
</div>
<div class="table-mobile-content text-capitalize">
<strike v-if="isDismissed">{{ confidence }}</strike>
<span v-else>{{ confidence }}</span>
<span :class="{ strikethrough: isDismissed }">{{ confidence }}</span>
</div>
</div>
<!-- This is hidden till we can hook up the actions
<div class="table-section section-20">
<div
class="table-mobile-header"
......@@ -112,12 +123,14 @@ export default {
{{ s__('Reports|Actions') }}
</div>
<div class="table-mobile-content vulnerabilities-action-buttons">
<security-dashboard-action-buttons
<vulnerability-action-buttons
:vulnerability="vulnerability"
:can-create-issue="canCreateIssue"
:can-dismiss-vulnerability="canDismissVulnerability"
:is-dismissed="isDismissed"
/>
</div>
</div>
-->
</div>
</template>
......
<script>
import { mapActions, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'SecurityDashboardActionButtons',
components: {
Icon,
LoadingButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
vulnerability: {
type: Object,
required: true,
},
canCreateIssue: {
type: Boolean,
required: false,
default: false,
},
canDismissVulnerability: {
type: Boolean,
required: false,
default: false,
},
isDismissed: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState('vulnerabilities', ['isCreatingIssue', 'isDismissingVulnerability']),
},
methods: {
...mapActions('vulnerabilities', [
'openModal',
'createIssue',
'dismissVulnerability',
'undoDismissal',
]),
handleCreateIssue() {
const { vulnerability } = this;
this.createIssue({ vulnerability, flashError: true });
},
handleDismissVulnerability() {
const { vulnerability } = this;
this.dismissVulnerability({ vulnerability, flashError: true });
},
handleUndoDismissal() {
const { vulnerability } = this;
this.undoDismissal({ vulnerability, flashError: true });
},
},
};
</script>
<template>
<div>
<button
key="more-info"
v-gl-tooltip
:aria-label="s__('Security Reports|More info')"
:title="s__('Security Reports|More info')"
class="btn btn-secondary js-more-info"
type="button"
@click="openModal({ vulnerability })"
>
<icon name="information" />
</button>
<loading-button
v-if="canCreateIssue"
key="create-issue"
v-gl-tooltip
:aria-label="s__('Security Reports|New Issue')"
:loading="isCreatingIssue"
:title="s__('Security Reports|New Issue')"
container-class="btn btn-inverted btn-success js-create-issue"
type="button"
@click="handleCreateIssue"
>
<icon name="issue-new" />
</loading-button>
<template v-if="canDismissVulnerability">
<loading-button
v-if="isDismissed"
key="undo-dismissal"
:label="s__('Security Reports|Undo Dismissal')"
:loading="isDismissingVulnerability"
container-class="btn btn-inverted btn-warning js-undo-dismissal"
type="button"
@click="handleUndoDismissal"
/>
<loading-button
v-else
key="dismiss-vulnerability"
v-gl-tooltip
:aria-label="s__('Security Reports|Dismiss Vulnerability')"
:loading="isDismissingVulnerability"
:title="s__('Security Reports|Dismiss Vulnerability')"
container-class="btn btn-inverted btn-warning js-dismiss-vulnerability"
type="button"
@click="handleDismissVulnerability"
>
<icon name="cancel" />
</loading-button>
</template>
</div>
</template>
<script>
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
export default {
name: 'VulnerabilityIssueLink',
......@@ -8,7 +8,7 @@ export default {
Icon,
},
directives: {
Tooltip,
GlTooltip: GlTooltipDirective,
},
props: {
issue: {
......@@ -31,7 +31,7 @@ export default {
<template>
<div class="d-inline">
<icon
v-tooltip
v-gl-tooltip
name="issues"
css-classes="text-success vertical-align-middle"
:title="s__('Security Dashboard|Issue Created')"
......
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import * as types from './mutation_types';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import createFlash from '~/flash';
export const setVulnerabilitiesEndpoint = ({ commit }, endpoint) => {
commit(types.SET_VULNERABILITIES_ENDPOINT, endpoint);
......@@ -30,17 +33,19 @@ export const requestVulnerabilitiesCount = ({ commit }) => {
commit(types.REQUEST_VULNERABILITIES_COUNT);
};
export const receiveVulnerabilitiesCountSuccess = ({ commit }, response) => {
commit(types.RECEIVE_VULNERABILITIES_COUNT_SUCCESS, response.data);
export const receiveVulnerabilitiesCountSuccess = ({ commit }, { data }) => {
commit(types.RECEIVE_VULNERABILITIES_COUNT_SUCCESS, data);
};
export const receiveVulnerabilitiesCountError = ({ commit }) => {
commit(types.RECEIVE_VULNERABILITIES_COUNT_ERROR);
};
export const fetchVulnerabilities = ({ state, dispatch }, page = 1) => {
export const fetchVulnerabilities = ({ state, dispatch }, pageNumber) => {
dispatch('requestVulnerabilities');
const page = pageNumber || (state.pageInfo && state.pageInfo.page) || 1;
axios({
method: 'GET',
url: state.vulnerabilitiesEndpoint,
......@@ -59,10 +64,10 @@ export const requestVulnerabilities = ({ commit }) => {
commit(types.REQUEST_VULNERABILITIES);
};
export const receiveVulnerabilitiesSuccess = ({ commit }, response = {}) => {
const normalizedHeaders = normalizeHeaders(response.headers);
export const receiveVulnerabilitiesSuccess = ({ commit }, { headers, data }) => {
const normalizedHeaders = normalizeHeaders(headers);
const pageInfo = parseIntPagination(normalizedHeaders);
const vulnerabilities = response.data;
const vulnerabilities = data;
commit(types.RECEIVE_VULNERABILITIES_SUCCESS, { pageInfo, vulnerabilities });
};
......@@ -71,4 +76,117 @@ export const receiveVulnerabilitiesError = ({ commit }) => {
commit(types.RECEIVE_VULNERABILITIES_ERROR);
};
export const openModal = ({ commit }, payload = {}) => {
$('#modal-mrwidget-security-issue').modal('show');
commit(types.SET_MODAL_DATA, payload);
};
export const createIssue = ({ dispatch }, { vulnerability, flashError }) => {
dispatch('requestCreateIssue');
axios
.post(vulnerability.vulnerability_feedback_url, {
vulnerability_feedback: {
feedback_type: 'issue',
category: vulnerability.report_type,
project_fingerprint: vulnerability.project_fingerprint,
vulnerability_data: {
...vulnerability,
category: vulnerability.report_type,
},
},
})
.then(({ data }) => {
dispatch('receiveCreateIssueSuccess', data);
})
.catch(() => {
dispatch('receiveCreateIssueError', { flashError });
});
};
export const requestCreateIssue = ({ commit }) => {
commit(types.REQUEST_CREATE_ISSUE);
};
export const receiveCreateIssueSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_CREATE_ISSUE_SUCCESS, payload);
};
export const receiveCreateIssueError = ({ commit }) => {
commit(types.RECEIVE_CREATE_ISSUE_ERROR);
createFlash(s__('Security Reports|There was an error creating the issue.'));
};
export const dismissVulnerability = ({ dispatch }, { vulnerability, flashError }) => {
dispatch('requestDismissVulnerability');
axios
.post(vulnerability.vulnerability_feedback_url, {
vulnerability_feedback: {
feedback_type: 'dismissal',
category: vulnerability.report_type,
project_fingerprint: vulnerability.project_fingerprint,
vulnerability_data: {
...vulnerability,
category: vulnerability.report_type,
},
},
})
.then(({ data }) => {
const { id } = vulnerability;
dispatch('receiveDismissVulnerabilitySuccess', { id, data });
})
.catch(() => {
dispatch('receiveDismissVulnerabilityError', { flashError });
});
};
export const requestDismissVulnerability = ({ commit }) => {
commit(types.REQUEST_DISMISS_VULNERABILITY);
};
export const receiveDismissVulnerabilitySuccess = ({ commit }, payload) => {
commit(types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS, payload);
};
export const receiveDismissVulnerabilityError = ({ commit }, { flashError }) => {
commit(types.RECEIVE_DISMISS_VULNERABILITY_ERROR);
if (flashError) {
createFlash(s__('Security Reports|There was an error dismissing the issue.'));
}
};
export const undoDismissal = ({ dispatch }, { vulnerability, flashError }) => {
const { vulnerability_feedback_url, dismissal_feedback } = vulnerability;
// eslint-disable-next-line camelcase
const url = `${vulnerability_feedback_url}/${dismissal_feedback.id}`;
dispatch('requestUndoDismissal');
axios
.delete(url)
.then(() => {
const { id } = vulnerability;
dispatch('receiveUndoDismissalSuccess', { id });
})
.catch(() => {
dispatch('receiveUndoDismissalError', { flashError });
});
};
export const requestUndoDismissal = ({ commit }) => {
commit(types.REQUEST_UNDO_DISMISSAL);
};
export const receiveUndoDismissalSuccess = ({ commit }, payload) => {
commit(types.RECEIVE_UNDO_DISMISSAL_SUCCESS, payload);
};
export const receiveUndoDismissalError = ({ commit }, { flashError }) => {
commit(types.RECEIVE_UNDO_DISMISSAL_ERROR);
if (flashError) {
createFlash(s__('Security Reports|There was an error undoing this dismissal.'));
}
};
export default () => {};
......@@ -7,3 +7,17 @@ export const SET_VULNERABILITIES_COUNT_ENDPOINT = 'SET_VULNERABILITIES_COUNT_END
export const REQUEST_VULNERABILITIES_COUNT = 'REQUEST_VULNERABILITIES_COUNT';
export const RECEIVE_VULNERABILITIES_COUNT_SUCCESS = 'RECEIVE_VULNERABILITIES_COUNT_SUCCESS';
export const RECEIVE_VULNERABILITIES_COUNT_ERROR = 'RECEIVE_VULNERABILITIES_COUNT_ERROR';
export const SET_MODAL_DATA = 'SET_MODAL_DATA';
export const REQUEST_CREATE_ISSUE = 'REQUEST_CREATE_ISSUE';
export const RECEIVE_CREATE_ISSUE_SUCCESS = 'RECEIVE_CREATE_ISSUE_SUCCESS';
export const RECEIVE_CREATE_ISSUE_ERROR = 'RECEIVE_CREATE_ISSUE_ERROR';
export const REQUEST_DISMISS_VULNERABILITY = 'REQUEST_DISMISS_VULNERABILITY';
export const RECEIVE_DISMISS_VULNERABILITY_SUCCESS = 'RECEIVE_DISMISS_VULNERABILITY_SUCCESS';
export const RECEIVE_DISMISS_VULNERABILITY_ERROR = 'RECEIVE_DISMISS_VULNERABILITY_ERROR';
export const REQUEST_UNDO_DISMISSAL = 'REQUEST_UNDO_DISMISSAL';
export const RECEIVE_UNDO_DISMISSAL_SUCCESS = 'RECEIVE_UNDO_DISMISSAL_SUCCESS';
export const RECEIVE_UNDO_DISMISSAL_ERROR = 'RECEIVE_UNDO_DISMISSAL_ERROR';
import Vue from 'vue';
import { s__ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import * as types from './mutation_types';
export default {
......@@ -32,4 +35,90 @@ export default {
state.isLoadingVulnerabilitiesCount = false;
state.hasError = true;
},
[types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload;
Vue.set(state.modal, 'title', vulnerability.name);
Vue.set(state.modal.data.description, 'value', vulnerability.description);
Vue.set(
state.modal.data.project,
'value',
vulnerability.project && vulnerability.project.full_name,
);
Vue.set(
state.modal.data.project,
'url',
vulnerability.project && vulnerability.project.full_path,
);
Vue.set(state.modal.data.file, 'value', vulnerability.location && vulnerability.location.file);
Vue.set(
state.modal.data.identifiers,
'value',
vulnerability.identifiers.length && vulnerability.identifiers,
);
Vue.set(state.modal.data.severity, 'value', vulnerability.severity);
Vue.set(state.modal.data.confidence, 'value', vulnerability.confidence);
Vue.set(state.modal.data.solution, 'value', vulnerability.solution);
Vue.set(state.modal.data.links, 'value', vulnerability.links);
Vue.set(state.modal.data.instances, 'value', vulnerability.instances);
Vue.set(state.modal, 'vulnerability', vulnerability);
Vue.set(state.modal.vulnerability, 'hasIssue', Boolean(vulnerability.issue_feedback));
Vue.set(state.modal, 'error', null);
},
[types.REQUEST_CREATE_ISSUE](state) {
state.isCreatingIssue = true;
Vue.set(state.modal, 'isCreatingNewIssue', true);
Vue.set(state.modal, 'error', null);
},
[types.RECEIVE_CREATE_ISSUE_SUCCESS](state, payload) {
// We don't cancel the loading state here because we're navigating away from the page
visitUrl(payload.issue_url);
},
[types.RECEIVE_CREATE_ISSUE_ERROR](state) {
state.isCreatingIssue = false;
Vue.set(state.modal, 'isCreatingNewIssue', false);
Vue.set(state.modal, 'error', 'There was an error creating the issue');
},
[types.REQUEST_DISMISS_VULNERABILITY](state) {
state.isDismissingVulnerability = true;
Vue.set(state.modal, 'isDismissingVulnerability', true);
Vue.set(state.modal, 'error', null);
},
[types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS](state, payload) {
const vulnerability = state.vulnerabilities.find(vuln => vuln.id === payload.id);
vulnerability.dismissal_feedback = payload.data;
state.isDismissingVulnerability = false;
Vue.set(state.modal, 'isDismissingVulnerability', false);
Vue.set(state.modal.vulnerability, 'isDismissed', true);
},
[types.RECEIVE_DISMISS_VULNERABILITY_ERROR](state) {
state.isDismissingVulnerability = false;
Vue.set(state.modal, 'isDismissingVulnerability', false);
Vue.set(
state.modal,
'error',
s__('Security Reports|There was an error dismissing the vulnerability.'),
);
},
[types.REQUEST_UNDO_DISMISSAL](state) {
state.isDismissingVulnerability = true;
Vue.set(state.modal, 'isDismissingVulnerability', true);
Vue.set(state.modal, 'error', null);
},
[types.RECEIVE_UNDO_DISMISSAL_SUCCESS](state, payload) {
const vulnerability = state.vulnerabilities.find(vuln => vuln.id === payload.id);
vulnerability.dismissal_feedback = null;
state.isDismissingVulnerability = false;
Vue.set(state.modal, 'isDismissingVulnerability', false);
Vue.set(state.modal.vulnerability, 'isDismissed', false);
},
[types.RECEIVE_UNDO_DISMISSAL_ERROR](state) {
state.isDismissingVulnerability = false;
Vue.set(state.modal, 'isDismissingVulnerability', false);
Vue.set(
state.modal,
'error',
s__('Security Reports|There was an error undoing the dismissal.'),
);
},
};
import { s__ } from '~/locale';
export default () => ({
hasError: false,
isLoadingVulnerabilities: true,
......@@ -7,4 +9,26 @@ export default () => ({
vulnerabilitiesCount: {},
vulnerabilitiesCountEndpoint: null,
vulnerabilitiesEndpoint: null,
activeVulnerability: null,
modal: {
data: {
description: { text: s__('Vulnerability|Description') },
project: {
text: s__('Vulnerability|Project'),
isLink: true,
},
file: { text: s__('Vulnerability|File') },
identifiers: { text: s__('Vulnerability|Identifiers') },
severity: { text: s__('Vulnerability|Severity') },
confidence: { text: s__('Vulnerability|Confidence') },
solution: { text: s__('Vulnerability|Solution') },
links: { text: s__('Vulnerability|Links') },
instances: { text: s__('Vulnerability|Instances') },
},
vulnerability: {},
isCreatingNewIssue: false,
isDismissingVulnerability: false,
},
isCreatingIssue: false,
isDismissingVulnerability: false,
});
---
title: Add modals and actions to the vulnerabilities in the Group security dashboard
merge_request: 7910
author:
type: added
import Vue from 'vue';
import Vuex from 'vuex';
import component from 'ee/security_dashboard/components/security_dashboard_action_buttons.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('Security Dashboard Action Buttons', () => {
let vm;
let props;
let actions;
beforeEach(() => {
props = { vulnerability: { id: 123 } };
actions = {
newIssue: jasmine.createSpy('newIssue'),
dismissVulnerability: jasmine.createSpy('dismissVulnerability'),
};
const Component = Vue.extend(component);
const store = new Vuex.Store({ actions });
vm = mountComponentWithStore(Component, { props, store });
});
afterEach(() => {
vm.$destroy();
});
it('should render three buttons', () => {
expect(vm.$el.querySelectorAll('.btn')).toHaveLength(3);
});
describe('More Info Button', () => {
it('should render the More info button', () => {
expect(vm.$el.querySelector('.js-more-info')).not.toBeNull();
});
});
describe('New Issue Button', () => {
it('should render the New Issue button', () => {
expect(vm.$el.querySelector('.js-new-issue')).not.toBeNull();
});
it('should trigger the `newIssue` action when clicked', () => {
vm.$el.querySelector('.js-new-issue').click();
expect(actions.newIssue).toHaveBeenCalledTimes(1);
});
});
describe('Dismiss Vulnerability Button', () => {
it('should render the Dismiss Vulnerability button', () => {
expect(vm.$el.querySelector('.js-dismiss-vulnerability')).not.toBeNull();
});
it('should trigger the `dismissVulnerability` action when clicked', () => {
vm.$el.querySelector('.js-dismiss-vulnerability').click();
expect(actions.dismissVulnerability).toHaveBeenCalledTimes(1);
});
});
});
import Vue from 'vue';
import component from 'ee/security_dashboard/components/security_dashboard_table_row.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import createStore from 'ee/security_dashboard/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import mockDataVulnerabilities from '../store/vulnerabilities/data/mock_data_vulnerabilities.json';
describe('Security Dashboard Table Row', () => {
let vm;
let props;
const store = createStore();
const Component = Vue.extend(component);
afterEach(() => {
vm.$destroy();
});
describe('when loading', () => {
beforeEach(() => {
props = { isLoading: true };
vm = mountComponent(Component, props);
vm = mountComponentWithStore(Component, { store, props });
});
afterEach(() => {
vm.$destroy();
});
it('should display the skeleton loader', () => {
......@@ -33,16 +36,15 @@ describe('Security Dashboard Table Row', () => {
});
describe('when loaded', () => {
beforeEach(() => {
const vulnerability = {
severity: 'high',
name: 'Test vulnerability',
confidence: 'medium',
project: { full_name: 'project name' },
};
const vulnerability = mockDataVulnerabilities[0];
beforeEach(() => {
props = { vulnerability };
vm = mountComponent(Component, props);
vm = mountComponentWithStore(Component, { store, props });
});
afterEach(() => {
vm.$destroy();
});
it('should not display the skeleton loader', () => {
......@@ -55,6 +57,13 @@ describe('Security Dashboard Table Row', () => {
);
});
it('should render the confidence', () => {
expect(vm.$el.querySelectorAll('.table-mobile-content')[2].textContent).toContain(
props.vulnerability.confidence,
);
});
describe('the project name', () => {
it('should render the name', () => {
expect(vm.$el.querySelectorAll('.table-mobile-content')[1].textContent).toContain(
props.vulnerability.name,
......@@ -67,10 +76,15 @@ describe('Security Dashboard Table Row', () => {
);
});
it('should render the confidence', () => {
expect(vm.$el.querySelectorAll('.table-mobile-content')[2].textContent).toContain(
props.vulnerability.confidence,
);
it('should fire the openModal action when clicked', () => {
spyOn(vm.$store, 'dispatch');
vm.$el.querySelector('.js-vulnerability-info').click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/openModal', {
vulnerability,
});
});
});
});
});
import Vue from 'vue';
import MockAdapater from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import component from 'ee/security_dashboard/components/security_dashboard_table.vue';
import createStore from 'ee/security_dashboard/store';
import mockDataVulnerabilities from 'ee/security_dashboard/store/modules/vulnerabilities/mock_data_vulnerabilities.json';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import waitForPromises from 'spec/helpers/wait_for_promises';
import { resetStore } from '../helpers';
import mockDataVulnerabilities from '../store/vulnerabilities/data/mock_data_vulnerabilities.json';
describe('Security Dashboard Table', () => {
const Component = Vue.extend(component);
......
import Vue from 'vue';
import component from 'ee/security_dashboard/components/vulnerability_action_buttons.vue';
import createStore from 'ee/security_dashboard/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
import mockDataVulnerabilities from '../store/vulnerabilities/data/mock_data_vulnerabilities.json';
describe('Security Dashboard Action Buttons', () => {
const Component = Vue.extend(component);
let vm;
let store;
let props;
beforeEach(() => {
store = createStore();
});
afterEach(() => {
vm.$destroy();
resetStore(store);
});
describe('with a fresh vulnerability', () => {
beforeEach(() => {
props = {
vulnerability: mockDataVulnerabilities[0],
canCreateIssue: true,
canDismissVulnerability: true,
};
vm = mountComponentWithStore(Component, { store, props });
spyOn(vm.$store, 'dispatch').and.returnValue(Promise.resolve());
});
afterEach(() => {
vm.$destroy();
});
it('should render three buttons', () => {
expect(vm.$el.querySelectorAll('.btn')).toHaveLength(3);
});
describe('More Info Button', () => {
let button;
beforeEach(() => {
button = vm.$el.querySelector('.js-more-info');
});
it('should render the More info button', () => {
expect(button).not.toBeNull();
});
it('should emit an `openModal` event when clicked', () => {
button.click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/openModal', {
vulnerability: mockDataVulnerabilities[0],
});
});
});
describe('Create Issue Button', () => {
let button;
beforeEach(() => {
button = vm.$el.querySelector('.js-create-issue');
});
it('should render the create issue button', () => {
expect(button).not.toBeNull();
});
it('should emit an `createIssue` event when clicked', () => {
button.click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/createIssue', {
vulnerability: mockDataVulnerabilities[0],
flashError: true,
});
});
});
describe('Dismiss Vulnerability Button', () => {
let button;
beforeEach(() => {
button = vm.$el.querySelector('.js-dismiss-vulnerability');
});
it('should render the dismiss vulnerability button', () => {
expect(button).not.toBeNull();
});
it('should emit an `dismissVulnerability` event when clicked', () => {
button.click();
expect(vm.$store.dispatch).toHaveBeenCalledWith('vulnerabilities/dismissVulnerability', {
vulnerability: mockDataVulnerabilities[0],
flashError: true,
});
});
});
});
describe('with a vulnerbility that has an issue', () => {
beforeEach(() => {
props = {
vulnerability: mockDataVulnerabilities[3],
};
vm = mountComponentWithStore(Component, { store, props });
});
afterEach(() => {
vm.$destroy();
});
it('should only render one button', () => {
expect(vm.$el.querySelectorAll('.btn')).toHaveLength(1);
});
it('should not render the create issue button', () => {
expect(vm.$el.querySelector('.js-create-issue')).toBeNull();
});
});
describe('with a vulnerbility that has been dismissed', () => {
beforeEach(() => {
props = {
vulnerability: mockDataVulnerabilities[2],
};
vm = mountComponentWithStore(Component, { store, props });
});
afterEach(() => {
vm.$destroy();
});
it('should only render one button', () => {
expect(vm.$el.querySelectorAll('.btn')).toHaveLength(1);
});
it('should not render the dismiss vulnerability button', () => {
expect(vm.$el.querySelector('.js-dismiss-vulnerability')).toBeNull();
});
});
});
......@@ -3,15 +3,37 @@ import axios from '~/lib/utils/axios_utils';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import mockDataVulnerabilities from 'ee/security_dashboard/store/modules/vulnerabilities/mock_data_vulnerabilities.json';
import mockDataVulnerabilitiesCount from 'ee/security_dashboard/store/modules/vulnerabilities/mock_data_vulnerabilities_count.json';
import initialState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
import * as types from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import * as actions from 'ee/security_dashboard/store/modules/vulnerabilities/actions';
import mockDataVulnerabilities from './data/mock_data_vulnerabilities.json';
import mockDataVulnerabilitiesCount from './data/mock_data_vulnerabilities_count.json';
describe('vulnerabiliites count actions', () => {
const data = mockDataVulnerabilitiesCount;
describe('setVulnerabilitiesCountEndpoint', () => {
it('should commit the correct mutuation', done => {
const state = initialState;
const endpoint = 'fakepath.json';
testAction(
actions.setVulnerabilitiesCountEndpoint,
endpoint,
state,
[
{
type: types.SET_VULNERABILITIES_COUNT_ENDPOINT,
payload: endpoint,
},
],
[],
done,
);
});
});
describe('fetchVulnerabilitesCount', () => {
let mock;
const state = initialState;
......@@ -255,20 +277,214 @@ describe('vulnerabilities actions', () => {
);
});
});
});
describe('setVulnerabilitiesCountEndpoint', () => {
it('should commit the correct mutuation', done => {
describe('openModal', () => {
it('should commit the SET_MODAL_DATA mutation', done => {
const state = initialState;
const endpoint = 'fakepath.json';
const vulnerability = mockDataVulnerabilities[0];
testAction(
actions.setVulnerabilitiesCountEndpoint,
endpoint,
actions.openModal,
{ vulnerability },
state,
[
{
type: types.SET_VULNERABILITIES_COUNT_ENDPOINT,
payload: endpoint,
type: types.SET_MODAL_DATA,
payload: { vulnerability },
},
],
[],
done,
);
});
});
describe('issue creation', () => {
describe('createIssue', () => {
const vulnerability = mockDataVulnerabilities[0];
const data = { issue_url: 'fakepath.html' };
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('on success', () => {
beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(200, { data });
});
it('should dispatch the request and success actions', done => {
testAction(
actions.createIssue,
{ vulnerability },
{},
[],
[
{ type: 'requestCreateIssue' },
{
type: 'receiveCreateIssueSuccess',
payload: { data },
},
],
done,
);
});
});
describe('on error', () => {
beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(404, {});
});
it('should dispatch the request and error actions', done => {
const flashError = false;
testAction(
actions.createIssue,
{ vulnerability, flashError },
{},
[],
[
{ type: 'requestCreateIssue' },
{ type: 'receiveCreateIssueError', payload: { flashError } },
],
done,
);
});
});
});
describe('receiveCreateIssueSuccess', () => {
it('should commit the success mutation', done => {
const state = initialState;
const data = mockDataVulnerabilities[0];
testAction(
actions.receiveCreateIssueSuccess,
{ data },
state,
[
{
type: types.RECEIVE_CREATE_ISSUE_SUCCESS,
payload: { data },
},
],
[],
done,
);
});
});
describe('receiveCreateIssueError', () => {
it('should commit the error mutation', done => {
const state = initialState;
testAction(
actions.receiveCreateIssueError,
{},
state,
[{ type: types.RECEIVE_CREATE_ISSUE_ERROR }],
[],
done,
);
});
});
describe('requestCreateIssue', () => {
it('should commit the request mutation', done => {
const state = initialState;
testAction(
actions.requestCreateIssue,
{},
state,
[{ type: types.REQUEST_CREATE_ISSUE }],
[],
done,
);
});
});
});
describe('vulnerability dismissal', () => {
describe('dismissVulnerability', () => {
const vulnerability = mockDataVulnerabilities[0];
const data = { vulnerability };
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('on success', () => {
beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(200, data);
});
it('should dispatch the request and success actions', done => {
testAction(
actions.dismissVulnerability,
{ vulnerability },
{},
[],
[
{ type: 'requestDismissVulnerability' },
{
type: 'receiveDismissVulnerabilitySuccess',
payload: { data, id: vulnerability.id },
},
],
done,
);
});
});
describe('on error', () => {
beforeEach(() => {
mock.onPost(vulnerability.vulnerability_feedback_url).replyOnce(404, {});
});
it('should dispatch the request and error actions', done => {
const flashError = false;
testAction(
actions.dismissVulnerability,
{ vulnerability, flashError },
{},
[],
[
{ type: 'requestDismissVulnerability' },
{ type: 'receiveDismissVulnerabilityError', payload: { flashError: false } },
],
done,
);
});
});
});
describe('receiveDismissVulnerabilitySuccess', () => {
it('should commit the success mutation', done => {
const state = initialState;
const data = mockDataVulnerabilities[0];
testAction(
actions.receiveDismissVulnerabilitySuccess,
{ data },
state,
[
{
type: types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS,
payload: { data },
},
],
[],
......@@ -276,4 +492,145 @@ describe('vulnerabilities actions', () => {
);
});
});
describe('receiveDismissVulnerabilityError', () => {
it('should commit the error mutation', done => {
const state = initialState;
testAction(
actions.receiveDismissVulnerabilityError,
{},
state,
[{ type: types.RECEIVE_DISMISS_VULNERABILITY_ERROR }],
[],
done,
);
});
});
describe('requestDismissVulnerability', () => {
it('should commit the request mutation', done => {
const state = initialState;
testAction(
actions.requestDismissVulnerability,
{},
state,
[{ type: types.REQUEST_DISMISS_VULNERABILITY }],
[],
done,
);
});
});
});
describe('undo vulnerability dismissal', () => {
describe('undoDismissal', () => {
const vulnerability = mockDataVulnerabilities[2];
const url = `${vulnerability.vulnerability_feedback_url}/${
vulnerability.dismissal_feedback.id
}`;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('on success', () => {
beforeEach(() => {
mock.onDelete(url).replyOnce(200, {});
});
it('should dispatch the request and success actions', done => {
testAction(
actions.undoDismissal,
{ vulnerability },
{},
[],
[
{ type: 'requestUndoDismissal' },
{ type: 'receiveUndoDismissalSuccess', payload: { id: vulnerability.id } },
],
done,
);
});
});
describe('on error', () => {
beforeEach(() => {
mock.onDelete(url).replyOnce(404, {});
});
it('should dispatch the request and error actions', done => {
const flashError = false;
testAction(
actions.undoDismissal,
{ vulnerability, flashError },
{},
[],
[
{ type: 'requestUndoDismissal' },
{ type: 'receiveUndoDismissalError', payload: { flashError: false } },
],
done,
);
});
});
});
describe('receiveUndoDismissalSuccess', () => {
it('should commit the success mutation', done => {
const state = initialState;
const data = mockDataVulnerabilities[0];
testAction(
actions.receiveUndoDismissalSuccess,
{ data },
state,
[
{
type: types.RECEIVE_UNDO_DISMISSAL_SUCCESS,
payload: { data },
},
],
[],
done,
);
});
});
describe('receiveUndoDismissalError', () => {
it('should commit the error mutation', done => {
const state = initialState;
testAction(
actions.receiveUndoDismissalError,
{},
state,
[{ type: types.RECEIVE_UNDO_DISMISSAL_ERROR }],
[],
done,
);
});
});
describe('requestUndoDismissal', () => {
it('should commit the request mutation', done => {
const state = initialState;
testAction(
actions.requestUndoDismissal,
{},
state,
[{ type: types.REQUEST_UNDO_DISMISSAL }],
[],
done,
);
});
});
});
......@@ -32,6 +32,7 @@
},
"dismissal_feedback": null,
"issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......@@ -81,6 +82,7 @@
},
"dismissal_feedback": null,
"issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......@@ -152,6 +154,7 @@
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
},
"issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......@@ -224,6 +227,7 @@
"branch": "master",
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
},
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......@@ -318,6 +322,7 @@
"branch": "master",
"project_fingerprint": "4e5b6966dd100170b4b1ad599c7058cce91b57b4"
},
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......@@ -367,6 +372,7 @@
},
"dismissal_feedback": null,
"issue_feedback": null,
"vulnerability_feedback_url": "https://example.com/vulnerability_feedback",
"description": "The cipher does not provide data integrity update 1",
"solution": "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
"location": {
......
import initialState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
import createState from 'ee/security_dashboard/store/modules/vulnerabilities/state';
import * as types from 'ee/security_dashboard/store/modules/vulnerabilities/mutation_types';
import mutations from 'ee/security_dashboard/store/modules/vulnerabilities/mutations';
import mockData from './data/mock_data_vulnerabilities.json';
describe('vulnerabilities module mutations', () => {
describe('SET_VULNERABILITIES_ENDPOINT', () => {
it('should set `vulnerabilitiesEndpoint` to `fakepath.json`', () => {
const state = initialState;
const state = createState();
const endpoint = 'fakepath.json';
mutations[types.SET_VULNERABILITIES_ENDPOINT](state, endpoint);
......@@ -19,7 +20,7 @@ describe('vulnerabilities module mutations', () => {
beforeEach(() => {
state = {
...initialState,
...createState(),
hasError: true,
};
mutations[types.REQUEST_VULNERABILITIES](state);
......@@ -40,10 +41,10 @@ describe('vulnerabilities module mutations', () => {
beforeEach(() => {
payload = {
vulnerabilities: [1, 2, 3, 4, 5],
vulnerabilities: mockData,
pageInfo: { a: 1, b: 2, c: 3 },
};
state = initialState;
state = createState();
mutations[types.RECEIVE_VULNERABILITIES_SUCCESS](state, payload);
});
......@@ -62,7 +63,7 @@ describe('vulnerabilities module mutations', () => {
describe('RECEIVE_VULNERABILITIES_ERROR', () => {
it('should set `isLoadingVulnerabilities` to `false`', () => {
const state = initialState;
const state = createState();
mutations[types.RECEIVE_VULNERABILITIES_ERROR](state);
......@@ -72,7 +73,7 @@ describe('vulnerabilities module mutations', () => {
describe('SET_VULNERABILITIES_COUNT_ENDPOINT', () => {
it('should set `vulnerabilitiesCountEndpoint` to `fakepath.json`', () => {
const state = initialState;
const state = createState();
const endpoint = 'fakepath.json';
mutations[types.SET_VULNERABILITIES_COUNT_ENDPOINT](state, endpoint);
......@@ -86,7 +87,7 @@ describe('vulnerabilities module mutations', () => {
beforeEach(() => {
state = {
...initialState,
...createState(),
hasError: true,
};
mutations[types.REQUEST_VULNERABILITIES_COUNT](state);
......@@ -106,8 +107,8 @@ describe('vulnerabilities module mutations', () => {
let state;
beforeEach(() => {
payload = { a: 1, b: 2, c: 3 };
state = initialState;
payload = mockData;
state = createState();
mutations[types.RECEIVE_VULNERABILITIES_COUNT_SUCCESS](state, payload);
});
......@@ -122,11 +123,267 @@ describe('vulnerabilities module mutations', () => {
describe('RECEIVE_VULNERABILITIES_COUNT_ERROR', () => {
it('should set `isLoadingVulnerabilitiesCount` to `false`', () => {
const state = initialState;
const state = createState();
mutations[types.RECEIVE_VULNERABILITIES_COUNT_ERROR](state);
expect(state.isLoadingVulnerabilitiesCount).toBeFalsy();
});
});
describe('SET_MODAL_DATA', () => {
const vulnerability = mockData[0];
let payload;
let state;
beforeEach(() => {
state = createState();
payload = { vulnerability };
mutations[types.SET_MODAL_DATA](state, payload);
});
it('should set the modal title', () => {
expect(state.modal.title).toEqual(vulnerability.name);
});
it('should set the modal description', () => {
expect(state.modal.data.description.value).toEqual(vulnerability.description);
});
it('should set the modal project', () => {
expect(state.modal.data.project.value).toEqual(vulnerability.project.full_name);
expect(state.modal.data.project.url).toEqual(vulnerability.project.full_path);
});
it('should set the modal file', () => {
expect(state.modal.data.file.value).toEqual(vulnerability.location.file);
});
it('should set the modal identifiers', () => {
expect(state.modal.data.identifiers.value).toEqual(vulnerability.identifiers);
});
it('should set the modal severity', () => {
expect(state.modal.data.severity.value).toEqual(vulnerability.severity);
});
it('should set the modal confidence', () => {
expect(state.modal.data.confidence.value).toEqual(vulnerability.confidence);
});
it('should set the modal solution', () => {
expect(state.modal.data.solution.value).toEqual(vulnerability.solution);
});
it('should set the modal links', () => {
expect(state.modal.data.links.value).toEqual(vulnerability.links);
});
it('should set the modal instances', () => {
expect(state.modal.data.instances.value).toEqual(vulnerability.instances);
});
it('should set the modal vulnerability', () => {
expect(state.modal.vulnerability).toEqual(vulnerability);
});
});
describe('REQUEST_CREATE_ISSUE', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.REQUEST_CREATE_ISSUE](state);
});
it('should set isCreatingIssue to true', () => {
expect(state.isCreatingIssue).toBe(true);
});
it('should set isCreatingNewIssue in the modal data to true', () => {
expect(state.modal.isCreatingNewIssue).toBe(true);
});
it('should nullify the error state on the modal', () => {
expect(state.modal.error).toBeNull();
});
});
describe('RECEIVE_CREATE_ISSUE_SUCCESS', () => {
it('should fire the visitUrl function on the issue URL', () => {
const state = createState();
const payload = { issue_url: 'fakepath.html' };
const visitUrl = spyOnDependency(mutations, 'visitUrl');
mutations[types.RECEIVE_CREATE_ISSUE_SUCCESS](state, payload);
expect(visitUrl).toHaveBeenCalledWith(payload.issue_url);
});
});
describe('RECEIVE_CREATE_ISSUE_ERROR', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.RECEIVE_CREATE_ISSUE_ERROR](state);
});
it('should set isCreatingIssue to false', () => {
expect(state.isCreatingIssue).toBe(false);
});
it('should set isCreatingNewIssue in the modal data to false', () => {
expect(state.modal.isCreatingNewIssue).toBe(false);
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error creating the issue');
});
});
describe('REQUEST_DISMISS_VULNERABILITY', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.REQUEST_DISMISS_VULNERABILITY](state);
});
it('should set isDismissingVulnerability to true', () => {
expect(state.isDismissingVulnerability).toBe(true);
});
it('should set isDismissingVulnerability in the modal data to true', () => {
expect(state.modal.isDismissingVulnerability).toBe(true);
});
it('should nullify the error state on the modal', () => {
expect(state.modal.error).toBeNull();
});
});
describe('RECEIVE_DISMISS_VULNERABILITY_SUCCESS', () => {
let state;
let payload;
let vulnerability;
let data;
beforeEach(() => {
state = createState();
state.vulnerabilities = mockData;
[vulnerability] = mockData;
data = { name: 'dismissal feedback' };
payload = { id: vulnerability.id, data };
mutations[types.RECEIVE_DISMISS_VULNERABILITY_SUCCESS](state, payload);
});
it('should set the dismissal feedback on the passed vulnerability', () => {
expect(vulnerability.dismissal_feedback).toEqual(data);
});
it('should set isDismissingVulnerability to false', () => {
expect(state.isDismissingVulnerability).toBe(false);
});
it('should set isDismissingVulnerability on the modal to false', () => {
expect(state.modal.isDismissingVulnerability).toBe(false);
});
it('shoulfd set isDissmissed on the modal vulnerability to be true', () => {
expect(state.modal.vulnerability.isDismissed).toBe(true);
});
});
describe('RECEIVE_DISMISS_VULNERABILITY_ERROR', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.RECEIVE_DISMISS_VULNERABILITY_ERROR](state);
});
it('should set isDismissingVulnerability to false', () => {
expect(state.isDismissingVulnerability).toBe(false);
});
it('should set isDismissingVulnerability in the modal data to false', () => {
expect(state.modal.isDismissingVulnerability).toBe(false);
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error dismissing the vulnerability.');
});
});
describe('REQUEST_UNDO_DISMISSAL', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.REQUEST_UNDO_DISMISSAL](state);
});
it('should set isDismissingVulnerability to true', () => {
expect(state.isDismissingVulnerability).toBe(true);
});
it('should set isDismissingVulnerability in the modal data to true', () => {
expect(state.modal.isDismissingVulnerability).toBe(true);
});
it('should nullify the error state on the modal', () => {
expect(state.modal.error).toBeNull();
});
});
describe('RECEIVE_UNDO_DISMISSAL_SUCCESS', () => {
let state;
let payload;
let vulnerability;
beforeEach(() => {
state = createState();
state.vulnerabilities = mockData;
[vulnerability] = mockData;
payload = { id: vulnerability.id };
mutations[types.RECEIVE_UNDO_DISMISSAL_SUCCESS](state, payload);
});
it('should set the dismissal feedback on the passed vulnerability', () => {
expect(vulnerability.dismissal_feedback).toBeNull();
});
it('should set isDismissingVulnerability to false', () => {
expect(state.isDismissingVulnerability).toBe(false);
});
it('should set isDismissingVulnerability on the modal to false', () => {
expect(state.modal.isDismissingVulnerability).toBe(false);
});
it('should set isDissmissed on the modal vulnerability to be false', () => {
expect(state.modal.vulnerability.isDismissed).toBe(false);
});
});
describe('RECEIVE_UNDO_DISMISSAL_ERROR', () => {
let state;
beforeEach(() => {
state = createState();
mutations[types.RECEIVE_UNDO_DISMISSAL_ERROR](state);
});
it('should set isDismissingVulnerability to false', () => {
expect(state.isDismissingVulnerability).toBe(false);
});
it('should set isDismissingVulnerability in the modal data to false', () => {
expect(state.modal.isDismissingVulnerability).toBe(false);
});
it('should set the error state on the modal', () => {
expect(state.modal.error).toEqual('There was an error undoing the dismissal.');
});
});
});
......@@ -6661,13 +6661,13 @@ msgstr ""
msgid "Reports|%{failedString} and %{resolvedString}"
msgstr ""
msgid "Reports|Class"
msgid "Reports|Actions"
msgstr ""
msgid "Reports|Confidence"
msgid "Reports|Class"
msgstr ""
msgid "Reports|Dismiss Vulnerability"
msgid "Reports|Confidence"
msgstr ""
msgid "Reports|Execution time"
......@@ -6676,12 +6676,6 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
msgid "Reports|More info"
msgstr ""
msgid "Reports|New Issue"
msgstr ""
msgid "Reports|Severity"
msgstr ""
......@@ -7017,12 +7011,39 @@ msgstr ""
msgid "Security Reports|At this time, the security dashboard only supports SAST."
msgstr ""
msgid "Security Reports|Dismiss Vulnerability"
msgstr ""
msgid "Security Reports|More info"
msgstr ""
msgid "Security Reports|New Issue"
msgstr ""
msgid "Security Reports|Security Dashboard Documentation"
msgstr ""
msgid "Security Reports|There was an error creating the issue."
msgstr ""
msgid "Security Reports|There was an error dismissing the issue."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr ""
msgid "Security Reports|There was an error fetching the dashboard. Please try again in a few moments or contact your support team."
msgstr ""
msgid "Security Reports|There was an error undoing the dismissal."
msgstr ""
msgid "Security Reports|There was an error undoing this dismissal."
msgstr ""
msgid "Security Reports|Undo Dismissal"
msgstr ""
msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr ""
......@@ -8696,6 +8717,33 @@ msgstr ""
msgid "VisibilityLevel|Unknown"
msgstr ""
msgid "Vulnerability|Confidence"
msgstr ""
msgid "Vulnerability|Description"
msgstr ""
msgid "Vulnerability|File"
msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
msgid "Vulnerability|Project"
msgstr ""
msgid "Vulnerability|Severity"
msgstr ""
msgid "Vulnerability|Solution"
msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
......@@ -9353,6 +9401,9 @@ msgstr ""
msgid "from"
msgstr ""
msgid "help"
msgstr ""
msgid "here"
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