Skip to content

Commit

Permalink
ThreatModal: Update flow (#40214)
Browse files Browse the repository at this point in the history
* Add ThreatDetailsModal component and stories

* changelog

* Fix type error

* Fix child component overflow-x styling

* Update scan package changelog

* Add util for fixerState and separate subcomponents

* Add user connection gate

* Add credentials gate

* Fix stories

* Fix stories

* Fix credentials type

* Update content flow

* Improve organization

* Separation and reorg

* Improve

* Remove redundant code

* Adjust modal content

* Generalize component name

* Updates after renaming

* Ensure close button exists for vulns modal

* changelog

* Updates/fixes

* Add detailed action

* changelog

* Add actionToConfirm flag for rendering conditional content based on action selected

* Update approach to single view

* Move notices to ThreatActions

* Add fixer notices

* Fix styling

* Additional improvements

* Fix gap

* Remove actionToConfirm flag, to be readded in integration

* Early return on ThreatActions for fixed threats

* Make threat status check optional

* Use custom button for threat details toggle (#40298)

* Use Button and override internal styles

* Fix classes

* Move fixerState comp to ThreatFixConfirmation

* Rely heavily on context provider

* Fix styles

---------

Co-authored-by: Nate Weller <nate.weller@automattic.com>
  • Loading branch information
dkmyta and nateweller authored Nov 25, 2024
1 parent b91118f commit 0f6c6cf
Show file tree
Hide file tree
Showing 16 changed files with 614 additions and 313 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Updates ThreatModal flow
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type Threat,
getFixerState,
getFixerAction,
getFixerMessage,
getFixerDescription,
} from '@automattic/jetpack-scan';
import { Tooltip } from '@wordpress/components';
import { useCallback, useMemo } from '@wordpress/element';
Expand Down Expand Up @@ -50,7 +50,7 @@ export default function ThreatFixerButton( {
return __( 'An auto-fixer is in progress.', 'jetpack' );
}

return getFixerMessage( threat );
return getFixerDescription( threat );
}, [ threat, fixerState ] );

const buttonText = useMemo( () => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { __ } from '@wordpress/i18n';
import { useMemo } from 'react';
import styles from './styles.module.scss';
import ThreatNotice from './threat-notice';

/**
* FixerStateNotice component
*
* @param {object} props - The component props.
* @param {object} props.fixerState - The state of the fixer (inProgress, error, stale).
* @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress.
* @param {boolean} props.fixerState.error - Whether the fixer encountered an error.
* @param {boolean} props.fixerState.stale - Whether the fixer is stale.
*
* @return {JSX.Element | null} The rendered fixer notice or null if no notice is available.
*/
const FixerStateNotice = ( {
fixerState,
}: {
fixerState: { inProgress: boolean; error: boolean; stale: boolean };
} ) => {
const { status, title, content } = useMemo( () => {
if ( fixerState.error ) {
return {
status: 'error' as const,
title: __( 'An error occurred auto-fixing this threat', 'jetpack' ),
content: __(
'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.',
'jetpack'
),
};
}

if ( fixerState.stale ) {
return {
status: 'error' as const,
title: __( 'The auto-fixer is taking longer than expected', 'jetpack' ),
content: __(
'Jetpack has been attempting to auto-fix this threat for too long, and something may have gone wrong. Please try again later or contact support.',
'jetpack'
),
};
}

if ( fixerState.inProgress ) {
return {
status: 'success' as const,
title: __( 'An auto-fixer is in progress', 'jetpack' ),
content: __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ),
};
}

return {};
}, [ fixerState ] );

return title ? (
<div className={ styles[ 'fixer-notice' ] }>
<ThreatNotice status={ status } title={ title } content={ content } />
</div>
) : null;
};

