Commit 776c9085 authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by David O'Regan

Improve UX for DAST profiles form

Redirect users to the previous page
upon DAST profile creation or updation
parent 332bfe8d
import {
redirectTo,
setUrlParams,
relativePathToAbsolute,
getBaseURL,
} from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export const returnToPreviousPageFactory = ({
onDemandScansPath,
profilesLibraryPath,
urlParamKey,
}) => (gid) => {
// when previous page is not On-demand scans page
// redirect user to profiles library page
if (!document.referrer?.includes(onDemandScansPath)) {
return redirectTo(profilesLibraryPath);
}
// Otherwise, redirect them back to On-demand scans page
// with corresponding profile id, if available
// for example, /on_demand_scans?site_profile_id=35
const previousPagePath = gid
? setUrlParams(
{ [urlParamKey]: getIdFromGraphQLId(gid) },
relativePathToAbsolute(onDemandScansPath, getBaseURL()),
)
: onDemandScansPath;
return redirectTo(previousPagePath);
};
...@@ -13,9 +13,9 @@ import { ...@@ -13,9 +13,9 @@ import {
GlFormRadioGroup, GlFormRadioGroup,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils'; import { initFormField } from 'ee/security_configuration/utils';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms'; import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql'; import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql';
import dastScannerProfileUpdateMutation from '../graphql/dast_scanner_profile_update.mutation.graphql'; import dastScannerProfileUpdateMutation from '../graphql/dast_scanner_profile_update.mutation.graphql';
...@@ -51,6 +51,10 @@ export default { ...@@ -51,6 +51,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
onDemandScansPath: {
type: String,
required: true,
},
profile: { profile: {
type: Object, type: Object,
required: false, required: false,
...@@ -81,6 +85,11 @@ export default { ...@@ -81,6 +85,11 @@ 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: {
...@@ -189,6 +198,7 @@ export default { ...@@ -189,6 +198,7 @@ export default {
({ ({
data: { data: {
[this.isEdit ? 'dastScannerProfileUpdate' : 'dastScannerProfileCreate']: { [this.isEdit ? 'dastScannerProfileUpdate' : 'dastScannerProfileCreate']: {
id,
errors = [], errors = [],
}, },
}, },
...@@ -197,7 +207,7 @@ export default { ...@@ -197,7 +207,7 @@ export default {
this.showErrors(errors); this.showErrors(errors);
this.loading = false; this.loading = false;
} else { } else {
redirectTo(this.profilesLibraryPath); this.returnToPreviousPage(id);
} }
}, },
) )
...@@ -215,7 +225,7 @@ export default { ...@@ -215,7 +225,7 @@ export default {
} }
}, },
discard() { discard() {
redirectTo(this.profilesLibraryPath); this.returnToPreviousPage();
}, },
showErrors(errors = []) { showErrors(errors = []) {
this.errors = errors; this.errors = errors;
......
...@@ -9,11 +9,12 @@ export default () => { ...@@ -9,11 +9,12 @@ export default () => {
return false; return false;
} }
const { projectFullPath, profilesLibraryPath } = el.dataset; const { projectFullPath, profilesLibraryPath, onDemandScansPath } = el.dataset;
const props = { const props = {
projectFullPath, projectFullPath,
profilesLibraryPath, profilesLibraryPath,
onDemandScansPath,
}; };
if (el.dataset.scannerProfile) { if (el.dataset.scannerProfile) {
......
mutation dastScannerProfileCreate($input: DastScannerProfileCreateInput!) { mutation dastScannerProfileCreate($input: DastScannerProfileCreateInput!) {
dastScannerProfileCreate(input: $input) { dastScannerProfileCreate(input: $input) {
id
errors errors
} }
} }
...@@ -10,9 +10,9 @@ import { ...@@ -10,9 +10,9 @@ import {
GlFormTextarea, GlFormTextarea,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils'; import { initFormField } from 'ee/security_configuration/utils';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import * as Sentry from '~/sentry/wrapper'; import * as Sentry from '~/sentry/wrapper';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { serializeFormObject } from '~/lib/utils/forms'; import { serializeFormObject } from '~/lib/utils/forms';
import validation from '~/vue_shared/directives/validation'; import validation from '~/vue_shared/directives/validation';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -45,6 +45,10 @@ export default { ...@@ -45,6 +45,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
onDemandScansPath: {
type: String,
required: true,
},
siteProfile: { siteProfile: {
type: Object, type: Object,
required: false, required: false,
...@@ -80,6 +84,11 @@ export default { ...@@ -80,6 +84,11 @@ export default {
token: null, token: null,
errorMessage: '', errorMessage: '',
errors: [], errors: [],
returnToPreviousPage: returnToPreviousPageFactory({
onDemandScansPath: this.onDemandScansPath,
profilesLibraryPath: this.profilesLibraryPath,
urlParamKey: 'site_profile_id',
}),
}; };
}, },
computed: { computed: {
...@@ -146,14 +155,17 @@ export default { ...@@ -146,14 +155,17 @@ export default {
.then( .then(
({ ({
data: { data: {
[this.isEdit ? 'dastSiteProfileUpdate' : 'dastSiteProfileCreate']: { errors = [] }, [this.isEdit ? 'dastSiteProfileUpdate' : 'dastSiteProfileCreate']: {
id,
errors = [],
},
}, },
}) => { }) => {
if (errors.length > 0) { if (errors.length > 0) {
this.showErrors({ message: errorMessage, errors }); this.showErrors({ message: errorMessage, errors });
this.isLoading = false; this.isLoading = false;
} else { } else {
redirectTo(this.profilesLibraryPath); this.returnToPreviousPage(id);
} }
}, },
) )
...@@ -171,7 +183,7 @@ export default { ...@@ -171,7 +183,7 @@ export default {
} }
}, },
discard() { discard() {
redirectTo(this.profilesLibraryPath); this.returnToPreviousPage();
}, },
captureException(exception) { captureException(exception) {
Sentry.captureException(exception); Sentry.captureException(exception);
......
...@@ -9,11 +9,12 @@ export default () => { ...@@ -9,11 +9,12 @@ export default () => {
return; return;
} }
const { fullPath, profilesLibraryPath } = el.dataset; const { fullPath, profilesLibraryPath, onDemandScansPath } = el.dataset;
const props = { const props = {
fullPath, fullPath,
profilesLibraryPath, profilesLibraryPath,
onDemandScansPath,
}; };
if (el.dataset.siteProfile) { if (el.dataset.siteProfile) {
......
...@@ -8,4 +8,5 @@ profiles_library_path: project_security_configuration_dast_profiles_path(@projec ...@@ -8,4 +8,5 @@ profiles_library_path: project_security_configuration_dast_profiles_path(@projec
scanner_profile: { id: @scanner_profile.to_global_id.to_s, name: @scanner_profile.name, scanner_profile: { id: @scanner_profile.to_global_id.to_s, 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 }.to_json } } show_debug_messages: @scanner_profile.show_debug_messages }.to_json,
on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
...@@ -4,4 +4,5 @@ ...@@ -4,4 +4,5 @@
- page_title s_('DastProfiles|New scanner profile') - page_title s_('DastProfiles|New scanner profile')
.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_profiles_path(@project, anchor: 'scanner-profiles') } } profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'scanner-profiles'),
on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
...@@ -5,4 +5,5 @@ ...@@ -5,4 +5,5 @@
.js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace, .js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace,
profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles'), profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles'),
site_profile: { id: @site_profile.to_global_id.to_s, name: @site_profile.name, target_url: @site_profile.dast_site.url }.to_json } } site_profile: { id: @site_profile.to_global_id.to_s, name: @site_profile.name, target_url: @site_profile.dast_site.url }.to_json,
on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
...@@ -4,4 +4,5 @@ ...@@ -4,4 +4,5 @@
- page_title s_('DastProfiles|New site profile') - page_title s_('DastProfiles|New site profile')
.js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace, .js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace,
profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles') } } profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles'),
on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
---
title: Redirect user to previous page after DAST profiles creation
merge_request: 51482
author:
type: changed
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import { TEST_HOST } from 'helpers/test_constants';
import * as urlUtility from '~/lib/utils/url_utility';
const fullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/dast_profiles`;
const onDemandScansPath = `${TEST_HOST}/${fullPath}/-/on_demand_scans`;
const urlParamKey = 'site_profile_id';
const originalReferrer = document.referrer;
const params = {
onDemandScansPath,
profilesLibraryPath,
urlParamKey,
};
const factory = (id) => returnToPreviousPageFactory(params)(id);
const setReferrer = (value = onDemandScansPath) => {
Object.defineProperty(document, 'referrer', {
value,
configurable: true,
});
};
const resetReferrer = () => {
setReferrer(originalReferrer);
};
describe('DAST Profiles redirector', () => {
describe('returnToPreviousPageFactory', () => {
beforeEach(() => {
jest.spyOn(urlUtility, 'redirectTo').mockImplementation();
});
it('default - redirects to profile library page', () => {
factory();
expect(urlUtility.redirectTo).toHaveBeenCalledWith(profilesLibraryPath);
});
describe('when a referrer is set', () => {
beforeEach(() => {
setReferrer();
});
afterEach(() => {
resetReferrer();
});
it('redirects to previous page', () => {
factory();
expect(urlUtility.redirectTo).toHaveBeenCalledWith(onDemandScansPath);
});
it('redirects to previous page with id', () => {
factory(2);
expect(urlUtility.redirectTo).toHaveBeenCalledWith(
`${onDemandScansPath}?site_profile_id=2`,
);
});
});
});
});
...@@ -16,6 +16,7 @@ jest.mock('~/lib/utils/url_utility', () => ({ ...@@ -16,6 +16,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
const projectFullPath = 'group/project'; const projectFullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_profiles`; const profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_profiles`;
const onDemandScansPath = `${TEST_HOST}/${projectFullPath}/-/on_demand_scans`;
const defaultProfile = scannerProfiles[0]; const defaultProfile = scannerProfiles[0];
const { const {
...@@ -30,6 +31,7 @@ const { ...@@ -30,6 +31,7 @@ const {
const defaultProps = { const defaultProps = {
profilesLibraryPath, profilesLibraryPath,
projectFullPath, projectFullPath,
onDemandScansPath,
}; };
describe('DAST Scanner Profile', () => { describe('DAST Scanner Profile', () => {
......
...@@ -20,6 +20,7 @@ localVue.use(VueApollo); ...@@ -20,6 +20,7 @@ localVue.use(VueApollo);
const [siteProfileOne] = siteProfiles; const [siteProfileOne] = siteProfiles;
const fullPath = 'group/project'; const fullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/dast_profiles`; const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/dast_profiles`;
const onDemandScansPath = `${TEST_HOST}/${fullPath}/-/on_demand_scans`;
const profileName = 'My DAST site profile'; const profileName = 'My DAST site profile';
const targetUrl = 'http://example.com'; const targetUrl = 'http://example.com';
const excludedUrls = 'http://example.com/logout'; const excludedUrls = 'http://example.com/logout';
...@@ -28,6 +29,7 @@ const requestHeaders = 'my-new-header=something'; ...@@ -28,6 +29,7 @@ const requestHeaders = 'my-new-header=something';
const defaultProps = { const defaultProps = {
profilesLibraryPath, profilesLibraryPath,
fullPath, fullPath,
onDemandScansPath,
}; };
const defaultRequestHandlers = { const defaultRequestHandlers = {
......
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