Commit 928e2216 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'djadmin-dast-profiles-ui-updates' into 'master'

Update DAST Profiles library UI

See merge request gitlab-org/gitlab!50576
parents 618f5977 d7f60c6e
...@@ -7,7 +7,11 @@ import { ...@@ -7,7 +7,11 @@ import {
GlSkeletonLoader, GlSkeletonLoader,
GlTable, GlTable,
GlTooltipDirective, GlTooltipDirective,
GlDropdown,
GlDropdownItem,
GlIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
export default { export default {
components: { components: {
...@@ -16,6 +20,9 @@ export default { ...@@ -16,6 +20,9 @@ export default {
GlModal, GlModal,
GlSkeletonLoader, GlSkeletonLoader,
GlTable, GlTable,
GlDropdown,
GlDropdownItem,
GlIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -88,8 +95,16 @@ export default { ...@@ -88,8 +95,16 @@ export default {
}, },
tableFields() { tableFields() {
const defaultClasses = ['gl-word-break-all']; const defaultClasses = ['gl-word-break-all'];
const dataFields = this.fields.map((key) => ({ key, class: defaultClasses })); const defaultThClasses = ['gl-bg-transparent!', 'gl-text-black-normal'];
const staticFields = [{ key: 'actions' }];
const dataFields = this.fields.map(({ key, label }) => ({
key,
label,
class: defaultClasses,
thClass: defaultThClasses,
}));
const staticFields = [{ key: 'actions', label: '', thClass: defaultThClasses }];
return [...dataFields, ...staticFields]; return [...dataFields, ...staticFields];
}, },
...@@ -105,6 +120,9 @@ export default { ...@@ -105,6 +120,9 @@ export default {
handleCancel() { handleCancel() {
this.toBeDeletedProfileId = null; this.toBeDeletedProfileId = null;
}, },
navigateToProfile({ editPath }) {
return visitUrl(editPath);
},
}, },
}; };
</script> </script>
...@@ -117,7 +135,11 @@ export default { ...@@ -117,7 +135,11 @@ export default {
:fields="tableFields" :fields="tableFields"
:items="profiles" :items="profiles"
stacked="md" stacked="md"
thead-class="gl-display-none" fixed
hover
thead-class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-pt-3!"
tbody-tr-class="gl-hover-cursor-pointer gl-hover-bg-blue-50!"
@row-clicked="navigateToProfile"
> >
<template v-if="hasError" #top-row> <template v-if="hasError" #top-row>
<td :colspan="tableFields.length"> <td :colspan="tableFields.length">
...@@ -135,7 +157,9 @@ export default { ...@@ -135,7 +157,9 @@ export default {
</template> </template>
<template #cell(profileName)="{ value }"> <template #cell(profileName)="{ value }">
<strong>{{ value }}</strong> <div class="gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis">
{{ value }}
</div>
</template> </template>
<template v-for="slotName in Object.keys($scopedSlots)" #[slotName]="slotScope"> <template v-for="slotName in Object.keys($scopedSlots)" #[slotName]="slotScope">
...@@ -146,21 +170,48 @@ export default { ...@@ -146,21 +170,48 @@ export default {
<div class="gl-text-right"> <div class="gl-text-right">
<slot name="actions" :profile="item"></slot> <slot name="actions" :profile="item"></slot>
<gl-dropdown
class="gl-display-none gl-display-sm-inline-flex!"
toggle-class="gl-border-0! gl-shadow-none!"
no-caret
right
category="tertiary"
>
<template #button-content>
<gl-icon name="ellipsis_v" />
<span class="gl-sr-only">{{ __('Actions') }}</span>
</template>
<gl-dropdown-item
v-if="item.editPath"
:href="item.editPath"
:title="s__('DastProfiles|Edit profile')"
>{{ __('Edit') }}</gl-dropdown-item
>
<gl-dropdown-item
variant="danger"
:title="s__('DastProfiles|Delete profile')"
@click="prepareProfileDeletion(item.id)"
>
{{ __('Delete') }}
</gl-dropdown-item>
</gl-dropdown>
<gl-button <gl-button
v-if="item.editPath" v-if="item.editPath"
:href="item.editPath" :href="item.editPath"
class="gl-ml-3 gl-my-1" category="tertiary"
class="gl-ml-3 gl-my-1 gl-display-sm-none"
size="small" size="small"
>{{ __('Edit') }}</gl-button >{{ __('Edit') }}</gl-button
> >
<gl-button <gl-button
v-gl-tooltip.hover.focus v-gl-tooltip.hover.focus
category="tertiary"
icon="remove" icon="remove"
variant="danger" variant="danger"
category="secondary"
size="small" size="small"
class="gl-mx-3 gl-my-1" class="gl-mx-3 gl-my-1 gl-display-sm-none"
:title="s__('DastProfiles|Delete profile')" :title="s__('DastProfiles|Delete profile')"
@click="prepareProfileDeletion(item.id)" @click="prepareProfileDeletion(item.id)"
/> />
......
<script> <script>
import { GlBadge } from '@gitlab/ui';
import {
SCAN_TYPE,
SCAN_TYPE_LABEL,
} from 'ee/security_configuration/dast_scanner_profiles/constants';
import ProfilesList from './dast_profiles_list.vue'; import ProfilesList from './dast_profiles_list.vue';
export default { export default {
components: { components: {
ProfilesList, ProfilesList,
GlBadge,
}, },
SCAN_TYPE,
SCAN_TYPE_LABEL,
}; };
</script> </script>
<template> <template>
<profiles-list v-bind="$attrs" v-on="$listeners" /> <profiles-list v-bind="$attrs" v-on="$listeners">
<template #cell(scanType)="{ value }">
<gl-badge size="sm" :variant="value === $options.SCAN_TYPE.ACTIVE ? 'warning' : 'neutral'">
{{ $options.SCAN_TYPE_LABEL[value].toLowerCase() }}
</gl-badge>
</template>
</profiles-list>
</template> </template>
<script> <script>
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlIcon, GlTooltipDirective, GlBadge, GlLink } from '@gitlab/ui';
import { import {
DAST_SITE_VALIDATION_STATUS, DAST_SITE_VALIDATION_STATUS,
DAST_SITE_VALIDATION_STATUS_PROPS, DAST_SITE_VALIDATION_STATUS_PROPS,
...@@ -9,6 +9,7 @@ import DastSiteValidationModal from 'ee/security_configuration/dast_site_validat ...@@ -9,6 +9,7 @@ import DastSiteValidationModal from 'ee/security_configuration/dast_site_validat
import dastSiteValidationsQuery from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validations.query.graphql'; import dastSiteValidationsQuery from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validations.query.graphql';
import { updateSiteProfilesStatuses } from '../graphql/cache_utils'; import { updateSiteProfilesStatuses } from '../graphql/cache_utils';
import ProfilesList from './dast_profiles_list.vue'; import ProfilesList from './dast_profiles_list.vue';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
...@@ -18,6 +19,8 @@ export default { ...@@ -18,6 +19,8 @@ export default {
components: { components: {
GlButton, GlButton,
GlIcon, GlIcon,
GlBadge,
GlLink,
DastSiteValidationModal, DastSiteValidationModal,
ProfilesList, ProfilesList,
}, },
...@@ -80,10 +83,7 @@ export default { ...@@ -80,10 +83,7 @@ export default {
computed: { computed: {
urlsPendingValidation() { urlsPendingValidation() {
return this.profiles.reduce((acc, { validationStatus, normalizedTargetUrl }) => { return this.profiles.reduce((acc, { validationStatus, normalizedTargetUrl }) => {
if ( if (this.isPendingValidation(validationStatus) && !acc.includes(normalizedTargetUrl)) {
[PENDING, INPROGRESS].includes(validationStatus) &&
!acc.includes(normalizedTargetUrl)
) {
return [...acc, normalizedTargetUrl]; return [...acc, normalizedTargetUrl];
} }
return acc; return acc;
...@@ -91,11 +91,16 @@ export default { ...@@ -91,11 +91,16 @@ export default {
}, },
}, },
methods: { methods: {
shouldShowValidationBtn(status) { isPendingValidation(status) {
return ( return [PENDING, INPROGRESS].includes(status);
this.glFeatures.securityOnDemandScansSiteValidation && },
(status === NONE || status === FAILED) shouldShowValidateBtn(status) {
); return [NONE, FAILED].includes(status);
},
validateBtnLabel(status) {
return status === FAILED
? s__('DastSiteValidation|Retry validation')
: s__('DastSiteValidation|Validate');
}, },
shouldShowValidationStatus(status) { shouldShowValidationStatus(status) {
return this.glFeatures.securityOnDemandScansSiteValidation && status !== NONE; return this.glFeatures.securityOnDemandScansSiteValidation && status !== NONE;
...@@ -122,28 +127,39 @@ export default { ...@@ -122,28 +127,39 @@ export default {
</script> </script>
<template> <template>
<profiles-list :full-path="fullPath" :profiles="profiles" v-bind="$attrs" v-on="$listeners"> <profiles-list :full-path="fullPath" :profiles="profiles" v-bind="$attrs" v-on="$listeners">
<template #head(validationStatus)="{ label }">
{{ label }}
<gl-link
href="https://docs.gitlab.com/ee/user/application_security/dast/#site-profile-validation"
target="_blank"
class="gl-text-gray-300 gl-ml-2"
>
<gl-icon name="question-o" />
</gl-link>
</template>
<template #cell(validationStatus)="{ value }"> <template #cell(validationStatus)="{ value }">
<template v-if="shouldShowValidationStatus(value)"> <template v-if="shouldShowValidationStatus(value)">
<span :class="$options.statuses[value].cssClass"> <gl-badge
{{ $options.statuses[value].label }}
</span>
<gl-icon
v-gl-tooltip v-gl-tooltip
name="question-o" size="sm"
class="gl-vertical-align-text-bottom gl-text-gray-300 gl-ml-2" :variant="$options.statuses[value].badgeVariant"
:title="$options.statuses[value].tooltipText" :title="$options.statuses[value].tooltipText"
/> >
<gl-icon :size="12" class="gl-mr-2" :name="$options.statuses[value].badgeIcon" />
{{ $options.statuses[value].label }}</gl-badge
>
</template> </template>
</template> </template>
<template #actions="{ profile }"> <template #actions="{ profile }">
<gl-button <gl-button
v-if="shouldShowValidationBtn(profile.validationStatus)" v-if="glFeatures.securityOnDemandScansSiteValidation"
:disabled="!shouldShowValidateBtn(profile.validationStatus)"
variant="info" variant="info"
category="secondary" category="tertiary"
size="small" size="small"
@click="setValidatingProfile(profile)" @click="setValidatingProfile(profile)"
>{{ s__('DastSiteValidation|Validate target site') }}</gl-button >{{ validateBtnLabel(profile.validationStatus) }}</gl-button
> >
</template> </template>
......
...@@ -22,7 +22,11 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({ ...@@ -22,7 +22,11 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({
}, },
}, },
component: DastSiteProfileList, component: DastSiteProfileList,
tableFields: ['profileName', 'targetUrl', 'validationStatus'], tableFields: [
{ label: s__('DastProfiles|Site name'), key: 'profileName' },
{ label: s__('DastProfiles|URL'), key: 'targetUrl' },
{ label: s__('DastProfiles|Validation status'), key: 'validationStatus' },
],
i18n: { i18n: {
createNewLinkText: s__('DastProfiles|Site Profile'), createNewLinkText: s__('DastProfiles|Site Profile'),
name: s__('DastProfiles|Site Profiles'), name: s__('DastProfiles|Site Profiles'),
...@@ -51,7 +55,10 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({ ...@@ -51,7 +55,10 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({
}, },
}, },
component: DastScannerProfileList, component: DastScannerProfileList,
tableFields: ['profileName'], tableFields: [
{ label: s__('DastProfiles|Scanner name'), key: 'profileName' },
{ label: s__('DastProfiles|Scan mode'), key: 'scanType' },
],
i18n: { i18n: {
createNewLinkText: s__('DastProfiles|Scanner Profile'), createNewLinkText: s__('DastProfiles|Scanner Profile'),
name: s__('DastProfiles|Scanner Profiles'), name: s__('DastProfiles|Scanner Profiles'),
......
...@@ -30,7 +30,8 @@ export const DAST_SITE_VALIDATION_STATUS = { ...@@ -30,7 +30,8 @@ export const DAST_SITE_VALIDATION_STATUS = {
const INPROGRESS_VALIDATION_PROPS = { const INPROGRESS_VALIDATION_PROPS = {
label: s__('DastSiteValidation|Validating...'), label: s__('DastSiteValidation|Validating...'),
cssClass: 'gl-text-blue-300', badgeVariant: 'info',
badgeIcon: 'status-running',
tooltipText: s__('DastSiteValidation|The validation is in progress. Please wait...'), tooltipText: s__('DastSiteValidation|The validation is in progress. Please wait...'),
}; };
...@@ -39,14 +40,16 @@ export const DAST_SITE_VALIDATION_STATUS_PROPS = { ...@@ -39,14 +40,16 @@ export const DAST_SITE_VALIDATION_STATUS_PROPS = {
[DAST_SITE_VALIDATION_STATUS.INPROGRESS]: INPROGRESS_VALIDATION_PROPS, [DAST_SITE_VALIDATION_STATUS.INPROGRESS]: INPROGRESS_VALIDATION_PROPS,
[DAST_SITE_VALIDATION_STATUS.PASSED]: { [DAST_SITE_VALIDATION_STATUS.PASSED]: {
label: s__('DastSiteValidation|Validated'), label: s__('DastSiteValidation|Validated'),
cssClass: 'gl-text-green-500', badgeVariant: 'success',
badgeIcon: 'status-success',
tooltipText: s__( tooltipText: s__(
'DastSiteValidation|Validation succeeded. Both active and passive scans can be run against the target site.', 'DastSiteValidation|Validation succeeded. Both active and passive scans can be run against the target site.',
), ),
}, },
[DAST_SITE_VALIDATION_STATUS.FAILED]: { [DAST_SITE_VALIDATION_STATUS.FAILED]: {
label: s__('DastSiteValidation|Validation failed'), label: s__('DastSiteValidation|Validation failed'),
cssClass: 'gl-text-red-500', badgeVariant: 'danger',
badgeIcon: 'status-failed',
tooltipText: s__('DastSiteValidation|The validation has failed. Please try again.'), tooltipText: s__('DastSiteValidation|The validation has failed. Please try again.'),
}, },
}; };
......
---
title: Improve DAST Profiles library designs
merge_request: 50576
author:
type: changed
...@@ -15,7 +15,7 @@ describe('EE - DastProfilesList', () => { ...@@ -15,7 +15,7 @@ describe('EE - DastProfilesList', () => {
const defaultProps = { const defaultProps = {
profiles: [], profiles: [],
tableLabel: 'Profiles Table', tableLabel: 'Profiles Table',
fields: ['profileName', 'targetUrl', 'validationStatus'], fields: [{ key: 'profileName' }, { key: 'targetUrl' }, { key: 'validationStatus' }],
hasMorePages: false, hasMorePages: false,
profilesPerPage: 10, profilesPerPage: 10,
errorMessage: '', errorMessage: '',
......
...@@ -10,7 +10,7 @@ describe('EE - DastScannerProfileList', () => { ...@@ -10,7 +10,7 @@ describe('EE - DastScannerProfileList', () => {
const defaultProps = { const defaultProps = {
profiles: [], profiles: [],
tableLabel: 'Scanner profiles', tableLabel: 'Scanner profiles',
fields: ['profileName'], fields: [{ key: 'profileName' }, { key: 'scanType' }],
profilesPerPage: 10, profilesPerPage: 10,
errorMessage: '', errorMessage: '',
errorDetails: [], errorDetails: [],
...@@ -30,7 +30,6 @@ describe('EE - DastScannerProfileList', () => { ...@@ -30,7 +30,6 @@ describe('EE - DastScannerProfileList', () => {
), ),
); );
}; };
const createComponent = wrapperFactory();
const createFullComponent = wrapperFactory(mount); const createFullComponent = wrapperFactory(mount);
const findProfileList = () => wrapper.find(ProfilesList); const findProfileList = () => wrapper.find(ProfilesList);
...@@ -40,7 +39,7 @@ describe('EE - DastScannerProfileList', () => { ...@@ -40,7 +39,7 @@ describe('EE - DastScannerProfileList', () => {
}); });
it('renders profile list properly', () => { it('renders profile list properly', () => {
createComponent({ createFullComponent({
propsData: { profiles: scannerProfiles }, propsData: { profiles: scannerProfiles },
}); });
...@@ -55,7 +54,7 @@ describe('EE - DastScannerProfileList', () => { ...@@ -55,7 +54,7 @@ describe('EE - DastScannerProfileList', () => {
it('sets listeners on profile list component', () => { it('sets listeners on profile list component', () => {
const inputHandler = jest.fn(); const inputHandler = jest.fn();
createComponent({ createFullComponent({
listeners: { listeners: {
input: inputHandler, input: inputHandler,
}, },
......
...@@ -24,7 +24,7 @@ describe('EE - DastSiteProfileList', () => { ...@@ -24,7 +24,7 @@ describe('EE - DastSiteProfileList', () => {
const defaultProps = { const defaultProps = {
profiles: [], profiles: [],
tableLabel: 'Site profiles', tableLabel: 'Site profiles',
fields: ['profileName', 'targetUrl', 'validationStatus'], fields: [{ key: 'profileName' }, { key: 'targetUrl' }, { key: 'validationStatus' }],
profilesPerPage: 10, profilesPerPage: 10,
errorMessage: '', errorMessage: '',
errorDetails: [], errorDetails: [],
...@@ -136,7 +136,7 @@ describe('EE - DastSiteProfileList', () => { ...@@ -136,7 +136,7 @@ describe('EE - DastSiteProfileList', () => {
${'in-progress'} | ${DAST_SITE_VALIDATION_STATUS.INPROGRESS} | ${'Validating...'} | ${false} ${'in-progress'} | ${DAST_SITE_VALIDATION_STATUS.INPROGRESS} | ${'Validating...'} | ${false}
${'passed'} | ${DAST_SITE_VALIDATION_STATUS.PASSED} | ${'Validated'} | ${false} ${'passed'} | ${DAST_SITE_VALIDATION_STATUS.PASSED} | ${'Validated'} | ${false}
${'failed'} | ${DAST_SITE_VALIDATION_STATUS.FAILED} | ${'Validation failed'} | ${true} ${'failed'} | ${DAST_SITE_VALIDATION_STATUS.FAILED} | ${'Validation failed'} | ${true}
`('profile with validation $status', ({ statusEnum, label, hasValidateButton }) => { `('profile with $status validation', ({ statusEnum, label, hasValidateButton }) => {
const profile = siteProfiles.find(({ validationStatus }) => validationStatus === statusEnum); const profile = siteProfiles.find(({ validationStatus }) => validationStatus === statusEnum);
it(`should show correct label`, () => { it(`should show correct label`, () => {
...@@ -144,17 +144,13 @@ describe('EE - DastSiteProfileList', () => { ...@@ -144,17 +144,13 @@ describe('EE - DastSiteProfileList', () => {
expect(validationStatusCell.innerText).toContain(label); expect(validationStatusCell.innerText).toContain(label);
}); });
it(`should ${hasValidateButton ? '' : 'not '}render validate button`, () => { it(`should ${hasValidateButton ? 'not ' : ''} disable validate button`, () => {
const actionsCell = getTableRowForProfile(profile).cells[3]; const actionsCell = getTableRowForProfile(profile).cells[3];
const validateButton = within(actionsCell).queryByRole('button', { const validateButton = within(actionsCell).queryByRole('button', {
name: /validate/i, name: /validate|Retry validation/i,
}); });
if (hasValidateButton) { expect(validateButton.hasAttribute('disabled')).toBe(!hasValidateButton);
expect(validateButton).not.toBeNull();
} else {
expect(validateButton).toBeNull();
}
}); });
}); });
......
...@@ -8827,6 +8827,9 @@ msgstr "" ...@@ -8827,6 +8827,9 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?" msgid "DastProfiles|Do you want to discard your changes?"
msgstr "" msgstr ""
msgid "DastProfiles|Edit profile"
msgstr ""
msgid "DastProfiles|Edit scanner profile" msgid "DastProfiles|Edit scanner profile"
msgstr "" msgstr ""
...@@ -8914,6 +8917,9 @@ msgstr "" ...@@ -8914,6 +8917,9 @@ msgstr ""
msgid "DastProfiles|Scanner Profiles" msgid "DastProfiles|Scanner Profiles"
msgstr "" msgstr ""
msgid "DastProfiles|Scanner name"
msgstr ""
msgid "DastProfiles|Show debug messages" msgid "DastProfiles|Show debug messages"
msgstr "" msgstr ""
...@@ -8923,6 +8929,9 @@ msgstr "" ...@@ -8923,6 +8929,9 @@ msgstr ""
msgid "DastProfiles|Site Profiles" msgid "DastProfiles|Site Profiles"
msgstr "" msgstr ""
msgid "DastProfiles|Site name"
msgstr ""
msgid "DastProfiles|Spider timeout" msgid "DastProfiles|Spider timeout"
msgstr "" msgstr ""
...@@ -8941,6 +8950,9 @@ msgstr "" ...@@ -8941,6 +8950,9 @@ msgstr ""
msgid "DastProfiles|Turn on AJAX spider" msgid "DastProfiles|Turn on AJAX spider"
msgstr "" msgstr ""
msgid "DastProfiles|URL"
msgstr ""
msgid "DastProfiles|Username" msgid "DastProfiles|Username"
msgstr "" msgstr ""
...@@ -8950,6 +8962,9 @@ msgstr "" ...@@ -8950,6 +8962,9 @@ msgstr ""
msgid "DastProfiles|Validated" msgid "DastProfiles|Validated"
msgstr "" msgstr ""
msgid "DastProfiles|Validation status"
msgstr ""
msgid "DastSiteValidation|Copy HTTP header to clipboard" msgid "DastSiteValidation|Copy HTTP header to clipboard"
msgstr "" msgstr ""
...@@ -8962,6 +8977,9 @@ msgstr "" ...@@ -8962,6 +8977,9 @@ msgstr ""
msgid "DastSiteValidation|Header validation" msgid "DastSiteValidation|Header validation"
msgstr "" msgstr ""
msgid "DastSiteValidation|Retry validation"
msgstr ""
msgid "DastSiteValidation|Step 1 - Choose site validation method" msgid "DastSiteValidation|Step 1 - Choose site validation method"
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