Skip to content

Commit

Permalink
Protect: Separate scan results and history DataViews (#40845)
Browse files Browse the repository at this point in the history
* Init project branch

* Protect: Refactor AdminSectionHero (#40516)

* Restore history header

* Pass status filter presets to consumer

* Restore early return

* Add plan level restrictions

* Init project branch

* Protect: Integrate ThreatsDataViews Component (#40076)

* Separate scan and history DataViews

* Reapply history routes

* Add filters

* Add toggle group control

* Add historic flag

* Update stories

* Remove unneeded filter handling

* Revert unnecessary threats data views updates

* Fix import

* Include fixer action as label in list view action dropdown

* Add field constants

* Reorg

* Add initialFields prop, update active to current where applicable

* Init project branch

* Fix exports and imports

* Move default values to function params

* Fix ts warning

* minor adjustment to definition of initial filters

* Add tsc-expect-error to dummy arg for minification cases

---------

Co-authored-by: Nate Weller <hello@nateweller.com>
Co-authored-by: Nate Weller <nate.weller@automattic.com>
  • Loading branch information
3 people committed Feb 3, 2025
1 parent f4bff7e commit 03ed78c
Show file tree
Hide file tree
Showing 18 changed files with 390 additions and 357 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import {
wordpress as coreIcon,
} from '@wordpress/icons';

export const THREAT_STATUSES: { value: string; label: string; variant?: 'success' | 'warning' }[] =
[
{ value: 'current', label: __( 'Active', 'jetpack-components' ), variant: 'warning' },
{ value: 'fixed', label: __( 'Fixed', 'jetpack-components' ), variant: 'success' },
{ value: 'ignored', label: __( 'Ignored', 'jetpack-components' ) },
];
export const THREAT_STATUSES: { value: string; label: string; variant?: 'success' }[] = [
{ value: 'fixed', label: __( 'Fixed', 'jetpack-components' ), variant: 'success' },
{ value: 'ignored', label: __( 'Ignored', 'jetpack-components' ) },
];

export const THREAT_TYPES = [
{ value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) },
Expand Down Expand Up @@ -45,6 +43,26 @@ export const THREAT_FIELD_FIRST_DETECTED = 'first-detected';
export const THREAT_FIELD_FIXED_ON = 'fixed-on';
export const THREAT_FIELD_AUTO_FIX = 'auto-fix';

export const CURRENT_TABLE_FIELDS = [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_TYPE,
THREAT_FIELD_AUTO_FIX,
];

export const HISTORIC_TABLE_FIELDS = [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_TYPE,
THREAT_FIELD_FIRST_DETECTED,
THREAT_FIELD_FIXED_ON,
];

export const LIST_FIELDS = [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_TYPE,
THREAT_FIELD_EXTENSION,
THREAT_FIELD_SIGNATURE,
];

export const THREAT_ACTION_FIX = 'fix';
export const THREAT_ACTION_IGNORE = 'ignore';
export const THREAT_ACTION_UNIGNORE = 'unignore';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getThreatType, type Threat } from '@automattic/jetpack-scan';
import { getFixerAction, getThreatType, type Threat } from '@automattic/jetpack-scan';
import {
type Action,
type ActionButton,
Expand All @@ -19,6 +19,8 @@ import Badge from '../badge';
import ThreatFixerButton from '../threat-fixer-button';
import ThreatSeverityBadge from '../threat-severity-badge';
import {
CURRENT_TABLE_FIELDS,
LIST_FIELDS,
THREAT_ACTION_FIX,
THREAT_ACTION_IGNORE,
THREAT_ACTION_UNIGNORE,
Expand All @@ -40,52 +42,62 @@ import {
THREAT_TYPES,
} from './constants';
import styles from './styles.module.scss';
import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-control';

export { HISTORIC_TABLE_FIELDS } from './constants';

/**
* DataViews component for displaying security threats.
*
* @param {object} props - Component props.
* @param {Array} props.data - Threats data.
* @param {Array} props.filters - Initial DataView filters.
* @param {Function} props.onChangeSelection - Callback function run when an item is selected.
* @param {Function} props.onFixThreats - Threat fix action callback.
* @param {Function} props.onIgnoreThreats - Threat ignore action callback.
* @param {Function} props.onUnignoreThreats - Threat unignore action callback.
* @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing.
* @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring.
* @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring.
* @param {object} props - Component props.
* @param {string} props.status - Flag to indicate if the threats are current or historic.
* @param {Array} props.data - Threats data.
* @param {Array} props.initialFilters - Initial DataView filters.
* @param {Array} props.initialFields - Initial DataView fields.
* @param {Function} props.onChangeSelection - Callback function run when an item is selected.
* @param {Function} props.onFixThreats - Threat fix action callback.
* @param {Function} props.onIgnoreThreats - Threat ignore action callback.
* @param {Function} props.onUnignoreThreats - Threat unignore action callback.
* @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing.
* @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring.
* @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring.
* @param {JSX.Element} props.header - Header component.
*
* @return {JSX.Element} The ThreatsDataViews component.
*/
export default function ThreatsDataViews( {
status = 'current',
data,
filters,
initialFields = CURRENT_TABLE_FIELDS,
initialFilters = [],
onChangeSelection,
isThreatEligibleForFix,
isThreatEligibleForIgnore,
isThreatEligibleForUnignore,
onFixThreats,
onIgnoreThreats,
onUnignoreThreats,
header,
}: {
status?: string;
data: Threat[];
filters?: Filter[];
initialFields?: string[];
initialFilters?: Filter[];
onChangeSelection?: ( selectedItemIds: string[] ) => void;
isThreatEligibleForFix?: ( threat: Threat ) => boolean;
isThreatEligibleForIgnore?: ( threat: Threat ) => boolean;
isThreatEligibleForUnignore?: ( threat: Threat ) => boolean;
onFixThreats?: ( threats: Threat[] ) => void;
onIgnoreThreats?: ActionButton< Threat >[ 'callback' ];
onUnignoreThreats?: ActionButton< Threat >[ 'callback' ];
header?: JSX.Element;
} ): JSX.Element {
const baseView = {
sort: {
field: 'severity',
direction: 'desc' as SortDirection,
},
search: '',
filters: filters || [],
filters: initialFilters,
page: 1,
perPage: 20,
};
Expand All @@ -100,19 +112,14 @@ export default function ThreatsDataViews( {
const defaultLayouts: SupportedLayouts = {
table: {
...baseView,
fields: [ THREAT_FIELD_SEVERITY, THREAT_FIELD_TYPE, THREAT_FIELD_AUTO_FIX ],
fields: initialFields,
titleField: THREAT_FIELD_TITLE,
descriptionField: THREAT_FIELD_DESCRIPTION,
showMedia: false,
},
list: {
...baseView,
fields: [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_TYPE,
THREAT_FIELD_EXTENSION,
THREAT_FIELD_SIGNATURE,
],
fields: LIST_FIELDS,
titleField: THREAT_FIELD_TITLE,
mediaField: THREAT_FIELD_ICON,
showMedia: true,
Expand Down Expand Up @@ -238,28 +245,6 @@ export default function ThreatsDataViews( {
);
},
},
{
id: THREAT_FIELD_STATUS,
label: __( 'Status', 'jetpack-components' ),
elements: THREAT_STATUSES,
getValue( { item }: { item: Threat } ) {
if ( ! item.status ) {
return 'current';
}
return (
THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? item.status
);
},
render( { item }: { item: Threat } ) {
if ( item.status ) {
const status = THREAT_STATUSES.find( ( { value } ) => value === item.status );
if ( status ) {
return <Badge variant={ status?.variant }>{ status.label }</Badge>;
}
}
return <Badge variant="warning">{ __( 'Active', 'jetpack-components' ) }</Badge>;
},
},
{
id: THREAT_FIELD_TYPE,
label: __( 'Type', 'jetpack-components' ),
Expand Down Expand Up @@ -300,6 +285,35 @@ export default function ThreatsDataViews( {
return item.extension ? item.extension.slug : '';
},
},
...( 'historic' === status && dataFields.includes( 'status' )
? [
{
id: THREAT_FIELD_STATUS,
label: __( 'Status', 'jetpack-components' ),
elements: THREAT_STATUSES,
getValue( { item }: { item: Threat } ) {
if ( ! item.status ) {
return 'current';
}
return (
THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ??
item.status
);
},
render( { item }: { item: Threat } ) {
if ( item.status ) {
const threatStatus = THREAT_STATUSES.find(
( { value } ) => value === item.status
);
if ( threatStatus ) {
return <Badge variant={ threatStatus?.variant }>{ threatStatus.label }</Badge>;
}
}
return <Badge variant="warning">{ __( 'Current', 'jetpack-components' ) }</Badge>;
},
},
]
: [] ),
...( dataFields.includes( 'severity' )
? [
{
Expand Down Expand Up @@ -366,7 +380,7 @@ export default function ThreatsDataViews( {
},
]
: [] ),
...( dataFields.includes( 'fixable' )
...( 'historic' !== status && dataFields.includes( 'fixable' )
? [
{
id: THREAT_FIELD_AUTO_FIX,
Expand Down Expand Up @@ -398,7 +412,7 @@ export default function ThreatsDataViews( {
];

return result;
}, [ dataFields, plugins, themes, signatures, onFixThreats ] );
}, [ dataFields, plugins, themes, signatures, status, onFixThreats ] );

/**
* DataView actions - collection of operations that can be performed upon each record.
Expand All @@ -408,10 +422,12 @@ export default function ThreatsDataViews( {
const actions = useMemo( () => {
const result: Action< Threat >[] = [];

if ( dataFields.includes( 'fixable' ) ) {
if ( dataFields.includes( 'fixable' ) && view.type === 'list' ) {
result.push( {
id: THREAT_ACTION_FIX,
label: __( 'Auto-fix', 'jetpack-components' ),
label: items => {
return getFixerAction( items[ 0 ] );
},
isPrimary: true,
callback: onFixThreats,
isEligible( item ) {
Expand Down Expand Up @@ -466,6 +482,7 @@ export default function ThreatsDataViews( {

return result;
}, [
view.type,
dataFields,
onFixThreats,
onIgnoreThreats,
Expand Down Expand Up @@ -511,13 +528,7 @@ export default function ThreatsDataViews( {
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
view={ view }
header={
<ThreatsStatusToggleGroupControl
data={ data }
view={ view }
onChangeView={ onChangeView }
/>
}
header={ header }
/>
);
}
Loading

0 comments on commit 03ed78c

Please sign in to comment.