Skip to content

Commit

Permalink
ThreatsDataViews: Add ToggleGroupControl (#39901)
Browse files Browse the repository at this point in the history
* Components: add hoverShow prop to IconTooltip

* changelog

* Add ThreatsDataView

changelog

* Add ToggleGroupControl filters to ThreatsDataView

* Components: add hoverShow prop to IconTooltip

* changelog

* Add ThreatsDataView

changelog

Use inline button for primary fixer action

* Fix rebase issues

* Fixes, updates

* Improve type checks

* Components: add hoverShow prop to IconTooltip

* changelog

* Add ThreatsDataView

* Story fixes

* Update toggle text

* Fix story data

* changelog

* Fix changelog entry

* Fix types

* Update approach to counting threats

* Fix tests

* Update lock file

* Revert lock file changes

* Set __nextHasNoMarginBottom to avoid deprecation warning

* Move toggle to dedicated file

* Add default filters to stories

* Wrap experimental component rendering in try/catch

* Update projects/js-packages/components/components/threats-data-views/index.tsx

Co-authored-by: Nate Weller <[email protected]>

* Update projects/js-packages/components/components/threats-data-views/index.tsx

Co-authored-by: Nate Weller <[email protected]>

* Update projects/js-packages/components/components/threats-data-views/index.tsx

Co-authored-by: Nate Weller <[email protected]>

* Update projects/js-packages/components/components/threats-data-views/threats-status-toggle-group-control.tsx

Co-authored-by: Nate Weller <[email protected]>

* Fix lint errors

---------

Co-authored-by: Nate Weller <[email protected]>
Co-authored-by: Nate Weller <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent 7c0b2a8 commit e9b0e4f
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Add ToggleGroupControl to ThreatsDataViews for easily toggling between Active and Historical threats
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
THREAT_TYPES,
} from './constants';
import styles from './styles.module.scss';
import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-control';

