Skip to content

Commit

Permalink
Protect: Update fixer UI to handle long running fixers (#39301)
Browse files Browse the repository at this point in the history
* Protect: React Query

changelog

changelog

* Add fixerStatus to initial state

* changelog

* Fix in_progress fixer state

* Fix tests

* Fix fixThreats apiFetch call

* Do not camelize fixerStatus in useFixersQuery initialData

* Protect: React Query

changelog

changelog

* Use fixableThreats prop from scan status

* Protect: React Query

changelog

changelog

* Protect: React Query

* Fix merge errors

* Ensure fixer status polling occurs when in_progress fixers exist

* Protect: React Query

* Invalidate scan status query on fixer status query success

* Protect: React Query

* Provide useFixersQuery threatIds default value

* Protect: React Query

* Reorder

* Account for fixerStatus being false in useFixers hook

* Protect: React Query

* Temporarily disable optimistically setting fixer status

* Protect: React Query

* Simplify QUERY_FIXERS_KEY, and update setQueryData return formatting

* Fixes and improvements

* Protect: React Query

* Update fixerInProgress logic

* Conflict corrections

* Fix fixInProgressThreatIds logic

* Fix fixable_threat_ids type

* Update property name

* Fix useFixersQuery cachedData check logic

* Protect: React Query

* Protect: React Query

* Protect: React Query

* Handle fixer status optimistically

* Add removed comment

* Remove file

* Fix types

* Fix docblocks

* Revert unintended changes

* Protect: React Query

* changelog

* Handle long running fixers

* Improve setting fixer status optimistically

* Handle possible API returns

* Handle statuses for nonexistent fixers

* Filter fixable threats list for fix all threats modal

* Fixes and improvements to existing code

* Disable threat actions when fixer is stale

* Changelog entry

* Revert fixers mutation update

* Improve use fixers query error handling

* Update notice message

* Readd removed comment

* Fix naming

* Update initial state default

* Update initial state default

* Ensure FixerStatus type matches response structure

* Fix typo

* Fix logic - fixer cannot be active and stale

* Improvements

* Improve useFixersQuery

* Add comments

* Apply dummy arg to avoid bad minification issues

* Use clsx for conditional class names

* Use memoized value for fixableList

* Remove memoization of date

* Remove memoization of initial query data from window

* Centralize fixer logic and attempt to simplify the hook implementation

* Fix syntax errors

* Move stale fixer check to top of renderFixerStatus logic

---------

Co-authored-by: Nate Weller <hello@nateweller.com>
  • Loading branch information
dkmyta and nateweller committed Sep 23, 2024
1 parent 469ba8e commit cec62c8
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 185 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Adds handling for long running fixers
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import {
Button,
Status,
} from '@automattic/jetpack-components';
import { Spinner, Popover } from '@wordpress/components';
import { Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Icon, help } from '@wordpress/icons';
import React, { useState, useCallback } from 'react';
import { help } from '@wordpress/icons';
import React, { useCallback } from 'react';
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import usePlan from '../../hooks/use-plan';
import useWafData from '../../hooks/use-waf-data';
import IconTooltip from '../icon-tooltip';
import styles from './styles.module.scss';

const UpgradePrompt = () => {
Expand Down Expand Up @@ -44,51 +45,21 @@ const UpgradePrompt = () => {
);
};

const FirewallSubheadingPopover = ( {
children = __(
'The free version of the firewall does not receive updates to automatic security rules.',
'jetpack-protect'
),
} ) => {
const [ showPopover, setShowPopover ] = useState( false );

const handleEnter = useCallback( () => {
setShowPopover( true );
}, [] );

const handleOut = useCallback( () => {
setShowPopover( false );
}, [] );

return (
<div
className={ styles[ 'icon-popover' ] }
onMouseLeave={ handleOut }
onMouseEnter={ handleEnter }
onClick={ handleEnter }
onFocus={ handleEnter }
onBlur={ handleOut }
role="presentation"
>
<Icon icon={ help } />
{ showPopover && (
<Popover noArrow={ false } offset={ 5 } inline={ true }>
<Text className={ styles[ 'popover-text' ] } variant={ 'body-small' }>
{ children }
</Text>
</Popover>
) }
</div>
);
};

const FirewallSubheadingContent = ( { className, text = '', popover = false, children } ) => {
const FirewallSubheadingContent = ( { className, text = '', popover = false } ) => {
return (
<div className={ styles[ 'firewall-subheading__content' ] }>
<Text className={ styles[ className ] } weight={ 600 }>
{ text }
</Text>
{ popover && <FirewallSubheadingPopover children={ children } /> }
{ popover && (
<IconTooltip
icon={ help }
text={ __(
'The free version of the firewall does not receive updates to automatic security rules.',
'jetpack-protect'
) }
/>
) }
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ svg.spinner {

&__content {
display: flex;

> .icon-popover {
display: flex;
margin-left: calc( var( --spacing-base ) / 2 ); // 4px
fill: var( --jp-gray-20 );
}
}
}

Expand All @@ -47,11 +41,6 @@ svg.spinner {
}
}

.popover-text {
width: 250px;
padding: calc( var( --spacing-base ) * 2 ); // 16px
}

.loading-text {
font-size: 18px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ const FixAllThreatsModal = ( { threatList = [] } ) => {

const [ threatIds, setThreatIds ] = useState( threatList.map( ( { id } ) => parseInt( id ) ) );

const handleCancelClick = useCallback( () => {
return event => {
const handleCancelClick = useCallback(
event => {
event.preventDefault();
setModal( { type: null } );
};
}, [ setModal ] );
},
[ setModal ]
);

const handleFixClick = useCallback( () => {
return async event => {
const handleFixClick = useCallback(
async event => {
event.preventDefault();

await fixThreats( threatIds );
setModal( { type: null } );
};
}, [ fixThreats, setModal, threatIds ] );
},
[ fixThreats, setModal, threatIds ]
);

const handleCheckboxClick = useCallback(
( checked, threat ) => {
Expand Down Expand Up @@ -63,12 +65,12 @@ const FixAllThreatsModal = ( { threatList = [] } ) => {
</div>

<div className={ styles.footer }>
<Button variant="secondary" onClick={ handleCancelClick() }>
<Button variant="secondary" onClick={ handleCancelClick }>
{ __( 'Cancel', 'jetpack-protect' ) }
</Button>
<Button
isLoading={ isFixersLoading }
onClick={ handleFixClick() }
onClick={ handleFixClick }
disabled={ ! threatIds.length }
>
{ __( 'Fix all threats', 'jetpack-protect' ) }
Expand Down
51 changes: 51 additions & 0 deletions projects/plugins/protect/src/js/components/icon-tooltip/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Text } from '@automattic/jetpack-components';
import { Icon, Popover } from '@wordpress/components';
import React, { useCallback, useState } from 'react';
import styles from './styles.module.scss';

const IconTooltip = ( { icon, iconClassName, iconSize, popoverPosition = 'top', text } ) => {
const [ showPopover, setShowPopover ] = useState( false );
const [ timeoutId, setTimeoutId ] = useState( null );

const handleEnter = useCallback( () => {
// Clear any existing timeout if user hovers back quickly
if ( timeoutId ) {
clearTimeout( timeoutId );
setTimeoutId( null );
}
setShowPopover( true );
}, [ timeoutId ] );

const handleOut = useCallback( () => {
// Set a timeout to delay the hiding of the popover
const id = setTimeout( () => {
setShowPopover( false );
setTimeoutId( null ); // Clear the timeout ID after the popover is hidden
}, 100 );

setTimeoutId( id );
}, [] );

return (
<div
className={ styles[ 'icon-popover' ] }
onMouseLeave={ handleOut }
onMouseEnter={ handleEnter }
onClick={ handleEnter }
onFocus={ handleEnter }
onBlur={ handleOut }
role="presentation"
>
<Icon className={ iconClassName } icon={ icon } size={ iconSize } />
{ showPopover && (
<Popover noArrow={ false } offset={ 5 } inline={ true } position={ popoverPosition }>
<Text className={ styles[ 'popover-text' ] } variant={ 'body-small' }>
{ text }
</Text>
</Popover>
) }
</div>
);
};

export default IconTooltip;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.icon-popover {
display: flex;
margin-left: calc( var( --spacing-base ) / 2 ); // 4px
fill: var( --jp-gray-20 );
}

.popover-text {
width: 250px;
padding: calc( var( --spacing-base ) * 2 ); // 16px
cursor: default;
}
Loading

0 comments on commit cec62c8

Please sign in to comment.