Skip to content
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: History fields preset #40580

Open
wants to merge 34 commits into
base: add/protect/core
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3c24030
Init project branch
nateweller Nov 14, 2024
be99f50
Protect: Add Go to Cloud and Scan now button to Protect primary heade…
dkmyta Nov 14, 2024
8dd5dde
Protect: Update Scan and History headers (#40058)
dkmyta Nov 14, 2024
90d6898
Protect: de-emphasize cloud link by using link variant (#40211)
nateweller Nov 18, 2024
dd21913
Protect: add ShieldIcon component
nateweller Nov 30, 2024
19c083e
Protect: Add ShieldIcon Component (#40402)
nateweller Dec 5, 2024
08cdcb2
Protect: Integrate ThreatsDataViews Component (#40076)
nateweller Dec 5, 2024
0a9047e
Components: Add ScanReport (#40419)
dkmyta Dec 5, 2024
aa9b8a3
Fix type errors
nateweller Dec 6, 2024
08d8de3
Protect: Refactor AdminSectionHero (#40516)
nateweller Dec 9, 2024
b33c95a
Protect: Update Scan History extension types (#40548)
dkmyta Dec 10, 2024
e13aa74
Protect: Add Home page (#40317)
dkmyta Dec 10, 2024
322819d
Protect: Integrate ScanReport (#40420)
dkmyta Dec 11, 2024
b397656
Add custom field set when viewing historic threats
dkmyta Dec 11, 2024
8e1c8ae
Init project branch
nateweller Nov 14, 2024
8a3dc9b
Protect: Add Go to Cloud and Scan now button to Protect primary heade…
dkmyta Nov 14, 2024
1c4ba1e
Protect: Update Scan and History headers (#40058)
dkmyta Nov 14, 2024
ef21cc4
Protect: de-emphasize cloud link by using link variant (#40211)
nateweller Nov 18, 2024
2f37bfc
Protect: add ShieldIcon component
nateweller Nov 30, 2024
ffdc98c
Protect: Add ShieldIcon Component (#40402)
nateweller Dec 5, 2024
71384f1
Protect: Integrate ThreatsDataViews Component (#40076)
nateweller Dec 5, 2024
4fccd4c
Components: Add ScanReport (#40419)
dkmyta Dec 5, 2024
f172be7
Fix type errors
nateweller Dec 6, 2024
d25db57
Protect: Refactor AdminSectionHero (#40516)
nateweller Dec 9, 2024
e86cfe1
Protect: Update Scan History extension types (#40548)
dkmyta Dec 10, 2024
08d2ba1
Protect: Add Home page (#40317)
dkmyta Dec 10, 2024
48fba20
Protect: Integrate ScanReport (#40420)
dkmyta Dec 11, 2024
ef6e5d0
Fix duplicate imports
nateweller Dec 15, 2024
62838d3
ScanReport: Fix defaultLayout (#40603)
dkmyta Dec 15, 2024
a877b8c
Update onboarding popover placement (#40550)
dkmyta Dec 15, 2024
7a47485
Adjust approach
dkmyta Dec 16, 2024
87f5b71
Rebase
dkmyta Dec 16, 2024
130eaf4
Fix JSDocs
dkmyta Dec 17, 2024
a1018bf
Rebase
dkmyta Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ 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 DEFAULT_TABLE_FIELDS = [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_THREAT,
THREAT_FIELD_TYPE,
THREAT_FIELD_FIRST_DETECTED,
THREAT_FIELD_FIXED_ON,
THREAT_FIELD_STATUS,
THREAT_FIELD_AUTO_FIX,
];

export const DEFAULT_LIST_FIELDS = [
THREAT_FIELD_SEVERITY,
THREAT_FIELD_TYPE,
THREAT_FIELD_EXTENSION,
THREAT_FIELD_SIGNATURE,
THREAT_FIELD_STATUS,
];

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 { getThreatType, getFixerAction, 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 {
DEFAULT_TABLE_FIELDS,
DEFAULT_LIST_FIELDS,
THREAT_ACTION_FIX,
THREAT_ACTION_IGNORE,
THREAT_ACTION_UNIGNORE,
Expand Down Expand Up @@ -100,19 +102,14 @@ export default function ThreatsDataViews( {
const defaultLayouts: SupportedLayouts = {
table: {
...baseView,
fields: [ THREAT_FIELD_SEVERITY, THREAT_FIELD_TYPE, THREAT_FIELD_AUTO_FIX ],
fields: DEFAULT_TABLE_FIELDS,
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: DEFAULT_LIST_FIELDS,
titleField: THREAT_FIELD_TITLE,
mediaField: THREAT_FIELD_ICON,
showMedia: true,
Expand All @@ -129,6 +126,28 @@ export default function ThreatsDataViews( {
...defaultLayouts.table,
} );

/**
* Compute the status filters from the view filters.
*/
const statusFilters = useMemo(
() =>
view.filters.reduce( ( acc, filter ) => {
if ( filter.field === THREAT_FIELD_STATUS ) {
if ( Array.isArray( filter.value ) ) {
for ( const value of filter.value ) {
if ( ! acc.includes( value ) ) {
acc.push( value );
}
}
} else if ( ! acc.includes( filter.value ) ) {
acc.push( filter.value );
}
}
return acc;
}, [] ),
[ view.filters ]
);

/**
* Compute values from the provided threats data.
*
Expand Down Expand Up @@ -238,31 +257,10 @@ 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' ),
enableHiding: false,
elements: THREAT_TYPES,
getValue( { item }: { item: Threat } ) {
return getThreatType( item ) ?? '';
Expand Down Expand Up @@ -300,6 +298,34 @@ export default function ThreatsDataViews( {
return item.extension ? item.extension.slug : '';
},
},
...( dataFields.includes( 'status' )
? [
{
id: THREAT_FIELD_STATUS,
label: __( 'Status', 'jetpack-components' ),
enableHiding: false,
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>;
},
},
]
: [] ),
...( dataFields.includes( 'severity' )
? [
{
Expand Down Expand Up @@ -328,7 +354,8 @@ export default function ThreatsDataViews( {
},
]
: [] ),
...( dataFields.includes( 'firstDetected' )
...( dataFields.includes( 'firstDetected' ) &&
( statusFilters.includes( 'fixed' ) || statusFilters.includes( 'ignored' ) )
? [
Comment on lines +357 to +358
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious if there's a possible alternative configuration that would allow us to still include the "first detected" field regardless of the current filters, but hide/show the particular field in the table view based on the current filters, instead of omitting the definition of the fields entirely. I'll follow up on this!

Copy link
Contributor Author

@dkmyta dkmyta Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a similar hope, and did come across isVisible which I thought might be an option but seemed to not function as I expected it to.

Copy link
Contributor

@nateweller nateweller Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use isVisible? Edit: ah, that might be a per-item method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on a potential approach using the onChangeView callback, will share shortly!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up that I did start with the onChangeView approach and it seemed to have its own limitations - IIRC there were challenges related to not being able to check the current view state against the changed view state because it was always outdated. Regardless, interested to see what you think might work reintroducing that take.

{
id: THREAT_FIELD_FIRST_DETECTED,
Expand All @@ -347,7 +374,7 @@ export default function ThreatsDataViews( {
},
]
: [] ),
...( dataFields.includes( 'fixedOn' )
...( dataFields.includes( 'fixedOn' ) && statusFilters.includes( 'fixed' )
? [
{
id: THREAT_FIELD_FIXED_ON,
Expand All @@ -366,7 +393,8 @@ export default function ThreatsDataViews( {
},
]
: [] ),
...( dataFields.includes( 'fixable' )
...( dataFields.includes( 'fixable' ) &&
( statusFilters.includes( 'current' ) || ! statusFilters.length )
? [
{
id: THREAT_FIELD_AUTO_FIX,
Expand Down Expand Up @@ -398,7 +426,7 @@ export default function ThreatsDataViews( {
];

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

/**
* DataView actions - collection of operations that can be performed upon each record.
Expand All @@ -408,10 +436,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 +496,7 @@ export default function ThreatsDataViews( {

return result;
}, [
view.type,
dataFields,
onFixThreats,
onIgnoreThreats,
Expand Down Expand Up @@ -515,6 +546,7 @@ export default function ThreatsDataViews( {
<ThreatsStatusToggleGroupControl
data={ data }
view={ view }
statusFilters={ statusFilters }
onChangeView={ onChangeView }
/>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ 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.
* @param {object} props - Component props.
* @param { Threat[]} props.data - Threats data.
* @param { View } props.view - The current view.
* @param { ThreatStatus[] } props.statusFilters - The current status filter value.
* @param { Function } props.onChangeView - Callback function to handle view changes.
*
* @return {JSX.Element|null} The component or null.
*/
export default function ThreatsStatusToggleGroupControl( {
data,
view,
statusFilters,
onChangeView,
}: {
data: Threat[];
view: View;
statusFilters: ThreatStatus[];
onChangeView: ( newView: View ) => void;
} ): JSX.Element {
/**
Expand Down Expand Up @@ -88,31 +92,19 @@ export default function ThreatsStatusToggleGroupControl( {
);

/**
* Memoized function to determine if a status filter is selected.
* Get the selected status filters.
*
* @param {Array} threatStatuses - List of threat statuses.
* @return {string|null} The selected status filter.
*/
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;
const selectedStatusFilterPreset = useMemo( () => {
if ( JSON.stringify( statusFilters ) === JSON.stringify( [ 'current' ] ) ) {
return 'active';
}
if ( isStatusFilterSelected( [ 'fixed', 'ignored' ] ) ) {
return 'historic' as const;
if ( JSON.stringify( statusFilters ) === JSON.stringify( [ 'fixed', 'ignored' ] ) ) {
return 'historic';
}
return '' as const;
}, [ isStatusFilterSelected ] );
return null;
}, [ statusFilters ] );

if ( ! ( activeThreatsCount + historicThreatsCount ) ) {
return null;
Expand All @@ -123,7 +115,7 @@ export default function ThreatsStatusToggleGroupControl( {
<div>
<div className={ styles[ 'toggle-group-control' ] }>
<ToggleGroupControl
value={ selectedValue }
value={ selectedStatusFilterPreset }
onChange={ onStatusFilterChange }
isBlock
hideLabelFromVision
Expand Down
3 changes: 2 additions & 1 deletion projects/plugins/protect/src/js/routes/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const HomePage = () => {
[ status ]
);

const showReport = !! status.lastChecked || SCAN_IN_PROGRESS_STATUSES.indexOf( status?.status ) >= 0;
const showReport =
!! status.lastChecked || SCAN_IN_PROGRESS_STATUSES.indexOf( status?.status ) >= 0;

return (
<AdminPage>
Expand Down
Loading