Commit 6c475235 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'leipert-decouple-security-reports-modal' into 'master'

Decouple Security Reports Modal from Store

See merge request gitlab-org/gitlab-ee!7834
parents b7f88d4b be06e700
<script> <script>
import { mapActions, mapState } from 'vuex'; import { s__ } from '~/locale';
import { s__ } from '~/locale'; import Modal from '~/vue_shared/components/gl_modal.vue';
import Modal from '~/vue_shared/components/gl_modal.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue'; import Icon from '~/vue_shared/components/icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import ExpandButton from '~/vue_shared/components/expand_button.vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
export default { export default {
components: { components: {
Modal, Modal,
LoadingButton, LoadingButton,
ExpandButton, ExpandButton,
Icon, Icon,
}, },
props: {
modal: {
type: Object,
required: true,
},
vulnerabilityFeedbackHelpPath: {
type: String,
required: false,
default: '',
},
canCreateIssuePermission: {
type: Boolean,
required: false,
default: false,
},
canCreateFeedbackPermission: {
type: Boolean,
required: false,
default: false,
},
},
computed: { computed: {
...mapState([
'modal',
'vulnerabilityFeedbackHelpPath',
'canCreateIssuePermission',
'canCreateFeedbackPermission',
]),
revertTitle() { revertTitle() {
return this.modal.vulnerability.isDismissed return this.modal.vulnerability.isDismissed
? s__('ciReport|Revert dismissal') ? s__('ciReport|Revert dismissal')
...@@ -27,6 +41,7 @@ ...@@ -27,6 +41,7 @@
}, },
hasDismissedBy() { hasDismissedBy() {
return ( return (
this.modal.vulnerability &&
this.modal.vulnerability.dismissalFeedback && this.modal.vulnerability.dismissalFeedback &&
this.modal.vulnerability.dismissalFeedback.pipeline && this.modal.vulnerability.dismissalFeedback.pipeline &&
this.modal.vulnerability.dismissalFeedback.author this.modal.vulnerability.dismissalFeedback.author
...@@ -43,12 +58,11 @@ ...@@ -43,12 +58,11 @@
}, },
}, },
methods: { methods: {
...mapActions(['dismissIssue', 'revertDismissIssue', 'createNewIssue']),
handleDismissClick() { handleDismissClick() {
if (this.modal.vulnerability.isDismissed) { if (this.modal.vulnerability.isDismissed) {
this.revertDismissIssue(); this.$emit('revertDismissIssue');
} else { } else {
this.dismissIssue(); this.$emit('dismissIssue');
} }
}, },
isLastValue(index, values) { isLastValue(index, values) {
...@@ -67,7 +81,7 @@ ...@@ -67,7 +81,7 @@
return key === 'links' && this.hasValue(field); return key === 'links' && this.hasValue(field);
}, },
}, },
}; };
</script> </script>
<template> <template>
<modal <modal
...@@ -196,6 +210,7 @@ ...@@ -196,6 +210,7 @@
>#{{ modal.vulnerability.dismissalFeedback.pipeline.id }}</a>. >#{{ modal.vulnerability.dismissalFeedback.pipeline.id }}</a>.
</template> </template>
<a <a
v-if="vulnerabilityFeedbackHelpPath"
:href="vulnerabilityFeedbackHelpPath" :href="vulnerabilityFeedbackHelpPath"
class="js-link-vulnerabilityFeedbackHelpPath" class="js-link-vulnerabilityFeedbackHelpPath"
> >
...@@ -245,7 +260,7 @@ ...@@ -245,7 +260,7 @@
:disabled="modal.isCreatingNewIssue" :disabled="modal.isCreatingNewIssue"
:label="__('Create issue')" :label="__('Create issue')"
container-class="js-create-issue-btn btn btn-success btn-inverted" container-class="js-create-issue-btn btn btn-success btn-inverted"
@click="createNewIssue" @click="$emit('createNewIssue')"
/> />
</template> </template>
</div> </div>
......
...@@ -120,7 +120,16 @@ export default { ...@@ -120,7 +120,16 @@ export default {
}, },
componentNames, componentNames,
computed: { computed: {
...mapState(['sast', 'sastContainer', 'dast', 'dependencyScanning', 'summaryCounts']), ...mapState([
'sast',
'sastContainer',
'dast',
'dependencyScanning',
'summaryCounts',
'modal',
'canCreateIssuePermission',
'canCreateFeedbackPermission',
]),
...mapGetters([ ...mapGetters([
'groupedSastText', 'groupedSastText',
'groupedSummaryText', 'groupedSummaryText',
...@@ -207,6 +216,9 @@ export default { ...@@ -207,6 +216,9 @@ export default {
'setPipelineId', 'setPipelineId',
'setCanCreateIssuePermission', 'setCanCreateIssuePermission',
'setCanCreateFeedbackPermission', 'setCanCreateFeedbackPermission',
'dismissIssue',
'revertDismissIssue',
'createNewIssue',
]), ]),
}, },
}; };
...@@ -311,7 +323,15 @@ export default { ...@@ -311,7 +323,15 @@ export default {
/> />
</template> </template>
<issue-modal /> <issue-modal
:modal="modal"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
:can-create-issue-permission="canCreateIssuePermission"
:can-create-feedback-permission="canCreateFeedbackPermission"
@createNewIssue="createNewIssue"
@dismissIssue="dismissIssue"
@revertDismissIssue="revertDismissIssue"
/>
</div> </div>
</report-section> </report-section>
</template> </template>
...@@ -92,7 +92,15 @@ export default { ...@@ -92,7 +92,15 @@ export default {
}, },
componentNames, componentNames,
computed: { computed: {
...mapState(['sast', 'dependencyScanning', 'sastContainer', 'dast']), ...mapState([
'sast',
'dependencyScanning',
'sastContainer',
'dast',
'modal',
'canCreateIssuePermission',
'canCreateFeedbackPermission',
]),
sastText() { sastText() {
return this.summaryTextBuilder(messages.SAST, this.sast.newIssues.length); return this.summaryTextBuilder(messages.SAST, this.sast.newIssues.length);
...@@ -188,6 +196,9 @@ export default { ...@@ -188,6 +196,9 @@ export default {
'setPipelineId', 'setPipelineId',
'setCanCreateIssuePermission', 'setCanCreateIssuePermission',
'setCanCreateFeedbackPermission', 'setCanCreateFeedbackPermission',
'dismissIssue',
'revertDismissIssue',
'createNewIssue',
]), ]),
summaryTextBuilder(reportType, issuesCount = 0) { summaryTextBuilder(reportType, issuesCount = 0) {
if (issuesCount === 0) { if (issuesCount === 0) {
...@@ -265,6 +276,14 @@ export default { ...@@ -265,6 +276,14 @@ export default {
class="js-dast-widget split-report-section" class="js-dast-widget split-report-section"
/> />
<issue-modal /> <issue-modal
:modal="modal"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
:can-create-issue-permission="canCreateIssuePermission"
:can-create-feedback-permission="canCreateFeedbackPermission"
@createNewIssue="createNewIssue"
@dismissIssue="dismissIssue"
@revertDismissIssue="revertDismissIssue"
/>
</div> </div>
</template> </template>
...@@ -36,7 +36,6 @@ export const RECEIVE_DEPENDENCY_SCANNING_ERROR = 'RECEIVE_DEPENDENCY_SCANNING_ER ...@@ -36,7 +36,6 @@ export const RECEIVE_DEPENDENCY_SCANNING_ERROR = 'RECEIVE_DEPENDENCY_SCANNING_ER
// Dismiss security issue // Dismiss security issue
export const SET_ISSUE_MODAL_DATA = 'SET_ISSUE_MODAL_DATA'; export const SET_ISSUE_MODAL_DATA = 'SET_ISSUE_MODAL_DATA';
export const CLEAR_ISSUE_MODAL_DATA = 'CLEAR_ISSUE_MODAL_DATA';
export const REQUEST_DISMISS_ISSUE = 'REQUEST_DISMISS_ISSUE'; export const REQUEST_DISMISS_ISSUE = 'REQUEST_DISMISS_ISSUE';
export const RECEIVE_DISMISS_ISSUE_SUCCESS = 'RECEIVE_DISMISS_ISSUE_SUCCESS'; export const RECEIVE_DISMISS_ISSUE_SUCCESS = 'RECEIVE_DISMISS_ISSUE_SUCCESS';
export const RECEIVE_DISMISS_ISSUE_ERROR = 'RECEIVE_DISMISS_ISSUE_ERROR'; export const RECEIVE_DISMISS_ISSUE_ERROR = 'RECEIVE_DISMISS_ISSUE_ERROR';
......
import Vue from 'vue'; import Vue from 'vue';
import component from 'ee/vue_shared/security_reports/components/modal.vue'; import component from 'ee/vue_shared/security_reports/components/modal.vue';
import state from 'ee/vue_shared/security_reports/store/state'; import createState from 'ee/vue_shared/security_reports/store/state';
import createStore from 'ee/vue_shared/security_reports/store'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('Security Reports modal', () => { describe('Security Reports modal', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
let vm; let vm;
const store = createStore();
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
vm.$store.replaceState(state());
});
beforeEach(() => {
store.dispatch('setVulnerabilityFeedbackPath', 'path');
store.dispatch('setVulnerabilityFeedbackHelpPath', 'feedbacksHelpPath');
store.dispatch('setPipelineId', 123);
}); });
describe('with permissions', () => { describe('with permissions', () => {
beforeEach(() => {
store.dispatch('setCanCreateIssuePermission', true);
store.dispatch('setCanCreateFeedbackPermission', true);
});
describe('with dismissed issue', () => { describe('with dismissed issue', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
tool: 'bundler_audit', canCreateFeedbackPermission: true,
message: 'Arbitrary file existence disclosure in Action Pack', };
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk', props.modal.vulnerability.isDismissed = true;
cve: 'CVE-2014-9999', props.modal.vulnerability.dismissalFeedback = {
file: 'Gemfile.lock', author: { username: 'jsmith' },
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', pipeline: { id: '123' },
title: 'Arbitrary file existence disclosure in Action Pack', };
path: 'Gemfile.lock', vm = mountComponent(Component, props);
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
}); });
it('renders dismissal author and associated pipeline', () => { it('renders dismissal author and associated pipeline', () => {
...@@ -75,36 +37,23 @@ describe('Security Reports modal', () => { ...@@ -75,36 +37,23 @@ describe('Security Reports modal', () => {
); );
}); });
it('calls revertDismissed when revert dismissal button is clicked', () => { it('emits revertDismissIssue when revert dismissal button is clicked', () => {
spyOn(vm, 'revertDismissIssue'); spyOn(vm, '$emit');
const button = vm.$el.querySelector('.js-dismiss-btn'); const button = vm.$el.querySelector('.js-dismiss-btn');
button.click(); button.click();
expect(vm.revertDismissIssue).toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('revertDismissIssue');
}); });
}); });
describe('with not dismissed isssue', () => { describe('with not dismissed issue', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
tool: 'bundler_audit', canCreateFeedbackPermission: true,
message: 'Arbitrary file existence disclosure in Action Pack', };
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk', vm = mountComponent(Component, props);
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
}); });
it('renders button to dismiss issue', () => { it('renders button to dismiss issue', () => {
...@@ -113,48 +62,69 @@ describe('Security Reports modal', () => { ...@@ -113,48 +62,69 @@ describe('Security Reports modal', () => {
); );
}); });
it('calls dismissIssue when dismiss issue button is clicked', () => { it('does not render create issue button', () => {
spyOn(vm, 'dismissIssue'); expect(vm.$el.querySelector('.js-create-issue-btn')).toBe(null);
});
it('renders create issue button and footer', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).not.toBe(null);
});
it('renders the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
});
it('emits dismissIssue when dismiss issue button is clicked', () => {
spyOn(vm, '$emit');
const button = vm.$el.querySelector('.js-dismiss-btn'); const button = vm.$el.querySelector('.js-dismiss-btn');
button.click(); button.click();
expect(vm.dismissIssue).toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('dismissIssue');
}); });
}); });
describe('with instances', () => { describe('with create issue', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
title: 'Absence of Anti-CSRF Tokens', canCreateIssuePermission: true,
riskcode: '1', };
riskdesc: 'Low (Medium)', vm = mountComponent(Component, props);
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>', });
pluginid: '123',
instances: [ it('does not render dismiss button', () => {
{ expect(vm.$el.querySelector('.js-dismiss-btn')).toBe(null);
uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc', });
method: 'GET',
evidence: it('renders create issue button', () => {
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>", expect(vm.$el.querySelector('.js-create-issue-btn')).not.toBe(null);
}, });
{
uri: 'http://192.168.32.236:3001/help/user/group/subgroups/index.md', it('renders the footer', () => {
method: 'GET', expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
evidence: });
"<form class='navbar-form' action='/search' accept-charset='UTF-8' method='get'>",
}, it('emits createIssue when create issue button is clicked', () => {
], spyOn(vm, '$emit');
description: ' No Anti-CSRF tokens were found in a HTML submission form. ',
solution: '', const button = vm.$el.querySelector('.js-create-issue-btn');
}, button.click();
status: 'failed',
}); expect(vm.$emit).toHaveBeenCalledWith('createNewIssue');
});
vm = mountComponentWithStore(Component, {
store,
}); });
describe('with instances', () => {
beforeEach(() => {
const props = {
modal: createState().modal,
};
props.modal.data.instances.value = [
{ uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc' },
{ uri: 'http://192.168.32.236:3001/help/user/group/subgroups/index.md' },
];
vm = mountComponent(Component, props);
}); });
it('renders instances list', () => { it('renders instances list', () => {
...@@ -169,30 +139,18 @@ describe('Security Reports modal', () => { ...@@ -169,30 +139,18 @@ describe('Security Reports modal', () => {
}); });
}); });
describe('data & create issue button', () => { describe('data', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
tool: 'bundler_audit', vulnerabilityFeedbackHelpPath: 'feedbacksHelpPath',
message: 'Arbitrary file existence disclosure in Action Pack', };
cve: 'CVE-2014-9999', props.modal.title = 'Arbitrary file existence disclosure in Action Pack';
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', props.modal.data.solution.value =
title: 'Arbitrary file existence disclosure in Action Pack', 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8';
path: 'Gemfile.lock', props.modal.data.file.value = 'Gemfile.lock';
urlPath: 'path/Gemfile.lock', props.modal.data.file.url = 'path/Gemfile.lock';
location: { vm = mountComponent(Component, props);
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
}); });
it('renders keys in `data`', () => { it('renders keys in `data`', () => {
...@@ -203,51 +161,25 @@ describe('Security Reports modal', () => { ...@@ -203,51 +161,25 @@ describe('Security Reports modal', () => {
}); });
it('renders link fields with link', () => { it('renders link fields with link', () => {
expect(vm.$el.querySelector('.js-link-file').getAttribute('href')).toEqual('path/Gemfile.lock'); expect(vm.$el.querySelector('.js-link-file').getAttribute('href')).toEqual(
'path/Gemfile.lock',
);
}); });
it('renders help link', () => { it('renders help link', () => {
expect(vm.$el.querySelector('.js-link-vulnerabilityFeedbackHelpPath').getAttribute('href')).toEqual('feedbacksHelpPath'); expect(
vm.$el.querySelector('.js-link-vulnerabilityFeedbackHelpPath').getAttribute('href'),
).toEqual('feedbacksHelpPath');
}); });
}); });
}); });
describe('without permissions', () => { describe('without permissions', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
tool: 'bundler_audit', };
message: 'Arbitrary file existence disclosure in Action Pack', vm = mountComponent(Component, props);
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
}); });
it('does not render action buttons', () => { it('does not render action buttons', () => {
...@@ -260,130 +192,13 @@ describe('Security Reports modal', () => { ...@@ -260,130 +192,13 @@ describe('Security Reports modal', () => {
}); });
}); });
describe('with permission to create issue', () => {
beforeEach(() => {
store.dispatch('setCanCreateIssuePermission', true);
store.dispatch('setModalData', {
issue: {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
});
it('does not render dismiss button', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).toBe(null);
});
it('renders create issue button', () => {
expect(vm.$el.querySelector('.js-create-issue-btn')).not.toBe(null);
});
it('renders the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
});
});
describe('with permission to dismiss issue', () => {
beforeEach(() => {
store.dispatch('setCanCreateFeedbackPermission', true);
store.dispatch('setModalData', {
issue: {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
dismissalFeedback: {
id: 1,
category: 'sast',
feedback_type: 'dismissal',
issue_id: null,
author: {
name: 'John Smith',
username: 'jsmith',
web_url: 'https;//gitlab.com/user1',
},
pipeline: {
id: 123,
path: '/jsmith/awesome-project/pipelines/123',
},
},
},
status: 'failed',
});
vm = mountComponentWithStore(Component, {
store,
});
});
it('does not render create issue button', () => {
expect(vm.$el.querySelector('.js-create-issue-btn')).toBe(null);
});
it('renders create issue button and footer', () => {
expect(vm.$el.querySelector('.js-dismiss-btn')).not.toBe(null);
});
it('renders the footer', () => {
expect(vm.$el.classList.contains('modal-hide-footer')).toEqual(false);
});
});
describe('with a resolved issue', () => { describe('with a resolved issue', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { const props = {
issue: { modal: createState().modal,
tool: 'bundler_audit', };
message: 'Arbitrary file existence disclosure in Action Pack', props.modal.isResolved = true;
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk', vm = mountComponent(Component, props);
cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
},
status: 'success',
});
vm = mountComponentWithStore(Component, {
store,
});
}); });
it('does not display the footer', () => { it('does not display the footer', () => {
......
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