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 {
GlFormRadioGroup,
} from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import * as Sentry from '~/sentry/wrapper';
import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql';
import dastScannerProfileUpdateMutation from '../graphql/dast_scanner_profile_update.mutation.graphql';
......@@ -51,6 +51,10 @@ export default {
type: String,
required: true,
},
onDemandScansPath: {
type: String,
required: true,
},
profile: {
type: Object,
required: false,
......@@ -81,6 +85,11 @@ export default {
initialFormValues: serializeFormObject(form),
loading: false,
showAlert: false,
returnToPreviousPage: returnToPreviousPageFactory({
onDemandScansPath: this.onDemandScansPath,
profilesLibraryPath: this.profilesLibraryPath,
urlParamKey: 'scanner_profile_id',
}),
};
},
spiderTimeoutRange: {
......@@ -189,6 +198,7 @@ export default {
({
data: {
[this.isEdit ? 'dastScannerProfileUpdate' : 'dastScannerProfileCreate']: {
id,
errors = [],
},
},
......@@ -197,7 +207,7 @@ export default {
this.showErrors(errors);
this.loading = false;
} else {
redirectTo(this.profilesLibraryPath);
this.returnToPreviousPage(id);
}
},
)
......@@ -215,7 +225,7 @@ export default {
}
},
discard() {
redirectTo(this.profilesLibraryPath);
this.returnToPreviousPage();
},
showErrors(errors = []) {
this.errors = errors;
......
......@@ -9,11 +9,12 @@ export default () => {
return false;
}
const { projectFullPath, profilesLibraryPath } = el.dataset;
const { projectFullPath, profilesLibraryPath, onDemandScansPath } = el.dataset;
const props = {
projectFullPath,
profilesLibraryPath,
onDemandScansPath,
};
if (el.dataset.scannerProfile) {
......
mutation dastScannerProfileCreate($input: DastScannerProfileCreateInput!) {
dastScannerProfileCreate(input: $input) {
id
errors
}
}
......@@ -10,9 +10,9 @@ import {
GlFormTextarea,
} from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils';
import { returnToPreviousPageFactory } from 'ee/security_configuration/dast_profiles/redirect';
import * as Sentry from '~/sentry/wrapper';
import { __, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { serializeFormObject } from '~/lib/utils/forms';
import validation from '~/vue_shared/directives/validation';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......@@ -45,6 +45,10 @@ export default {
type: String,
required: true,
},
onDemandScansPath: {
type: String,
required: true,
},
siteProfile: {
type: Object,
required: false,
......@@ -80,6 +84,11 @@ export default {
token: null,
errorMessage: '',
errors: [],
returnToPreviousPage: returnToPreviousPageFactory({
onDemandScansPath: this.onDemandScansPath,
profilesLibraryPath: this.profilesLibraryPath,
urlParamKey: 'site_profile_id',
}),
};
},
computed: {
......@@ -146,14 +155,17 @@ export default {
.then(
({
data: {
[this.isEdit ? 'dastSiteProfileUpdate' : 'dastSiteProfileCreate']: { errors = [] },
[this.isEdit ? 'dastSiteProfileUpdate' : 'dastSiteProfileCreate']: {
id,
errors = [],
},
},
}) => {
if (errors.length > 0) {
this.showErrors({ message: errorMessage, errors });
this.isLoading = false;
} else {
redirectTo(this.profilesLibraryPath);
this.returnToPreviousPage(id);
}
},
)
......@@ -171,7 +183,7 @@ export default {
}
},
discard() {
redirectTo(this.profilesLibraryPath);
this.returnToPreviousPage();
},
captureException(exception) {
Sentry.captureException(exception);
......
......@@ -9,11 +9,12 @@ export default () => {
return;
}
const { fullPath, profilesLibraryPath } = el.dataset;
const { fullPath, profilesLibraryPath, onDemandScansPath } = el.dataset;
const props = {
fullPath,
profilesLibraryPath,
onDemandScansPath,
};
if (el.dataset.siteProfile) {
......
......@@ -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,
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 }.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 @@
- page_title s_('DastProfiles|New scanner profile')
.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 @@
.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'),
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 @@
- page_title s_('DastProfiles|New site profile')
.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', () => ({
const projectFullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${projectFullPath}/-/security/configuration/dast_profiles`;
const onDemandScansPath = `${TEST_HOST}/${projectFullPath}/-/on_demand_scans`;
const defaultProfile = scannerProfiles[0];
const {
......@@ -30,6 +31,7 @@ const {
const defaultProps = {
profilesLibraryPath,
projectFullPath,
onDemandScansPath,
};
describe('DAST Scanner Profile', () => {
......
......@@ -20,6 +20,7 @@ localVue.use(VueApollo);
const [siteProfileOne] = siteProfiles;
const fullPath = 'group/project';
const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/dast_profiles`;
const onDemandScansPath = `${TEST_HOST}/${fullPath}/-/on_demand_scans`;
const profileName = 'My DAST site profile';
const targetUrl = 'http://example.com';
const excludedUrls = 'http://example.com/logout';
......@@ -28,6 +29,7 @@ const requestHeaders = 'my-new-header=something';
const defaultProps = {
profilesLibraryPath,
fullPath,
onDemandScansPath,
};
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