Commit 16d095e2 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch...

Merge branch '241362-dast-scanner-profile-library-implementation-iteration-1-add-new-profile-link-frontend' into 'master'

DAST Scanner Profile Library: Add new-profile link

See merge request gitlab-org/gitlab!40469
parents 5ec10aa4 16e54231
<script> <script>
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { GlButton, GlTab, GlTabs } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlTab, GlTabs } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ProfilesList from './dast_profiles_list.vue'; import ProfilesList from './dast_profiles_list.vue';
import dastSiteProfilesQuery from '../graphql/dast_site_profiles.query.graphql'; import dastSiteProfilesQuery from '../graphql/dast_site_profiles.query.graphql';
import dastSiteProfilesDelete from '../graphql/dast_site_profiles_delete.mutation.graphql'; import dastSiteProfilesDelete from '../graphql/dast_site_profiles_delete.mutation.graphql';
import * as cacheUtils from '../graphql/cache_utils'; import * as cacheUtils from '../graphql/cache_utils';
import { getProfileSettings } from '../settings/profiles';
export default { export default {
components: { components: {
GlButton, GlDropdown,
GlDropdownItem,
GlTab, GlTab,
GlTabs, GlTabs,
ProfilesList, ProfilesList,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
newDastSiteProfilePath: { createNewProfilePaths: {
type: String, type: Object,
required: true, required: true,
validator: ({ scannerProfile, siteProfile }) =>
Boolean(scannerProfile) && Boolean(siteProfile),
}, },
projectFullPath: { projectFullPath: {
type: String, type: String,
...@@ -60,6 +66,16 @@ export default { ...@@ -60,6 +66,16 @@ export default {
}, },
}, },
computed: { computed: {
profileSettings() {
const { glFeatures, createNewProfilePaths } = this;
return getProfileSettings(
{
createNewProfilePaths,
},
glFeatures,
);
},
hasMoreSiteProfiles() { hasMoreSiteProfiles() {
return this.siteProfilesPageInfo.hasNextPage; return this.siteProfilesPageInfo.hasNextPage;
}, },
...@@ -152,6 +168,11 @@ export default { ...@@ -152,6 +168,11 @@ export default {
}, },
profilesPerPage: 10, profilesPerPage: 10,
i18n: { i18n: {
heading: s__('DastProfiles|Manage Profiles'),
newProfileDropdownLabel: s__('DastProfiles|New Profile'),
subHeading: s__(
'DastProfiles|Save commonly used configurations for target sites and scan specifications as profiles. Use these with an on-demand scan.',
),
errorMessages: { errorMessages: {
fetchNetworkError: s__( fetchNetworkError: s__(
'DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.', 'DastProfiles|Could not fetch site profiles. Please refresh the page, or try again later.',
...@@ -170,23 +191,25 @@ export default { ...@@ -170,23 +191,25 @@ export default {
<header> <header>
<div class="gl-display-flex gl-align-items-center gl-pt-6 gl-pb-4"> <div class="gl-display-flex gl-align-items-center gl-pt-6 gl-pb-4">
<h2 class="my-0"> <h2 class="my-0">
{{ s__('DastProfiles|Manage Profiles') }} {{ $options.i18n.heading }}
</h2> </h2>
<gl-button <gl-dropdown
:href="newDastSiteProfilePath" :text="$options.i18n.newProfileDropdownLabel"
category="primary"
variant="success" variant="success"
right
class="gl-ml-auto" class="gl-ml-auto"
> >
{{ s__('DastProfiles|New Site Profile') }} <gl-dropdown-item
</gl-button> v-for="{ i18n, createNewProfilePath, key } in profileSettings"
:key="key"
:href="createNewProfilePath"
>
{{ i18n.title }}
</gl-dropdown-item>
</gl-dropdown>
</div> </div>
<p> <p>
{{ {{ $options.i18n.subHeading }}
s__(
'DastProfiles|Save commonly used configurations for target sites and scan specifications as profiles. Use these with an on-demand scan.',
)
}}
</p> </p>
</header> </header>
......
...@@ -10,11 +10,14 @@ export default () => { ...@@ -10,11 +10,14 @@ export default () => {
} }
const { const {
dataset: { newDastSiteProfilePath, projectFullPath }, dataset: { newDastScannerProfilePath, newDastSiteProfilePath, projectFullPath },
} = el; } = el;
const props = { const props = {
newDastSiteProfilePath, createNewProfilePaths: {
scannerProfile: newDastScannerProfilePath,
siteProfile: newDastSiteProfilePath,
},
projectFullPath, projectFullPath,
}; };
......
import { s__ } from '~/locale';
const hasNoFeatureFlagOrIsEnabled = glFeatures => ([, { featureFlag }]) => {
if (!featureFlag) {
return true;
}
return Boolean(glFeatures[featureFlag]);
};
export const getProfileSettings = ({ createNewProfilePaths }, glFeatures) => {
const settings = {
siteProfiles: {
key: 'siteProfiles',
createNewProfilePath: createNewProfilePaths.siteProfile,
i18n: {
title: s__('DastProfiles|Site Profile'),
},
},
scannerProfiles: {
key: 'scannerProfiles',
createNewProfilePath: createNewProfilePaths.scannerProfile,
featureFlag: 'securityOnDemandScansScannerProfiles',
i18n: {
title: s__('DastProfiles|Scanner Profile'),
},
},
};
return Object.fromEntries(
Object.entries(settings).filter(hasNoFeatureFlagOrIsEnabled(glFeatures)),
);
};
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
module Projects module Projects
class DastProfilesController < Projects::ApplicationController class DastProfilesController < Projects::ApplicationController
before_action :authorize_read_on_demand_scans! before_action :authorize_read_on_demand_scans!
before_action do
push_frontend_feature_flag(:security_on_demand_scans_scanner_profiles, project, default_enabled: false)
end
def index def index
end end
......
...@@ -2,5 +2,6 @@ ...@@ -2,5 +2,6 @@
- breadcrumb_title s_('DastProfiles|Manage profiles') - breadcrumb_title s_('DastProfiles|Manage profiles')
- page_title s_('DastProfiles|Manage profiles') - page_title s_('DastProfiles|Manage profiles')
.js-dast-profiles{ data: { new_dast_site_profile_path: new_namespace_project_dast_site_profile_path(namespace_id: @project.namespace, project_id: @project.path), .js-dast-profiles{ data: { new_dast_site_profile_path: new_project_dast_site_profile_path(@project),
project_full_path: @project.path_with_namespace } } new_dast_scanner_profile_path: new_project_dast_scanner_profile_path(@project),
project_full_path: @project.path_with_namespace } }
---
title: 'DAST Scanner Profile Library: change new-profile button to dropdown'
merge_request: 40469
author:
type: changed
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import { within } from '@testing-library/dom'; import { within } from '@testing-library/dom';
import { merge } from 'lodash'; import { merge } from 'lodash';
import { GlDropdown } from '@gitlab/ui';
import DastProfiles from 'ee/dast_profiles/components/dast_profiles.vue'; import DastProfiles from 'ee/dast_profiles/components/dast_profiles.vue';
import DastProfilesList from 'ee/dast_profiles/components/dast_profiles_list.vue'; import DastProfilesList from 'ee/dast_profiles/components/dast_profiles_list.vue';
const TEST_NEW_DAST_SCANNER_PROFILE_PATH = '/-/on_demand_scans/scanner_profiles/new';
const TEST_NEW_DAST_SITE_PROFILE_PATH = '/-/on_demand_scans/site_profiles/new'; const TEST_NEW_DAST_SITE_PROFILE_PATH = '/-/on_demand_scans/site_profiles/new';
const TEST_PROJECT_FULL_PATH = '/namespace/project'; const TEST_PROJECT_FULL_PATH = '/namespace/project';
...@@ -12,7 +14,10 @@ describe('EE - DastProfiles', () => { ...@@ -12,7 +14,10 @@ describe('EE - DastProfiles', () => {
const createComponentFactory = (mountFn = shallowMount) => (options = {}) => { const createComponentFactory = (mountFn = shallowMount) => (options = {}) => {
const defaultProps = { const defaultProps = {
newDastSiteProfilePath: TEST_NEW_DAST_SITE_PROFILE_PATH, createNewProfilePaths: {
scannerProfile: TEST_NEW_DAST_SCANNER_PROFILE_PATH,
siteProfile: TEST_NEW_DAST_SITE_PROFILE_PATH,
},
projectFullPath: TEST_PROJECT_FULL_PATH, projectFullPath: TEST_PROJECT_FULL_PATH,
}; };
...@@ -43,28 +48,68 @@ describe('EE - DastProfiles', () => { ...@@ -43,28 +48,68 @@ describe('EE - DastProfiles', () => {
const createComponent = createComponentFactory(); const createComponent = createComponentFactory();
const createFullComponent = createComponentFactory(mount); const createFullComponent = createComponentFactory(mount);
const withFeatureFlag = (featureFlagName, { enabled, disabled }) => {
it.each([true, false])(`with ${featureFlagName} enabled: "%s"`, featureFlagStatus => {
createComponent({
provide: {
glFeatures: {
[featureFlagName]: featureFlagStatus,
},
},
});
if (featureFlagStatus) {
enabled();
} else {
disabled();
}
});
};
const withinComponent = () => within(wrapper.element); const withinComponent = () => within(wrapper.element);
const getSiteProfilesComponent = () => wrapper.find(DastProfilesList); const getSiteProfilesComponent = () => wrapper.find(DastProfilesList);
const getDropdownComponent = () => wrapper.find(GlDropdown);
const getSiteProfilesDropdownItem = text =>
within(getDropdownComponent().element).queryByText(text);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('header', () => { describe('header', () => {
beforeEach(() => { it('shows a heading that describes the purpose of the page', () => {
createFullComponent(); createFullComponent();
});
it('shows a heading that describes the purpose of the page', () => {
const heading = withinComponent().getByRole('heading', { name: /manage profiles/i }); const heading = withinComponent().getByRole('heading', { name: /manage profiles/i });
expect(heading).not.toBe(null); expect(heading).not.toBe(null);
}); });
it(`shows a "New Site Profile" anchor that links to ${TEST_NEW_DAST_SITE_PROFILE_PATH}`, () => { it('has a "New Profile" dropdown menu', () => {
const newProfileButton = withinComponent().getByRole('link', { name: /new site profile/i }); createComponent();
expect(getDropdownComponent().props('text')).toBe('New Profile');
});
it(`shows a "Site Profile" dropdown item that links to ${TEST_NEW_DAST_SITE_PROFILE_PATH}`, () => {
createComponent();
expect(newProfileButton.getAttribute('href')).toBe(TEST_NEW_DAST_SITE_PROFILE_PATH); expect(getSiteProfilesDropdownItem('Site Profile').getAttribute('href')).toBe(
TEST_NEW_DAST_SITE_PROFILE_PATH,
);
});
describe(`shows a "Scanner Profile" dropdown item that links to ${TEST_NEW_DAST_SCANNER_PROFILE_PATH}`, () => {
withFeatureFlag('securityOnDemandScansScannerProfiles', {
enabled: () => {
expect(getSiteProfilesDropdownItem('Scanner Profile').getAttribute('href')).toBe(
TEST_NEW_DAST_SCANNER_PROFILE_PATH,
);
},
disabled: () => {
expect(getSiteProfilesDropdownItem('Scanner Profile')).toBe(null);
},
});
}); });
}); });
......
...@@ -16,6 +16,10 @@ RSpec.describe "projects/dast_profiles/index", type: :view do ...@@ -16,6 +16,10 @@ RSpec.describe "projects/dast_profiles/index", type: :view do
expect(rendered).to include '/on_demand_scans/profiles/dast_site_profiles/new' expect(rendered).to include '/on_demand_scans/profiles/dast_site_profiles/new'
end end
it 'passes new dast scanner profile path' do
expect(rendered).to include '/on_demand_scans/profiles/dast_scanner_profiles/new'
end
it 'passes project\'s full path' do it 'passes project\'s full path' do
expect(rendered).to include @project.path_with_namespace expect(rendered).to include @project.path_with_namespace
end end
......
...@@ -7749,10 +7749,10 @@ msgstr "" ...@@ -7749,10 +7749,10 @@ msgstr ""
msgid "DastProfiles|Manage profiles" msgid "DastProfiles|Manage profiles"
msgstr "" msgstr ""
msgid "DastProfiles|New Scanner Profile" msgid "DastProfiles|New Profile"
msgstr "" msgstr ""
msgid "DastProfiles|New Site Profile" msgid "DastProfiles|New Scanner Profile"
msgstr "" msgstr ""
msgid "DastProfiles|New scanner profile" msgid "DastProfiles|New scanner profile"
...@@ -7776,6 +7776,12 @@ msgstr "" ...@@ -7776,6 +7776,12 @@ msgstr ""
msgid "DastProfiles|Save profile" msgid "DastProfiles|Save profile"
msgstr "" msgstr ""
msgid "DastProfiles|Scanner Profile"
msgstr ""
msgid "DastProfiles|Site Profile"
msgstr ""
msgid "DastProfiles|Site Profiles" msgid "DastProfiles|Site Profiles"
msgstr "" msgstr ""
......
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