Commit f462aaad authored by David O'Regan's avatar David O'Regan

Merge branch 'djadmin-scanner-profile-composable' into 'master'

Make scanner profile form composable

See merge request gitlab-org/gitlab!56928
parents ae3776b8 01827b9f
...@@ -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"
...@@ -278,6 +271,7 @@ export default { ...@@ -278,6 +271,7 @@ export default {
<gl-form-group :label="s__('DastProfiles|Profile name')"> <gl-form-group :label="s__('DastProfiles|Profile name')">
<gl-form-input <gl-form-input
v-model="form.profileName.value" v-model="form.profileName.value"
name="profile_name"
class="mw-460" class="mw-460"
data-testid="profile-name-input" data-testid="profile-name-input"
type="text" type="text"
...@@ -311,6 +305,7 @@ export default { ...@@ -311,6 +305,7 @@ export default {
</template> </template>
<gl-form-input-group <gl-form-input-group
v-model.number="form.spiderTimeout.value" v-model.number="form.spiderTimeout.value"
name="spider_timeout"
class="mw-460" class="mw-460"
data-testid="spider-timeout-input" data-testid="spider-timeout-input"
type="number" type="number"
...@@ -338,6 +333,7 @@ export default { ...@@ -338,6 +333,7 @@ export default {
</template> </template>
<gl-form-input-group <gl-form-input-group
v-model.number="form.targetTimeout.value" v-model.number="form.targetTimeout.value"
name="target_timeout"
class="mw-460" class="mw-460"
data-testid="target-timeout-input" data-testid="target-timeout-input"
type="number" type="number"
......
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
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User sees Scanner profile' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let(:profile_form_path) {new_project_security_configuration_dast_scans_dast_scanner_profile_path(project)}
let(:profile_library_path) { project_security_configuration_dast_scans_path(project) }
before_all do
project.add_developer(user)
end
before do
sign_in(user)
end
context 'when feature is available' do
before do
stub_licensed_features(security_on_demand_scans: true)
visit(profile_form_path)
end
it 'shows the form' do
expect(page).to have_gitlab_http_status(:ok)
expect(page).to have_content("New scanner profile")
end
it 'on submit', :js do
fill_in_profile_form
expect(current_path).to eq(profile_library_path)
end
it 'on cancel', :js do
click_button 'Cancel'
expect(current_path).to eq(profile_library_path)
end
end
context 'when feature is not available' do
before do
visit(profile_form_path)
end
it 'renders a 404' do
expect(page).to have_gitlab_http_status(:not_found)
end
end
def fill_in_profile_form
fill_in 'profile_name', with: "hello"
fill_in 'spider_timeout', with: "1"
fill_in 'target_timeout', with: "2"
click_button 'Save profile'
wait_for_requests
end
end
...@@ -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();
}); });
}); });
}); });
......
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