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

Protect: Add fixer status to initial state #39125

Merged
merged 67 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
cfe4e3b
Protect: React Query
nateweller Aug 20, 2024
07f4237
Add fixerStatus to initial state
dkmyta Aug 28, 2024
c3de3f7
changelog
dkmyta Aug 28, 2024
d4b71ee
Fix in_progress fixer state
dkmyta Aug 28, 2024
9758e3b
Fix tests
dkmyta Aug 28, 2024
ba489fe
Fix fixThreats apiFetch call
dkmyta Aug 28, 2024
a392ec1
Do not camelize fixerStatus in useFixersQuery initialData
dkmyta Aug 28, 2024
b510e1e
Protect: React Query
nateweller Aug 20, 2024
49fda28
Merge base
dkmyta Aug 28, 2024
3695e1a
Use fixableThreats prop from scan status
dkmyta Aug 28, 2024
4eac461
Protect: React Query
nateweller Aug 20, 2024
d5a02c8
Protect: React Query
nateweller Aug 20, 2024
25b5a39
Merge base
dkmyta Aug 28, 2024
29bfbd3
Merge base
dkmyta Aug 28, 2024
26c3d28
Fix merge errors
dkmyta Aug 28, 2024
a054033
Ensure fixer status polling occurs when in_progress fixers exist
dkmyta Aug 28, 2024
3501471
Protect: React Query
nateweller Aug 20, 2024
06ce482
Merge base
dkmyta Aug 28, 2024
6ca566f
Invalidate scan status query on fixer status query success
dkmyta Aug 28, 2024
a36715f
Protect: React Query
nateweller Aug 20, 2024
1e71db5
Merge base
dkmyta Aug 28, 2024
3e4e594
Provide useFixersQuery threatIds default value
dkmyta Aug 28, 2024
78f9445
Protect: React Query
nateweller Aug 20, 2024
6dbd810
Merge base
dkmyta Aug 29, 2024
423d69f
Reorder
dkmyta Aug 29, 2024
a66ea68
Account for fixerStatus being false in useFixers hook
dkmyta Aug 29, 2024
517e486
Protect: React Query
nateweller Aug 20, 2024
ab98802
Temporarily disable optimistically setting fixer status
dkmyta Aug 29, 2024
d4a3275
Merge base
dkmyta Aug 29, 2024
4ac4cb7
Protect: React Query
nateweller Aug 20, 2024
d96c395
Fix conflicts
dkmyta Aug 29, 2024
7008ddf
Simplify QUERY_FIXERS_KEY, and update setQueryData return formatting
dkmyta Aug 29, 2024
696be85
Fixes and improvements
dkmyta Aug 29, 2024
9a9d7c8
Protect: React Query
nateweller Aug 20, 2024
13fa583
Rebase, fix conflicts
dkmyta Aug 30, 2024
80be81a
Update fixerInProgress logic
dkmyta Aug 30, 2024
59accac
Conflict corrections
dkmyta Aug 30, 2024
17a4927
Fix fixInProgressThreatIds logic
dkmyta Aug 30, 2024
0aa61cd
Fix fixable_threat_ids type
dkmyta Aug 30, 2024
774d5a5
Update property name
dkmyta Aug 30, 2024
09cb682
Fix useFixersQuery cachedData check logic
dkmyta Aug 30, 2024
0f9c56b
Protect: React Query
nateweller Aug 20, 2024
cc09189
Merge base, fix conflicts
dkmyta Aug 30, 2024
ca111cf
Protect: React Query
nateweller Aug 20, 2024
030c5e0
Fix conflicts
dkmyta Aug 31, 2024
160323a
Protect: React Query
nateweller Aug 20, 2024
20cf5d8
Merge base, fix conflicts
dkmyta Sep 3, 2024
3e9a641
Handle fixer status optimistically
dkmyta Sep 3, 2024
bbc404b
Add removed comment
dkmyta Sep 3, 2024
63ed7b3
Remove file
dkmyta Sep 3, 2024
eba64a8
Fix types
dkmyta Sep 3, 2024
f958d78
Fix docblocks
dkmyta Sep 4, 2024
a098021
Revert unintended changes
dkmyta Sep 5, 2024
a294482
Protect: React Query
nateweller Aug 20, 2024
da226ee
Merge base, fix conflicts
dkmyta Sep 6, 2024
ee9f0d5
Merge trunk
dkmyta Sep 9, 2024
118821a
changelog
dkmyta Sep 9, 2024
c44e4be
Improve setting fixer status optimistically
dkmyta Sep 9, 2024
7992a92
Handle possible API returns
dkmyta Sep 9, 2024
d29f8e6
Merge branch 'trunk' into add/protect-fixer-status-to-initial-state
dkmyta Sep 10, 2024
593d1cb
Revert fixers mutation update
dkmyta Sep 11, 2024
77c1d50
Improve use fixers query error handling
dkmyta Sep 11, 2024
2e5be46
Update notice message
dkmyta Sep 11, 2024
12df4fe
Readd removed comment
dkmyta Sep 11, 2024
5b4bd22
Merge branch 'trunk' into add/protect-fixer-status-to-initial-state
dkmyta Sep 11, 2024
6ad1341
Update initial state default
dkmyta Sep 12, 2024
a9ddd99
Merge branch 'trunk' into add/protect-fixer-status-to-initial-state
dkmyta Sep 12, 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
@@ -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 @@ -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
30 changes: 27 additions & 3 deletions projects/plugins/protect/src/js/data/scan/use-fixers-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { __ } from '@wordpress/i18n';
import API from '../../api';
import { QUERY_FIXERS_KEY } from '../../constants';
import useNotices from '../../hooks/use-notices';
import { FixersStatus } from '../../types/fixers';

