Commit 64888bff authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'djadmin-pipeline-dast-modal' into 'master'

Implement DAST Modal for Pipeline View

See merge request gitlab-org/gitlab!38267
parents 7a56b0ce 3d949510
<script> <script>
import { GlButton, GlCard, GlCollapse, GlCollapseToggleDirective, GlSprintf } from '@gitlab/ui'; import {
GlButton,
GlCard,
GlCollapse,
GlCollapseToggleDirective,
GlSprintf,
GlModalDirective,
} from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { getFormattedSummary } from '../helpers'; import { getFormattedSummary } from '../helpers';
import { COLLAPSE_SECURITY_REPORTS_SUMMARY_LOCAL_STORAGE_KEY as LOCAL_STORAGE_KEY } from '../constants'; import { COLLAPSE_SECURITY_REPORTS_SUMMARY_LOCAL_STORAGE_KEY as LOCAL_STORAGE_KEY } from '../constants';
import Modal from 'ee/vue_shared/security_reports/components/dast_modal.vue';
export default { export default {
name: 'SecurityReportsSummary', name: 'SecurityReportsSummary',
...@@ -12,9 +20,11 @@ export default { ...@@ -12,9 +20,11 @@ export default {
GlCard, GlCard,
GlCollapse, GlCollapse,
GlSprintf, GlSprintf,
Modal,
}, },
directives: { directives: {
collapseToggle: GlCollapseToggleDirective, collapseToggle: GlCollapseToggleDirective,
GlModal: GlModalDirective,
}, },
props: { props: {
summary: { summary: {
...@@ -54,6 +64,14 @@ export default { ...@@ -54,6 +64,14 @@ export default {
this.isVisible = !shouldHideSummaryDetails; this.isVisible = !shouldHideSummaryDetails;
} }
}, },
methods: {
hasScannedResources(scanSummary) {
return scanSummary.scannedResources?.nodes?.length > 0;
},
downloadLink(scanSummary) {
return scanSummary.scannedResourcesCsvPath || '';
},
},
}; };
</script> </script>
...@@ -86,10 +104,34 @@ export default { ...@@ -86,10 +104,34 @@ export default {
" "
/> />
<template v-if="scanSummary.scannedResourcesCount !== undefined"> <template v-if="scanSummary.scannedResourcesCount !== undefined">
<gl-button
v-if="hasScannedResources(scanSummary)"
v-gl-modal.dastUrl
variant="link"
data-testid="modal-button"
>
(<gl-sprintf
:message="
n__('%d URL scanned', '%d URLs scanned', scanSummary.scannedResourcesCount)
"
/>)
</gl-button>
<template v-else>
(<gl-sprintf (<gl-sprintf
:message="n__('%d URL scanned', '%d URLs scanned', scanSummary.scannedResourcesCount)" :message="
n__('%d URL scanned', '%d URLs scanned', scanSummary.scannedResourcesCount)
"
/>) />)
</template> </template>
<modal
v-if="hasScannedResources(scanSummary)"
:scanned-urls="scanSummary.scannedResources.nodes"
:scanned-resources-count="scanSummary.scannedResourcesCount"
:download-link="downloadLink(scanSummary)"
/>
</template>
</div> </div>
</div> </div>
</gl-collapse> </gl-collapse>
......
...@@ -5,6 +5,13 @@ query($fullPath: ID!, $pipelineIid: ID!) { ...@@ -5,6 +5,13 @@ query($fullPath: ID!, $pipelineIid: ID!) {
dast { dast {
vulnerabilitiesCount vulnerabilitiesCount
scannedResourcesCount scannedResourcesCount
scannedResourcesCsvPath
scannedResources {
nodes {
requestMethod
url
}
}
} }
sast { sast {
vulnerabilitiesCount vulnerabilitiesCount
......
...@@ -12,6 +12,7 @@ module EE ...@@ -12,6 +12,7 @@ module EE
def security def security
if pipeline.expose_security_dashboard? if pipeline.expose_security_dashboard?
push_frontend_feature_flag(:pipelines_security_report_summary, default_enabled: false)
render_show render_show
else else
redirect_to pipeline_path(pipeline) redirect_to pipeline_path(pipeline)
......
---
title: Implement DAST Modal for Pipeline View
merge_request: 38267
author:
type: added
...@@ -4,6 +4,7 @@ import { trimText } from 'helpers/text_helper'; ...@@ -4,6 +4,7 @@ import { trimText } from 'helpers/text_helper';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import SecurityReportsSummary from 'ee/security_dashboard/components/security_reports_summary.vue'; import SecurityReportsSummary from 'ee/security_dashboard/components/security_reports_summary.vue';
import Modal from 'ee/vue_shared/security_reports/components/dast_modal.vue';
describe('Security reports summary component', () => { describe('Security reports summary component', () => {
useLocalStorageSpy(); useLocalStorageSpy();
...@@ -19,12 +20,14 @@ describe('Security reports summary component', () => { ...@@ -19,12 +20,14 @@ describe('Security reports summary component', () => {
stubs: { stubs: {
GlSprintf, GlSprintf,
GlCard: { template: '<div><slot name="header" /><slot /></div>' }, GlCard: { template: '<div><slot name="header" /><slot /></div>' },
Modal,
}, },
...options, ...options,
}); });
}; };
const findToggleButton = () => wrapper.find('[data-testid="collapse-button"]'); const findToggleButton = () => wrapper.find('[data-testid="collapse-button"]');
const findModalButton = () => wrapper.find('[data-testid="modal-button"]');
beforeEach(() => { beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true); jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
...@@ -52,6 +55,7 @@ describe('Security reports summary component', () => { ...@@ -52,6 +55,7 @@ describe('Security reports summary component', () => {
}); });
expect(trimText(wrapper.text())).toContain(string); expect(trimText(wrapper.text())).toContain(string);
expect(findModalButton().exists()).toBe(false);
}); });
it.each` it.each`
...@@ -154,4 +158,54 @@ describe('Security reports summary component', () => { ...@@ -154,4 +158,54 @@ describe('Security reports summary component', () => {
expect(findToggleButton().exists()).toBe(false); expect(findToggleButton().exists()).toBe(false);
}); });
}); });
describe('with scanned resources', () => {
const glModalDirective = jest.fn();
const dastProps = {
vulnerabilitiesCount: 10,
scannedResourcesCount: 149,
scannedResources: {
nodes: [
{
requestMethod: 'GET',
url: 'https://weburl',
},
],
},
};
beforeEach(() => {
createWrapper({
directives: {
glModal: {
bind(el, { modifiers }) {
glModalDirective(modifiers);
},
},
},
propsData: {
summary: { dast: dastProps },
},
});
});
it('should have the modal with id dastUrl', () => {
const modal = wrapper.find(Modal);
expect(modal.exists()).toBe(true);
expect(modal.attributes('modalid')).toBe('dastUrl');
});
it('should contain a link with Scanned URLs count', () => {
expect(findModalButton().exists()).toBe(true);
expect(findModalButton().text()).toContain(
`(${dastProps.scannedResourcesCount} URLs scanned)`,
);
});
it('should link it to the given modal', () => {
expect(glModalDirective).toHaveBeenCalledWith({ dastUrl: true });
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment