Skip to content

Commit

Permalink
Protect: Add fixer status to initial state (#39125)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkmyta authored and gogdzl committed Oct 25, 2024
1 parent 4406f9d commit 76555c4
Show file tree
Hide file tree
Showing 15 changed files with 90 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Adds a fixable_threats status property
7 changes: 7 additions & 0 deletions projects/packages/protect-models/src/class-status-model.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class Status_Model {
*/
public $status;

/**
* List of fixable threat IDs.
*
* @var string[]
*/
public $fixable_threat_ids = array();

/**
* WordPress core status.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Adds a fixable_threats status property
4 changes: 4 additions & 0 deletions projects/packages/protect-status/src/class-scan-status.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ private static function normalize_api_data( $scan_data ) {

if ( isset( $scan_data->threats ) && is_array( $scan_data->threats ) ) {
foreach ( $scan_data->threats as $threat ) {
if ( isset( $threat->fixable ) && $threat->fixable ) {
$status->fixable_threat_ids[] = $threat->id;
}

if ( isset( $threat->extension->type ) ) {
if ( 'plugin' === $threat->extension->type ) {
// add the extension if it does not yet exist in the status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public function get_sample_status() {
'num_plugins_threats' => 1,
'num_themes_threats' => 0,
'status' => 'idle',
'fixable_threat_ids' => array( '69353714' ),
'plugins' => array(
new Extension_Model(
array(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Adds fixer status to the initial state
8 changes: 6 additions & 2 deletions projects/plugins/protect/src/class-jetpack-protect.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Automattic\Jetpack\Protect\REST_Controller;
use Automattic\Jetpack\Protect\Scan_History;
use Automattic\Jetpack\Protect\Site_Health;
use Automattic\Jetpack\Protect\Threats;
use Automattic\Jetpack\Protect_Status\Plan;
use Automattic\Jetpack\Protect_Status\Protect_Status;
use Automattic\Jetpack\Protect_Status\Scan_Status;
Expand Down Expand Up @@ -206,12 +207,15 @@ public function initial_state() {
global $wp_version;
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$refresh_status_from_wpcom = isset( $_GET['checkPlan'] );
$initial_state = array(
$status = Status::get_status( $refresh_status_from_wpcom );

$initial_state = array(
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'credentials' => Credentials::get_credential_array(),
'status' => Status::get_status( $refresh_status_from_wpcom ),
'status' => $status,
'fixerStatus' => Threats::fix_threats_status( $status->fixable_threat_ids ),
'scanHistory' => Scan_History::get_scan_history( $refresh_status_from_wpcom ),
'installedPlugins' => Plugins_Installer::get_plugins(),
'installedThemes' => Sync_Functions::get_themes(),
Expand Down
4 changes: 4 additions & 0 deletions projects/plugins/protect/src/class-threats.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ public static function fix_threats( $threat_ids ) {
* @return bool|array
*/
public static function fix_threats_status( $threat_ids ) {
if ( empty( $threat_ids ) ) {
return false;
}

$api_base = self::get_api_base();
if ( is_wp_error( $api_base ) ) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
display: flex;
border-radius: var( --jp-border-radius ); // 4px
overflow: hidden;
z-index: 1;

&.notice--info {
border-left: 4px solid var( --jp-yellow-20 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const PaidAccordionItem = ( {
[ styles[ 'accordion-body-close' ] ]: ! open,
} );

const { fixersStatus } = useFixers();
const { fixInProgressThreatIds } = useFixers();

const handleClick = useCallback( () => {
if ( ! open ) {
Expand Down Expand Up @@ -122,7 +122,7 @@ export const PaidAccordionItem = ( {
<div>
{ fixable && (
<>
{ fixersStatus?.threats?.[ id ]?.status === 'in_progress' ? (
{ fixInProgressThreatIds.includes( id ) ? (
<Spinner color="black" />
) : (
<Icon icon={ check } className={ styles[ 'icon-check' ] } size={ 28 } />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const ThreatAccordionItem = ( {
const { setModal } = useModal();
const { recordEvent } = useAnalyticsTracks();

const { fixersStatus } = useFixers();
const fixerInProgress = fixersStatus?.threats?.[ id ]?.status === 'in_progress';
const { fixInProgressThreatIds } = useFixers();
const fixerInProgress = fixInProgressThreatIds.includes( id );

const learnMoreButton = source ? (
<Button variant="link" isExternalLink={ true } weight="regular" href={ source }>
Expand Down Expand Up @@ -145,7 +145,6 @@ const ThreatAccordionItem = ( {
isDestructive={ true }
variant="secondary"
onClick={ handleUnignoreThreatClick() }
disabled={ fixerInProgress }
>
{ __( 'Unignore threat', 'jetpack-protect' ) }
</Button>
Expand All @@ -156,6 +155,7 @@ const ThreatAccordionItem = ( {
isDestructive={ true }
variant="secondary"
onClick={ handleIgnoreThreatClick() }
disabled={ fixerInProgress }
>
{ __( 'Ignore threat', 'jetpack-protect' ) }
</Button>
Expand Down
33 changes: 30 additions & 3 deletions projects/plugins/protect/src/js/data/scan/use-fixers-query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useConnection } from '@automattic/jetpack-connection';
import { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
import { __ } from '@wordpress/i18n';
import { useEffect, useMemo } from 'react';
import API from '../../api';
import { QUERY_FIXERS_KEY, QUERY_HISTORY_KEY, QUERY_SCAN_STATUS_KEY } from '../../constants';
import useNotices from '../../hooks/use-notices';
Expand Down Expand Up @@ -31,12 +32,23 @@ export default function useFixersQuery( {
skipUserConnection: true,
} );

return useQuery( {
// Memoize initialData to prevent recalculating on every render
const initialData: FixersStatus = useMemo(
() =>
window.jetpackProtectInitialState?.fixerStatus || {
ok: true,
threats: {},
},
[]
);

const fixersQuery = useQuery( {
queryKey: [ QUERY_FIXERS_KEY ],
queryFn: async () => {
// Fetch fixer status from API
const data = await API.getFixersStatus( threatIds );
const cachedData = queryClient.getQueryData( [ QUERY_FIXERS_KEY ] ) as
| { threats: object }
| FixersStatus
| undefined;

// Check if any fixers have completed, by comparing the latest data against the cache.
Expand All @@ -63,8 +75,10 @@ export default function useFixersQuery( {
}
} );

// Return the fetched data so the query resolves
return data;
},
retry: false,
refetchInterval( query ) {
if ( ! usePolling || ! query.state.data ) {
return false;
Expand All @@ -82,7 +96,20 @@ export default function useFixersQuery( {

return false;
},
initialData: { threats: {} }, // to do: provide initial data in window.jetpackProtectInitialState
initialData: initialData,
enabled: isRegistered,
} );

// Handle error if present in the query result
useEffect( () => {
if ( fixersQuery.isError && fixersQuery.error ) {
// Reset the query data to initial state
queryClient.setQueryData( [ QUERY_FIXERS_KEY ], initialData );

// Show an error notice
showErrorNotice( __( 'An error occurred while fetching fixers status.', 'jetpack-protect' ) );
}
}, [ fixersQuery.isError, fixersQuery.error, queryClient, initialData, showErrorNotice ] );

return fixersQuery;
}
33 changes: 16 additions & 17 deletions projects/plugins/protect/src/js/hooks/use-fixers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import useFixersMutation from '../data/scan/use-fixers-mutation';
import useFixersQuery from '../data/scan/use-fixers-query';
import useScanStatusQuery from '../data/scan/use-scan-status-query';
import { FixersStatus } from '../types/fixers';
import { Threat } from '../types/threats';

type UseFixersResult = {
fixableThreats: Threat[];
fixableThreatIds: number[];
fixInProgressThreatIds: number[];
fixersStatus: FixersStatus;
fixThreats: ( threatIds: number[] ) => Promise< unknown >;
isLoading: boolean;
Expand All @@ -20,26 +20,25 @@ type UseFixersResult = {
export default function useFixers(): UseFixersResult {
const { data: status } = useScanStatusQuery();
const fixersMutation = useFixersMutation();

const fixableThreats = useMemo( () => {
const threats = [
...( status?.core?.threats || [] ),
...( status?.plugins?.map( plugin => plugin.threats ).flat() || [] ),
...( status?.themes?.map( theme => theme.threats ).flat() || [] ),
...( status?.files || [] ),
...( status?.database || [] ),
];

return threats.filter( threat => threat.fixable );
}, [ status ] );

const { data: fixersStatus } = useFixersQuery( {
threatIds: fixableThreats.map( threat => threat.id ),
threatIds: status.fixableThreatIds,
usePolling: true,
} );

// List of threat IDs that are currently being fixed.
const fixInProgressThreatIds = useMemo(
() =>
Object.entries( fixersStatus?.threats || {} )
.filter(
( [ , threat ]: [ string, { status?: string } ] ) => threat.status === 'in_progress'
)
.map( ( [ id ] ) => parseInt( id ) ),
[ fixersStatus ]
);

return {
fixableThreats,
fixableThreatIds: status.fixableThreatIds,
fixInProgressThreatIds,
fixersStatus,
fixThreats: fixersMutation.mutateAsync,
isLoading: fixersMutation.isPending,
Expand Down
1 change: 1 addition & 0 deletions projects/plugins/protect/src/js/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ declare global {
registrationNonce: string;
credentials: [ Record< string, unknown > ];
status: ScanStatus;
fixerStatus: FixersStatus;
scanHistory: ScanStatus;
installedPlugins: {
[ key: string ]: PluginData;
Expand Down
3 changes: 3 additions & 0 deletions projects/plugins/protect/src/js/types/scans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export type ScanStatus = {
/** The current status of the scanner. */
status: 'unavailable' | 'provisioning' | 'idle' | 'scanning' | 'scheduled';

/** The IDs of fixable threats. */
fixableThreatIds: number[];

/** The current scan progress, only available from the Scan API. */
current_progress: number | null;

Expand Down

0 comments on commit 76555c4

Please sign in to comment.