/**
* Fixers Mutatation Hook
Expand All @@ -15,9 +16,32 @@ export default function useFixersMutation(): UseMutationResult {

return useMutation( {
mutationFn: API.fixThreats,
onSuccess: data => {
// The data returned from the API is the same as the data we need to update the cache.
queryClient.setQueryData( [ QUERY_FIXERS_KEY ], data );
dkmyta marked this conversation as resolved.
Show resolved Hide resolved
onSuccess: async ( data, threatIds ) => {
// Get the current cached data for threats
const cachedData = queryClient.getQueryData( [ QUERY_FIXERS_KEY ] ) as
| FixersStatus
| undefined;

// Optimistically update the fixer status to 'in_progress' for the selected threats.
if ( cachedData && cachedData.threats ) {
// Create a copy of the threats data
const updatedData = { ...cachedData.threats };

threatIds.forEach( id => {
if ( updatedData[ id ] ) {
updatedData[ id ] = {
...updatedData[ id ],
status: 'in_progress',
};
}
} );

// Set the updated data back in the cache
queryClient.setQueryData( [ QUERY_FIXERS_KEY ], {
...cachedData,
threats: updatedData, // Replace the threats with the updated version
} );
}

// Show a success notice.
showSuccessNotice(
Expand Down
68 changes: 42 additions & 26 deletions projects/plugins/protect/src/js/data/scan/use-fixers-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,39 +31,55 @@ export default function useFixersQuery( {
skipUserConnection: true,
} );

const initialData: FixersStatus = window.jetpackProtectInitialState?.fixerStatus || {
ok: false,
threats: {},
};

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

// Check if any fixers have completed, by comparing the latest data against the cache.
Object.keys( data?.threats ).forEach( ( threatId: string ) => {
// Find the specific threat in the cached data.
const threat = data?.threats[ threatId ];
const cachedThreat = cachedData?.threats?.[ threatId ];
// Check if any fixers have completed, by comparing the latest data against the cache.
Object.keys( data?.threats ).forEach( ( threatId: string ) => {
const threat = data?.threats[ threatId ];
const cachedThreat = cachedData?.threats?.[ threatId ];

if (
cachedThreat &&
cachedThreat.status === 'in_progress' &&
threat.status !== 'in_progress'
) {
// Invalidate related queries when a fixer has completed.
queryClient.invalidateQueries( { queryKey: [ QUERY_SCAN_STATUS_KEY ] } );
queryClient.invalidateQueries( { queryKey: [ QUERY_HISTORY_KEY ] } );
if (
cachedThreat &&
cachedThreat.status === 'in_progress' &&
threat.status !== 'in_progress'
) {
// Invalidate related queries when a fixer has completed.
queryClient.invalidateQueries( { queryKey: [ QUERY_SCAN_STATUS_KEY ] } );
queryClient.invalidateQueries( { queryKey: [ QUERY_HISTORY_KEY ] } );

// Show a relevant notice.
if ( threat.status === 'fixed' ) {
showSuccessNotice( __( 'Threat fixed successfully.', 'jetpack-protect' ) );
} else {
showErrorNotice( __( 'Threat could not be fixed.', 'jetpack-protect' ) );
// Show a relevant notice.
if ( threat.status === 'fixed' ) {
showSuccessNotice( __( 'Threat fixed successfully.', 'jetpack-protect' ) );
} else {
showErrorNotice( __( 'Threat could not be fixed.', 'jetpack-protect' ) );
}
}
}
} );
} );

// Return the fetched data so the query resolves
return data;
} catch ( error ) {
// Handle the error, show notice, and return a default response
showErrorNotice(
__( 'An error occurred while fetching the fixer status.', 'jetpack-protect' )
);

return data;
// Return a default value or handle the error as needed.
return initialData;
}
dkmyta marked this conversation as resolved.
Show resolved Hide resolved
},
refetchInterval( query ) {
if ( ! usePolling || ! query.state.data ) {
Expand All @@ -82,7 +98,7 @@ export default function useFixersQuery( {

return false;
},
initialData: { threats: {} }, // to do: provide initial data in window.jetpackProtectInitialState
initialData: initialData,
enabled: isRegistered,
} );
}
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 ] );

Comment on lines -23 to -35
Copy link
Contributor

Choose a reason for hiding this comment

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

Very happy to delete this 😄

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
Loading