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 {
} 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"
......@@ -278,6 +271,7 @@ export default {
<gl-form-group :label="s__('DastProfiles|Profile name')">
<gl-form-input
v-model="form.profileName.value"
name="profile_name"
class="mw-460"
data-testid="profile-name-input"
type="text"
......@@ -311,6 +305,7 @@ export default {
</template>
<gl-form-input-group
v-model.number="form.spiderTimeout.value"
name="spider_timeout"
class="mw-460"
data-testid="spider-timeout-input"
type="number"
......@@ -338,6 +333,7 @@ export default {
</template>
<gl-form-input-group
v-model.number="form.targetTimeout.value"
name="target_timeout"
class="mw-460"
data-testid="target-timeout-input"
type="number"
......
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
# 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
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();
});
});
});
......
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