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 {
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { isEqual } from 'lodash';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import { initFormField } from 'ee/security_configuration/utils';
import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
import { __, s__ } from '~/locale';
......@@ -47,23 +46,20 @@ export default {
type: String,
required: true,
},
profilesLibraryPath: {
type: String,
required: true,
},
onDemandScansPath: {
type: String,
required: true,
},
profile: {
type: Object,
required: false,
default: () => ({}),
},
showHeader: {
type: Boolean,
required: false,
default: true,
},
},
data() {
const {
name = '',
profileName = '',
spiderTimeout = '',
targetTimeout = '',
scanType = SCAN_TYPE.PASSIVE,
......@@ -72,7 +68,7 @@ export default {
} = this.profile;
const form = {
profileName: initFormField({ value: name }),
profileName: initFormField({ value: profileName }),
spiderTimeout: initFormField({ value: spiderTimeout }),
targetTimeout: initFormField({ value: targetTimeout }),
scanType: initFormField({ value: scanType }),
......@@ -85,11 +81,6 @@ export default {
initialFormValues: serializeFormObject(form),
loading: false,
showAlert: false,
returnToPreviousPage: returnToPreviousPageFactory({
onDemandScansPath: this.onDemandScansPath,
profilesLibraryPath: this.profilesLibraryPath,
urlParamKey: 'scanner_profile_id',
}),
};
},
spiderTimeoutRange: {
......@@ -150,7 +141,7 @@ export default {
);
},
isSubmitDisabled() {
return this.formHasErrors || this.requiredFieldEmpty || this.isPolicyProfile;
return this.isPolicyProfile;
},
isPolicyProfile() {
return Boolean(this.profile?.referencedInSecurityPolicies?.length);
......@@ -210,7 +201,9 @@ export default {
this.showErrors(errors);
this.loading = false;
} else {
this.returnToPreviousPage(id);
this.$emit('success', {
id,
});
}
},
)
......@@ -228,7 +221,7 @@ export default {
}
},
discard() {
this.returnToPreviousPage();
this.$emit('cancel');
},
showErrors(errors = []) {
this.errors = errors;
......@@ -245,7 +238,7 @@ export default {
<template>
<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
v-if="isPolicyProfile"
......
import Vue from 'vue';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DastScannerProfileForm from './components/dast_scanner_profile_form.vue';
import apolloProvider from './graphql/provider';
......@@ -13,20 +14,30 @@ export default () => {
const props = {
projectFullPath,
profilesLibraryPath,
onDemandScansPath,
};
if (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({
el,
apolloProvider,
render(h) {
return h(DastScannerProfileForm, {
props,
on: {
success: returnToPreviousPage,
cancel: returnToPreviousPage,
},
});
},
});
......
......@@ -5,7 +5,7 @@
.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'),
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,
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,
......
---
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
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 { 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 profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_scans`;
......@@ -81,6 +76,17 @@ describe('DAST Scanner Profile', () => {
it('form renders properly', () => {
createComponent();
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', () => {
......@@ -88,18 +94,18 @@ describe('DAST Scanner Profile', () => {
createComponent();
});
describe('is disabled if', () => {
describe('is enabled even if', () => {
it('form contains errors', async () => {
findProfileNameInput().vm.$emit('input', profileName);
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 () => {
findProfileNameInput().vm.$emit('input', '');
await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
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', () => {
});
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);
});
......@@ -199,8 +205,10 @@ describe('DAST Scanner Profile', () => {
});
});
it('redirects to the profiles library', () => {
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
it('emits success event with correct params', () => {
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', () => {
......@@ -258,14 +266,14 @@ describe('DAST Scanner Profile', () => {
createFullComponent();
});
describe('form empty', () => {
it('redirects to the profiles library', () => {
describe('when form is empty', () => {
it('emits cancel event', () => {
findCancelButton().vm.$emit('click');
expect(redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
expect(wrapper.emitted('cancel')).toBeTruthy();
});
});
describe('form not empty', () => {
describe('when form is not empty', () => {
beforeEach(() => {
findProfileNameInput().setValue(profileName);
});
......@@ -276,9 +284,9 @@ describe('DAST Scanner Profile', () => {
expect(findCancelModal().vm.show).toHaveBeenCalled();
});
it('redirects to the profiles library if confirmed', () => {
it('emits cancel event', () => {
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