-
Notifications
You must be signed in to change notification settings - Fork 800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ThreatsDataViews: Add ToggleGroupControl #39901
Changes from 31 commits
6c3c360
81d25c6
f919501
aaa4bed
9b5bdd2
18277d1
2ebc6a0
2d529b8
df3ce66
81594ac
65fd9e3
e353eef
0802197
f27d02f
e87dbbf
ae443bf
02e98fc
2031331
78c626e
f5eb54c
2fffd3b
dd7cee7
c2b6b0f
e047efc
2f17bb7
1806863
3dcbe6c
e55e556
9752c68
1fa9179
f2e4c56
8b491ed
23e531b
9314e35
ec476f6
ac20168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -1,15 +1,15 @@ | ||
import { getThreatType, type Threat } from '@automattic/jetpack-scan'; | ||
import { | ||
type Action, | ||
type ActionButton, | ||
type Field, | ||
type FieldType, | ||
type Filter, | ||
type SortDirection, | ||
type SupportedLayouts, | ||
type View, | ||
Action, | ||
ActionButton, | ||
DataViews, | ||
Field, | ||
FieldType, | ||
Filter, | ||
filterSortAndPaginate, | ||
SortDirection, | ||
SupportedLayouts, | ||
type View, | ||
dkmyta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} from '@wordpress/dataviews'; | ||
import { dateI18n } from '@wordpress/date'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
@@ -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. | ||
|
@@ -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 threat themes. | ||
* @member {object[]} plugins - List of unique threat plugins. | ||
dkmyta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @member {object[]} signatures - List of unique threat signatures. | ||
* @member {string[]} dataFields - List of unique fields. | ||
* @member {Array} dataFields - List of unique fields. | ||
dkmyta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
const { | ||
themes, | ||
|
@@ -526,6 +527,13 @@ export default function ThreatsDataViews( { | |
onChangeView={ onChangeView } | ||
paginationInfo={ paginationInfo } | ||
view={ view } | ||
header={ | ||
<ThreatsStatusToggleGroupControl | ||
data={ data } | ||
view={ view } | ||
onChangeView={ onChangeView } | ||
/> | ||
} | ||
Comment on lines
+530
to
+536
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clean! 👍 |
||
/> | ||
); | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor - we could update the |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,6 @@ const data = [ | |
type: 'plugin' as const, | ||
}, | ||
fixedIn: '3.2.4', | ||
status: 'current' as const, | ||
}, | ||
]; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
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 ] | ||
); | ||
|
||
try { | ||
if ( ! ( activeThreatsCount + historicThreatsCount ) ) { | ||
return null; | ||
} | ||
|
||
let selectedValue = ''; | ||
if ( isStatusFilterSelected( [ 'current' ] ) ) { | ||
selectedValue = 'active'; | ||
} else if ( isStatusFilterSelected( [ 'fixed', 'ignored' ] ) ) { | ||
selectedValue = 'historic'; | ||
} | ||
|
||
dkmyta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea - could we extract the
<ThreatsStatusToggleGroupControl>
component into a separate file?It could be passed
data: Threats[]
andview: View
properties, and compute/memoize its own values for counts/selected/etc.It could also accept the existing
onChangeView: ( newView: View ) => void
callback, and call it with the full updated view objects on click.The objective being to keep this main index file clean and straightforward, and isolate this custom functionality in its own file. Using the data/view/onChangeView properties directly essentially make it like a mini plugin/extension for the DataViews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second idea - since these are experimental components, what do you think about wrapping this component with a
try/catch
or React Error Boundary?This will not help in the case of import errors, but if any breaking changes are missed, it will prevent the component from crashing the entire scan screen.