export default FixerStateNotice;
133 changes: 44 additions & 89 deletions projects/js-packages/components/components/threat-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components';
import { type Threat, getFixerState } from '@automattic/jetpack-scan';
import { Modal, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useMemo } from 'react';
import { type Threat } from '@automattic/jetpack-scan';
import { Modal } from '@wordpress/components';
import { createContext } from 'react';
import Text from '../text';
import CredentialsGate from './credentials-gate';
import ThreatSeverityBadge from '../threat-severity-badge';
import styles from './styles.module.scss';
import ThreatActions from './threat-actions';
import ThreatFixDetails from './threat-fix-details';
import ThreatTechnicalDetails from './threat-technical-details';
import UserConnectionGate from './user-connection-gate';
import ThreatFixConfirmation from './threat-fix-confirmation';
interface ThreatModalContextType {
closeModal: () => void;
threat: Threat;
handleUpgradeClick?: () => void;
userConnectionNeeded: boolean;
handleConnectUser: () => void;
userIsConnecting: boolean;
siteCredentialsNeeded: boolean;
credentialsIsFetching: boolean;
credentialsRedirectUrl: string;
handleFixThreatClick?: ( threats: Threat[] ) => void;
handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
}

export const ThreatModalContext = createContext< ThreatModalContextType | null >( null );

/**
* ThreatModal component
Expand Down Expand Up @@ -61,92 +72,36 @@ export default function ThreatModal( {
const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner;
const siteCredentialsNeeded = ! credentials || credentials.length === 0;

const fixerState = useMemo( () => {
return getFixerState( threat.fixer );
}, [ threat.fixer ] );

const getModalTitle = useMemo( () => {
if ( userConnectionNeeded ) {
return <Text variant="title-small">{ __( 'User connection needed', 'jetpack' ) }</Text>;
}

if ( siteCredentialsNeeded ) {
return <Text variant="title-small">{ __( 'Site credentials needed', 'jetpack' ) }</Text>;
}

return (
<>
<Text variant="title-small">{ threat.title }</Text>
{ !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
</>
);
}, [ userConnectionNeeded, siteCredentialsNeeded, threat.title, threat.severity ] );

return (
<Modal
title={
<div className={ styles.title }>
<Text variant="title-small">{ threat.title }</Text>
{ !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
</div>
}
size="large"
title={ <div className={ styles.title }>{ getModalTitle }</div> }
{ ...modalProps }
>
<div className={ styles[ 'threat-details' ] }>
<UserConnectionGate
userConnectionNeeded={ userConnectionNeeded }
userIsConnecting={ userIsConnecting }
handleConnectUser={ handleConnectUser }
<ThreatModalContext.Provider
value={ {
closeModal: modalProps.onRequestClose,
threat,
handleUpgradeClick,
userConnectionNeeded,
handleConnectUser,
userIsConnecting,
siteCredentialsNeeded,
credentialsIsFetching,
credentialsRedirectUrl,
handleFixThreatClick,
handleIgnoreThreatClick,
handleUnignoreThreatClick,
} }
>
<CredentialsGate
siteCredentialsNeeded={ siteCredentialsNeeded }
credentialsIsFetching={ credentialsIsFetching }
credentialsRedirectUrl={ credentialsRedirectUrl }
>
<>
{ fixerState.error && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'An error occurred auto-fixing this threat.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.stale && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.inProgress && ! fixerState.stale && (
<Notice isDismissible={ false } status="success">
<Text>{ __( 'The auto-fixer is in progress.', 'jetpack' ) }</Text>
</Notice>
) }
<div className={ styles.section }>
{ !! threat.description && <Text>{ threat.description }</Text> }

{ !! threat.source && (
<div>
<Button
variant="link"
isExternalLink={ true }
weight="regular"
href={ threat.source }
>
{ __( 'See more technical details of this threat', 'jetpack' ) }
</Button>
</div>
) }
</div>

<ThreatFixDetails threat={ threat } handleUpgradeClick={ handleUpgradeClick } />

<ThreatTechnicalDetails threat={ threat } />

<ThreatActions
threat={ threat }
closeModal={ modalProps.onRequestClose }
handleFixThreatClick={ handleFixThreatClick }
handleIgnoreThreatClick={ handleIgnoreThreatClick }
handleUnignoreThreatClick={ handleUnignoreThreatClick }
fixerState={ fixerState }
/>
</>
</CredentialsGate>
</UserConnectionGate>
<ThreatFixConfirmation />
</ThreatModalContext.Provider>
</div>
</Modal>
);
Expand Down
Loading

0 comments on commit 0f6c6cf

Please sign in to comment.