Commit 274aad49 authored by Alexander Turinske's avatar Alexander Turinske

Create new selection_summary component for GraphQL

- creates copy of old selection_summary that relies on Vuex
  and makes it rely on GraphQL instead
- in the future the new selection_summary will replace the
  old one everywhere
- add tests for new selection_summary component
parent 4039d4ab
......@@ -3,7 +3,7 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import { GlEmptyState, GlFormCheckbox } from '@gitlab/ui';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import SecurityDashboardTableRow from './security_dashboard_table_row.vue';
import SelectionSummary from './selection_summary.vue';
import SelectionSummary from './selection_summary_vuex.vue';
export default {
name: 'SecurityDashboardTable',
......
<script>
import { __, n__ } from '~/locale';
import { mapActions, mapGetters } from 'vuex';
import { s__, __, n__ } from '~/locale';
import { GlDeprecatedButton, GlFormSelect } from '@gitlab/ui';
import toast from '~/vue_shared/plugins/global_toast';
import createFlash from '~/flash';
import dismissVulnerability from '../graphql/dismissVulnerability.graphql';
const REASON_NONE = __('[No reason]');
const REASON_WONT_FIX = __("Won't fix / Accept risk");
......@@ -13,13 +15,29 @@ export default {
GlDeprecatedButton,
GlFormSelect,
},
props: {
refetchVulnerabilities: {
type: Function,
required: true,
},
deselectAllVulnerabilities: {
type: Function,
required: true,
},
selectedVulnerabilities: {
type: Array,
required: true,
},
},
data: () => ({
dismissalReason: null,
}),
computed: {
...mapGetters('vulnerabilities', ['selectedVulnerabilitiesCount']),
selectedVulnerabilitiesCount() {
return this.selectedVulnerabilities.length;
},
canDismissVulnerability() {
return this.dismissalReason && this.selectedVulnerabilitiesCount > 0;
return Boolean(this.dismissalReason && this.selectedVulnerabilitiesCount > 0);
},
message() {
return n__(
......@@ -30,17 +48,40 @@ export default {
},
},
methods: {
...mapActions('vulnerabilities', ['dismissSelectedVulnerabilities']),
dismissalSuccessMessage() {
return n__(
'%d vulnerability dismissed',
'%d vulnerabilities dismissed',
this.selectedVulnerabilities.length,
);
},
handleDismiss() {
if (!this.canDismissVulnerability) {
return;
}
if (!this.canDismissVulnerability) return;
if (this.dismissalReason === REASON_NONE) {
this.dismissSelectedVulnerabilities();
} else {
this.dismissSelectedVulnerabilities({ comment: this.dismissalReason });
}
},
dismissSelectedVulnerabilities() {
const promises = this.selectedVulnerabilities.map(vulnerability =>
this.$apollo.mutate({
mutation: dismissVulnerability,
variables: { id: vulnerability.id, comment: this.dismissalReason },
}),
);
Promise.all(promises)
.then(() => {
toast(this.dismissalSuccessMessage());
this.deselectAllVulnerabilities();
})
.catch(() => {
createFlash(
s__('Security Reports|There was an error dismissing the vulnerabilities.'),
'alert',
);
})
.finally(() => {
this.refetchVulnerabilities();
});
},
},
dismissalReasons: [
......@@ -61,9 +102,9 @@ export default {
class="mx-3 w-auto"
:options="$options.dismissalReasons"
/>
<gl-deprecated-button type="submit" variant="close" :disabled="!canDismissVulnerability">{{
__('Dismiss Selected')
}}</gl-deprecated-button>
<gl-deprecated-button type="submit" variant="close" :disabled="!canDismissVulnerability">
{{ __('Dismiss Selected') }}
</gl-deprecated-button>
</form>
</div>
</template>
<script>
import { __, n__ } from '~/locale';
import { mapActions, mapGetters } from 'vuex';
import { GlDeprecatedButton, GlFormSelect } from '@gitlab/ui';
const REASON_NONE = __('[No reason]');
const REASON_WONT_FIX = __("Won't fix / Accept risk");
const REASON_FALSE_POSITIVE = __('False positive');
export default {
name: 'SelectionSummary',
components: {
GlDeprecatedButton,
GlFormSelect,
},
data: () => ({
dismissalReason: null,
}),
computed: {
...mapGetters('vulnerabilities', ['selectedVulnerabilitiesCount']),
canDismissVulnerability() {
return this.dismissalReason && this.selectedVulnerabilitiesCount > 0;
},
message() {
return n__(
'Dismiss %d selected vulnerability as',
'Dismiss %d selected vulnerabilities as',
this.selectedVulnerabilitiesCount,
);
},
},
methods: {
...mapActions('vulnerabilities', ['dismissSelectedVulnerabilities']),
handleDismiss() {
if (!this.canDismissVulnerability) {
return;
}
if (this.dismissalReason === REASON_NONE) {
this.dismissSelectedVulnerabilities();
} else {
this.dismissSelectedVulnerabilities({ comment: this.dismissalReason });
}
},
},
dismissalReasons: [
{ value: null, text: __('Select a reason') },
REASON_FALSE_POSITIVE,
REASON_WONT_FIX,
REASON_NONE,
],
};
</script>
<template>
<div class="card">
<form class="card-body d-flex align-items-center" @submit.prevent="handleDismiss">
<span>{{ message }}</span>
<gl-form-select
v-model="dismissalReason"
class="mx-3 w-auto"
:options="$options.dismissalReasons"
/>
<gl-deprecated-button type="submit" variant="close" :disabled="!canDismissVulnerability">{{
__('Dismiss Selected')
}}</gl-deprecated-button>
</form>
</div>
</template>
mutation ($id: ID!, $comment: String! ) {
dismissVulnerability(input: {id: $id, comment: $comment}) {
errors
}
}
import { mount } from '@vue/test-utils';
import SelectionSummary from 'ee//security_dashboard/components/selection_summary.vue';
describe('Selection Summary component', () => {
let wrapper;
const defaultData = {
dismissalReason: null,
};
const createComponent = ({ props = {}, data = defaultData }) => {
wrapper = mount(SelectionSummary, {
propsData: {
refetchVulnerabilities: jest.fn(),
deselectAllVulnerabilities: jest.fn(),
selectedVulnerabilities: [],
...props,
},
data: () => data,
});
};
beforeEach(() => {
createComponent({});
});
afterEach(() => {
wrapper.destroy();
});
describe('computed', () => {
describe('selectedVulnerabilitiesCount', () => {
it('returns the length if this.selectedVulnerabilities is empty', () => {
expect(wrapper.vm.selectedVulnerabilitiesCount).toBe(0);
});
it('returns the length if this.selectedVulnerabilities is not empty', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
expect(wrapper.vm.selectedVulnerabilitiesCount).toBe(1);
});
});
describe('canDismissVulnerability', () => {
it('returns true if there is a dismissal reason and a selectedVulnerabilitiesCount greater than zero', () => {
createComponent({
props: { selectedVulnerabilities: [{ id: 'id_0' }] },
data: { dismissalReason: 'Will Not Fix' },
});
expect(wrapper.vm.canDismissVulnerability).toBe(true);
});
it('returns false if there is a dismissal reason and not a selectedVulnerabilitiesCount greater than zero', () => {
createComponent({
props: { selectedVulnerabilities: [] },
data: { dismissalReason: 'Will Not Fix' },
});
expect(wrapper.vm.canDismissVulnerability).toBe(false);
});
it('returns false if there is not a dismissal reason and a selectedVulnerabilitiesCount greater than zero', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
expect(wrapper.vm.canDismissVulnerability).toBe(false);
});
it('returns false if there is not a dismissal reason and not a selectedVulnerabilitiesCount greater than zero', () => {
expect(wrapper.vm.canDismissVulnerability).toBe(false);
});
});
describe('message', () => {
it('returns the right message for zero selected vulnerabilities', () => {
expect(wrapper.vm.message).toBe('Dismiss 0 selected vulnerabilities as');
});
it('returns the right message for one selected vulnerabilities', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
expect(wrapper.vm.message).toBe('Dismiss 1 selected vulnerability as');
});
it('returns the right message for greater than one selected vulnerabilities', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] } });
expect(wrapper.vm.message).toBe('Dismiss 2 selected vulnerabilities as');
});
});
});
describe('methods', () => {
describe('getSuccessMessage', () => {
it('returns the right message for zero selected vulnerabilities', () => {
expect(wrapper.vm.dismissalSuccessMessage()).toBe('0 vulnerabilities dismissed');
});
it('returns the right message for one selected vulnerabilities', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
expect(wrapper.vm.dismissalSuccessMessage()).toBe('1 vulnerability dismissed');
});
it('returns the right message for greater than one selected vulnerabilities', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] } });
expect(wrapper.vm.dismissalSuccessMessage()).toBe('2 vulnerabilities dismissed');
});
});
describe('handleDismiss', () => {
it('does call dismissSelectedVulnerabilities when canDismissVulnerability is true', () => {
createComponent({
props: { selectedVulnerabilities: [{ id: 'id_0' }] },
data: { dismissalReason: 'Will Not Fix' },
});
const spy = jest.spyOn(wrapper.vm, 'dismissSelectedVulnerabilities').mockImplementation();
wrapper.vm.handleDismiss();
expect(spy).toHaveBeenCalled();
});
it('does not call dismissSelectedVulnerabilities when canDismissVulnerability is false', () => {
const spy = jest.spyOn(wrapper.vm, 'dismissSelectedVulnerabilities');
wrapper.vm.handleDismiss();
expect(spy).not.toHaveBeenCalled();
});
});
});
});
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