Commit 33114d58 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch 'djadmin-refactor-profile-summaries' into 'master'

Move DAST profile summaries into separate components

See merge request gitlab-org/gitlab!61589
parents de7dabeb 39227882
......@@ -14,14 +14,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import {
SCAN_TYPE_LABEL,
SCAN_TYPE,
} from 'ee/security_configuration/dast_scanner_profiles/constants';
import {
EXCLUDED_URLS_SEPARATOR,
TARGET_TYPES,
} from 'ee/security_configuration/dast_site_profiles_form/constants';
import { SCAN_TYPE } from 'ee/security_configuration/dast_scanner_profiles/constants';
import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_validation/constants';
import { initFormField } from 'ee/security_configuration/utils';
import { convertToGraphQLId } from '~/graphql_shared/utils';
......@@ -32,7 +25,6 @@ import RefSelector from '~/ref/components/ref_selector.vue';
import { REF_TYPE_BRANCHES } from '~/ref/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import validation from '~/vue_shared/directives/validation';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import dastProfileCreateMutation from '../graphql/dast_profile_create.mutation.graphql';
import dastProfileUpdateMutation from '../graphql/dast_profile_update.mutation.graphql';
import {
......@@ -47,7 +39,6 @@ import {
} from '../settings';
import ScannerProfileSelector from './profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from './profile_selector/site_profile_selector.vue';
import ProfileSelectorSummaryCell from './profile_selector/summary_cell.vue';
export const ON_DEMAND_SCANS_STORAGE_KEY = 'on-demand-scans-new-form';
......@@ -72,13 +63,11 @@ const createProfilesApolloOptions = (name, field, { fetchQuery, fetchError }) =>
});
export default {
SCAN_TYPE_LABEL,
enabledRefTypes: [REF_TYPE_BRANCHES],
saveAndRunScanBtnId: 'scan-submit-button',
saveScanBtnId: 'scan-save-button',
components: {
RefSelector,
ProfileSelectorSummaryCell,
ScannerProfileSelector,
SiteProfileSelector,
GlAlert,
......@@ -98,7 +87,6 @@ export default {
GlTooltip: GlTooltipDirective,
validation: validation(),
},
mixins: [glFeatureFlagsMixin()],
apollo: {
scannerProfiles: createProfilesApolloOptions(
'scannerProfiles',
......@@ -234,12 +222,6 @@ export default {
selectedBranch,
};
},
hasExcludedUrls() {
return this.selectedSiteProfile.excludedUrls?.length > 0;
},
targetTypeValue() {
return TARGET_TYPES[this.selectedSiteProfile.targetType].text;
},
storageKey() {
return `${this.projectPath}/${ON_DEMAND_SCANS_STORAGE_KEY}`;
},
......@@ -333,7 +315,6 @@ export default {
this.selectedScannerProfileId = this.selectedScannerProfileId ?? selectedScannerProfileId;
},
},
EXCLUDED_URLS_SEPARATOR,
};
</script>
......@@ -460,103 +441,16 @@ export default {
v-model="selectedScannerProfileId"
class="gl-mb-5"
:profiles="scannerProfiles"
>
<template v-if="selectedScannerProfile" #summary>
<div class="row">
<profile-selector-summary-cell
:class="{ 'gl-text-red-500': hasProfilesConflict }"
:label="s__('DastProfiles|Scan mode')"
:value="$options.SCAN_TYPE_LABEL[selectedScannerProfile.scanType]"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|Spider timeout')"
:value="n__('%d minute', '%d minutes', selectedScannerProfile.spiderTimeout || 0)"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Target timeout')"
:value="n__('%d second', '%d seconds', selectedScannerProfile.targetTimeout || 0)"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|AJAX spider')"
:value="selectedScannerProfile.useAjaxSpider ? __('On') : __('Off')"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Debug messages')"
:value="
selectedScannerProfile.showDebugMessages
? s__('DastProfiles|Show debug messages')
: s__('DastProfiles|Hide debug messages')
"
/>
</div>
</template>
</scanner-profile-selector>
:selected-profile="selectedScannerProfile"
:has-conflict="hasProfilesConflict"
/>
<site-profile-selector
v-model="selectedSiteProfileId"
class="gl-mb-5"
:profiles="siteProfiles"
>
<template v-if="selectedSiteProfile" #summary>
<div class="row">
<profile-selector-summary-cell
:class="{ 'gl-text-red-500': hasProfilesConflict }"
:label="s__('DastProfiles|Target URL')"
:value="selectedSiteProfile.targetUrl"
/>
<profile-selector-summary-cell
v-if="glFeatures.securityDastSiteProfilesApiOption"
:label="s__('DastProfiles|Site type')"
:value="targetTypeValue"
/>
</div>
<template v-if="glFeatures.securityDastSiteProfilesAdditionalFields">
<template v-if="selectedSiteProfile.auth.enabled">
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|Authentication URL')"
:value="selectedSiteProfile.auth.url"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|Username')"
:value="selectedSiteProfile.auth.username"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Password')"
value="••••••••"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|Username form field')"
:value="selectedSiteProfile.auth.usernameField"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Password form field')"
:value="selectedSiteProfile.auth.passwordField"
/>
</div>
</template>
<div class="row">
<profile-selector-summary-cell
v-if="hasExcludedUrls"
:label="s__('DastProfiles|Excluded URLs')"
:value="selectedSiteProfile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)"
/>
<profile-selector-summary-cell
v-if="selectedSiteProfile.requestHeaders"
:label="s__('DastProfiles|Request headers')"
:value="__('[Redacted]')"
/>
</div>
</template>
</template>
</site-profile-selector>
:selected-profile="selectedSiteProfile"
:has-conflict="hasProfilesConflict"
/>
<gl-alert
v-if="hasProfilesConflict"
......
<script>
import { SCAN_TYPE_LABEL } from 'ee/security_configuration/dast_scanner_profiles/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProfileSelector from './profile_selector.vue';
import ScannerProfileSummary from './scanner_profile_summary.vue';
export default {
name: 'OnDemandScansScannerProfileSelector',
components: {
ProfileSelector,
ScannerProfileSummary,
},
mixins: [glFeatureFlagsMixin()],
inject: {
scannerProfilesLibraryPath: {
default: '',
......@@ -23,6 +23,16 @@ export default {
required: false,
default: () => [],
},
selectedProfile: {
type: Object,
required: false,
default: null,
},
hasConflict: {
type: Boolean,
required: false,
default: null,
},
},
computed: {
formattedProfiles() {
......@@ -56,7 +66,11 @@ export default {
<template #new-profile>{{ s__('OnDemandScans|Create new scanner profile') }}</template>
<template #manage-profile>{{ s__('OnDemandScans|Manage scanner profiles') }}</template>
<template #summary>
<slot name="summary"></slot>
<scanner-profile-summary
v-if="selectedProfile"
:profile="selectedProfile"
:has-conflict="hasConflict"
/>
</template>
</profile-selector>
</template>
<script>
import { SCAN_TYPE_LABEL } from 'ee/security_configuration/dast_scanner_profiles/constants';
import ProfileSelectorSummaryCell from './summary_cell.vue';
export default {
name: 'DastScannerProfileSummary',
components: {
ProfileSelectorSummaryCell,
},
props: {
profile: {
type: Object,
required: true,
},
hasConflict: {
type: Boolean,
required: false,
default: false,
},
},
SCAN_TYPE_LABEL,
};
</script>
<template>
<div>
<div class="row">
<profile-selector-summary-cell
:class="{ 'gl-text-red-500': hasConflict }"
:label="s__('DastProfiles|Scan mode')"
:value="$options.SCAN_TYPE_LABEL[profile.scanType]"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|Spider timeout')"
:value="n__('%d minute', '%d minutes', profile.spiderTimeout || 0)"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Target timeout')"
:value="n__('%d second', '%d seconds', profile.targetTimeout || 0)"
/>
</div>
<div class="row">
<profile-selector-summary-cell
:label="s__('DastProfiles|AJAX spider')"
:value="profile.useAjaxSpider ? __('On') : __('Off')"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Debug messages')"
:value="
profile.showDebugMessages
? s__('DastProfiles|Show debug messages')
: s__('DastProfiles|Hide debug messages')
"
/>
</div>
</div>
</template>
......@@ -3,11 +3,13 @@ import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProfileSelector from './profile_selector.vue';
import SiteProfileSummary from './site_profile_summary.vue';
export default {
name: 'OnDemandScansSiteProfileSelector',
components: {
ProfileSelector,
SiteProfileSummary,
},
mixins: [glFeatureFlagsMixin()],
inject: {
......@@ -24,6 +26,16 @@ export default {
required: false,
default: () => [],
},
selectedProfile: {
type: Object,
required: false,
default: null,
},
hasConflict: {
type: Boolean,
required: false,
default: null,
},
},
computed: {
formattedProfiles() {
......@@ -61,7 +73,11 @@ export default {
<template #new-profile>{{ s__('OnDemandScans|Create new site profile') }}</template>
<template #manage-profile>{{ s__('OnDemandScans|Manage site profiles') }}</template>
<template #summary>
<slot name="summary"></slot>
<site-profile-summary
v-if="selectedProfile"
:profile="selectedProfile"
:has-conflict="hasConflict"
/>
</template>
</profile-selector>
</template>
<script>
import {
EXCLUDED_URLS_SEPARATOR,
TARGET_TYPES,
} from 'ee/security_configuration/dast_site_profiles_form/constants';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProfileSelectorSummaryCell from './summary_cell.vue';
export default {
name: 'DastSiteProfileSummary',
i18n: {
targetUrl: s__('DastProfiles|Target URL'),
targetType: s__('DastProfiles|Site type'),
authUrl: s__('DastProfiles|Authentication URL'),
username: s__('DastProfiles|Username'),
password: s__('DastProfiles|Password'),
usernameField: s__('DastProfiles|Username form field'),
passwordField: s__('DastProfiles|Password form field'),
excludedUrls: s__('DastProfiles|Excluded URLs'),
requestHeaders: s__('DastProfiles|Request headers'),
},
components: {
ProfileSelectorSummaryCell,
},
mixins: [glFeatureFlagsMixin()],
props: {
profile: {
type: Object,
required: true,
},
hasConflict: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
hasExcludedUrls() {
return this.profile.excludedUrls?.length > 0;
},
targetTypeValue() {
return TARGET_TYPES[this.profile.targetType].text;
},
},
EXCLUDED_URLS_SEPARATOR,
};
</script>
<template>
<div>
<div class="row">
<profile-selector-summary-cell
:class="{ 'gl-text-red-500': hasConflict }"
:label="$options.i18n.targetUrl"
:value="profile.targetUrl"
/>
<profile-selector-summary-cell
v-if="glFeatures.securityDastSiteProfilesApiOption"
:label="$options.i18n.targetType"
:value="targetTypeValue"
/>
</div>
<template v-if="glFeatures.securityDastSiteProfilesAdditionalFields">
<template v-if="profile.auth.enabled">
<div class="row">
<profile-selector-summary-cell :label="$options.i18n.authUrl" :value="profile.auth.url" />
</div>
<div class="row">
<profile-selector-summary-cell
:label="$options.i18n.username"
:value="profile.auth.username"
/>
<profile-selector-summary-cell :label="$options.i18n.password" value="••••••••" />
</div>
<div class="row">
<profile-selector-summary-cell
:label="$options.i18n.usernameField"
:value="profile.auth.usernameField"
/>
<profile-selector-summary-cell
:label="$options.i18n.passwordField"
:value="profile.auth.passwordField"
/>
</div>
</template>
<div class="row">
<profile-selector-summary-cell
v-if="hasExcludedUrls"
:label="$options.i18n.excludedUrls"
:value="profile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)"
/>
<profile-selector-summary-cell
v-if="profile.requestHeaders"
:label="$options.i18n.requestHeaders"
:value="__('[Redacted]')"
/>
</div>
</template>
</div>
</template>
......@@ -534,6 +534,8 @@ describe('OnDemandScansForm', () => {
});
describe('scanner profile summary', () => {
const [{ id }] = scannerProfiles;
beforeEach(() => {
createComponent({
provide: {
......@@ -544,6 +546,12 @@ describe('OnDemandScansForm', () => {
});
});
it('renders profile summary when a valid profile is selected', async () => {
await selectScannerProfile({ id });
expect(findProfileSummary().exists()).toBe(true);
});
it('does not render the summary provided an invalid profile ID', async () => {
await selectScannerProfile({ id: 'gid://gitlab/DastScannerProfile/123' });
......@@ -552,7 +560,7 @@ describe('OnDemandScansForm', () => {
});
describe('site profile summary', () => {
const [authEnabledProfile] = siteProfiles;
const [{ id }] = siteProfiles;
beforeEach(() => {
createComponent({
......@@ -565,22 +573,10 @@ describe('OnDemandScansForm', () => {
});
});
it('renders all fields correctly', async () => {
await selectSiteProfile(authEnabledProfile);
const summary = wrapper.find(SiteProfileSelector).text();
const defaultPassword = '••••••••';
const defaultRequestHeaders = '[Redacted]';
const defaultSiteType = 'Website';
expect(summary).toMatch(authEnabledProfile.targetUrl);
expect(summary).toMatch(authEnabledProfile.excludedUrls.join(','));
expect(summary).toMatch(authEnabledProfile.auth.url);
expect(summary).toMatch(authEnabledProfile.auth.username);
expect(summary).toMatch(authEnabledProfile.auth.usernameField);
expect(summary).toMatch(authEnabledProfile.auth.passwordField);
expect(summary).toMatch(defaultPassword);
expect(summary).toMatch(defaultRequestHeaders);
expect(summary).toMatch(defaultSiteType);
it('renders profile summary when a valid profile is selected', async () => {
await selectSiteProfile({ id });
expect(findProfileSummary().exists()).toBe(true);
});
it('does not render the summary provided an invalid profile ID', async () => {
......
......@@ -311,9 +311,6 @@ exports[`OnDemandScansScannerProfileSelector renders properly with profiles 1`]
<!---->
</a>
<div>
Scanner profile #1's summary
</div>
</div>
<!---->
<!---->
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DastScannerProfileSummary renders properly 1`] = `
<div>
<div
class="row"
>
<profile-selector-summary-cell-stub
class=""
label="Scan mode"
value="Passive"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="Spider timeout"
value="5 minutes"
/>
<profile-selector-summary-cell-stub
label="Target timeout"
value="10 seconds"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="AJAX spider"
value="Off"
/>
<profile-selector-summary-cell-stub
label="Debug messages"
value="Hide debug messages"
/>
</div>
</div>
`;
......@@ -311,9 +311,6 @@ exports[`OnDemandScansSiteProfileSelector renders properly with profiles 1`] = `
<!---->
</a>
<div>
Site profile #1's summary
</div>
</div>
<!---->
<!---->
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DastSiteProfileSummary renders properly 1`] = `
<div>
<div
class="row"
>
<profile-selector-summary-cell-stub
class=""
label="Target URL"
value="https://foo.com"
/>
<profile-selector-summary-cell-stub
label="Site type"
value="Website"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="Authentication URL"
value="https://foo.com/login"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="Username"
value="admin"
/>
<profile-selector-summary-cell-stub
label="Password"
value="••••••••"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="Username form field"
value="username"
/>
<profile-selector-summary-cell-stub
label="Password form field"
value="password"
/>
</div>
<div
class="row"
>
<profile-selector-summary-cell-stub
label="Excluded URLs"
value="https://foo.com/logout,https://foo.com/send_mail"
/>
<profile-selector-summary-cell-stub
label="Request headers"
value="[Redacted]"
/>
</div>
</div>
`;
......@@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
import ProfileSelector from 'ee/on_demand_scans/components/profile_selector/profile_selector.vue';
import OnDemandScansScannerProfileSelector from 'ee/on_demand_scans/components/profile_selector/scanner_profile_selector.vue';
import ScannerProfileSummary from 'ee/on_demand_scans/components/profile_selector/scanner_profile_summary.vue';
import { scannerProfiles } from '../../mocks/mock_data';
const TEST_LIBRARY_PATH = '/test/scanner/profiles/library/path';
......@@ -61,6 +62,16 @@ describe('OnDemandScansScannerProfileSelector', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('render summary component ', () => {
const selectedProfile = profiles[0];
createComponent({
propsData: { profiles, value: selectedProfile.id, selectedProfile },
});
expect(wrapper.findComponent(ScannerProfileSummary).exists()).toBe(true);
});
it('sets listeners on profile selector component', () => {
const inputHandler = jest.fn();
createComponent({
......
import { shallowMount } from '@vue/test-utils';
import App from 'ee/on_demand_scans/components/profile_selector/scanner_profile_summary';
import { scannerProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
const [profile] = scannerProfiles;
describe('DastScannerProfileSummary', () => {
let wrapper;
const createWrapper = (props = {}) => {
wrapper = shallowMount(App, {
propsData: {
profile,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders properly', () => {
createWrapper();
expect(wrapper.element).toMatchSnapshot();
});
});
......@@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
import ProfileSelector from 'ee/on_demand_scans/components/profile_selector/profile_selector.vue';
import OnDemandScansSiteProfileSelector from 'ee/on_demand_scans/components/profile_selector/site_profile_selector.vue';
import SiteProfileSummary from 'ee/on_demand_scans/components/profile_selector/site_profile_summary.vue';
import { siteProfiles } from '../../mocks/mock_data';
const TEST_LIBRARY_PATH = '/test/site/profiles/library/path';
......@@ -68,6 +69,26 @@ describe('OnDemandScansSiteProfileSelector', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe('profile summary', () => {
it('is rendered when a profile is selected', () => {
const selectedProfile = profiles[0];
createComponent({
propsData: { profiles, value: selectedProfile.id, selectedProfile },
});
expect(wrapper.findComponent(SiteProfileSummary).exists()).toBe(true);
});
it('is not rendered when no profile is selected', () => {
createComponent({
propsData: { profiles, selectedProfile: null },
});
expect(wrapper.findComponent(SiteProfileSummary).exists()).toBe(false);
});
});
it('sets listeners on profile selector component', () => {
const inputHandler = jest.fn();
createComponent({
......
import { shallowMount } from '@vue/test-utils';
import App from 'ee/on_demand_scans/components/profile_selector/site_profile_summary';
import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
const [profile] = siteProfiles;
describe('DastSiteProfileSummary', () => {
let wrapper;
const createWrapper = (props = {}) => {
wrapper = shallowMount(App, {
propsData: {
profile,
...props,
},
provide: {
glFeatures: {
securityDastSiteProfilesAdditionalFields: true,
securityDastSiteProfilesApiOption: true,
},
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders properly', () => {
createWrapper();
expect(wrapper.element).toMatchSnapshot();
});
});
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