Skip to content

Commit

Permalink
Do not show scan error when optimistically scanning (#38703)
Browse files Browse the repository at this point in the history
  • Loading branch information
nateweller authored Aug 6, 2024
1 parent 6f15e10 commit 9d976a8
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SCAN_STATUS_SCHEDULED } from '../../../../constants';

export const jetpackProtectInitialState = {
apiRoot: 'http://localhost/wp-json/',
apiNonce: 'f2d2d42e2a',
Expand All @@ -7,7 +9,7 @@ export const jetpackProtectInitialState = {
num_threats: 6,
num_plugins_threats: 3,
num_themes_threats: 3,
status: 'scheduled',
status: SCAN_STATUS_SCHEDULED,
wordpress: {
version: '5.9.3',
threats: [],
Expand Down
29 changes: 19 additions & 10 deletions projects/plugins/protect/src/js/components/pricing-table/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const ConnectedPricingTable = ( { onScanAdd } ) => {
skipUserConnection: true,
} );

const { refreshPlan, refreshStatus } = useDispatch( STORE_ID );
const { refreshPlan, refreshStatus, startScanOptimistically } = useDispatch( STORE_ID );

const [ getProtectFreeButtonIsLoading, setGetProtectFreeButtonIsLoading ] = useState( false );
const [ getScanButtonIsLoading, setGetScanButtonIsLoading ] = useState( false );
Expand All @@ -54,17 +54,26 @@ const ConnectedPricingTable = ( { onScanAdd } ) => {
onScanAdd();
} );

const getProtectFree = useCallback( () => {
const getProtectFree = useCallback( async () => {
recordEvent( 'jetpack_protect_connected_product_activated' );
setGetProtectFreeButtonIsLoading( true );
handleRegisterSite()
.then( () => setGetProtectFreeButtonIsLoading( false ) )
.then( () => {
refreshPlan();
refreshWaf();
refreshStatus( true );
} );
}, [ handleRegisterSite, recordEvent, refreshWaf, refreshPlan, refreshStatus ] );
try {
await handleRegisterSite();
startScanOptimistically();
await refreshPlan();
await refreshWaf();
await refreshStatus( true );
} finally {
setGetProtectFreeButtonIsLoading( false );
}
}, [
handleRegisterSite,
recordEvent,
refreshWaf,
refreshPlan,
refreshStatus,
startScanOptimistically,
] );

const args = {
title: __( 'Stay one step ahead of threats', 'jetpack-protect' ),
Expand Down
14 changes: 14 additions & 0 deletions projects/plugins/protect/src/js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,17 @@ export const FREE_PLUGIN_SUPPORT_URL = 'https://wordpress.org/support/plugin/jet
export const PAID_PLUGIN_SUPPORT_URL = 'https://jetpack.com/contact-support/?rel=support';

export const JETPACK_SCAN_SLUG = 'jetpack_scan';

/**
* Scan Status Constants
*/
export const SCAN_STATUS_SCHEDULED = 'scheduled';
export const SCAN_STATUS_SCANNING = 'scanning';
export const SCAN_STATUS_OPTIMISTICALLY_SCANNING = 'optimistically_scanning';
export const SCAN_STATUS_IDLE = 'idle';
export const SCAN_STATUS_UNAVAILABLE = 'unavailable';
export const SCAN_IN_PROGRESS_STATUSES = [
SCAN_STATUS_SCHEDULED,
SCAN_STATUS_SCANNING,
SCAN_STATUS_OPTIMISTICALLY_SCANNING,
];
45 changes: 20 additions & 25 deletions projects/plugins/protect/src/js/routes/scan/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ScanFooter from '../../components/scan-footer';
import SeventyFiveLayout from '../../components/seventy-five-layout';
import Summary from '../../components/summary';
import ThreatsList from '../../components/threats-list';
import { SCAN_STATUS_UNAVAILABLE } from '../../constants';
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import { OnboardingContext } from '../../hooks/use-onboarding';
import useProtectData from '../../hooks/use-protect-data';
Expand Down Expand Up @@ -72,6 +73,7 @@ const ErrorSection = ( { errorMessage, errorCode } ) => {
};

const ScanningSection = ( { currentProgress } ) => {
const { hasRequiredPlan } = useProtectData();
const { stats } = useWafData();
const { globalStats } = stats;
const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities );
Expand Down Expand Up @@ -105,7 +107,7 @@ const ScanningSection = ( { currentProgress } ) => {
<H3 style={ { textWrap: 'balance' } }>
{ __( 'Your results will be ready soon', 'jetpack-protect' ) }
</H3>
{ currentProgress !== null && currentProgress >= 0 && (
{ hasRequiredPlan && currentProgress !== null && currentProgress >= 0 && (
<ProgressBar value={ currentProgress } />
) }
<Text>
Expand Down Expand Up @@ -152,16 +154,20 @@ const DefaultSection = () => {
};

const ScanPage = () => {
const { lastChecked, error, errorCode, errorMessage, hasRequiredPlan } = useProtectData();
const { lastChecked, hasRequiredPlan } = useProtectData();
const { refreshStatus } = useDispatch( STORE_ID );
const { statusIsFetching, scanIsUnavailable, status } = useSelect( select => ( {
statusIsFetching: select( STORE_ID ).getStatusIsFetching(),
scanIsUnavailable: select( STORE_ID ).getScanIsUnavailable(),
status: select( STORE_ID ).getStatus(),
} ) );
const { scanInProgress, statusIsFetching, scanIsUnavailable, status, scanError } = useSelect(
select => ( {
scanError: select( STORE_ID ).scanError(),
scanInProgress: select( STORE_ID ).scanInProgress(),
scanIsUnavailable: select( STORE_ID ).getScanIsUnavailable(),
status: select( STORE_ID ).getStatus(),
statusIsFetching: select( STORE_ID ).getStatusIsFetching(),
} )
);

let currentScanStatus;
if ( status.error || scanIsUnavailable ) {
if ( scanError ) {
currentScanStatus = 'error';
} else if ( ! lastChecked ) {
currentScanStatus = 'in_progress';
Expand All @@ -183,33 +189,22 @@ const ScanPage = () => {

// retry fetching status if it is not available
useEffect( () => {
if ( ! statusIsFetching && 'unavailable' === status.status && ! scanIsUnavailable ) {
if ( ! statusIsFetching && SCAN_STATUS_UNAVAILABLE === status.status && ! scanIsUnavailable ) {
refreshStatus( true );
}
}, [ statusIsFetching, status.status, refreshStatus, scanIsUnavailable ] );

const renderSection = useMemo( () => {
// Error
if ( error || scanIsUnavailable ) {
return <ErrorSection errorMessage={ errorMessage } errorCode={ errorCode } />;
if ( scanInProgress ) {
return <ScanningSection currentProgress={ status.currentProgress } />;
}

// Scanning
const scanningStatuses = new Set( [ 'scheduled', 'scanning', 'optimistically_scanning' ] );
if ( scanningStatuses.has( status.status ) || ! lastChecked ) {
return <ScanningSection currentProgress={ status.currentProgress } />;
if ( scanError ) {
return <ErrorSection errorMessage={ scanError.message } errorCode={ scanError.code } />;
}

return <DefaultSection />;
}, [
error,
errorMessage,
errorCode,
scanIsUnavailable,
status.status,
status.currentProgress,
lastChecked,
] );
}, [ scanInProgress, status.currentProgress, scanError ] );

return (
<OnboardingContext.Provider value={ onboardingSteps }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import apiFetch from '@wordpress/api-fetch';
import { useDispatch, useSelect } from '@wordpress/data';
import camelize from 'camelize';
import { useEffect } from 'react';
import {
SCAN_IN_PROGRESS_STATUSES,
SCAN_STATUS_IDLE,
SCAN_STATUS_UNAVAILABLE,
} from '../../constants';
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import { STORE_ID } from '../../state/store';

Expand All @@ -20,11 +25,11 @@ const useStatusPolling = () => {
const pollDuration = 10000;

const statusIsInProgress = currentStatus =>
[ 'scheduled', 'scanning' ].indexOf( currentStatus ) >= 0;
SCAN_IN_PROGRESS_STATUSES.indexOf( currentStatus ) >= 0;

// if there has never been a scan, and the scan status is idle, then we must still be getting set up
const scanIsInitializing = ( currentStatus, lastChecked ) =>
! lastChecked && currentStatus === 'idle';
! lastChecked && SCAN_STATUS_IDLE === currentStatus;

const pollStatus = () => {
return new Promise( ( resolve, reject ) => {
Expand Down Expand Up @@ -74,7 +79,7 @@ const useStatusPolling = () => {
setStatusIsFetching( true );
pollStatus()
.then( newStatus => {
setScanIsUnavailable( 'unavailable' === newStatus.status );
setScanIsUnavailable( SCAN_STATUS_UNAVAILABLE === newStatus.status );
setStatus( camelize( newStatus ) );
recordEvent( 'jetpack_protect_scan_completed', {
scan_status: newStatus.status,
Expand Down
5 changes: 3 additions & 2 deletions projects/plugins/protect/src/js/state/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import apiFetch from '@wordpress/api-fetch';
import { sprintf, _n, __ } from '@wordpress/i18n';
import camelize from 'camelize';
import API from '../api';
import { SCAN_STATUS_UNAVAILABLE } from '../constants';

const SET_CREDENTIALS_STATE_IS_FETCHING = 'SET_CREDENTIALS_STATE_IS_FETCHING';
const SET_CREDENTIALS_STATE = 'SET_CREDENTIALS_STATE';
Expand Down Expand Up @@ -84,7 +85,7 @@ const refreshStatus =
return fetchStatus( hardRefresh )
.then( checkStatus )
.then( status => {
dispatch( setScanIsUnavailable( 'unavailable' === status.status ) );
dispatch( setScanIsUnavailable( SCAN_STATUS_UNAVAILABLE === status.status ) );
dispatch( setStatus( camelize( status ) ) );
resolve( status );
} )
Expand Down Expand Up @@ -120,7 +121,7 @@ const refreshScanHistory = () => {
*/
const checkStatus = ( currentStatus, attempts = 0 ) => {
return new Promise( ( resolve, reject ) => {
if ( 'unavailable' === currentStatus.status && attempts < 3 ) {
if ( SCAN_STATUS_UNAVAILABLE === currentStatus.status && attempts < 3 ) {
fetchStatus( true )
.then( newStatus => {
setTimeout( () => {
Expand Down
3 changes: 2 additions & 1 deletion projects/plugins/protect/src/js/state/reducers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { combineReducers } from '@wordpress/data';
import camelize from 'camelize';
import { SCAN_STATUS_OPTIMISTICALLY_SCANNING } from '../constants';
import {
SET_CREDENTIALS_STATE,
SET_CREDENTIALS_STATE_IS_FETCHING,
Expand Down Expand Up @@ -61,7 +62,7 @@ const status = ( state = {}, action ) => {
case SET_STATUS_PROGRESS:
return { ...state, currentProgress: action.currentProgress };
case START_SCAN_OPTIMISTICALLY:
return { ...state, currentProgress: 0, status: 'optimistically_scanning' };
return { ...state, currentProgress: 0, status: SCAN_STATUS_OPTIMISTICALLY_SCANNING };
}
return state;
};
Expand Down
75 changes: 75 additions & 0 deletions projects/plugins/protect/src/js/state/selectors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,76 @@
import { __ } from '@wordpress/i18n';
import { SCAN_IN_PROGRESS_STATUSES, SCAN_STATUS_OPTIMISTICALLY_SCANNING } from '../constants';

/**
* Scan in progress selector.
*
* @param {object} state - The current state.
* @returns {boolean} Whether a scan is in progress.
*/
const scanInProgress = state => {
const { status, error, lastChecked } = selectors.getStatus( state );
const unavailable = selectors.getScanIsUnavailable( state );

// When "optimistically" scanning, ignore any other status or error.
if ( SCAN_STATUS_OPTIMISTICALLY_SCANNING === status ) {
return true;
}

// If there is an error or the scan is unavailable, scanning is not in progress.
if ( error || unavailable ) {
return false;
}

// If the status is one of the scanning statuses, or if we have never checked, we are scanning.
if ( SCAN_IN_PROGRESS_STATUSES.includes( status.status ) || ! lastChecked ) {
return true;
}

return false;
};

/**
* Scan error selector.
*
* @param {object} state - The current state.
*
* @typedef {object} ScanError
* @property {string} code - The code identifying the type of error.
* @property {string} message - A message describing the error.
*
* @returns {ScanError|null} The error object or null.
*/
const scanError = state => {
const { status, error, errorCode, errorMessage } = selectors.getStatus( state );
const unavailable = selectors.getScanIsUnavailable( state );
const isFetching = selectors.getStatusIsFetching( state );

// When "optimistically" scanning, ignore any errors.
if ( SCAN_STATUS_OPTIMISTICALLY_SCANNING === status ) {
return null;
}

// While still fetching the status, ignore any errors.
if ( isFetching ) {
return null;
}

// If the scan results include an error, return it.
if ( error ) {
return { code: errorCode, message: errorMessage };
}

// If the scan is unavailable, return an error.
if ( unavailable ) {
return {
code: 'scan_unavailable',
message: __( 'We are having problems scanning your site.', 'jetpack-protect' ),
};
}

return null;
};

const selectors = {
getCredentials: state => state.credentials || null,
getCredentialsIsFetching: state => state.credentialsIsFetching || false,
Expand All @@ -8,6 +81,8 @@ const selectors = {
getStatusIsFetching: state => state.statusIsFetching || false,
getScanIsUnavailable: state => state.scanIsUnavailable || false,
getScanIsEnqueuing: state => state.scanIsEnqueuing || false,
scanInProgress,
scanError,
getWpVersion: state => state.wpVersion || '',
getJetpackScan: state => state.jetpackScan || {},
getThreatsUpdating: state => state.threatsUpdating || {},
Expand Down

0 comments on commit 9d976a8

Please sign in to comment.