Commit 01827b9f authored by Dheeraj Joshi's avatar Dheeraj Joshi

Make scanner profile form composable

- Refactor form to make it reusable
- Add showHeader Optional prop
- Remove submit button disabled state
parent 128aaba1
...@@ -13,7 +13,6 @@ import { ...@@ -13,7 +13,6 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import { initFormField } from 'ee/security_configuration/utils'; import { initFormField } from 'ee/security_configuration/utils';
import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms'; import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
...@@ -47,23 +46,20 @@ export default { ...@@ -47,23 +46,20 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
profilesLibraryPath: {
type: String,
required: true,
},
onDemandScansPath: {
type: String,
required: true,
},
profile: { profile: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
showHeader: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
const { const {
name = '', profileName = '',
spiderTimeout = '', spiderTimeout = '',
targetTimeout = '', targetTimeout = '',
scanType = SCAN_TYPE.PASSIVE, scanType = SCAN_TYPE.PASSIVE,
...@@ -72,7 +68,7 @@ export default { ...@@ -72,7 +68,7 @@ export default {
} = this.profile; } = this.profile;
const form = { const form = {
profileName: initFormField({ value: name }), profileName: initFormField({ value: profileName }),
spiderTimeout: initFormField({ value: spiderTimeout }), spiderTimeout: initFormField({ value: spiderTimeout }),
targetTimeout: initFormField({ value: targetTimeout }), targetTimeout: initFormField({ value: targetTimeout }),
scanType: initFormField({ value: scanType }), scanType: initFormField({ value: scanType }),
...@@ -85,11 +81,6 @@ export default { ...@@ -85,11 +81,6 @@ export default {
initialFormValues: serializeFormObject(form), initialFormValues: serializeFormObject(form),
loading: false, loading: false,
showAlert: false, showAlert: false,
returnToPreviousPage: returnToPreviousPageFactory({
onDemandScansPath: this.onDemandScansPath,
profilesLibraryPath: this.profilesLibraryPath,
urlParamKey: 'scanner_profile_id',
}),
}; };
}, },
spiderTimeoutRange: { spiderTimeoutRange: {
...@@ -150,7 +141,7 @@ export default { ...@@ -150,7 +141,7 @@ export default {
); );
}, },
isSubmitDisabled() { isSubmitDisabled() {
return this.formHasErrors || this.requiredFieldEmpty || this.isPolicyProfile; return this.isPolicyProfile;
}, },
isPolicyProfile() { isPolicyProfile() {
return Boolean(this.profile?.referencedInSecurityPolicies?.length); return Boolean(this.profile?.referencedInSecurityPolicies?.length);
...@@ -210,7 +201,9 @@ export default { ...@@ -210,7 +201,9 @@ export default {
this.showErrors(errors); this.showErrors(errors);
this.loading = false; this.loading = false;
} else { } else {
this.returnToPreviousPage(id); this.$emit('success', {
id,
});
} }
}, },
) )
...@@ -228,7 +221,7 @@ export default { ...@@ -228,7 +221,7 @@ export default {
} }
}, },
discard() { discard() {
this.returnToPreviousPage(); this.$emit('cancel');
}, },
showErrors(errors = []) { showErrors(errors = []) {
this.errors = errors; this.errors = errors;
...@@ -245,7 +238,7 @@ export default { ...@@ -245,7 +238,7 @@ export default {
<template> <template>
<gl-form @submit.prevent="onSubmit"> <gl-form @submit.prevent="onSubmit">
<h2 class="gl-mb-6">{{ i18n.title }}</h2> <h2 v-if="showHeader" class="gl-mb-6">{{ i18n.title }}</h2>
<gl-alert <gl-alert
v-if="isPolicyProfile" v-if="isPolicyProfile"
......
import Vue from 'vue'; import Vue from 'vue';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DastScannerProfileForm from './components/dast_scanner_profile_form.vue'; import DastScannerProfileForm from './components/dast_scanner_profile_form.vue';
import apolloProvider from './graphql/provider'; import apolloProvider from './graphql/provider';
...@@ -13,20 +14,30 @@ export default () => { ...@@ -13,20 +14,30 @@ export default () => {
const props = { const props = {
projectFullPath, projectFullPath,
profilesLibraryPath,
onDemandScansPath,
}; };
if (el.dataset.scannerProfile) { if (el.dataset.scannerProfile) {
props.profile = convertObjectPropsToCamelCase(JSON.parse(el.dataset.scannerProfile)); props.profile = convertObjectPropsToCamelCase(JSON.parse(el.dataset.scannerProfile));
} }
const returnToPreviousPage = ({ id } = {}) => {
returnToPreviousPageFactory({
onDemandScansPath,
profilesLibraryPath,
urlParamKey: 'scanner_profile_id',
})(id);
};
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
render(h) { render(h) {
return h(DastScannerProfileForm, { return h(DastScannerProfileForm, {
props, props,
on: {
success: returnToPreviousPage,
cancel: returnToPreviousPage,
},
}); });
}, },
}); });
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.js-dast-scanner-profile-form{ data: { project_full_path: @project.path_with_namespace, .js-dast-scanner-profile-form{ data: { project_full_path: @project.path_with_namespace,
profiles_library_path: project_security_configuration_dast_scans_path(@project, anchor: 'scanner-profiles'), profiles_library_path: project_security_configuration_dast_scans_path(@project, anchor: 'scanner-profiles'),
scanner_profile: { id: @scanner_profile.to_global_id.to_s, name: @scanner_profile.name, scanner_profile: { id: @scanner_profile.to_global_id.to_s, profile_name: @scanner_profile.name,
spider_timeout: @scanner_profile.spider_timeout, target_timeout: @scanner_profile.target_timeout, spider_timeout: @scanner_profile.spider_timeout, target_timeout: @scanner_profile.target_timeout,
scan_type: @scanner_profile.scan_type.upcase, use_ajax_spider: @scanner_profile.use_ajax_spider, scan_type: @scanner_profile.scan_type.upcase, use_ajax_spider: @scanner_profile.use_ajax_spider,
show_debug_messages: @scanner_profile.show_debug_messages, referenced_in_security_policies: @scanner_profile.referenced_in_security_policies }.to_json, show_debug_messages: @scanner_profile.show_debug_messages, referenced_in_security_policies: @scanner_profile.referenced_in_security_policies }.to_json,
......
---
title: Always display submit button for DAST Scanner Profile form
merge_request: 56928
author:
type: changed
...@@ -8,11 +8,6 @@ import dastScannerProfileCreateMutation from 'ee/security_configuration/dast_sca ...@@ -8,11 +8,6 @@ import dastScannerProfileCreateMutation from 'ee/security_configuration/dast_sca
import dastScannerProfileUpdateMutation from 'ee/security_configuration/dast_scanner_profiles/graphql/dast_scanner_profile_update.mutation.graphql'; import dastScannerProfileUpdateMutation from 'ee/security_configuration/dast_scanner_profiles/graphql/dast_scanner_profile_update.mutation.graphql';
import { scannerProfiles, policyScannerProfile } from 'ee_jest/on_demand_scans/mocks/mock_data'; import { scannerProfiles, policyScannerProfile } from 'ee_jest/on_demand_scans/mocks/mock_data';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { redirectTo } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
}));
const projectFullPath = 'group/project'; const projectFullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_scans`; const profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_scans`;
...@@ -81,6 +76,17 @@ describe('DAST Scanner Profile', () => { ...@@ -81,6 +76,17 @@ describe('DAST Scanner Profile', () => {
it('form renders properly', () => { it('form renders properly', () => {
createComponent(); createComponent();
expect(findForm().exists()).toBe(true); expect(findForm().exists()).toBe(true);
expect(findForm().text()).toContain('New scanner profile');
});
it('when show header is disabled', () => {
createComponent({
propsData: {
...defaultProps,
showHeader: false,
},
});
expect(findForm().text()).not.toContain('New scanner profile');
}); });
describe('submit button', () => { describe('submit button', () => {
...@@ -88,18 +94,18 @@ describe('DAST Scanner Profile', () => { ...@@ -88,18 +94,18 @@ describe('DAST Scanner Profile', () => {
createComponent(); createComponent();
}); });
describe('is disabled if', () => { describe('is enabled even if', () => {
it('form contains errors', async () => { it('form contains errors', async () => {
findProfileNameInput().vm.$emit('input', profileName); findProfileNameInput().vm.$emit('input', profileName);
await findSpiderTimeoutInput().vm.$emit('input', '12312'); await findSpiderTimeoutInput().vm.$emit('input', '12312');
expect(findSubmitButton().props('disabled')).toBe(true); expect(findSubmitButton().props('disabled')).toBe(false);
}); });
it('at least one field is empty', async () => { it('at least one field is empty', async () => {
findProfileNameInput().vm.$emit('input', ''); findProfileNameInput().vm.$emit('input', '');
await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout); await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
await findTargetTimeoutInput().vm.$emit('input', targetTimeout); await findTargetTimeoutInput().vm.$emit('input', targetTimeout);
expect(findSubmitButton().props('disabled')).toBe(true); expect(findSubmitButton().props('disabled')).toBe(false);
}); });
}); });
...@@ -159,7 +165,7 @@ describe('DAST Scanner Profile', () => { ...@@ -159,7 +165,7 @@ describe('DAST Scanner Profile', () => {
}); });
it('populates the fields with the data passed in via the profile prop or default values', () => { it('populates the fields with the data passed in via the profile prop or default values', () => {
expect(findProfileNameInput().element.value).toBe(profile?.name ?? ''); expect(findProfileNameInput().element.value).toBe(profile?.profileName ?? '');
expect(findScanType().vm.$attrs.checked).toBe(profile?.scanType ?? SCAN_TYPE.PASSIVE); expect(findScanType().vm.$attrs.checked).toBe(profile?.scanType ?? SCAN_TYPE.PASSIVE);
}); });
...@@ -199,8 +205,10 @@ describe('DAST Scanner Profile', () => { ...@@ -199,8 +205,10 @@ describe('DAST Scanner Profile', () => {
}); });
}); });
it('redirects to the profiles library', () => { it('emits success event with correct params', () => {
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); expect(wrapper.emitted('success')).toBeTruthy();
expect(wrapper.emitted('success')).toHaveLength(1);
expect(wrapper.emitted('success')[0]).toStrictEqual([{ id: 30203 }]);
}); });
it('does not show an alert', () => { it('does not show an alert', () => {
...@@ -258,14 +266,14 @@ describe('DAST Scanner Profile', () => { ...@@ -258,14 +266,14 @@ describe('DAST Scanner Profile', () => {
createFullComponent(); createFullComponent();
}); });
describe('form empty', () => { describe('when form is empty', () => {
it('redirects to the profiles library', () => { it('emits cancel event', () => {
findCancelButton().vm.$emit('click'); findCancelButton().vm.$emit('click');
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); expect(wrapper.emitted('cancel')).toBeTruthy();
}); });
}); });
describe('form not empty', () => { describe('when form is not empty', () => {
beforeEach(() => { beforeEach(() => {
findProfileNameInput().setValue(profileName); findProfileNameInput().setValue(profileName);
}); });
...@@ -276,9 +284,9 @@ describe('DAST Scanner Profile', () => { ...@@ -276,9 +284,9 @@ describe('DAST Scanner Profile', () => {
expect(findCancelModal().vm.show).toHaveBeenCalled(); expect(findCancelModal().vm.show).toHaveBeenCalled();
}); });
it('redirects to the profiles library if confirmed', () => { it('emits cancel event', () => {
findCancelModal().vm.$emit('ok'); findCancelModal().vm.$emit('ok');
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath); expect(wrapper.emitted('cancel')).toBeTruthy();
}); });
}); });
}); });
......
import { screen, within } from '@testing-library/dom';
import initBundler from 'ee/security_configuration/dast_scanner_profiles/dast_scanner_profiles_bundle';
import { waitForText } from 'helpers/wait_for_text';
import { mockIssueLink } from '../test_helpers/mock_data/vulnerabilities_mock_data';
// import { mockVulnerability } from './mock_data';
describe('Scanner Profile', () => {
let vm;
let container;
const createComponent = () => {
setFixtures('<div class="js-dast-scanner-profile-form"></div>');
const el = document.querySelector('.js-dast-scanner-profile-form');
const elDataSet = {
profilesLibraryPath: 'group/project',
projectFullPath: '/security/configuration/a',
onDemandScansPath: '/security/configuration/b',
};
Object.assign(el.dataset, {
...elDataSet,
});
container.appendChild(el);
return initBundler(el);
};
beforeEach(() => {
vm = createComponent();
});
afterEach(() => {
vm.$destroy();
vm = null;
container = null;
});
it("displays the vulnerability's status", () => {
const headerBody = screen.getByTestId('vulnerability-detail-body');
expect(within(headerBody).getByText(mockVulnerability.state)).toBeInstanceOf(HTMLElement);
});
it("displays the vulnerability's severity", () => {
const severitySection = screen.getByTestId('severity');
const severityValue = within(severitySection).getByTestId('value');
expect(severityValue.textContent.toLowerCase()).toContain(
mockVulnerability.severity.toLowerCase(),
);
});
it("displays a heading containing the vulnerability's title", () => {
expect(screen.getByRole('heading', { name: mockVulnerability.title })).toBeInstanceOf(
HTMLElement,
);
});
it("displays the vulnerability's description", () => {
expect(screen.getByText(mockVulnerability.description)).toBeInstanceOf(HTMLElement);
});
it('displays related issues', async () => {
const relatedIssueTitle = await waitForText(mockIssueLink.title);
expect(relatedIssueTitle).toBeInstanceOf(HTMLElement);
});
});
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