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

Merge branch 'djadmin-dast-redirect-profiles' into 'master'

Improve UX for DAST Profile forms

See merge request gitlab-org/gitlab!51482
parents 92feabd8 776c9085
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