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: Update use fixers query error prop handling #39498

Merged
merged 13 commits into from
Oct 2, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Adds handling for FixerStatus error props
12 changes: 12 additions & 0 deletions projects/plugins/protect/src/js/data/scan/use-fixers-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ export default function useFixersMutation(): UseMutationResult {
return useMutation( {
mutationFn: API.fixThreats,
onSuccess: data => {
// Handle a top level error
if ( data.ok === false ) {
throw new Error( data.error );
}

const isThreatLevelError = Object.values( data.threats ).every( threat => 'error' in threat );

// Handle a threat level error
if ( isThreatLevelError ) {
throw new Error();
}

// The data returned from the API is the same as the data we need to update the cache.
queryClient.setQueryData( [ QUERY_FIXERS_KEY ], data );

Expand Down
70 changes: 40 additions & 30 deletions projects/plugins/protect/src/js/data/scan/use-fixers-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import API from '../../api';
import { QUERY_FIXERS_KEY, QUERY_HISTORY_KEY, QUERY_SCAN_STATUS_KEY } from '../../constants';
import { fixerTimestampIsStale } from '../../hooks/use-fixers';
import useNotices from '../../hooks/use-notices';
import { FixersStatus } from '../../types/fixers';
import { FixersStatus, ThreatFixStatus } from '../../types/fixers';

const initialData: FixersStatus = window.jetpackProtectInitialState?.fixerStatus || {
ok: true,
Expand Down Expand Up @@ -74,33 +74,37 @@ export default function useFixersQuery( {
| FixersStatus
| undefined;

// Handle a top level error
if ( data.ok === false ) {
throw new Error( data.error );
}

const successes: string[] = [];
const failures: string[] = [];

Object.keys( data?.threats || {} ).forEach( threatId => {
Object.keys( data.threats || {} ).forEach( threatId => {
const threat = data.threats[ threatId ];
const cachedThreat = cachedData?.threats?.[ threatId ];

if ( cachedThreat?.status === 'in_progress' ) {
// If still in progress
if ( threat.status === 'in_progress' ) {
if (
! fixerTimestampIsStale( cachedThreat.last_updated ) &&
fixerTimestampIsStale( threat.last_updated )
) {
failures.push( threatId );
}
}

// Handle completion of fixers
if ( threat.status !== 'in_progress' ) {
queryClient.invalidateQueries( { queryKey: [ QUERY_SCAN_STATUS_KEY ] } );
queryClient.invalidateQueries( { queryKey: [ QUERY_HISTORY_KEY ] } );

if ( threat.status === 'fixed' ) {
successes.push( threatId );
if ( cachedData.ok === true ) {
const cachedThreat = cachedData.threats?.[ threatId ];

if ( cachedThreat && cachedThreat.status === 'in_progress' ) {
if ( threat.status === 'in_progress' ) {
if (
! fixerTimestampIsStale( cachedThreat.last_updated ) &&
fixerTimestampIsStale( threat.last_updated )
) {
failures.push( threatId );
}
} else {
failures.push( threatId );
queryClient.invalidateQueries( { queryKey: [ QUERY_SCAN_STATUS_KEY ] } );
queryClient.invalidateQueries( { queryKey: [ QUERY_HISTORY_KEY ] } );

if ( threat.status === 'fixed' ) {
successes.push( threatId );
} else {
failures.push( threatId );
}
}
}
}
Expand All @@ -117,15 +121,21 @@ export default function useFixersQuery( {
return false;
}

const inProgressNotStale = Object.values( query.state.data.threats ).some(
( threat: { status: string; last_updated: string } ) =>
threat.status === 'in_progress' && ! fixerTimestampIsStale( threat.last_updated )
);
const data = query.state.data;

if ( data.ok === true ) {
const inProgressNotStale = Object.values( data.threats ).some(
( threat: ThreatFixStatus ) =>
'status' in threat &&
threat.status === 'in_progress' &&
! fixerTimestampIsStale( threat.last_updated )
);

// Refetch while any threats are still in progress and not stale.
if ( inProgressNotStale ) {
// Refetch on a shorter interval first, then slow down if it is taking a while.
return query.state.dataUpdateCount < 5 ? 5000 : 15000;
// Refetch while any threats are still in progress and not stale.
if ( inProgressNotStale ) {
// Refetch on a shorter interval first, then slow down if it is taking a while.
return query.state.dataUpdateCount < 5 ? 5000 : 15000;
}
}

return false;
Expand Down
15 changes: 13 additions & 2 deletions projects/plugins/protect/src/js/hooks/use-fixers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => {
};

export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => {
return fixerStatus.status === 'in_progress' && fixerTimestampIsStale( fixerStatus.last_updated );
return (
'status' in fixerStatus &&
fixerStatus.status === 'in_progress' &&
fixerTimestampIsStale( fixerStatus.last_updated )
);
};

type UseFixersResult = {
Expand All @@ -40,13 +44,20 @@ export default function useFixers(): UseFixersResult {

const isThreatFixInProgress = useCallback(
( threatId: number ) => {
return fixersStatus?.threats?.[ threatId ]?.status === 'in_progress';
if ( fixersStatus.ok === false ) {
return false;
}
const threatFix = fixersStatus.threats?.[ threatId ];
return threatFix && 'status' in threatFix && threatFix.status === 'in_progress';
},
[ fixersStatus ]
);

const isThreatFixStale = useCallback(
( threatId: number ) => {
if ( fixersStatus.ok === false ) {
return false;
}
const threatFixStatus = fixersStatus?.threats?.[ threatId ];
return threatFixStatus ? fixerStatusIsStale( threatFixStatus ) : false;
},
Expand Down
39 changes: 33 additions & 6 deletions projects/plugins/protect/src/js/types/fixers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
export type FixerStatus = 'not_started' | 'in_progress' | 'fixed' | 'not_fixed';

export type FixersStatus = {
ok: boolean;
threats: {
[ key: number ]: ThreatFixStatus;
};
/**
* Threat Fix Status
*
* Individual fixer status for a threat.
*/
export type ThreatFixStatusError = {
error: string;
};

export type ThreatFixStatus = {
export type ThreatFixStatusSuccess = {
status: FixerStatus;
last_updated: string;
};

export type ThreatFixStatus = ThreatFixStatusError | ThreatFixStatusSuccess;

/**
* Fixers Status
*
* Overall status of all fixers.
*/
type FixersStatusBase = {
ok: boolean; // Discriminator for overall success
};

export type FixersStatusError = FixersStatusBase & {
ok: false;
error: string;
};

export type FixersStatusSuccess = FixersStatusBase & {
ok: true;
threats: {
[ key: number ]: ThreatFixStatus;
};
};

export type FixersStatus = FixersStatusSuccess | FixersStatusError;