/**
* DataViews component for displaying security threats.
Expand Down Expand Up @@ -146,10 +147,10 @@ export default function ThreatsDataViews( {
/**
* Compute values from the provided threats data.
*
* @member {object[]} themes - List of unique themes included in the threats data.
* @member {object[]} plugins - List of unique plugins included in the threats data.
* @member {object[]} themes - List of unique themes included in the threats data.
* @member {object[]} plugins - plugins included in the threats data.
* @member {object[]} signatures - List of unique threat signatures.
* @member {string[]} dataFields - List of unique fields.
* @member {string[]} dataFields - List of unique fields.
*/
const {
themes,
Expand Down Expand Up @@ -526,6 +527,13 @@ export default function ThreatsDataViews( {
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
view={ view }
header={
<ThreatsStatusToggleGroupControl
data={ data }
view={ view }
onChangeView={ onChangeView }
/>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Default.args = {
fixedIn: '1.12.4',
severity: 3,
fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' },
fixer: { status: 'in_progress', last_updated: new Date().toISOString() },
fixer: { status: 'in_progress', lastUpdated: new Date().toISOString() },
status: 'current',
filename: null,
context: null,
Expand Down Expand Up @@ -176,7 +176,7 @@ FixerStatuses.args = {
severity: 4,
fixer: null,
fixedOn: '2024-07-15T22:01:42.000Z',
status: 'fixed',
status: 'current',
fixable: { fixer: 'update', target: '6.4.4', extensionStatus: 'inactive' },
version: '6.4.3',
source: '',
Expand All @@ -190,7 +190,7 @@ FixerStatuses.args = {
fixedIn: '1.2.4',
severity: 3,
fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' },
fixer: { status: 'in_progress', last_updated: new Date().toISOString() },
fixer: { status: 'in_progress', lastUpdated: new Date().toISOString() },
status: 'current',
source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3',
extension: {
Expand All @@ -209,7 +209,7 @@ FixerStatuses.args = {
fixedIn: '2.22.22',
severity: 3,
fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' },
fixer: { status: 'in_progress', last_updated: new Date( '1999-01-01' ).toISOString() },
fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() },
status: 'current',
source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3',
extension: {
Expand Down Expand Up @@ -260,6 +260,13 @@ FixerStatuses.args = {
},
},
],
filters: [
{
field: 'status',
operator: 'isAny',
value: [ 'current' ],
},
],
onFixThreats: () =>
alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert
onIgnoreThreats: () =>
Expand All @@ -280,7 +287,6 @@ FreeResults.args = {
description:
'Versions 3.2.3 and earlier are affected by an issue where cached queries within shortcodes could lead to object injection. This is related to the recent WordPress 4.8.3 security release.This issue can only be exploited by users who can edit content and add shortcodes, but we still recommend all users running WooCommerce 3.x upgrade to 3.2 to mitigate this issue.',
fixedIn: '3.2.4',
status: 'current',
source: 'https://wpscan.com/vulnerability/1d0470df-4671-47ac-8d87-a165e8f7d502',
extension: {
name: 'WooCommerce',
Expand All @@ -296,7 +302,6 @@ FreeResults.args = {
description:
'The WooCommerce WordPress plugin was affected by an Authenticated Stored XSS security vulnerability.',
fixedIn: '3.4.6',
status: 'current',
source: 'https://wpscan.com/vulnerability/7275a176-d579-471a-8492-df8edbdf27de',
extension: {
name: 'WooCommerce',
Expand All @@ -311,7 +316,6 @@ FreeResults.args = {
description:
'The plugin was affected by an authenticated (admin+) RCE in the settings page due to input validation failure and weak $cache_path check in the WP Super Cache Settings -> Cache Location option. Direct access to the wp-cache-config.php file is not prohibited, so this vulnerability can be exploited for a web shell injection.\r\n\r\nAnother possible attack vector: from XSS (via another plugin affected by XSS) to RCE.',
fixedIn: '1.7.2',
status: 'current',
source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3',
extension: {
name: 'WP Super Cache',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
border-color: #EDFFEE;

svg {
fill: black;
fill: var( --jp-black );
}
}

.toggle-group-control__option {
white-space: nowrap;
padding: 0 12px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const data = [
type: 'plugin' as const,
},
fixedIn: '3.2.4',
status: 'current' as const,
},
];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { type Threat, type ThreatStatus } from '@automattic/jetpack-scan';
import {
__experimentalToggleGroupControl as ToggleGroupControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControlOption as ToggleGroupControlOption, // eslint-disable-line @wordpress/no-unsafe-wp-apis
} from '@wordpress/components';
import { type View } from '@wordpress/dataviews';
import { useMemo, useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import styles from './styles.module.scss';

/**
* ToggleGroupControl component for filtering threats by status.
* @param {object} props - Component props.
* @param { Threat[]} props.data - Threats data.
* @param { View } props.view - The current view.
* @param { Function } props.onChangeView - Callback function to handle view changes.
* @return {JSX.Element|null} The component or null.
*/
export default function ThreatsStatusToggleGroupControl( {
data,
view,
onChangeView,
}: {
data: Threat[];
view: View;
onChangeView: ( newView: View ) => void;
} ): JSX.Element {
/**
* Compute values from the provided threats data.
*
* @member {number} activeThreatsCount - Count of active threats.
* @member {number} historicThreatsCount - Count of historic threats.
*/
const {
activeThreatsCount,
historicThreatsCount,
}: {
activeThreatsCount: number;
historicThreatsCount: number;
} = useMemo( () => {
return data.reduce(
( acc, threat ) => {
if ( threat.status ) {
if ( threat.status === 'current' ) {
acc.activeThreatsCount++;
} else {
acc.historicThreatsCount++;
}
}
return acc;
},
{
activeThreatsCount: 0,
historicThreatsCount: 0,
}
);
}, [ data ] );

/**
* Callback function to handle the status change filter.
*
* @param {string} newStatus - The new status filter value.
*/
const onStatusFilterChange = useCallback(
( newStatus: string ) => {
const updatedFilters = view.filters.filter( filter => filter.field !== 'status' );

if ( newStatus === 'active' ) {
updatedFilters.push( {
field: 'status',
operator: 'isAny',
value: [ 'current' ],
} );
} else if ( newStatus === 'historic' ) {
updatedFilters.push( {
field: 'status',
operator: 'isAny',
value: [ 'fixed', 'ignored' ],
} );
}

onChangeView( {
...view,
filters: updatedFilters,
} );
},
[ view, onChangeView ]
);

/**
* Memoized function to determine if a status filter is selected.
*
* @param {Array} threatStatuses - List of threat statuses.
*/
const isStatusFilterSelected = useMemo(
() => ( threatStatuses: ThreatStatus[] ) =>
view.filters.some(
filter =>
filter.field === 'status' &&
Array.isArray( filter.value ) &&
filter.value.length === threatStatuses.length &&
threatStatuses.every( threatStatus => filter.value.includes( threatStatus ) )
),
[ view.filters ]
);

const selectedValue = useMemo( () => {
if ( isStatusFilterSelected( [ 'current' ] ) ) {
return 'active' as const;
}
if ( isStatusFilterSelected( [ 'fixed', 'ignored' ] ) ) {
return 'historic' as const;
}
return '' as const;
}, [ isStatusFilterSelected ] );

if ( ! ( activeThreatsCount + historicThreatsCount ) ) {
return null;
}

try {
return (
<ToggleGroupControl
className={ styles[ 'toggle-group-control' ] }
value={ selectedValue }
onChange={ onStatusFilterChange }
__nextHasNoMarginBottom
>
<ToggleGroupControlOption
value="active"
label={
<span className={ styles[ 'toggle-group-control__option' ] }>
{ sprintf(
/* translators: %d: number of active threats */ __(
'Active threats (%d)',
'jetpack'
),
activeThreatsCount
) }
</span>
}
/>
<ToggleGroupControlOption
value="historic"
label={
<span className={ styles[ 'toggle-group-control__option' ] }>
{ sprintf(
/* translators: %d: number of historic threats */
__( 'History (%d)', 'jetpack' ),
historicThreatsCount
) }
</span>
}
/>
</ToggleGroupControl>
);
} catch ( error ) {
return null;
}
}

0 comments on commit e9b0e4f

Please sign in to comment.