Commit d7f60c6e authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by Kushal Pandya

Update DAST Profiles library UI

Implement design improvements for
Site & Scanner profile library pages
parent 3f107950
......@@ -7,7 +7,11 @@ import {
GlSkeletonLoader,
GlTable,
GlTooltipDirective,
GlDropdown,
GlDropdownItem,
GlIcon,
} from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
export default {
components: {
......@@ -16,6 +20,9 @@ export default {
GlModal,
GlSkeletonLoader,
GlTable,
GlDropdown,
GlDropdownItem,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -88,8 +95,16 @@ export default {
},
tableFields() {
const defaultClasses = ['gl-word-break-all'];
const dataFields = this.fields.map((key) => ({ key, class: defaultClasses }));
const staticFields = [{ key: 'actions' }];
const defaultThClasses = ['gl-bg-transparent!', 'gl-text-black-normal'];
const dataFields = this.fields.map(({ key, label }) => ({
key,
label,
class: defaultClasses,
thClass: defaultThClasses,
}));
const staticFields = [{ key: 'actions', label: '', thClass: defaultThClasses }];
return [...dataFields, ...staticFields];
},
......@@ -105,6 +120,9 @@ export default {
handleCancel() {
this.toBeDeletedProfileId = null;
},
navigateToProfile({ editPath }) {
return visitUrl(editPath);
},
},
};
</script>
......@@ -117,7 +135,11 @@ export default {
:fields="tableFields"
:items="profiles"
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>
<td :colspan="tableFields.length">
......@@ -135,7 +157,9 @@ export default {
</template>
<template #cell(profileName)="{ value }">
<strong>{{ value }}</strong>
<div class="gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis">
{{ value }}
</div>
</template>
<template v-for="slotName in Object.keys($scopedSlots)" #[slotName]="slotScope">
......@@ -146,21 +170,48 @@ export default {
<div class="gl-text-right">
<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
v-if="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"
>{{ __('Edit') }}</gl-button
>
<gl-button
v-gl-tooltip.hover.focus
category="tertiary"
icon="remove"
variant="danger"
category="secondary"
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')"
@click="prepareProfileDeletion(item.id)"
/>
......
<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';
export default {
components: {
ProfilesList,
GlBadge,
},
SCAN_TYPE,
SCAN_TYPE_LABEL,
};
</script>
<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>
<script>
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlButton, GlIcon, GlTooltipDirective, GlBadge, GlLink } from '@gitlab/ui';
import {
DAST_SITE_VALIDATION_STATUS,
DAST_SITE_VALIDATION_STATUS_PROPS,
......@@ -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 { updateSiteProfilesStatuses } from '../graphql/cache_utils';
import ProfilesList from './dast_profiles_list.vue';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
......@@ -18,6 +19,8 @@ export default {
components: {
GlButton,
GlIcon,
GlBadge,
GlLink,
DastSiteValidationModal,
ProfilesList,
},
......@@ -80,10 +83,7 @@ export default {
computed: {
urlsPendingValidation() {
return this.profiles.reduce((acc, { validationStatus, normalizedTargetUrl }) => {
if (
[PENDING, INPROGRESS].includes(validationStatus) &&
!acc.includes(normalizedTargetUrl)
) {
if (this.isPendingValidation(validationStatus) && !acc.includes(normalizedTargetUrl)) {
return [...acc, normalizedTargetUrl];
}
return acc;
......@@ -91,11 +91,16 @@ export default {
},
},
methods: {
shouldShowValidationBtn(status) {
return (
this.glFeatures.securityOnDemandScansSiteValidation &&
(status === NONE || status === FAILED)
);
isPendingValidation(status) {
return [PENDING, INPROGRESS].includes(status);
},
shouldShowValidateBtn(status) {
return [NONE, FAILED].includes(status);
},
validateBtnLabel(status) {
return status === FAILED
? s__('DastSiteValidation|Retry validation')
: s__('DastSiteValidation|Validate');
},
shouldShowValidationStatus(status) {
return this.glFeatures.securityOnDemandScansSiteValidation && status !== NONE;
......@@ -122,28 +127,39 @@ export default {
</script>
<template>
<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 v-if="shouldShowValidationStatus(value)">
<span :class="$options.statuses[value].cssClass">
{{ $options.statuses[value].label }}
</span>
<gl-icon
<gl-badge
v-gl-tooltip
name="question-o"
class="gl-vertical-align-text-bottom gl-text-gray-300 gl-ml-2"
size="sm"
:variant="$options.statuses[value].badgeVariant"
: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 #actions="{ profile }">
<gl-button
v-if="shouldShowValidationBtn(profile.validationStatus)"
v-if="glFeatures.securityOnDemandScansSiteValidation"
:disabled="!shouldShowValidateBtn(profile.validationStatus)"
variant="info"
category="secondary"
category="tertiary"
size="small"
@click="setValidatingProfile(profile)"
>{{ s__('DastSiteValidation|Validate target site') }}</gl-button
>{{ validateBtnLabel(profile.validationStatus) }}</gl-button
>
</template>
......
......@@ -22,7 +22,11 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({
},
},
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: {
createNewLinkText: s__('DastProfiles|Site Profile'),
name: s__('DastProfiles|Site Profiles'),
......@@ -51,7 +55,10 @@ export const getProfileSettings = ({ createNewProfilePaths }) => ({
},
},
component: DastScannerProfileList,
tableFields: ['profileName'],
tableFields: [
{ label: s__('DastProfiles|Scanner name'), key: 'profileName' },
{ label: s__('DastProfiles|Scan mode'), key: 'scanType' },
],
i18n: {
createNewLinkText: s__('DastProfiles|Scanner Profile'),
name: s__('DastProfiles|Scanner Profiles'),
......
......@@ -30,7 +30,8 @@ export const DAST_SITE_VALIDATION_STATUS = {
const INPROGRESS_VALIDATION_PROPS = {
label: s__('DastSiteValidation|Validating...'),
cssClass: 'gl-text-blue-300',
badgeVariant: 'info',
badgeIcon: 'status-running',
tooltipText: s__('DastSiteValidation|The validation is in progress. Please wait...'),
};
......@@ -39,14 +40,16 @@ export const DAST_SITE_VALIDATION_STATUS_PROPS = {
[DAST_SITE_VALIDATION_STATUS.INPROGRESS]: INPROGRESS_VALIDATION_PROPS,
[DAST_SITE_VALIDATION_STATUS.PASSED]: {
label: s__('DastSiteValidation|Validated'),
cssClass: 'gl-text-green-500',
badgeVariant: 'success',
badgeIcon: 'status-success',
tooltipText: s__(
'DastSiteValidation|Validation succeeded. Both active and passive scans can be run against the target site.',
),
},
[DAST_SITE_VALIDATION_STATUS.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.'),
},
};
......
---
title: Improve DAST Profiles library designs
merge_request: 50576
author:
type: changed
......@@ -15,7 +15,7 @@ describe('EE - DastProfilesList', () => {
const defaultProps = {
profiles: [],
tableLabel: 'Profiles Table',
fields: ['profileName', 'targetUrl', 'validationStatus'],
fields: [{ key: 'profileName' }, { key: 'targetUrl' }, { key: 'validationStatus' }],
hasMorePages: false,
profilesPerPage: 10,
errorMessage: '',
......
......@@ -10,7 +10,7 @@ describe('EE - DastScannerProfileList', () => {
const defaultProps = {
profiles: [],
tableLabel: 'Scanner profiles',
fields: ['profileName'],
fields: [{ key: 'profileName' }, { key: 'scanType' }],
profilesPerPage: 10,
errorMessage: '',
errorDetails: [],
......@@ -30,7 +30,6 @@ describe('EE - DastScannerProfileList', () => {
),
);
};
const createComponent = wrapperFactory();
const createFullComponent = wrapperFactory(mount);
const findProfileList = () => wrapper.find(ProfilesList);
......@@ -40,7 +39,7 @@ describe('EE - DastScannerProfileList', () => {
});
it('renders profile list properly', () => {
createComponent({
createFullComponent({
propsData: { profiles: scannerProfiles },
});
......@@ -55,7 +54,7 @@ describe('EE - DastScannerProfileList', () => {
it('sets listeners on profile list component', () => {
const inputHandler = jest.fn();
createComponent({
createFullComponent({
listeners: {
input: inputHandler,
},
......
......@@ -24,7 +24,7 @@ describe('EE - DastSiteProfileList', () => {
const defaultProps = {
profiles: [],
tableLabel: 'Site profiles',
fields: ['profileName', 'targetUrl', 'validationStatus'],
fields: [{ key: 'profileName' }, { key: 'targetUrl' }, { key: 'validationStatus' }],
profilesPerPage: 10,
errorMessage: '',
errorDetails: [],
......@@ -136,7 +136,7 @@ describe('EE - DastSiteProfileList', () => {
${'in-progress'} | ${DAST_SITE_VALIDATION_STATUS.INPROGRESS} | ${'Validating...'} | ${false}
${'passed'} | ${DAST_SITE_VALIDATION_STATUS.PASSED} | ${'Validated'} | ${false}
${'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);
it(`should show correct label`, () => {
......@@ -144,17 +144,13 @@ describe('EE - DastSiteProfileList', () => {
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 validateButton = within(actionsCell).queryByRole('button', {
name: /validate/i,
name: /validate|Retry validation/i,
});
if (hasValidateButton) {
expect(validateButton).not.toBeNull();
} else {
expect(validateButton).toBeNull();
}
expect(validateButton.hasAttribute('disabled')).toBe(!hasValidateButton);
});
});
......
......@@ -8824,6 +8824,9 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?"
msgstr ""
msgid "DastProfiles|Edit profile"
msgstr ""
msgid "DastProfiles|Edit scanner profile"
msgstr ""
......@@ -8911,6 +8914,9 @@ msgstr ""
msgid "DastProfiles|Scanner Profiles"
msgstr ""
msgid "DastProfiles|Scanner name"
msgstr ""
msgid "DastProfiles|Show debug messages"
msgstr ""
......@@ -8920,6 +8926,9 @@ msgstr ""
msgid "DastProfiles|Site Profiles"
msgstr ""
msgid "DastProfiles|Site name"
msgstr ""
msgid "DastProfiles|Spider timeout"
msgstr ""
......@@ -8938,6 +8947,9 @@ msgstr ""
msgid "DastProfiles|Turn on AJAX spider"
msgstr ""
msgid "DastProfiles|URL"
msgstr ""
msgid "DastProfiles|Username"
msgstr ""
......@@ -8947,6 +8959,9 @@ msgstr ""
msgid "DastProfiles|Validated"
msgstr ""
msgid "DastProfiles|Validation status"
msgstr ""
msgid "DastSiteValidation|Copy HTTP header to clipboard"
msgstr ""
......@@ -8959,6 +8974,9 @@ msgstr ""
msgid "DastSiteValidation|Header validation"
msgstr ""
msgid "DastSiteValidation|Retry validation"
msgstr ""
msgid "DastSiteValidation|Step 1 - Choose site validation method"
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