From b91975605e5de9d6b7ccf886ccff3795bbd21fc6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:03:25 -0800 Subject: [PATCH 01/67] Add ThreatDetailsModal component and stories --- .../components/threat-details-modal/index.tsx | 274 ++++++++++++++++++ .../stories/index.stories.tsx | 67 +++++ .../threat-details-modal/styles.module.scss | 35 +++ .../components/threat-fixer-button/index.tsx | 65 +---- projects/js-packages/scan/src/utils/index.ts | 74 +++++ 5 files changed, 455 insertions(+), 60 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/index.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx new file mode 100644 index 0000000000000..cd718a5ed51c5 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -0,0 +1,274 @@ +import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { + type Threat, + fixerIsInError, + fixerIsInProgress, + fixerStatusIsStale, + getFixerAction, + getFixerMessage, +} from '@automattic/jetpack-scan'; +import { Modal, Notice } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useMemo, useCallback } from 'react'; +import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; +import DiffViewer from '../diff-viewer'; +import MarkedLines from '../marked-lines'; +import Text from '../text'; +import styles from './styles.module.scss'; + +const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ) => { + if ( ! threat.filename && ! threat.context && ! threat.diff ) { + return null; + } + + return ( +
+ { __( 'The technical details', 'jetpack' ) } + { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ); +}; + +const ThreatFixDetails = ( { + threat, + handleUpgradeClick, +}: { + threat: Threat; + handleUpgradeClick: () => void; +} ) => { + const title = useMemo( () => { + if ( threat.status === 'fixed' ) { + return __( 'How did Jetpack fix it?', 'jetpack' ); + } + if ( threat.status === 'current' && threat.fixable ) { + return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); + } + return __( 'How to fix it?', 'jetpack' ); + }, [ threat ] ); + + const fix = useMemo( () => { + // The threat has a fixed version available, but no auto-fix is available. + // The user needs to update the extension to the fixed version. + if ( ! threat.fixable && threat.fixedIn ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Update %1$s to version %2$s.', 'jetpack' ), + threat.extension.name, + threat.fixedIn + ); + } + + // The threat has an auto-fix available. + return getFixerMessage( threat ); + }, [ threat ] ); + + if ( ! threat.fixable && ! threat.fixedIn ) { + return null; + } + + return ( +
+ { title } + { fix } + + { !! handleUpgradeClick && ( + + ) } +
+ ); +}; + +const ThreatActions = ( { + threat, + closeModal, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + fixerState, +}: { + threat: Threat; + closeModal: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + const fixerAction = useMemo( () => { + return getFixerAction( threat ); + }, [ threat ] ); + + const onFixClick = useCallback( () => { + handleFixThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleFixThreatClick, closeModal ] ); + + const onIgnoreClick = useCallback( () => { + handleIgnoreThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleIgnoreThreatClick, closeModal ] ); + + const onUnignoreClick = useCallback( () => { + handleUnignoreThreatClick( [ threat ] ); + closeModal(); + }, [ threat, handleUnignoreThreatClick, closeModal ] ); + + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { + return null; + } + + return ( +
+
+ +
+
+ { 'ignored' === threat.status && !! handleUnignoreThreatClick && ( + + ) } + { 'current' === threat.status && ( + <> + { !! handleIgnoreThreatClick && ( + + ) } + { threat.fixable && !! handleFixThreatClick && ( + + ) } + + ) } +
+
+ ); +}; + +/** + * ThreatDetailsModal component + * + * @param {object} props - The props. + * @param {object} props.threat - The threat. + * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. + * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. + * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. + * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. + * + * @return {JSX.Element} The threat details modal. + */ +export default function ThreatDetailsModal( { + threat, + handleUpgradeClick, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + ...modalProps +}: { + threat: Threat; + handleUpgradeClick?: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + [ key: string ]: unknown; +} ): JSX.Element { + const fixerState = useMemo( () => { + const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); + const error = threat.fixer && fixerIsInError( threat.fixer ); + const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); + return { inProgress, error, stale }; + }, [ threat.fixer ] ); + + const title = useMemo( () => { + if ( threat.title ) { + return threat.title; + } + + if ( threat.status === 'fixed' ) { + return __( 'What was the problem?', 'jetpack' ); + } + + return __( 'What is the problem?', 'jetpack' ); + }, [ threat ] ); + + return ( + +
+ { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
+ + { !! threat.description && { threat.description } } + + { !! threat.source && ( +
+ +
+ ) } +
+ + + + + + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> +
+
+ ); +} diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx new file mode 100644 index 0000000000000..46b1e6a93baea --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -0,0 +1,67 @@ +import { useCallback, useState } from 'react'; +import Button from '../../button/index.js'; +import ThreatDetailsModal from '../index.js'; + +export default { + title: 'JS Packages/Components/Threat Details Modal', + component: ThreatDetailsModal, +}; + +const Base = args => { + const [ isOpen, setIsOpen ] = useState( false ); + const onClick = useCallback( () => setIsOpen( true ), [] ); + const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + return ( +
+ + { isOpen ? : null } +
+ ); +}; + +export const ThreatResult = Base.bind( {} ); +ThreatResult.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const VulnerableExtension = Base.bind( {} ); +VulnerableExtension.args = { + threat: { + id: 184847701, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', + description: + 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', + fixedIn: '1.12.4', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'WP Super Cache', + slug: 'wp-super-cache', + version: '1.6.3', + type: 'plugin', + }, + }, + handleUpgradeClick: () => {}, +}; diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-details-modal/styles.module.scss new file mode 100644 index 0000000000000..528c9e5f25ba5 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/styles.module.scss @@ -0,0 +1,35 @@ +.threat-details { + display: flex; + flex-direction: column; + gap: calc( var( --spacing-base ) * 3 ); // 24px +} + +.section { + display: flex; + flex-direction: column; + gap: calc( var( --spacing-base ) * 2 ); // 16px +} + +.title { + display: flex; + align-items: center; + gap: calc( var( --spacing-base ) * 1.5 ); // 12px +} + +.filename { + background-color: var( --jp-gray-0 ); + padding: calc( var( --spacing-base ) * 3 ); // 24px + overflow-x: auto; +} + +.modal-actions { + display: flex; + justify-content: space-between; + padding-top: calc( var( --spacing-base ) * 3 ); // 24px + border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index cd15c0294c313..a2ef72666c23a 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -4,6 +4,8 @@ import { fixerIsInError, fixerIsInProgress, fixerStatusIsStale, + getFixerAction, + getFixerMessage, } from '@automattic/jetpack-scan'; import { Tooltip } from '@wordpress/components'; import { useCallback, useMemo } from '@wordpress/element'; @@ -53,54 +55,7 @@ export default function ThreatFixerButton( { return __( 'An auto-fixer is in progress.', 'jetpack' ); } - switch ( threat.fixable.fixer ) { - case 'delete': - if ( threat.filename ) { - if ( threat.filename.endsWith( '/' ) ) { - return __( 'Deletes the directory that the infected file is in.', 'jetpack' ); - } - - if ( threat.signature === 'Core.File.Modification' ) { - return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack' ); - } - - return __( 'Deletes the infected file.', 'jetpack' ); - } - - if ( threat.extension?.type === 'plugin' ) { - return __( 'Deletes the plugin directory to fix the threat.', 'jetpack' ); - } - - if ( threat.extension?.type === 'theme' ) { - return __( 'Deletes the theme directory to fix the threat.', 'jetpack' ); - } - break; - case 'update': - return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack' ); - case 'replace': - case 'rollback': - if ( threat.filename ) { - return threat.signature === 'Core.File.Modification' - ? __( - 'Replaces the modified core WordPress file with the original clean version from the WordPress source code.', - 'jetpack' - ) - : __( - 'Replaces the infected file with a previously backed up version that is clean.', - 'jetpack' - ); - } - - if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { - return __( - 'Replaces the default salt keys in wp-config.php with unique ones.', - 'jetpack' - ); - } - break; - default: - return __( 'An auto-fixer is available.', 'jetpack' ); - } + return getFixerMessage( threat ); }, [ threat, fixerState ] ); const buttonText = useMemo( () => { @@ -112,18 +67,8 @@ export default function ThreatFixerButton( { return __( 'Error', 'jetpack' ); } - switch ( threat.fixable.fixer ) { - case 'delete': - return __( 'Delete', 'jetpack' ); - case 'update': - return __( 'Update', 'jetpack' ); - case 'replace': - case 'rollback': - return __( 'Replace', 'jetpack' ); - default: - return __( 'Fix', 'jetpack' ); - } - }, [ threat.fixable, fixerState.error ] ); + return getFixerAction( threat ); + }, [ threat, fixerState.error ] ); const handleClick = useCallback( ( event: React.MouseEvent ) => { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index be4fe59047694..6023e76f7e0d6 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -1,3 +1,4 @@ +import { __, sprintf } from '@wordpress/i18n'; import { Threat, ThreatFixStatus, FIXER_IS_STALE_THRESHOLD } from '..'; export const getThreatType = ( threat: Threat ) => { @@ -35,3 +36,76 @@ export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => { fixerTimestampIsStale( fixerStatus.lastUpdated ) ); }; + +export const getFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + return __( 'Delete', 'jetpack-scan' ); + case 'update': + return __( 'Update', 'jetpack-scan' ); + case 'replace': + case 'rollback': + return __( 'Replace', 'jetpack-scan' ); + default: + return __( 'Fix', 'jetpack-scan' ); + } +}; + +export const getFixerMessage = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + if ( threat.filename.endsWith( '/' ) ) { + return __( 'Deletes the directory that the infected file is in.', 'jetpack-scan' ); + } + + if ( threat.signature === 'Core.File.Modification' ) { + return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack-scan' ); + } + + return __( 'Deletes the infected file.', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Deletes the plugin directory to fix the threat.', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Deletes the theme directory to fix the threat.', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.fixedIn && threat.extension.name ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Updates %1$s to version %2$s', 'jetpack-scan' ), + threat.extension.name, + threat.fixedIn + ); + } + return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack-scan' ); + case 'replace': + case 'rollback': + if ( threat.filename ) { + return threat.signature === 'Core.File.Modification' + ? __( + 'Replaces the modified core WordPress file with the original clean version from the WordPress source code.', + 'jetpack-scan' + ) + : __( + 'Replaces the infected file with a previously backed up version that is clean.', + 'jetpack-scan' + ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( + 'Replaces the default salt keys in wp-config.php with unique ones.', + 'jetpack-scan' + ); + } + break; + default: + return __( 'Jetpack will auto-fix the threat.', 'jetpack-scan' ); + } +}; From 15587ca13a8d83dee346d24cd57975840f2fed4e Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:04:15 -0800 Subject: [PATCH 02/67] changelog --- .../components/changelog/add-components-threat-details-modal | 4 ++++ .../scan/changelog/add-components-threat-details-modal | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 projects/js-packages/components/changelog/add-components-threat-details-modal create mode 100644 projects/js-packages/scan/changelog/add-components-threat-details-modal diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal new file mode 100644 index 0000000000000..3caa1f5d56878 --- /dev/null +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ThreatDetailsModal component and stories diff --git a/projects/js-packages/scan/changelog/add-components-threat-details-modal b/projects/js-packages/scan/changelog/add-components-threat-details-modal new file mode 100644 index 0000000000000..3caa1f5d56878 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-components-threat-details-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds ThreatDetailsModal component and stories From 2bebb0df357d74e732006958e4ffe70859057f0c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:13:09 -0800 Subject: [PATCH 03/67] Fix type error --- projects/js-packages/scan/src/utils/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 6023e76f7e0d6..2f473ac0a22ad 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -21,19 +21,19 @@ export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { return now.getTime() - lastUpdated.getTime() >= FIXER_IS_STALE_THRESHOLD; }; -export const fixerIsInError = ( fixerStatus: ThreatFixStatus ) => { - return 'error' in fixerStatus && fixerStatus.error; +export const fixerIsInError = ( fixerStatus: ThreatFixStatus ): boolean => { + return !! ( 'error' in fixerStatus && fixerStatus.error ); }; -export const fixerIsInProgress = ( fixerStatus: ThreatFixStatus ) => { - return 'status' in fixerStatus && fixerStatus.status === 'in_progress'; +export const fixerIsInProgress = ( fixerStatus: ThreatFixStatus ): boolean => { + return !! ( 'status' in fixerStatus && fixerStatus.status === 'in_progress' ); }; -export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => { +export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ): boolean => { return ( fixerIsInProgress( fixerStatus ) && 'lastUpdated' in fixerStatus && - fixerTimestampIsStale( fixerStatus.lastUpdated ) + !! fixerTimestampIsStale( fixerStatus.lastUpdated ) ); }; From dddfdaf58662e6f9c05c6220f7e87a666f5ecda6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 13:16:33 -0800 Subject: [PATCH 04/67] Fix child component overflow-x styling --- .../components/components/diff-viewer/styles.module.scss | 2 +- .../components/components/marked-lines/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/js-packages/components/components/diff-viewer/styles.module.scss b/projects/js-packages/components/components/diff-viewer/styles.module.scss index 16c44d670f7f9..3e7f2eab65375 100644 --- a/projects/js-packages/components/components/diff-viewer/styles.module.scss +++ b/projects/js-packages/components/components/diff-viewer/styles.module.scss @@ -14,7 +14,7 @@ display: flex; font-family: "Courier 10 Pitch", Courier, monospace; flex-direction: row; - overflow-x: scroll; + overflow-x: auto; white-space: pre; } diff --git a/projects/js-packages/components/components/marked-lines/styles.module.scss b/projects/js-packages/components/components/marked-lines/styles.module.scss index 49733a4d9ebd2..dab9e17fcafb2 100644 --- a/projects/js-packages/components/components/marked-lines/styles.module.scss +++ b/projects/js-packages/components/components/marked-lines/styles.module.scss @@ -4,7 +4,7 @@ font-family: monospace; display: flex; flex-direction: row; - overflow-x: scroll; + overflow-x: auto; } .marked-lines__marked-line { From 172fbb0360d0786b933dee00990d486629005845 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 14:37:00 -0800 Subject: [PATCH 05/67] Update scan package changelog --- .../scan/changelog/add-components-threat-details-modal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/scan/changelog/add-components-threat-details-modal b/projects/js-packages/scan/changelog/add-components-threat-details-modal index 3caa1f5d56878..a81430663b3df 100644 --- a/projects/js-packages/scan/changelog/add-components-threat-details-modal +++ b/projects/js-packages/scan/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds utilities for retrieving fixer messaging From d39a616b8b41c84d747f5f3423ab5e28b8f06a5d Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 15:24:26 -0800 Subject: [PATCH 06/67] Add util for fixerState and separate subcomponents --- .../components/threat-details-modal/index.tsx | 178 +----------------- .../threat-details-modal/threat-actions.tsx | 102 ++++++++++ .../threat-fix-details.tsx | 68 +++++++ .../threat-technical-details.tsx | 36 ++++ .../components/threat-fixer-button/index.tsx | 9 +- projects/js-packages/scan/src/utils/index.ts | 8 + 6 files changed, 223 insertions(+), 178 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-actions.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index cd718a5ed51c5..79e14047fa3ca 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,174 +1,13 @@ import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components'; -import { - type Threat, - fixerIsInError, - fixerIsInProgress, - fixerStatusIsStale, - getFixerAction, - getFixerMessage, -} from '@automattic/jetpack-scan'; +import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal, Notice } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo, useCallback } from 'react'; -import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; -import DiffViewer from '../diff-viewer'; -import MarkedLines from '../marked-lines'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; import Text from '../text'; import styles from './styles.module.scss'; - -const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ) => { - if ( ! threat.filename && ! threat.context && ! threat.diff ) { - return null; - } - - return ( -
- { __( 'The technical details', 'jetpack' ) } - { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } -
- ); -}; - -const ThreatFixDetails = ( { - threat, - handleUpgradeClick, -}: { - threat: Threat; - handleUpgradeClick: () => void; -} ) => { - const title = useMemo( () => { - if ( threat.status === 'fixed' ) { - return __( 'How did Jetpack fix it?', 'jetpack' ); - } - if ( threat.status === 'current' && threat.fixable ) { - return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); - } - return __( 'How to fix it?', 'jetpack' ); - }, [ threat ] ); - - const fix = useMemo( () => { - // The threat has a fixed version available, but no auto-fix is available. - // The user needs to update the extension to the fixed version. - if ( ! threat.fixable && threat.fixedIn ) { - return sprintf( - /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ - __( 'Update %1$s to version %2$s.', 'jetpack' ), - threat.extension.name, - threat.fixedIn - ); - } - - // The threat has an auto-fix available. - return getFixerMessage( threat ); - }, [ threat ] ); - - if ( ! threat.fixable && ! threat.fixedIn ) { - return null; - } - - return ( -
- { title } - { fix } - - { !! handleUpgradeClick && ( - - ) } -
- ); -}; - -const ThreatActions = ( { - threat, - closeModal, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, - fixerState, -}: { - threat: Threat; - closeModal: () => void; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ) => { - const fixerAction = useMemo( () => { - return getFixerAction( threat ); - }, [ threat ] ); - - const onFixClick = useCallback( () => { - handleFixThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleFixThreatClick, closeModal ] ); - - const onIgnoreClick = useCallback( () => { - handleIgnoreThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleIgnoreThreatClick, closeModal ] ); - - const onUnignoreClick = useCallback( () => { - handleUnignoreThreatClick( [ threat ] ); - closeModal(); - }, [ threat, handleUnignoreThreatClick, closeModal ] ); - - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - - return ( -
-
- -
-
- { 'ignored' === threat.status && !! handleUnignoreThreatClick && ( - - ) } - { 'current' === threat.status && ( - <> - { !! handleIgnoreThreatClick && ( - - ) } - { threat.fixable && !! handleFixThreatClick && ( - - ) } - - ) } -
-
- ); -}; +import ThreatActions from './threat-actions'; +import ThreatFixDetails from './threat-fix-details'; +import ThreatTechnicalDetails from './threat-technical-details'; /** * ThreatDetailsModal component @@ -198,10 +37,7 @@ export default function ThreatDetailsModal( { [ key: string ]: unknown; } ): JSX.Element { const fixerState = useMemo( () => { - const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); - const error = threat.fixer && fixerIsInError( threat.fixer ); - const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); - return { inProgress, error, stale }; + return getFixerState( threat.fixer ); }, [ threat.fixer ] ); const title = useMemo( () => { diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx new file mode 100644 index 0000000000000..d14a2901fb4bf --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -0,0 +1,102 @@ +import { Button } from '@automattic/jetpack-components'; +import { Threat, getFixerAction } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import React, { useCallback, useMemo } from 'react'; +import styles from './styles.module.scss'; + +/** + * ThreatActions component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing action details. + * @param {Function} props.closeModal - Function to close the modal. + * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. + * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. + * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. + * @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 action buttons or null if no actions are available. + */ +const ThreatActions = ( { + threat, + closeModal, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + fixerState, +}: { + threat: Threat; + closeModal: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ): JSX.Element => { + const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); + + const onFixClick = useCallback( () => { + handleFixThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleFixThreatClick, closeModal ] ); + + const onIgnoreClick = useCallback( () => { + handleIgnoreThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleIgnoreThreatClick, closeModal ] ); + + const onUnignoreClick = useCallback( () => { + handleUnignoreThreatClick?.( [ threat ] ); + closeModal(); + }, [ threat, handleUnignoreThreatClick, closeModal ] ); + + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { + return null; + } + + return ( +
+
+ +
+
+ { threat.status === 'ignored' && handleUnignoreThreatClick && ( + + ) } + { threat.status === 'current' && ( + <> + { handleIgnoreThreatClick && ( + + ) } + { threat.fixable && handleFixThreatClick && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx new file mode 100644 index 0000000000000..441348aadc92b --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx @@ -0,0 +1,68 @@ +import { Threat, getFixerMessage } from '@automattic/jetpack-scan'; +import { __, sprintf } from '@wordpress/i18n'; +import React, { useMemo } from 'react'; +import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; +import Text from '../text'; +import styles from './styles.module.scss'; + +/** + * ThreatFixDetails component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing fix details. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade click events. + * + * @return {JSX.Element | null} The rendered fix details or null if no fixable details are available. + */ +const ThreatFixDetails = ( { + threat, + handleUpgradeClick, +}: { + threat: Threat; + handleUpgradeClick: () => void; +} ): JSX.Element => { + const title = useMemo( () => { + if ( threat.status === 'fixed' ) { + return __( 'How did Jetpack fix it?', 'jetpack' ); + } + if ( threat.status === 'current' && threat.fixable ) { + return __( 'How can Jetpack auto-fix this threat?', 'jetpack' ); + } + return __( 'How to fix it?', 'jetpack' ); + }, [ threat ] ); + + const fix = useMemo( () => { + // The threat has a fixed version available, but no auto-fix is available. + // The user needs to update the extension to the fixed version. + if ( ! threat.fixable && threat.fixedIn ) { + return sprintf( + /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */ + __( 'Update %1$s to version %2$s.', 'jetpack' ), + threat.extension.name, + threat.fixedIn + ); + } + // The threat has an auto-fix available. + return getFixerMessage( threat ); + }, [ threat ] ); + + if ( ! threat.fixable && ! threat.fixedIn ) { + return null; + } + + return ( +
+ { title } + { fix } + { handleUpgradeClick && ( + + ) } +
+ ); +}; + +export default ThreatFixDetails; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx new file mode 100644 index 0000000000000..38793f704886d --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx @@ -0,0 +1,36 @@ +import { Threat } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import DiffViewer from '../diff-viewer'; +import MarkedLines from '../marked-lines'; +import Text from '../text'; +import styles from './styles.module.scss'; + +/** + * ThreatTechnicalDetails component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing technical details. + * + * @return {JSX.Element | null} The rendered technical details or null if no details are available. + */ +const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { + if ( ! threat.filename && ! threat.context && ! threat.diff ) { + return null; + } + + return ( +
+ { __( 'The technical details', 'jetpack' ) } + { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ); +}; + +export default ThreatTechnicalDetails; diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index a2ef72666c23a..11520c139ceee 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -1,9 +1,7 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, - fixerIsInError, - fixerIsInProgress, - fixerStatusIsStale, + getFixerState, getFixerAction, getFixerMessage, } from '@automattic/jetpack-scan'; @@ -32,10 +30,7 @@ export default function ThreatFixerButton( { className?: string; } ): JSX.Element { const fixerState = useMemo( () => { - const inProgress = threat.fixer && fixerIsInProgress( threat.fixer ); - const error = threat.fixer && fixerIsInError( threat.fixer ); - const stale = threat.fixer && fixerStatusIsStale( threat.fixer ); - return { inProgress, error, stale }; + return getFixerState( threat.fixer ); }, [ threat.fixer ] ); const tooltipText = useMemo( () => { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 2f473ac0a22ad..8b186b065bb33 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -37,6 +37,14 @@ export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ): boolean => { ); }; +export const getFixerState = ( fixerStatus: ThreatFixStatus ) => { + return { + inProgress: fixerStatus && fixerIsInProgress( fixerStatus ), + error: fixerStatus && fixerIsInError( fixerStatus ), + stale: fixerStatus && fixerStatusIsStale( fixerStatus ), + }; +}; + export const getFixerAction = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': From e1c5bf4ffaad03c7bd6381aa4dbab3a488483a22 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 15:56:21 -0800 Subject: [PATCH 07/67] Add ThreatDetailsModal to ThreatsDataViews --- .../components/threats-data-views/index.tsx | 111 ++++++++++-------- .../stories/index.stories.tsx | 4 + 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index aec1f1c3086a6..b8807b5513ed7 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,7 +1,6 @@ import { getThreatType, type Threat } from '@automattic/jetpack-scan'; import { type Action, - type ActionButton, type Field, type FieldType, type Filter, @@ -16,10 +15,10 @@ import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; import Badge from '../badge'; +import ThreatDetailsModal from '../threat-details-modal'; import ThreatFixerButton from '../threat-fixer-button'; import ThreatSeverityBadge from '../threat-severity-badge'; import { - THREAT_ACTION_FIX, THREAT_ACTION_IGNORE, THREAT_ACTION_UNIGNORE, THREAT_FIELD_AUTO_FIX, @@ -50,6 +49,7 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {Array} props.data - Threats data. * @param {Array} props.filters - Initial DataView filters. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. + * @param {Function} props.handleUpgradeClick - Callback function run when the upgrade button is clicked. * @param {Function} props.onFixThreats - Threat fix action callback. * @param {Function} props.onIgnoreThreats - Threat ignore action callback. * @param {Function} props.onUnignoreThreats - Threat unignore action callback. @@ -63,6 +63,7 @@ export default function ThreatsDataViews( { data, filters, onChangeSelection, + handleUpgradeClick, isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, @@ -73,12 +74,13 @@ export default function ThreatsDataViews( { data: Threat[]; filters?: Filter[]; onChangeSelection?: ( selectedItemIds: string[] ) => void; + handleUpgradeClick?: () => void; isThreatEligibleForFix?: ( threat: Threat ) => boolean; isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; onFixThreats?: ( threats: Threat[] ) => void; - onIgnoreThreats?: ActionButton< Threat >[ 'callback' ]; - onUnignoreThreats?: ActionButton< Threat >[ 'callback' ]; + onIgnoreThreats?: ( threats: Threat[] ) => void; + onUnignoreThreats?: ( threats: Threat[] ) => void; } ): JSX.Element { const baseView = { sort: { @@ -144,6 +146,19 @@ export default function ThreatsDataViews( { ...defaultLayouts.table, } ); + const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); + + const showThreatDetails = useCallback( + ( threat: Threat ) => () => { + setOpenThreat( threat ); + }, + [] + ); + + const hideThreatDetails = useCallback( () => { + setOpenThreat( null ); + }, [] ); + /** * Compute values from the provided threats data. * @@ -405,7 +420,11 @@ export default function ThreatsDataViews( { return null; } - return ; + if ( ! isThreatEligibleForFix( item ) ) { + return null; + } + + return ; }, }, ] @@ -413,7 +432,7 @@ export default function ThreatsDataViews( { ]; return result; - }, [ dataFields, plugins, themes, signatures, onFixThreats ] ); + }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatDetails ] ); /** * DataView actions - collection of operations that can be performed upon each record. @@ -423,32 +442,13 @@ export default function ThreatsDataViews( { const actions = useMemo( () => { const result: Action< Threat >[] = []; - if ( dataFields.includes( 'fixable' ) ) { - result.push( { - id: THREAT_ACTION_FIX, - label: __( 'Auto-fix', 'jetpack' ), - isPrimary: true, - supportsBulk: true, - callback: onFixThreats, - isEligible( item ) { - if ( ! onFixThreats ) { - return false; - } - if ( isThreatEligibleForFix ) { - return isThreatEligibleForFix( item ); - } - return !! item.fixable; - }, - } ); - } - if ( dataFields.includes( 'status' ) ) { result.push( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), - isPrimary: true, - isDestructive: true, - callback: onIgnoreThreats, + callback: ( items: Threat[] ) => { + showThreatDetails( items[ 0 ] )(); + }, isEligible( item ) { if ( ! onIgnoreThreats ) { return false; @@ -465,9 +465,9 @@ export default function ThreatsDataViews( { result.push( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), - isPrimary: true, - isDestructive: true, - callback: onUnignoreThreats, + callback: ( items: Threat[] ) => { + showThreatDetails( items[ 0 ] )(); + }, isEligible( item ) { if ( ! onUnignoreThreats ) { return false; @@ -483,10 +483,9 @@ export default function ThreatsDataViews( { return result; }, [ dataFields, - onFixThreats, + showThreatDetails, onIgnoreThreats, onUnignoreThreats, - isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, ] ); @@ -517,23 +516,35 @@ export default function ThreatsDataViews( { const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] ); return ( - + + } + /> + { openThreat ? ( + - } - /> + ) : null } + ); } diff --git a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx index 52afdded5ad24..c7f446d61688f 100644 --- a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx @@ -153,6 +153,7 @@ Default.args = { value: [ 'current' ], }, ], + isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => @@ -267,6 +268,7 @@ FixerStatuses.args = { value: [ 'current' ], }, ], + isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => @@ -325,4 +327,6 @@ FreeResults.args = { }, }, ], + handleUpgradeClick: () => + alert( 'Upgrade action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert }; From 9f0892e01c8d8ae661371ed1c4759d15361e3f8b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:40:18 -0800 Subject: [PATCH 08/67] Add user connection gate --- .../components/threat-details-modal/index.tsx | 109 +++++++++++------- .../stories/index.stories.tsx | 35 ++++++ .../user-connection-gate.tsx | 74 ++++++++++++ 3 files changed, 174 insertions(+), 44 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 79e14047fa3ca..99fc1a39e215d 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -8,12 +8,17 @@ 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'; /** * ThreatDetailsModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. + * @param {boolean} props.isUserConnected - Whether the user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user is connecting. + * @param {Function} props.handleConnectUser - The handleConnectUser function. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. @@ -23,6 +28,10 @@ import ThreatTechnicalDetails from './threat-technical-details'; */ export default function ThreatDetailsModal( { threat, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, @@ -30,6 +39,10 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; handleUpgradeClick?: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; @@ -55,55 +68,63 @@ export default function ThreatDetailsModal( { return (
- { fixerState.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState.inProgress && ! fixerState.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
+ void } + isUserConnected={ isUserConnected } + hasConnectedOwner={ hasConnectedOwner } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + > + { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
- { !! threat.description && { threat.description } } + { !! threat.description && { threat.description } } - { !! threat.source && ( -
- -
- ) } -
+ { !! threat.source && ( +
+ +
+ ) } +
- + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> +
); diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 46b1e6a93baea..51b67773e3687 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -19,6 +19,37 @@ const Base = args => { ); }; +export const UserConnectionNeeded = Base.bind( {} ); +UserConnectionNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const ThreatResult = Base.bind( {} ); ThreatResult.args = { threat: { @@ -41,6 +72,10 @@ ThreatResult.args = { marks: {}, }, }, + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx new file mode 100644 index 0000000000000..91068e3721c0e --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -0,0 +1,74 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import React, { ReactNode } from 'react'; +import styles from './styles.module.scss'; + +const UserConnectionGate = ( { + closeModal, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + children, +}: { + closeModal: () => void; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + children: ReactNode; +} ) => { + if ( ! isUserConnected || ! hasConnectedOwner ) { + return ( + <> + { __( 'User connection needed', 'jetpack' ) } + + + { __( + 'Before Jetpack can ignore and auto-fix threats on your site, a user connection is needed.', + 'jetpack' + ) } + + } + /> + + + { __( + 'A user connection provides Jetpack the access necessary to perform these tasks.', + 'jetpack' + ) } + + + + { __( + 'Once you’ve secured a user connection, all Jetpack features will be available for use.', + 'jetpack' + ) } + + +
+ + +
+ + ); + } + + return <>{ children }; +}; + +export default UserConnectionGate; From d955bb16d890eb63473449053e48410e23bfca2a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:52:48 -0800 Subject: [PATCH 09/67] Add credentials gate --- .../threat-details-modal/credentials-gate.tsx | 71 ++++++++++++ .../components/threat-details-modal/index.tsx | 105 ++++++++++-------- .../stories/index.stories.tsx | 50 +++++++++ 3 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx new file mode 100644 index 0000000000000..e855a08133106 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -0,0 +1,71 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { Notice } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import React, { ReactNode } from 'react'; +import styles from './styles.module.scss'; + +const CredentialsGate = ( { + closeModal, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, + children, +}: { + closeModal: () => void; + credentials: boolean; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; + children: ReactNode; +} ) => { + if ( ! credentials || credentials.length === 0 ) { + return ( + <> + { __( 'Site credentials needed', 'jetpack' ) } + + + { __( + 'Before Jetpack can auto-fix threats on your site, it needs your server credentials.', + 'jetpack' + ) } + + } + /> + + + { __( + 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.', + 'jetpack' + ) } + + + + { __( + 'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.', + 'jetpack' + ) } + + +
+ + +
+ + ); + } + + return children; +}; + +export default CredentialsGate; diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 99fc1a39e215d..0efbca385bebc 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -4,6 +4,7 @@ import { Modal, Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo } from 'react'; import Text from '../text'; +import CredentialsGate from './credentials-gate'; import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; @@ -19,6 +20,9 @@ import UserConnectionGate from './user-connection-gate'; * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. * @param {Function} props.handleConnectUser - The handleConnectUser function. + * @param {boolean} props.credentials - Whether the user has credentials. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. + * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. @@ -32,6 +36,9 @@ export default function ThreatDetailsModal( { hasConnectedOwner, userIsConnecting, handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, @@ -43,6 +50,9 @@ export default function ThreatDetailsModal( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; + credentials: boolean; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; handleUpgradeClick?: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; @@ -75,55 +85,62 @@ export default function ThreatDetailsModal( { userIsConnecting={ userIsConnecting } handleConnectUser={ handleConnectUser } > - { fixerState.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState.inProgress && ! fixerState.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
+ void } + credentials={ credentials } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + > + { fixerState.error && ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ) } + { fixerState.stale && ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ) } + { fixerState.inProgress && ! fixerState.stale && ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ) } +
+
+ { title } + { !! threat.severity && } +
- { !! threat.description && { threat.description } } + { !! threat.description && { threat.description } } - { !! threat.source && ( -
- -
- ) } -
+ { !! threat.source && ( +
+ +
+ ) } +
- + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + fixerState={ fixerState } + /> + diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 51b67773e3687..dbcfce12809b5 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -44,12 +44,49 @@ UserConnectionNeeded.args = { isUserConnected: false, hasConnectedOwner: false, userIsConnecting: false, + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; +export const CredentialsNeeded = Base.bind( {} ); +CredentialsNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const ThreatResult = Base.bind( {} ); ThreatResult.args = { threat: { @@ -76,6 +113,9 @@ ThreatResult.args = { hasConnectedOwner: true, userIsConnecting: false, handleConnectUser: () => {}, + credentials: true, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -98,5 +138,15 @@ VulnerableExtension.args = { type: 'plugin', }, }, + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: () => {}, + credentials: true, + credentialsIsFetching: false, + credentialsRedirectUrl: '', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, handleUpgradeClick: () => {}, }; From 32a755976d613ba0e846d577cb74a584f393f94f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:56:46 -0800 Subject: [PATCH 10/67] Fix stories --- .../threat-details-modal/stories/index.stories.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 51b67773e3687..089493971f11a 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -43,7 +43,6 @@ UserConnectionNeeded.args = { }, isUserConnected: false, hasConnectedOwner: false, - userIsConnecting: false, handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, @@ -74,8 +73,6 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -98,5 +95,7 @@ VulnerableExtension.args = { type: 'plugin', }, }, + isUserConnected: true, + hasConnectedOwner: true, handleUpgradeClick: () => {}, }; From d1d546e6a9601c6f95698c1365e747a3a490e4b1 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 18:58:34 -0800 Subject: [PATCH 11/67] Fix stories --- .../stories/index.stories.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index dbcfce12809b5..84117c428a059 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -43,10 +43,6 @@ UserConnectionNeeded.args = { }, isUserConnected: false, hasConnectedOwner: false, - userIsConnecting: false, - credentials: false, - credentialsIsFetching: false, - credentialsRedirectUrl: '', handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, @@ -77,8 +73,6 @@ CredentialsNeeded.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: false, credentialsIsFetching: false, credentialsRedirectUrl: '', @@ -111,11 +105,7 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: true, - credentialsIsFetching: false, - credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -140,13 +130,6 @@ VulnerableExtension.args = { }, isUserConnected: true, hasConnectedOwner: true, - userIsConnecting: false, - handleConnectUser: () => {}, credentials: true, - credentialsIsFetching: false, - credentialsRedirectUrl: '', - handleFixThreatClick: () => {}, - handleIgnoreThreatClick: () => {}, - handleUnignoreThreatClick: () => {}, handleUpgradeClick: () => {}, }; From 69b07560260eba4a951a27a3c335fe9c783096e9 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 14 Nov 2024 19:22:10 -0800 Subject: [PATCH 12/67] Fix credentials type --- .../components/threat-details-modal/credentials-gate.tsx | 2 +- .../components/components/threat-details-modal/index.tsx | 4 ++-- .../components/threat-details-modal/stories/index.stories.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index e855a08133106..421d71692c776 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -12,7 +12,7 @@ const CredentialsGate = ( { children, }: { closeModal: () => void; - credentials: boolean; + credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; children: ReactNode; diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 0efbca385bebc..8f570207e8331 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -20,7 +20,7 @@ import UserConnectionGate from './user-connection-gate'; * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. * @param {Function} props.handleConnectUser - The handleConnectUser function. - * @param {boolean} props.credentials - Whether the user has credentials. + * @param {object} props.credentials - The credentials. * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function. @@ -50,7 +50,7 @@ export default function ThreatDetailsModal( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; - credentials: boolean; + credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; handleUpgradeClick?: () => void; diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 84117c428a059..5ad6cc99e9b35 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -105,7 +105,7 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -130,6 +130,6 @@ VulnerableExtension.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleUpgradeClick: () => {}, }; From 2f59aa878eedb4ce5695059cd555e6dc91863a4b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 11:40:37 -0800 Subject: [PATCH 13/67] Update content flow --- .../threat-details-modal/credentials-gate.tsx | 38 ++++- .../components/threat-details-modal/index.tsx | 134 ++++++++++-------- .../stories/index.stories.tsx | 76 +++++----- .../threat-details-modal/threat-actions.tsx | 34 ++++- .../threat-details-gate.tsx | 119 ++++++++++++++++ .../user-connection-gate.tsx | 39 ++++- 6 files changed, 333 insertions(+), 107 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index 421d71692c776..d0afc64db976d 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -1,22 +1,45 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; import styles from './styles.module.scss'; +/** + * CredentialsGate component + * + * @param {object} props - The component props. + * @param {Function} props.closeModal - Function to close the modal. + * @param {false | Record[]} props.credentials - The server credentials, or `false` if not set. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. + * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. + * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. + * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. + * @param {ReactNode} props.children - The child components to render if credentials are set. + * + * @return {JSX.Element} The rendered CredentialsGate component. + */ const CredentialsGate = ( { closeModal, credentials, credentialsIsFetching, credentialsRedirectUrl, + showThreatDetails, + setShowThreatDetails, children, }: { closeModal: () => void; credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; + showThreatDetails?: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! credentials || credentials.length === 0 ) { return ( <> @@ -49,9 +72,16 @@ const CredentialsGate = ( {
- +
+ { ! showThreatDetails && ( + + ) } + +
-
- ) } - + { !! threat.source && ( +
+ +
+ ) } + - + - + - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - fixerState={ fixerState } - /> - - + void } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + showThreatDetails={ showThreatDetails } + setShowThreatDetails={ setShowThreatDetails } + fixerState={ fixerState } + /> + + + ); diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 5ad6cc99e9b35..8e02fc92de343 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -19,8 +19,8 @@ const Base = args => { ); }; -export const UserConnectionNeeded = Base.bind( {} ); -UserConnectionNeeded.args = { +export const ThreatResult = Base.bind( {} ); +ThreatResult.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -41,16 +41,39 @@ UserConnectionNeeded.args = { marks: {}, }, }, - isUserConnected: false, - hasConnectedOwner: false, - handleConnectUser: () => {}, + isUserConnected: true, + hasConnectedOwner: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; -export const CredentialsNeeded = Base.bind( {} ); -CredentialsNeeded.args = { +export const VulnerableExtension = Base.bind( {} ); +VulnerableExtension.args = { + threat: { + id: 184847701, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', + description: + 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', + fixedIn: '1.12.4', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'WP Super Cache', + slug: 'wp-super-cache', + version: '1.6.3', + type: 'plugin', + }, + }, + isUserConnected: true, + hasConnectedOwner: true, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + handleUpgradeClick: () => {}, +}; + +export const UserConnectionNeeded = Base.bind( {} ); +UserConnectionNeeded.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -71,18 +94,16 @@ CredentialsNeeded.args = { marks: {}, }, }, - isUserConnected: true, - hasConnectedOwner: true, - credentials: false, - credentialsIsFetching: false, - credentialsRedirectUrl: '', + isUserConnected: false, + hasConnectedOwner: false, + handleConnectUser: () => {}, handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; -export const ThreatResult = Base.bind( {} ); -ThreatResult.args = { +export const CredentialsNeeded = Base.bind( {} ); +CredentialsNeeded.args = { threat: { id: 185869885, signature: 'EICAR_AV_Test', @@ -105,31 +126,10 @@ ThreatResult.args = { }, isUserConnected: true, hasConnectedOwner: true, - credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentials: false, + credentialsIsFetching: false, + credentialsRedirectUrl: '', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, }; - -export const VulnerableExtension = Base.bind( {} ); -VulnerableExtension.args = { - threat: { - id: 184847701, - signature: 'Vulnerable.WP.Extension', - title: 'Vulnerable Plugin: WP Super Cache (version 1.6.3)', - description: - 'The plugin WP Super Cache (version 1.6.3) has a known vulnerability. The WP Super Cache plugin before version 1.7.2 is vulnerable to an authenticated RCE in the settings page.', - fixedIn: '1.12.4', - source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', - extension: { - name: 'WP Super Cache', - slug: 'wp-super-cache', - version: '1.6.3', - type: 'plugin', - }, - }, - isUserConnected: true, - hasConnectedOwner: true, - credentials: [ { type: 'managed', role: 'main', still_valid: true } ], - handleUpgradeClick: () => {}, -}; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index d14a2901fb4bf..fa8f14e16bb05 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useMemo } from 'react'; +import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react'; import styles from './styles.module.scss'; /** @@ -13,6 +13,8 @@ import styles from './styles.module.scss'; * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. + * @param {boolean} props.showThreatDetails - Whether to show the threat details. + * @param {Function} props.setShowThreatDetails - Function to set the showThreatDetails state. * @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. @@ -26,6 +28,8 @@ const ThreatActions = ( { handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, + showThreatDetails, + setShowThreatDetails, fixerState, }: { threat: Threat; @@ -33,6 +37,8 @@ const ThreatActions = ( { handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; + showThreatDetails: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -52,20 +58,38 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); + const onContinueClick = useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ); + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { return null; } return (
-
+
+ { ! showThreatDetails && ( + + ) }
{ threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } @@ -75,7 +99,7 @@ const ThreatActions = ( { +
+ ) } +
+ + + + + + + + ); + } + + return <>{ children }; +}; + +export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx index 91068e3721c0e..86c0f5f7e85d2 100644 --- a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -1,15 +1,32 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; import styles from './styles.module.scss'; +/** + * UserConnectionGate component + * + * @param {object} props - The component props. + * @param {Function} props.closeModal - Function to close the modal. + * @param {boolean} props.isUserConnected - Whether the current user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. + * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. + * @param {ReactNode} props.children - The child components to render if the user is connected. + * + * @return {JSX.Element} The rendered UserConnectionGate component. + */ const UserConnectionGate = ( { closeModal, isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser, + showThreatDetails, + setShowThreatDetails, children, }: { closeModal: () => void; @@ -17,8 +34,15 @@ const UserConnectionGate = ( { hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; + showThreatDetails?: boolean; + setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const onShowThreatDetailsClick = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + if ( ! isUserConnected || ! hasConnectedOwner ) { return ( <> @@ -52,9 +76,16 @@ const UserConnectionGate = ( {
- +
+ { ! showThreatDetails && ( + + ) } + +
-
- ) } -
- + + - - void } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index 2b059a2f27dee..af524bbb60d13 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,12 +1,9 @@ -import { Text, Button } from '@automattic/jetpack-components'; import { type Threat } from '@automattic/jetpack-scan'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import React, { ReactNode, Dispatch, SetStateAction } from 'react'; -import ThreatSeverityBadge from '../threat-severity-badge'; -import styles from './styles.module.scss'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; +import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; /** @@ -58,47 +55,10 @@ const ThreatDetailsGate = ( { if ( showThreatDetails ) { return ( <> - { fixerState?.error && ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ) } - { fixerState?.stale && ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ) } - { fixerState?.inProgress && ! fixerState?.stale && ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ) } -
-
- { title } - { !! threat.severity && } -
- - { !! threat.description && { threat.description } } - - { !! threat.source && ( -
- -
- ) } -
- + + - - { + if ( fixerState.error ) { + return ( + + { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } + + ); + } + if ( fixerState.stale ) { + return ( + + { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } + + ); + } + if ( fixerState.inProgress && ! fixerState.stale ) { + return ( + + { __( 'The auto-fixer is in progress.', 'jetpack' ) } + + ); + } + return null; +}; + +export default ThreatNotices; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx b/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx new file mode 100644 index 0000000000000..7cc9af64bcdb1 --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-summary.tsx @@ -0,0 +1,25 @@ +import { Button } from '@automattic/jetpack-components'; +import { type Threat } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import Text from '../text'; +import ThreatSeverityBadge from '../threat-severity-badge'; +import styles from './styles.module.scss'; + +const ThreatSummary = ( { threat, title }: { threat: Threat; title: string } ): JSX.Element => ( +
+
+ { title } + { !! threat.severity && } +
+ { !! threat.description && { threat.description } } + { !! threat.source && ( +
+ +
+ ) } +
+); + +export default ThreatSummary; From 05ac09ac6d2fb1a2caa107cad6d9f9589e854953 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 14:51:44 -0800 Subject: [PATCH 15/67] Separation and reorg --- .../threat-details-modal/credentials-gate.tsx | 19 +--- .../components/threat-details-modal/index.tsx | 94 +++++++++---------- .../threat-details-modal/threat-actions.tsx | 39 ++------ .../threat-details-actions.tsx | 73 ++++++++++++++ .../threat-details-gate.tsx | 54 +++-------- .../threat-fixer-modal.tsx | 69 ++++++++++++++ .../user-connection-gate.tsx | 30 ++---- 7 files changed, 225 insertions(+), 153 deletions(-) create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-actions.tsx create mode 100644 projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index d0afc64db976d..4ae668908cdb3 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -1,44 +1,35 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; +import React, { ReactNode, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * CredentialsGate component * * @param {object} props - The component props. - * @param {Function} props.closeModal - Function to close the modal. * @param {false | Record[]} props.credentials - The server credentials, or `false` if not set. * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. * @param {ReactNode} props.children - The child components to render if credentials are set. * * @return {JSX.Element} The rendered CredentialsGate component. */ const CredentialsGate = ( { - closeModal, credentials, credentialsIsFetching, credentialsRedirectUrl, - showThreatDetails, - setShowThreatDetails, children, }: { - closeModal: () => void; credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; - showThreatDetails?: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; + children: ReactNode; } ) => { - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick } = + useContext( ThreatDetailsModalContext ); if ( ! credentials || credentials.length === 0 ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 4b66ad74b265e..23c21e9778cdd 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,16 +1,20 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import CredentialsGate from './credentials-gate'; +import { useMemo, useState, createContext, useCallback } from 'react'; import styles from './styles.module.scss'; -import ThreatActions from './threat-actions'; import ThreatDetailsGate from './threat-details-gate'; -import ThreatFixDetails from './threat-fix-details'; -import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; -import UserConnectionGate from './user-connection-gate'; +import ThreatFixerModal from './threat-fixer-modal'; +interface ThreatDetailsModalContextType { + closeModal: () => void; + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onContinueClick: () => void; +} + +export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContextType | null >( + null +); /** * ThreatDetailsModal component @@ -81,52 +85,44 @@ export default function ThreatDetailsModal( { return (
- void } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } + void, + onShowThreatDetailsClick: useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ), + onContinueClick: useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ), + } } > - void } - isUserConnected={ isUserConnected } - hasConnectedOwner={ hasConnectedOwner } - userIsConnecting={ userIsConnecting } - handleConnectUser={ handleConnectUser } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } + - void } + - - - - - void } - handleFixThreatClick={ handleFixThreatClick } - handleIgnoreThreatClick={ handleIgnoreThreatClick } - handleUnignoreThreatClick={ handleUnignoreThreatClick } - showThreatDetails={ showThreatDetails } - setShowThreatDetails={ setShowThreatDetails } - fixerState={ fixerState } - /> - - - + fixerState={ fixerState } + handleUpgradeClick={ handleUpgradeClick } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + /> + +
); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index fa8f14e16bb05..a07f7355d99ac 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -1,20 +1,18 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { Dispatch, SetStateAction, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * ThreatActions component * * @param {object} props - The component props. * @param {object} props.threat - The threat object containing action details. - * @param {Function} props.closeModal - Function to close the modal. * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Function} props.setShowThreatDetails - Function to set the showThreatDetails state. * @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. @@ -24,23 +22,19 @@ import styles from './styles.module.scss'; */ const ThreatActions = ( { threat, - closeModal, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, - showThreatDetails, - setShowThreatDetails, fixerState, }: { threat: Threat; - closeModal: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - showThreatDetails: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { + const { closeModal, onShowThreatDetailsClick } = useContext( ThreatDetailsModalContext ); + const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { @@ -58,15 +52,6 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - const onContinueClick = useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ); - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { return null; } @@ -74,22 +59,14 @@ const ThreatActions = ( { return (
- { ! showThreatDetails && ( - - ) } +
{ threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } @@ -99,7 +76,7 @@ const ThreatActions = ( { +
+
+ { threat.status === 'ignored' && ( + + ) } + { threat.status === 'current' && ( + <> + + + { threat.fixable && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index af524bbb60d13..f79a883351158 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,29 +1,24 @@ import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactNode, Dispatch, SetStateAction } from 'react'; -import ThreatActions from './threat-actions'; +import React, { ReactNode, useContext } from 'react'; +import ThreatDetailsActions from './threat-details-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; +import { ThreatDetailsModalContext } from '.'; /** * ThreatDetailsGate component * - * @param {object} props - The component props. - * @param {string} props.title - The title of the threat details. - * @param {Threat} props.threat - The threat object containing details. - * @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 status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. - * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. - * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * @param {Function} props.closeModal - Function to close the modal. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. - * @param {ReactNode} props.children - The child components to render if details are not shown. + * @param {object} props - The component props. + * @param {string} props.title - The title of the threat details. + * @param {Threat} props.threat - The threat object containing details. + * @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 status is stale. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. + * @param {ReactNode} props.children - The child components to render if details are not shown. * * @return {JSX.Element} The rendered ThreatDetailsGate component. */ @@ -32,26 +27,16 @@ const ThreatDetailsGate = ( { threat, fixerState, handleUpgradeClick, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, - closeModal, - showThreatDetails, - setShowThreatDetails, children, }: { title: string; threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleUpgradeClick: () => void; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - closeModal: () => void; - showThreatDetails: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { + const { showThreatDetails } = useContext( ThreatDetailsModalContext ); + if ( showThreatDetails ) { return ( <> @@ -59,16 +44,7 @@ const ThreatDetailsGate = ( { - + ); } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx new file mode 100644 index 0000000000000..7afbaa5129abc --- /dev/null +++ b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx @@ -0,0 +1,69 @@ +import { type Threat } from '@automattic/jetpack-scan'; +import CredentialsGate from './credentials-gate'; +import ThreatActions from './threat-actions'; +import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; +import ThreatSummary from './threat-summary'; +import ThreatTechnicalDetails from './threat-technical-details'; +import UserConnectionGate from './user-connection-gate'; + +const ThreatFixerModal = ( { + title, + threat, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, + fixerState, + handleUpgradeClick, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, +}: { + title: string; + threat: Threat; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + credentials: false | Record< string, unknown >[]; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; + handleUpgradeClick: () => void; + handleFixThreatClick?: ( threats: Threat[] ) => void; + handleIgnoreThreatClick?: ( threats: Threat[] ) => void; + handleUnignoreThreatClick?: ( threats: Threat[] ) => void; +} ) => { + return ( + + + + + + + + + + ); +}; + +export default ThreatFixerModal; diff --git a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx index 86c0f5f7e85d2..c5336b53618f8 100644 --- a/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/user-connection-gate.tsx @@ -1,47 +1,37 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import React, { ReactNode, Dispatch, SetStateAction, useCallback } from 'react'; +import React, { ReactNode, useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatDetailsModalContext } from '.'; /** * UserConnectionGate component * - * @param {object} props - The component props. - * @param {Function} props.closeModal - Function to close the modal. - * @param {boolean} props.isUserConnected - Whether the current user is connected. - * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} [props.showThreatDetails] - Whether to show the threat details. - * @param {Dispatch>} props.setShowThreatDetails - Function to toggle threat details visibility. - * @param {ReactNode} props.children - The child components to render if the user is connected. + * @param {object} props - The component props. + * @param {boolean} props.isUserConnected - Whether the current user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {ReactNode} props.children - The child components to render if the user is connected. * * @return {JSX.Element} The rendered UserConnectionGate component. */ const UserConnectionGate = ( { - closeModal, isUserConnected, hasConnectedOwner, userIsConnecting, handleConnectUser, - showThreatDetails, - setShowThreatDetails, children, }: { - closeModal: () => void; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; handleConnectUser: () => void; - showThreatDetails?: boolean; - setShowThreatDetails: Dispatch< SetStateAction< boolean > >; children: ReactNode; } ) => { - const onShowThreatDetailsClick = useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick } = + useContext( ThreatDetailsModalContext ); if ( ! isUserConnected || ! hasConnectedOwner ) { return ( From 1f25735889b3a316a8cd32a7b451f0e26e72ae87 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 14:59:59 -0800 Subject: [PATCH 16/67] Improve --- .../components/components/threat-details-modal/index.tsx | 3 +-- .../threat-details-modal/threat-fix-details.tsx | 2 +- .../threat-details-modal/threat-fixer-modal.tsx | 8 +++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 23c21e9778cdd..a2e2de9ad972a 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -108,6 +108,7 @@ export default function ThreatDetailsModal( { void; + handleUpgradeClick?: () => void; } ): JSX.Element => { const title = useMemo( () => { if ( threat.status === 'fixed' ) { diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx index 7afbaa5129abc..a99f3b99e1b2e 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx @@ -10,6 +10,7 @@ import UserConnectionGate from './user-connection-gate'; const ThreatFixerModal = ( { title, threat, + fixerState, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -17,14 +18,13 @@ const ThreatFixerModal = ( { credentials, credentialsIsFetching, credentialsRedirectUrl, - fixerState, - handleUpgradeClick, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, }: { title: string; threat: Threat; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -32,8 +32,6 @@ const ThreatFixerModal = ( { credentials: false | Record< string, unknown >[]; credentialsIsFetching: boolean; credentialsRedirectUrl: string; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleUpgradeClick: () => void; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; @@ -52,7 +50,7 @@ const ThreatFixerModal = ( { > - + Date: Fri, 15 Nov 2024 15:12:08 -0800 Subject: [PATCH 17/67] Remove redundant code --- .../components/threat-details-modal/index.tsx | 4 +- .../threat-details-modal/threat-actions.tsx | 53 +++++++------- .../threat-details-actions.tsx | 73 ------------------- .../threat-details-gate.tsx | 4 +- ...-modal.tsx => threat-fix-confirmation.tsx} | 6 +- 5 files changed, 35 insertions(+), 105 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-details-modal/threat-details-actions.tsx rename projects/js-packages/components/components/threat-details-modal/{threat-fixer-modal.tsx => threat-fix-confirmation.tsx} (96%) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index a2e2de9ad972a..842458b2a42c4 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -4,7 +4,7 @@ import { __ } from '@wordpress/i18n'; import { useMemo, useState, createContext, useCallback } from 'react'; import styles from './styles.module.scss'; import ThreatDetailsGate from './threat-details-gate'; -import ThreatFixerModal from './threat-fixer-modal'; +import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatDetailsModalContextType { closeModal: () => void; showThreatDetails: boolean; @@ -105,7 +105,7 @@ export default function ThreatDetailsModal( { fixerState={ fixerState } handleUpgradeClick={ handleUpgradeClick } > - void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ): JSX.Element => { - const { closeModal, onShowThreatDetailsClick } = useContext( ThreatDetailsModalContext ); + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + useContext( ThreatDetailsModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -52,41 +53,43 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - return (
- + { ! showThreatDetails && ( + + ) }
- { threat.status === 'ignored' && handleUnignoreThreatClick && ( - ) } { threat.status === 'current' && ( <> - { handleIgnoreThreatClick && ( - - ) } - { threat.fixable && handleFixThreatClick && ( + + { threat.fixable && ( -
-
- { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> - - - { threat.fixable && ( - - ) } - - ) } -
-
- ); -}; - -export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index f79a883351158..1b8eec457358d 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,6 +1,6 @@ import { type Threat } from '@automattic/jetpack-scan'; import React, { ReactNode, useContext } from 'react'; -import ThreatDetailsActions from './threat-details-actions'; +import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; @@ -44,7 +44,7 @@ const ThreatDetailsGate = ( { - + ); } diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx rename to projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index a99f3b99e1b2e..493d376735912 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fixer-modal.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -7,7 +7,7 @@ import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; -const ThreatFixerModal = ( { +const ThreatFixConfirmation = ( { title, threat, fixerState, @@ -54,14 +54,14 @@ const ThreatFixerModal = ( { ); }; -export default ThreatFixerModal; +export default ThreatFixConfirmation; From e7ff7318ff3948d3018c9496cdaaaac9ebf1b0fb Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 15 Nov 2024 15:16:31 -0800 Subject: [PATCH 18/67] Adjust modal content --- .../components/threat-details-modal/index.tsx | 5 ++++- .../threat-details-modal/threat-fix-confirmation.tsx | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 842458b2a42c4..a8ad37feefad3 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -21,6 +21,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex * * @param {object} props - The props. * @param {object} props.threat - The threat. + * @param {boolean} props.showDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -37,6 +38,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, + showDetails = true, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -51,6 +53,7 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; + showDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -64,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( true ); + const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index 493d376735912..ce31f3ec13ea7 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -3,12 +3,12 @@ import CredentialsGate from './credentials-gate'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; +// import ThreatSummary from './threat-summary'; +// import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { - title, + // title, threat, fixerState, isUserConnected, @@ -48,10 +48,11 @@ const ThreatFixConfirmation = ( { credentialsIsFetching={ credentialsIsFetching } credentialsRedirectUrl={ credentialsRedirectUrl } > + { /* TODO: Determine what we want to display here */ } - + { /* */ } - + { /* */ } Date: Fri, 15 Nov 2024 15:35:17 -0800 Subject: [PATCH 19/67] Fix implementation and stories --- .../components/threats-data-views/index.tsx | 43 +- .../stories/index.stories.tsx | 413 +++++++++++++++--- 2 files changed, 399 insertions(+), 57 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index b8807b5513ed7..ffb5f861b3608 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -56,6 +56,13 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr * @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing. * @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring. * @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring. + * @param {boolean} props.isUserConnected - Whether the user is connected. + * @param {boolean} props.hasConnectedOwner - Whether the site has a connected owner. + * @param {boolean} props.userIsConnecting - Whether the user is connecting. + * @param {Function} props.handleConnectUser - Function to handle the user connection process. + * @param {object[]} props.credentials - The credentials. + * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching. + * @param {string} props.credentialsRedirectUrl - The credentials redirect URL. * * @return {JSX.Element} The ThreatsDataViews component. */ @@ -64,23 +71,37 @@ export default function ThreatsDataViews( { filters, onChangeSelection, handleUpgradeClick, - isThreatEligibleForFix, - isThreatEligibleForIgnore, - isThreatEligibleForUnignore, onFixThreats, onIgnoreThreats, onUnignoreThreats, + isThreatEligibleForFix, + isThreatEligibleForIgnore, + isThreatEligibleForUnignore, + isUserConnected, + hasConnectedOwner, + userIsConnecting, + handleConnectUser, + credentials, + credentialsIsFetching, + credentialsRedirectUrl, }: { data: Threat[]; filters?: Filter[]; onChangeSelection?: ( selectedItemIds: string[] ) => void; handleUpgradeClick?: () => void; - isThreatEligibleForFix?: ( threat: Threat ) => boolean; - isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; - isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; onFixThreats?: ( threats: Threat[] ) => void; onIgnoreThreats?: ( threats: Threat[] ) => void; onUnignoreThreats?: ( threats: Threat[] ) => void; + isThreatEligibleForFix?: ( threat: Threat ) => boolean; + isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; + isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; + isUserConnected: boolean; + hasConnectedOwner: boolean; + userIsConnecting: boolean; + handleConnectUser: () => void; + credentials: false | Record< string, unknown >[]; + credentialsIsFetching: boolean; + credentialsRedirectUrl: string; } ): JSX.Element { const baseView = { sort: { @@ -538,11 +559,19 @@ export default function ThreatsDataViews( { { openThreat ? ( ) : null } diff --git a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx index c7f446d61688f..e3de4146a59d5 100644 --- a/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threats-data-views/stories/index.stories.tsx @@ -18,6 +18,129 @@ export default { ], }; +export const FixerStatuses = args => ; +FixerStatuses.args = { + data: [ + { + id: 13216959, + signature: 'Vulnerable.WP.Core', + title: 'Vulnerable WordPress Version (6.4.3)', + description: 'This threat has an auto-fixer available. ', + firstDetected: '2024-07-15T21:56:50.000Z', + severity: 4, + fixer: null, + fixedOn: '2024-07-15T22:01:42.000Z', + status: 'current', + fixable: { fixer: 'update', target: '6.4.4', extensionStatus: 'inactive' }, + version: '6.4.3', + source: '', + }, + { + id: 12345678910, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Plugin: Example Plugin (version 1.2.3)', + description: 'This threat has an in-progress auto-fixer.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '1.2.4', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date().toISOString() }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Plugin', + slug: 'example-plugin', + version: '1.2.3', + type: 'plugin', + }, + }, + { + id: 12345678911, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Theme: Example Theme (version 2.2.2)', + description: 'This threat has an in-progress auto-fixer that is taking too long.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '2.22.22', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Theme', + slug: 'example-theme', + version: '2.2.2', + type: 'theme', + }, + }, + { + id: 12345678912, + signature: 'Vulnerable.WP.Extension', + title: 'Vulnerable Theme: Example Theme II (version 3.3.3)', + description: 'This threat has a fixer with an error status.', + firstDetected: '2024-10-02T17:34:59.000Z', + fixedIn: '3.4.5', + severity: 3, + fixable: { fixer: 'update', target: '1.12.4', extensionStatus: 'inactive' }, + fixer: { status: 'error', error: 'error' }, + status: 'current', + source: 'https://wpscan.com/vulnerability/733d8a02-0d44-4b78-bbb2-37e447acd2f3', + extension: { + name: 'Example Theme II', + slug: 'example-theme-2', + version: '3.3.3', + type: 'theme', + }, + }, + { + id: 185868972, + signature: 'EICAR_AV_Test_Suspicious', + title: 'Malicious code found in file: jptt_eicar.php', + description: 'This threat has no auto-fixer available.', + firstDetected: '2024-10-07T20:40:15.000Z', + fixedIn: null, + severity: 1, + fixable: false, + status: 'current', + filename: '/var/www/html/wp-content/uploads/jptt_eicar.php', + context: { + '6': 'echo << + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsIsFetching: false, + credentialsRedirectUrl: '', + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + export const Default = args => ; Default.args = { data: [ @@ -153,6 +276,11 @@ Default.args = { value: [ 'current' ], }, ], + isUserConnected: true, + hasConnectedOwner: true, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert @@ -165,85 +293,199 @@ Default.args = { ), }; -export const FixerStatuses = args => ; -FixerStatuses.args = { +export const UserConnectedRequired = args => ; +UserConnectedRequired.args = { data: [ { - id: 13216959, - signature: 'Vulnerable.WP.Core', - title: 'Vulnerable WordPress Version (6.4.3)', - description: 'This threat has an auto-fixer available. ', - firstDetected: '2024-07-15T21:56:50.000Z', + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + +export const CredentialsRequired = args => ; +CredentialsRequired.args = { + data: [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << true, onFixThreats: () => - alert( 'Fix threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onIgnoreThreats: () => alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert onUnignoreThreats: () => From 00511f6b38fd83566ac857b258054a8326bb2a56 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 10:02:43 -0800 Subject: [PATCH 20/67] Update flag --- .../components/components/threat-details-modal/index.tsx | 8 ++++---- .../components/components/threats-data-views/index.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index a8ad37feefad3..6705b5eb7e635 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -21,7 +21,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.showDetails - Whether to show the details. + * @param {boolean} props.skipThreatDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -38,7 +38,7 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, - showDetails = true, + skipThreatDetails = false, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -53,7 +53,7 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; - showDetails?: boolean; + skipThreatDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -67,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); + const [ showThreatDetails, setShowThreatDetails ] = useState( skipThreatDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index ffb5f861b3608..8c580941c77e2 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -559,7 +559,7 @@ export default function ThreatsDataViews( { { openThreat ? ( Date: Mon, 18 Nov 2024 10:18:53 -0800 Subject: [PATCH 21/67] Fix display logic --- .../components/components/threat-details-modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 6705b5eb7e635..fd1627cac1daa 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -67,7 +67,7 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( skipThreatDetails ); + const [ showThreatDetails, setShowThreatDetails ] = useState( ! skipThreatDetails ); const fixerState = useMemo( () => { return getFixerState( threat.fixer ); From 8c0991716abaa5c787a13d838c8e0e271a7595e8 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 11:16:11 -0800 Subject: [PATCH 22/67] Add showThreatDetails action to threat title --- .../components/threats-data-views/index.tsx | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 8c580941c77e2..9990a596acff6 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -1,3 +1,4 @@ +import { Button } from '@automattic/jetpack-components'; import { getThreatType, type Threat } from '@automattic/jetpack-scan'; import { type Action, @@ -168,16 +169,20 @@ export default function ThreatsDataViews( { } ); const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); + const [ skipThreatDetails, setSkipThreatDetails ] = useState< boolean >( false ); const showThreatDetails = useCallback( - ( threat: Threat ) => () => { - setOpenThreat( threat ); - }, + ( threat: Threat, skipDetails = false ) => + () => { + setOpenThreat( threat ); + setSkipThreatDetails( skipDetails ); + }, [] ); const hideThreatDetails = useCallback( () => { setOpenThreat( null ); + setSkipThreatDetails( false ); }, [] ); /** @@ -262,7 +267,15 @@ export default function ThreatsDataViews( { enableGlobalSearch: true, enableHiding: false, render: ( { item }: { item: Threat } ) => ( -
{ item.title }
+ ), }, { @@ -445,7 +458,9 @@ export default function ThreatsDataViews( { return null; } - return ; + return ( + + ); }, }, ] @@ -468,7 +483,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ] )(); + showThreatDetails( items[ 0 ], true )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -487,7 +502,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ] )(); + showThreatDetails( items[ 0 ], true )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -559,7 +574,7 @@ export default function ThreatsDataViews( { { openThreat ? ( Date: Mon, 18 Nov 2024 11:50:49 -0800 Subject: [PATCH 23/67] Move context provided a component level up --- .../threat-details-modal/credentials-gate.tsx | 5 +- .../components/threat-details-modal/index.tsx | 68 ++++----------- .../threat-details-modal/threat-actions.tsx | 4 +- .../threat-details-gate.tsx | 4 +- .../threat-fix-confirmation.tsx | 10 +-- .../user-connection-gate.tsx | 4 +- .../components/threats-data-views/index.tsx | 86 ++++++++++++------- 7 files changed, 89 insertions(+), 92 deletions(-) diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx index 4ae668908cdb3..966982b80b76a 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx @@ -2,9 +2,8 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import React, { ReactNode, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; -import { ThreatDetailsModalContext } from '.'; - /** * CredentialsGate component * @@ -29,7 +28,7 @@ const CredentialsGate = ( { children: ReactNode; } ) => { const { closeModal, showThreatDetails, onShowThreatDetailsClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); if ( ! credentials || credentials.length === 0 ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index fd1627cac1daa..95804111f8235 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -1,27 +1,16 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, createContext, useCallback } from 'react'; +import { useMemo } from 'react'; import styles from './styles.module.scss'; import ThreatDetailsGate from './threat-details-gate'; import ThreatFixConfirmation from './threat-fix-confirmation'; -interface ThreatDetailsModalContextType { - closeModal: () => void; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onContinueClick: () => void; -} - -export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContextType | null >( - null -); /** * ThreatDetailsModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.skipThreatDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -38,7 +27,6 @@ export const ThreatDetailsModalContext = createContext< ThreatDetailsModalContex */ export default function ThreatDetailsModal( { threat, - skipThreatDetails = false, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -53,7 +41,6 @@ export default function ThreatDetailsModal( { ...modalProps }: { threat: Threat; - skipThreatDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -67,8 +54,6 @@ export default function ThreatDetailsModal( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; [ key: string ]: unknown; } ): JSX.Element { - const [ showThreatDetails, setShowThreatDetails ] = useState( ! skipThreatDetails ); - const fixerState = useMemo( () => { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); @@ -88,43 +73,28 @@ export default function ThreatDetailsModal( { return (
- void, - onShowThreatDetailsClick: useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ), - onContinueClick: useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ), - } } + - - - - + isUserConnected={ isUserConnected } + hasConnectedOwner={ hasConnectedOwner } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + credentials={ credentials } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + /> +
); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx index 3a6d3be1d580f..2e2d6f3d51cc8 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; -import { ThreatDetailsModalContext } from '.'; /** * ThreatActions component @@ -34,7 +34,7 @@ const ThreatActions = ( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); diff --git a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx index 1b8eec457358d..5b37d2ce5ac47 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-details-gate.tsx @@ -1,11 +1,11 @@ import { type Threat } from '@automattic/jetpack-scan'; import React, { ReactNode, useContext } from 'react'; +import { ThreatModalContext } from '../threats-data-views'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; -import { ThreatDetailsModalContext } from '.'; /** * ThreatDetailsGate component @@ -35,7 +35,7 @@ const ThreatDetailsGate = ( { handleUpgradeClick: () => void; children: ReactNode; } ) => { - const { showThreatDetails } = useContext( ThreatDetailsModalContext ); + const { showThreatDetails } = useContext( ThreatModalContext ); if ( showThreatDetails ) { return ( diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx index ce31f3ec13ea7..c5c9a0f137399 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-details-modal/threat-fix-confirmation.tsx @@ -3,12 +3,12 @@ import CredentialsGate from './credentials-gate'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; -// import ThreatSummary from './threat-summary'; -// import ThreatTechnicalDetails from './threat-technical-details'; +import ThreatSummary from './threat-summary'; +import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { - // title, + title, threat, fixerState, isUserConnected, @@ -50,9 +50,9 @@ const ThreatFixConfirmation = ( { > { /* TODO: Determine what we want to display here */ } - { /* */ } + - { /* */ } + { const { closeModal, showThreatDetails, onShowThreatDetailsClick } = - useContext( ThreatDetailsModalContext ); + useContext( ThreatModalContext ); if ( ! isUserConnected || ! hasConnectedOwner ) { return ( diff --git a/projects/js-packages/components/components/threats-data-views/index.tsx b/projects/js-packages/components/components/threats-data-views/index.tsx index 9990a596acff6..4e045c49f7bea 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -14,7 +14,7 @@ import { import { dateI18n } from '@wordpress/date'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState, createContext } from 'react'; import Badge from '../badge'; import ThreatDetailsModal from '../threat-details-modal'; import ThreatFixerButton from '../threat-fixer-button'; @@ -43,6 +43,15 @@ import { import styles from './styles.module.scss'; import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-control'; +interface ThreatModalContextType { + closeModal: () => void; + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onHideThreatDetailsClick: () => void; +} + +export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); + /** * DataViews component for displaying security threats. * @@ -169,22 +178,32 @@ export default function ThreatsDataViews( { } ); const [ openThreat, setOpenThreat ] = useState< Threat | null >( null ); - const [ skipThreatDetails, setSkipThreatDetails ] = useState< boolean >( false ); + const [ showThreatDetails, setShowThreatDetails ] = useState< boolean >( true ); - const showThreatDetails = useCallback( - ( threat: Threat, skipDetails = false ) => + const showThreatModal = useCallback( + ( threat: Threat, showDetails = true ) => () => { setOpenThreat( threat ); - setSkipThreatDetails( skipDetails ); + setShowThreatDetails( showDetails ); }, [] ); - const hideThreatDetails = useCallback( () => { + const hideThreatModal = useCallback( () => { setOpenThreat( null ); - setSkipThreatDetails( false ); + setShowThreatDetails( true ); }, [] ); + const onShowThreatDetails = useCallback( + () => setShowThreatDetails( true ), + [ setShowThreatDetails ] + ); + + const onHideThreatDetails = useCallback( + () => setShowThreatDetails( false ), + [ setShowThreatDetails ] + ); + /** * Compute values from the provided threats data. * @@ -272,7 +291,7 @@ export default function ThreatsDataViews( { variant="link" size="small" weight="regular" - onClick={ showThreatDetails( item ) } + onClick={ showThreatModal( item ) } > { item.title } @@ -459,7 +478,7 @@ export default function ThreatsDataViews( { } return ( - + ); }, }, @@ -468,7 +487,7 @@ export default function ThreatsDataViews( { ]; return result; - }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatDetails ] ); + }, [ plugins, themes, dataFields, signatures, isThreatEligibleForFix, showThreatModal ] ); /** * DataView actions - collection of operations that can be performed upon each record. @@ -483,7 +502,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ], true )(); + showThreatModal( items[ 0 ], false )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -502,7 +521,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatDetails( items[ 0 ], true )(); + showThreatModal( items[ 0 ], false )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -519,7 +538,7 @@ export default function ThreatsDataViews( { return result; }, [ dataFields, - showThreatDetails, + showThreatModal, onIgnoreThreats, onUnignoreThreats, isThreatEligibleForIgnore, @@ -572,22 +591,31 @@ export default function ThreatsDataViews( { } /> { openThreat ? ( - + + + ) : null } ); From e6ca520267434f18f36d3b058fc1674fff9bacd3 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:41:43 -0800 Subject: [PATCH 24/67] Reorg and fix stories --- .../add-components-threat-details-modal | 2 +- .../credentials-gate.tsx | 4 +- .../index.tsx | 71 +++++++++++++------ .../stories/index.stories.tsx | 23 ++++-- .../styles.module.scss | 0 .../threat-actions.tsx | 12 ++-- .../threat-details-gate.tsx | 2 +- .../threat-fix-confirmation.tsx | 0 .../threat-fix-details.tsx | 0 .../threat-notice.tsx | 0 .../threat-summary.tsx | 0 .../threat-technical-details.tsx | 0 .../user-connection-gate.tsx | 4 +- .../components/threats-data-views/index.tsx | 56 ++++++--------- .../contextualized-connection/index.jsx | 4 +- .../prompts/site-type/index.jsx | 4 +- 16 files changed, 103 insertions(+), 79 deletions(-) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/credentials-gate.tsx (96%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/index.tsx (60%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/stories/index.stories.tsx (86%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/styles.module.scss (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-actions.tsx (92%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-details-gate.tsx (97%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-confirmation.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-notice.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-summary.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-technical-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/user-connection-gate.tsx (96%) diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal index 3caa1f5d56878..f1a9596ce3b78 100644 --- a/projects/js-packages/components/changelog/add-components-threat-details-modal +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds ThreatModal component and stories diff --git a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx rename to projects/js-packages/components/components/threat-modal/credentials-gate.tsx index 966982b80b76a..6d950da64ac63 100644 --- a/projects/js-packages/components/components/threat-details-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -2,8 +2,8 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import React, { ReactNode, useContext } from 'react'; -import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * CredentialsGate component * @@ -65,7 +65,7 @@ const CredentialsGate = ( {
{ ! showThreatDetails && ( ) } - { isOpen ? : null } + + { isOpen ? ( + + ) : null }
); }; diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/styles.module.scss rename to projects/js-packages/components/components/threat-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx similarity index 92% rename from projects/js-packages/components/components/threat-details-modal/threat-actions.tsx rename to projects/js-packages/components/components/threat-modal/threat-actions.tsx index 2e2d6f3d51cc8..1e697deca85a6 100644 --- a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { Threat, getFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; -import { ThreatModalContext } from '../threats-data-views'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatActions component @@ -33,7 +33,7 @@ const ThreatActions = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -58,7 +58,7 @@ const ThreatActions = ( {
{ ! showThreatDetails && ( ) } @@ -80,7 +80,7 @@ const ThreatActions = ( { ) } diff --git a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx index 7424fe8dc5b51..c6137d533c95d 100644 --- a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx +++ b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx @@ -42,7 +42,7 @@ const SiteTypeQuestionComponent = props => { } }, [ stateStepSlug, updatingStep, updateRecommendationsStep, addViewedRecommendation ] ); - const onContinueClick = useCallback( () => { + const onHideThreatDetailsClick = useCallback( () => { saveRecommendationsData(); analytics.tracks.recordEvent( 'jetpack_recommendations_site_type_answered', answers ); }, [ answers, saveRecommendationsData ] ); @@ -75,7 +75,7 @@ const SiteTypeQuestionComponent = props => { ) } />
-
From 924768ab7b2e2730fe5f033bc55949da710a6051 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:43:37 -0800 Subject: [PATCH 25/67] changelog --- .../add-components-threats-data-views-modal-integration | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration new file mode 100644 index 0000000000000..96ed49ef842ed --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Adds ThreatModal and integrates with ThreatsDataViews From abde93a8f2c0fb7bcae572259e67469881c03459 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:46:10 -0800 Subject: [PATCH 26/67] Remove unintended changes --- .../client/components/contextualized-connection/index.jsx | 4 ++-- .../_inc/client/recommendations/prompts/site-type/index.jsx | 4 ++-- .../add-components-threats-data-views-modal-integration | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx b/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx index d56414c4f2d3e..308dd36ebd5f0 100644 --- a/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx +++ b/projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx @@ -35,7 +35,7 @@ const ContextualizedConnection = props => { setHasSeenWCConnectionModal(); }, [ setHasSeenWCConnectionModal ] ); - const onHideThreatDetailsClick = useCallback( () => { + const onContinueClick = useCallback( () => { analytics.tracks.recordJetpackClick( 'contextualized_connection_continue_button' ); }, [] ); @@ -54,7 +54,7 @@ const ContextualizedConnection = props => { className="jp-contextualized-connection__button" label={ __( 'Continue to Jetpack', 'jetpack' ) } href={ redirectTo } - onClick={ onHideThreatDetailsClick } + onClick={ onContinueClick } > { __( 'Continue to Jetpack', 'jetpack' ) } diff --git a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx index c6137d533c95d..7424fe8dc5b51 100644 --- a/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx +++ b/projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx @@ -42,7 +42,7 @@ const SiteTypeQuestionComponent = props => { } }, [ stateStepSlug, updatingStep, updateRecommendationsStep, addViewedRecommendation ] ); - const onHideThreatDetailsClick = useCallback( () => { + const onContinueClick = useCallback( () => { saveRecommendationsData(); analytics.tracks.recordEvent( 'jetpack_recommendations_site_type_answered', answers ); }, [ answers, saveRecommendationsData ] ); @@ -75,7 +75,7 @@ const SiteTypeQuestionComponent = props => { ) } />
-
diff --git a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration b/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration deleted file mode 100644 index 96ed49ef842ed..0000000000000 --- a/projects/plugins/jetpack/changelog/add-components-threats-data-views-modal-integration +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Adds ThreatModal and integrates with ThreatsDataViews From fddb7f93f1cb052e0083b7f8ce3eb1e5b13e436c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:48:55 -0800 Subject: [PATCH 27/67] Generalize component name --- .../changelog/add-components-threat-details-modal | 2 +- .../components/components/threat-details-modal/index.tsx | 6 +++--- .../threat-details-modal/stories/index.stories.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/projects/js-packages/components/changelog/add-components-threat-details-modal b/projects/js-packages/components/changelog/add-components-threat-details-modal index 3caa1f5d56878..f1a9596ce3b78 100644 --- a/projects/js-packages/components/changelog/add-components-threat-details-modal +++ b/projects/js-packages/components/changelog/add-components-threat-details-modal @@ -1,4 +1,4 @@ Significance: minor Type: added -Adds ThreatDetailsModal component and stories +Adds ThreatModal component and stories diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-details-modal/index.tsx index 79e14047fa3ca..b2caf3b09dd52 100644 --- a/projects/js-packages/components/components/threat-details-modal/index.tsx +++ b/projects/js-packages/components/components/threat-details-modal/index.tsx @@ -10,7 +10,7 @@ import ThreatFixDetails from './threat-fix-details'; import ThreatTechnicalDetails from './threat-technical-details'; /** - * ThreatDetailsModal component + * ThreatModal component * * @param {object} props - The props. * @param {object} props.threat - The threat. @@ -19,9 +19,9 @@ import ThreatTechnicalDetails from './threat-technical-details'; * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * - * @return {JSX.Element} The threat details modal. + * @return {JSX.Element} The threat modal. */ -export default function ThreatDetailsModal( { +export default function ThreatModal( { threat, handleUpgradeClick, handleFixThreatClick, diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx index 46b1e6a93baea..41f6b83388f3a 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx @@ -1,10 +1,10 @@ import { useCallback, useState } from 'react'; import Button from '../../button/index.js'; -import ThreatDetailsModal from '../index.js'; +import ThreatModal from '../index.js'; export default { - title: 'JS Packages/Components/Threat Details Modal', - component: ThreatDetailsModal, + title: 'JS Packages/Components/Threat Modal', + component: ThreatModal, }; const Base = args => { @@ -14,7 +14,7 @@ const Base = args => { return (
- { isOpen ? : null } + { isOpen ? : null }
); }; From d3aaff751f7b78e80d1ea2f58eecf26e8646a61f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:51:22 -0800 Subject: [PATCH 28/67] Updates after renaming --- .../components/{threat-details-modal => threat-modal}/index.tsx | 0 .../stories/index.stories.tsx | 2 +- .../{threat-details-modal => threat-modal}/styles.module.scss | 0 .../{threat-details-modal => threat-modal}/threat-actions.tsx | 0 .../threat-fix-details.tsx | 0 .../threat-technical-details.tsx | 0 6 files changed, 1 insertion(+), 1 deletion(-) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/index.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/stories/index.stories.tsx (96%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/styles.module.scss (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-actions.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-fix-details.tsx (100%) rename projects/js-packages/components/components/{threat-details-modal => threat-modal}/threat-technical-details.tsx (100%) diff --git a/projects/js-packages/components/components/threat-details-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/index.tsx rename to projects/js-packages/components/components/threat-modal/index.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx similarity index 96% rename from projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx rename to projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 41f6b83388f3a..e1e2f954c6184 100644 --- a/projects/js-packages/components/components/threat-details-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -13,7 +13,7 @@ const Base = args => { const onRequestClose = useCallback( () => setIsOpen( false ), [] ); return (
- + { isOpen ? : null }
); diff --git a/projects/js-packages/components/components/threat-details-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/styles.module.scss rename to projects/js-packages/components/components/threat-modal/styles.module.scss diff --git a/projects/js-packages/components/components/threat-details-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-actions.tsx rename to projects/js-packages/components/components/threat-modal/threat-actions.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-fix-details.tsx rename to projects/js-packages/components/components/threat-modal/threat-fix-details.tsx diff --git a/projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx similarity index 100% rename from projects/js-packages/components/components/threat-details-modal/threat-technical-details.tsx rename to projects/js-packages/components/components/threat-modal/threat-technical-details.tsx From 3851638f4a3563b6b35bc25ae60f17b9ca5745bf Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 18 Nov 2024 13:53:25 -0800 Subject: [PATCH 29/67] Ensure close button exists for vulns modal --- .../components/components/threat-modal/threat-actions.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index d14a2901fb4bf..ee1fad99cd075 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -52,10 +52,6 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) { - return null; - } - return (
From 0c7099ed41abb5651a88299992c5b06051c6034f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 11:51:46 -0800 Subject: [PATCH 30/67] changelog --- .../changelog/update-components-threat-details-modal-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/components/changelog/update-components-threat-details-modal-flow diff --git a/projects/js-packages/components/changelog/update-components-threat-details-modal-flow b/projects/js-packages/components/changelog/update-components-threat-details-modal-flow new file mode 100644 index 0000000000000..988212c757caf --- /dev/null +++ b/projects/js-packages/components/changelog/update-components-threat-details-modal-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates ThreatModal flow From 670837869f3fa7f78879ae2ececc41e525e821c2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 12:10:03 -0800 Subject: [PATCH 31/67] Updates/fixes --- .../threat-modal/credentials-gate.tsx | 1 + .../components/threat-modal/index.tsx | 34 ++++++++----------- .../threat-modal/stories/index.stories.tsx | 15 +++++++- .../threat-modal/threat-actions.tsx | 12 ++++--- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx index dfd11f2b79e6e..893d3b2db8760 100644 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -37,6 +37,7 @@ const CredentialsGate = ( { <> { __( diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 8726727e37ff7..e62510b03ba65 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,7 +1,7 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, createContext, useCallback } from 'react'; +import { useMemo, createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; @@ -11,7 +11,7 @@ interface ThreatModalContextType { closeModal: () => void; showThreatDetails: boolean; onShowThreatDetailsClick: () => void; - onContinueClick: () => void; + onHideThreatDetailsClick: () => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -21,7 +21,6 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * * @param {object} props - The props. * @param {object} props.threat - The threat. - * @param {boolean} props.showDetails - Whether to show the details. * @param {boolean} props.isUserConnected - Whether the user is connected. * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner. * @param {boolean} props.userIsConnecting - Whether the user is connecting. @@ -33,12 +32,13 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function. * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. - * + * @param {boolean} props.showThreatDetails - Whether to show the threat details. + * @param {Function} props.onShowThreatDetailsClick - The onShowThreatDetailsClick function. + * @param {Function} props.onHideThreatDetailsClick - The onHideThreatDetailsClick function. * @return {JSX.Element} The threat modal. */ export default function ThreatModal( { threat, - showDetails = true, isUserConnected, hasConnectedOwner, userIsConnecting, @@ -50,10 +50,12 @@ export default function ThreatModal( { handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, + showThreatDetails, + onShowThreatDetailsClick, + onHideThreatDetailsClick, ...modalProps }: { threat: Threat; - showDetails?: boolean; isUserConnected: boolean; hasConnectedOwner: boolean; userIsConnecting: boolean; @@ -65,13 +67,13 @@ export default function ThreatModal( { handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; - [ key: string ]: unknown; -} ): JSX.Element { + showThreatDetails: boolean; + onShowThreatDetailsClick: () => void; + onHideThreatDetailsClick: () => void; +} & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; - const [ showThreatDetails, setShowThreatDetails ] = useState( showDetails ); - const fixerState = useMemo( () => { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); @@ -109,15 +111,9 @@ export default function ThreatModal( { void, - onShowThreatDetailsClick: useCallback( - () => setShowThreatDetails( true ), - [ setShowThreatDetails ] - ), - onContinueClick: useCallback( - () => setShowThreatDetails( false ), - [ setShowThreatDetails ] - ), + closeModal: modalProps.onRequestClose, + onShowThreatDetailsClick, + onHideThreatDetailsClick, } } > { const [ isOpen, setIsOpen ] = useState( false ); const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + + const [ showThreatDetails, setShowThreatDetails ] = useState( true ); + const onShowThreatDetails = useCallback( () => setShowThreatDetails( true ), [] ); + const onHideThreatDetails = useCallback( () => setShowThreatDetails( false ), [] ); + return (
- { isOpen ? : null } + { isOpen ? ( + + ) : null }
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index be7e478cfb3f9..ae7c4958a4c70 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -33,7 +33,7 @@ const ThreatActions = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, showThreatDetails, onShowThreatDetailsClick, onContinueClick } = + const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); @@ -53,6 +53,10 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); + if ( ! threat.status ) { + return null; + } + return (
{ ! showThreatDetails && ( @@ -65,7 +69,7 @@ const ThreatActions = ( { @@ -75,7 +79,7 @@ const ThreatActions = ( { ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx index 01f01b93cbcd6..a2af5a645b903 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx @@ -1,4 +1,4 @@ -import { Threat, getFixerMessage } from '@automattic/jetpack-scan'; +import { Threat, getFixerDescription } from '@automattic/jetpack-scan'; import { __, sprintf } from '@wordpress/i18n'; import React, { useMemo } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; @@ -43,7 +43,7 @@ const ThreatFixDetails = ( { ); } // The threat has an auto-fix available. - return getFixerMessage( threat ); + return getFixerDescription( threat ); }, [ threat ] ); if ( ! threat.fixable && ! threat.fixedIn ) { diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 8b186b065bb33..1ce3b30ada3e0 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -55,11 +55,50 @@ export const getFixerAction = ( threat: Threat ) => { case 'rollback': return __( 'Replace', 'jetpack-scan' ); default: - return __( 'Fix', 'jetpack-scan' ); + return __( 'Auto-fix', 'jetpack-scan' ); } }; -export const getFixerMessage = ( threat: Threat ) => { +export const getDetailedFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + return __( 'Delete file', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Delete plugin from site', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Delete theme from site', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.extension?.type === 'plugin' ) { + return __( 'Update plugin to newer version', 'jetpack-scan' ); + } + if ( threat.extension?.type === 'theme' ) { + return __( 'Update theme to newer version', 'jetpack-scan' ); + } + return __( 'Update', 'jetpack-scan' ); + break; + case 'replace': + case 'rollback': + if ( threat.filename ) { + return __( 'Replace from backup', 'jetpack-scan' ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( 'Replace default salts', 'jetpack-scan' ); + } + break; + default: + return __( 'Auto-fix', 'jetpack-scan' ); + } +}; + +export const getFixerDescription = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': if ( threat.filename ) { From 4e50a2611871fc0fcf9a2d610ec66148d6a92a65 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 13:51:00 -0800 Subject: [PATCH 33/67] changelog --- .../add-components-threats-data-views-modal-integration | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration diff --git a/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration b/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration new file mode 100644 index 0000000000000..ccbcd7068d542 --- /dev/null +++ b/projects/js-packages/scan/changelog/add-components-threats-data-views-modal-integration @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds utility for retrieving a detailed action description From 4709a1c13ceecd7510791107c63d0329749007ff Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 14:02:56 -0800 Subject: [PATCH 34/67] Add detailed action --- .../components/threat-fixer-button/index.tsx | 4 +- .../threat-modal/threat-actions.tsx | 6 +-- .../threat-modal/threat-fix-confirmation.tsx | 2 - .../threat-modal/threat-fix-details.tsx | 4 +- projects/js-packages/scan/src/utils/index.ts | 43 ++++++++++++++++++- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/projects/js-packages/components/components/threat-fixer-button/index.tsx b/projects/js-packages/components/components/threat-fixer-button/index.tsx index 11520c139ceee..24d77053ef4f8 100644 --- a/projects/js-packages/components/components/threat-fixer-button/index.tsx +++ b/projects/js-packages/components/components/threat-fixer-button/index.tsx @@ -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'; @@ -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( () => { diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index ae7c4958a4c70..1927b6da1523f 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,5 +1,5 @@ import { Button } from '@automattic/jetpack-components'; -import { Threat, getFixerAction } from '@automattic/jetpack-scan'; +import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useMemo, useContext } from 'react'; import styles from './styles.module.scss'; @@ -36,7 +36,7 @@ const ThreatActions = ( { const { closeModal, showThreatDetails, onShowThreatDetailsClick, onHideThreatDetailsClick } = useContext( ThreatModalContext ); - const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] ); + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { handleFixThreatClick?.( [ threat ] ); @@ -92,7 +92,7 @@ const ThreatActions = ( { > { fixerState.error || fixerState.stale ? __( 'Retry fix', 'jetpack' ) - : fixerAction } + : detailedFixerAction } ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index 81de9c3be18c4..c50b9e44231eb 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -4,7 +4,6 @@ import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; import UserConnectionGate from './user-connection-gate'; const ThreatFixConfirmation = ( { @@ -48,7 +47,6 @@ const ThreatFixConfirmation = ( { - { case 'rollback': return __( 'Replace', 'jetpack-scan' ); default: - return __( 'Fix', 'jetpack-scan' ); + return __( 'Auto-fix', 'jetpack-scan' ); } }; -export const getFixerMessage = ( threat: Threat ) => { +export const getDetailedFixerAction = ( threat: Threat ) => { + switch ( threat.fixable && threat.fixable.fixer ) { + case 'delete': + if ( threat.filename ) { + return __( 'Delete file', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'plugin' ) { + return __( 'Delete plugin from site', 'jetpack-scan' ); + } + + if ( threat.extension?.type === 'theme' ) { + return __( 'Delete theme from site', 'jetpack-scan' ); + } + break; + case 'update': + if ( threat.extension?.type === 'plugin' ) { + return __( 'Update plugin to newer version', 'jetpack-scan' ); + } + if ( threat.extension?.type === 'theme' ) { + return __( 'Update theme to newer version', 'jetpack-scan' ); + } + return __( 'Update', 'jetpack-scan' ); + break; + case 'replace': + case 'rollback': + if ( threat.filename ) { + return __( 'Replace from backup', 'jetpack-scan' ); + } + + if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) { + return __( 'Replace default salts', 'jetpack-scan' ); + } + break; + default: + return __( 'Auto-fix', 'jetpack-scan' ); + } +}; + +export const getFixerDescription = ( threat: Threat ) => { switch ( threat.fixable && threat.fixable.fixer ) { case 'delete': if ( threat.filename ) { From 799aa0bc84b3a28ab6d920565680b79e3384280f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 14:03:33 -0800 Subject: [PATCH 35/67] changelog --- .../changelog/update-components-threat-details-modal-flow | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/scan/changelog/update-components-threat-details-modal-flow diff --git a/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow b/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow new file mode 100644 index 0000000000000..ccbcd7068d542 --- /dev/null +++ b/projects/js-packages/scan/changelog/update-components-threat-details-modal-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Adds utility for retrieving a detailed action description From 6c19fcd8ce7648e952d0bc28ff73f9a54ee8477b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Tue, 19 Nov 2024 15:13:20 -0800 Subject: [PATCH 36/67] Add actionToConfirm flag for rendering conditional content based on action selected --- .../threat-modal/credentials-gate.tsx | 2 +- .../components/threat-modal/index.tsx | 9 +- .../threat-modal/stories/index.stories.tsx | 12 ++- .../threat-modal/threat-actions.tsx | 52 ++++++------ .../threat-modal/threat-details-actions.tsx | 82 +++++++++++++++++++ .../threat-modal/threat-details-gate.tsx | 46 +++++------ .../threat-modal/threat-fix-confirmation.tsx | 1 + .../threat-modal/threat-fix-details.tsx | 1 + .../threat-modal/user-connection-gate.tsx | 2 +- projects/js-packages/scan/src/utils/index.ts | 20 ++--- 10 files changed, 159 insertions(+), 68 deletions(-) create mode 100644 projects/js-packages/components/components/threat-modal/threat-details-actions.tsx diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx index 893d3b2db8760..e134007f99912 100644 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx @@ -65,7 +65,7 @@ const CredentialsGate = ( {
{ ! showThreatDetails && ( ) } - ) } +
- { threat.status === 'ignored' && ( - ) } { threat.status === 'current' && ( <> - - { threat.fixable && ( + { actionToConfirm === 'ignore' && ( + + ) } + { threat.fixable && actionToConfirm === 'fix' && ( ) } diff --git a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx new file mode 100644 index 0000000000000..e87a17d320a0e --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx @@ -0,0 +1,82 @@ +import { Button } from '@automattic/jetpack-components'; +import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { __ } from '@wordpress/i18n'; +import React, { useCallback, useMemo, useContext } from 'react'; +import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; + +/** + * ThreatActions component + * + * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing action details. + * @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 action buttons or null if no actions are available. + */ +const ThreatDetailsActions = ( { + threat, + fixerState, +}: { + threat: Threat; + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ): JSX.Element => { + const { onHideThreatDetailsClick } = useContext( ThreatModalContext ); + + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); + + const onHideThreatDetails = useCallback( + ( action: string ) => { + return () => { + onHideThreatDetailsClick( action ); + }; + }, + [ onHideThreatDetailsClick ] + ); + + if ( ! threat.status ) { + return null; + } + + return ( +
+
+ { threat.status === 'ignored' && ( + + ) } + { threat.status === 'current' && ( + <> + + { threat.fixable && ( + + ) } + + ) } +
+
+ ); +}; + +export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx index 0e5a4a7ebfeb9..2eeb955e0902e 100644 --- a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx @@ -1,6 +1,6 @@ import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactNode, useContext } from 'react'; -import ThreatActions from './threat-actions'; +import React, { ReactElement, useContext } from 'react'; +import ThreatDetailsActions from './threat-details-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; @@ -10,14 +10,14 @@ import { ThreatModalContext } from '.'; /** * ThreatDetailsGate component * - * @param {object} props - The component props. - * @param {Threat} props.threat - The threat object containing details. - * @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 status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {ReactNode} props.children - The child components to render if details are not shown. + * @param {object} props - The component props. + * @param {Threat} props.threat - The threat object containing details. + * @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 status is stale. + * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. + * @param {ReactElement} props.children - The child components to render if details are not shown. * * @return {JSX.Element} The rendered ThreatDetailsGate component. */ @@ -30,23 +30,23 @@ const ThreatDetailsGate = ( { threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleUpgradeClick: () => void; - children: ReactNode; -} ) => { + children: ReactElement; +} ): JSX.Element => { const { showThreatDetails } = useContext( ThreatModalContext ); - if ( showThreatDetails ) { - return ( - <> - - - - - - - ); + if ( ! showThreatDetails ) { + return children; } - return <>{ children }; + return ( + <> + + + + + + + ); }; export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index c50b9e44231eb..26b61835dcf87 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -47,6 +47,7 @@ const ThreatFixConfirmation = ( { + { /* TODO: Add confirmation message for ignoring/unignoring threats */ } { ! showThreatDetails && ( ) } + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); +}; + +export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx b/projects/js-packages/components/components/threat-modal/credentials-gate.tsx deleted file mode 100644 index e134007f99912..0000000000000 --- a/projects/js-packages/components/components/threat-modal/credentials-gate.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import React, { ReactElement, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * CredentialsGate component - * - * @param {object} props - The component props. - * @param {boolean} props.siteCredentialsNeeded - Whether the credentials exist. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * @param {ReactElement} props.children - The child components to render if credentials are set. - * - * @return {JSX.Element} The rendered CredentialsGate component. - */ -const CredentialsGate = ( { - siteCredentialsNeeded, - credentialsIsFetching, - credentialsRedirectUrl, - children, -}: { - siteCredentialsNeeded: boolean; - credentialsIsFetching: boolean; - credentialsRedirectUrl: string; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails, onShowThreatDetailsClick } = useContext( ThreatModalContext ); - - if ( ! siteCredentialsNeeded ) { - return children; - } - - return ( - <> - - { __( - 'Before Jetpack can auto-fix threats on your site, it needs your server credentials.', - 'jetpack' - ) } - - } - /> - - - { __( - 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.', - 'jetpack' - ) } - - - - { __( - 'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.', - 'jetpack' - ) } - - -
- { ! showThreatDetails && ( - - ) } - -
- - ); -}; - -export default CredentialsGate; diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 4b546d8329350..9eb6d7f738f13 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,18 +1,14 @@ import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import { useMemo, createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; -import ThreatDetailsGate from './threat-details-gate'; import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; actionToConfirm: string | null; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onHideThreatDetailsClick: ( action: string ) => void; + setActionToConfirm: ( action: string ) => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -34,9 +30,8 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * @param {string} props.actionToConfirm - The action to confirm. - * @param {boolean} props.showThreatDetails - Whether to show the threat details. - * @param {Function} props.onShowThreatDetailsClick - The onShowThreatDetailsClick function. - * @param {Function} props.onHideThreatDetailsClick - The onHideThreatDetailsClick function. + * @param {Function} props.setActionToConfirm - The setActionToConfirm function. + * * @return {JSX.Element} The threat modal. */ export default function ThreatModal( { @@ -53,9 +48,7 @@ export default function ThreatModal( { handleIgnoreThreatClick, handleUnignoreThreatClick, actionToConfirm, - showThreatDetails, - onShowThreatDetailsClick, - onHideThreatDetailsClick, + setActionToConfirm, ...modalProps }: { threat: Threat; @@ -71,9 +64,7 @@ export default function ThreatModal( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; actionToConfirm: string | null; - showThreatDetails: boolean; - onShowThreatDetailsClick: () => void; - onHideThreatDetailsClick: ( action: string ) => void; + setActionToConfirm: ( action: string ) => void; } & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; @@ -82,64 +73,39 @@ export default function ThreatModal( { return getFixerState( threat.fixer ); }, [ threat.fixer ] ); - const getModalTitle = useMemo( () => { - if ( userConnectionNeeded && ! showThreatDetails ) { - return { __( 'User connection needed', 'jetpack' ) }; - } - - if ( siteCredentialsNeeded && ! showThreatDetails ) { - return { __( 'Site credentials needed', 'jetpack' ) }; - } - - return ( - <> - { threat.title } - { !! threat.severity && } - - ); - }, [ - userConnectionNeeded, - siteCredentialsNeeded, - showThreatDetails, - threat.title, - threat.severity, - ] ); - return ( { getModalTitle }
} + title={ +
+ { threat.title } + { !! threat.severity && } +
+ } size="large" { ...modalProps } >
- - - + userConnectionNeeded={ userConnectionNeeded } + userIsConnecting={ userIsConnecting } + handleConnectUser={ handleConnectUser } + siteCredentialsNeeded={ siteCredentialsNeeded } + credentialsIsFetching={ credentialsIsFetching } + credentialsRedirectUrl={ credentialsRedirectUrl } + handleFixThreatClick={ handleFixThreatClick } + handleIgnoreThreatClick={ handleIgnoreThreatClick } + handleUnignoreThreatClick={ handleUnignoreThreatClick } + />
diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 4c8cddcc2b5c8..55b76371f1004 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -12,16 +12,7 @@ const Base = args => { const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); - const [ actionToConfirm, setActionToConfirm ] = useState( null ); - const [ showThreatDetails, setShowThreatDetails ] = useState( true ); - const onShowThreatDetails = useCallback( () => { - setShowThreatDetails( true ); - setActionToConfirm( null ); - }, [] ); - const onHideThreatDetails = useCallback( ( action: string ) => { - setShowThreatDetails( false ); - setActionToConfirm( action ); - }, [] ); + const [ actionToConfirm, setActionToConfirm ] = useState( 'all' ); return (
@@ -31,9 +22,7 @@ const Base = args => { { ...args } onRequestClose={ onRequestClose } actionToConfirm={ actionToConfirm } - showThreatDetails={ showThreatDetails } - onShowThreatDetailsClick={ onShowThreatDetails } - onHideThreatDetailsClick={ onHideThreatDetails } + setActionToConfirm={ setActionToConfirm } /> ) : null }
@@ -70,6 +59,38 @@ ThreatResult.args = { handleUnignoreThreatClick: () => {}, }; +export const AdditionalConnectionsNeeded = Base.bind( {} ); +AdditionalConnectionsNeeded.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const UserConnectionNeeded = Base.bind( {} ); UserConnectionNeeded.args = { threat: { @@ -95,6 +116,8 @@ UserConnectionNeeded.args = { isUserConnected: false, hasConnectedOwner: false, handleConnectUser: () => {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, @@ -126,7 +149,7 @@ CredentialsNeeded.args = { hasConnectedOwner: true, credentials: false, credentialsIsFetching: false, - credentialsRedirectUrl: '', + credentialsRedirectUrl: '#', handleFixThreatClick: () => {}, handleIgnoreThreatClick: () => {}, handleUnignoreThreatClick: () => {}, diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 16fc8c4ee780a..bd20ce0a775c2 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -8,6 +8,22 @@ display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px + + &__title { + display: flex; + justify-content: flex-start; + align-items: center; + } + + &__closed { + max-height: 0; + overflow: hidden; + } + + &__open { + max-height: fit-content; + overflow: hidden; + } } .title { @@ -24,13 +40,25 @@ .modal-actions { display: flex; - justify-content: space-between; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); +} - .threat-actions { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px; - margin-left: auto; +.notice__title { + display: flex; + + svg { + margin-right: calc( var( --spacing-base ) / 2 ); // 4px + } + + p { + font-weight: bold; } +} + +.notice__actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; } \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index a1298eb73cf75..9a6892a784371 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; -import { Threat } from '@automattic/jetpack-scan'; +import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -10,6 +10,7 @@ import { ThreatModalContext } from '.'; * * @param {object} props - The component props. * @param {object} props.threat - The threat object containing action details. + * @param {boolean} props.disabled - Whether the actions are disabled. * @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. @@ -22,19 +23,22 @@ import { ThreatModalContext } from '.'; */ const ThreatActions = ( { threat, + disabled, fixerState, handleFixThreatClick, handleIgnoreThreatClick, handleUnignoreThreatClick, }: { threat: Threat; + disabled?: boolean; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; handleFixThreatClick?: ( threats: Threat[] ) => void; handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal, actionToConfirm, onShowThreatDetailsClick } = - useContext( ThreatModalContext ); + const { closeModal } = useContext( ThreatModalContext ); + + const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); const onFixClick = useCallback( () => { handleFixThreatClick?.( [ threat ] ); @@ -57,41 +61,39 @@ const ThreatActions = ( { return (
- -
- { threat.status === 'ignored' && actionToConfirm === 'un-ignore' && ( - + ) } + { threat.status === 'current' && ( + <> + - ) } - { threat.status === 'current' && ( - <> - { actionToConfirm === 'ignore' && ( - - ) } - { threat.fixable && actionToConfirm === 'fix' && ( - - ) } - - ) } -
+ { threat.fixable && ( + + ) } + + ) }
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx deleted file mode 100644 index e87a17d320a0e..0000000000000 --- a/projects/js-packages/components/components/threat-modal/threat-details-actions.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; -import { __ } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * ThreatActions component - * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing action details. - * @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 action buttons or null if no actions are available. - */ -const ThreatDetailsActions = ( { - threat, - fixerState, -}: { - threat: Threat; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ): JSX.Element => { - const { onHideThreatDetailsClick } = useContext( ThreatModalContext ); - - const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); - - const onHideThreatDetails = useCallback( - ( action: string ) => { - return () => { - onHideThreatDetailsClick( action ); - }; - }, - [ onHideThreatDetailsClick ] - ); - - if ( ! threat.status ) { - return null; - } - - return ( -
-
- { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> - - { threat.fixable && ( - - ) } - - ) } -
-
- ); -}; - -export default ThreatDetailsActions; diff --git a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx b/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx deleted file mode 100644 index 2eeb955e0902e..0000000000000 --- a/projects/js-packages/components/components/threat-modal/threat-details-gate.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { type Threat } from '@automattic/jetpack-scan'; -import React, { ReactElement, useContext } from 'react'; -import ThreatDetailsActions from './threat-details-actions'; -import ThreatFixDetails from './threat-fix-details'; -import ThreatNotice from './threat-notice'; -import ThreatSummary from './threat-summary'; -import ThreatTechnicalDetails from './threat-technical-details'; -import { ThreatModalContext } from '.'; - -/** - * ThreatDetailsGate component - * - * @param {object} props - The component props. - * @param {Threat} props.threat - The threat object containing details. - * @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 status is stale. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade clicks. - * @param {ReactElement} props.children - The child components to render if details are not shown. - * - * @return {JSX.Element} The rendered ThreatDetailsGate component. - */ -const ThreatDetailsGate = ( { - threat, - fixerState, - handleUpgradeClick, - children, -}: { - threat: Threat; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleUpgradeClick: () => void; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails } = useContext( ThreatModalContext ); - - if ( ! showThreatDetails ) { - return children; - } - - return ( - <> - - - - - - - ); -}; - -export default ThreatDetailsGate; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index 26b61835dcf87..a3073537a0762 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,14 +1,16 @@ import { type Threat } from '@automattic/jetpack-scan'; -import CredentialsGate from './credentials-gate'; +import { __ } from '@wordpress/i18n'; +import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; -import UserConnectionGate from './user-connection-gate'; +import ThreatTechnicalDetails from './threat-technical-details'; const ThreatFixConfirmation = ( { threat, fixerState, + handleUpgradeClick, userConnectionNeeded, userIsConnecting, handleConnectUser, @@ -21,6 +23,7 @@ const ThreatFixConfirmation = ( { }: { threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; + handleUpgradeClick: () => void; userConnectionNeeded: boolean; userIsConnecting: boolean; handleConnectUser: () => void; @@ -32,32 +35,55 @@ const ThreatFixConfirmation = ( { handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ) => { return ( - - - <> - { /* TODO: Determine what we want to display here */ } - - - - { /* TODO: Add confirmation message for ignoring/unignoring threats */ } - - - - + <> + + + + + { siteCredentialsNeeded && userConnectionNeeded && ( + + ) } + { ! siteCredentialsNeeded && userConnectionNeeded && ( + + ) } + { siteCredentialsNeeded && ! userConnectionNeeded && ( + + ) } + + ); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 38793f704886d..bb52a38042c93 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,8 +1,10 @@ +import { Text, Button } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; +import { chevronDown, chevronUp } from '@wordpress/icons'; +import { useState, useCallback } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; -import Text from '../text'; import styles from './styles.module.scss'; /** @@ -14,21 +16,38 @@ import styles from './styles.module.scss'; * @return {JSX.Element | null} The rendered technical details or null if no details are available. */ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { + const [ open, setOpen ] = useState( false ); + + const toggleOpen = useCallback( () => { + setOpen( ! open ); + }, [ open ] ); + if ( ! threat.filename && ! threat.context && ! threat.diff ) { return null; } return (
- { __( 'The technical details', 'jetpack' ) } - { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } +
+ { __( 'The technical details', 'jetpack' ) } +
+
+ { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx b/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx deleted file mode 100644 index 1bc2079aa61ec..0000000000000 --- a/projects/js-packages/components/components/threat-modal/user-connection-gate.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import React, { ReactElement, useContext } from 'react'; -import styles from './styles.module.scss'; -import { ThreatModalContext } from '.'; - -/** - * UserConnectionGate component - * - * @param {object} props - The component props. - * @param {boolean} props.userConnectionNeeded - Whether the current user is connected or the site has a connected owner. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {ReactElement} props.children - The child components to render if the user is connected. - * - * @return {JSX.Element} The rendered UserConnectionGate component. - */ -const UserConnectionGate = ( { - userConnectionNeeded, - userIsConnecting, - handleConnectUser, - children, -}: { - userConnectionNeeded: boolean; - userIsConnecting: boolean; - handleConnectUser: () => void; - children: ReactElement; -} ): JSX.Element => { - const { showThreatDetails, onShowThreatDetailsClick } = useContext( ThreatModalContext ); - - if ( ! userConnectionNeeded ) { - return children; - } - - return ( - <> - - { __( - 'Before Jetpack can ignore and auto-fix threats on your site, a user connection is needed.', - 'jetpack' - ) } - - } - /> - - - { __( - 'A user connection provides Jetpack the access necessary to perform these tasks.', - 'jetpack' - ) } - - - - { __( - 'Once you’ve secured a user connection, all Jetpack features will be available for use.', - 'jetpack' - ) } - - -
- { ! showThreatDetails && ( - - ) } - -
- - ); -}; - -export default UserConnectionGate; From c084a5f1e0293f8bea46623932827766960ca697 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 12:08:52 -0800 Subject: [PATCH 38/67] Move notices to ThreatActions --- .../components/components/threat-modal/threat-actions.tsx | 2 ++ .../components/threat-modal/threat-fix-confirmation.tsx | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 9a6892a784371..cbbc9915d3e14 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -3,6 +3,7 @@ import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,6 +62,7 @@ const ThreatActions = ( { return (
+ { threat.status === 'ignored' && ( - ) } - { credentialsRedirectUrl && ( - - ) } -
-
- } - /> - ); -}; - -export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx new file mode 100644 index 0000000000000..fa4dd5e4f87dc --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx @@ -0,0 +1,46 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; + +/** + * FixerNotice 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 FixerNotice = ( { + fixerState, +}: { + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + let status: 'error' | 'success' | undefined; + let title: string | undefined; + let content: string | undefined; + + if ( fixerState.error ) { + status = 'error'; + title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); + content = __( 'Please try again later or contact support.', 'jetpack' ); + } else if ( fixerState.stale ) { + status = 'error'; + title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); + content = __( 'Please try again later or contact support.', 'jetpack' ); + } else if ( fixerState.inProgress ) { + status = 'success'; + title = __( 'An auto-fixer is in progress', 'jetpack' ); + content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); + } + + return title ? ( +
+ +
+ ) : null; +}; + +export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index bd20ce0a775c2..138f112bb97ad 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -38,26 +38,39 @@ overflow-x: auto; } -.modal-actions { - display: flex; - justify-content: flex-end; - gap: calc( var( --spacing-base ) * 2 ); // 16px; +.modal-footer { padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} + +.fixer-notice { + padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } .notice__title { display: flex; - - svg { - margin-right: calc( var( --spacing-base ) / 2 ); // 4px - } + gap: calc( var( --spacing-base ) / 2 ); // 4px p { font-weight: bold; } } +svg.spinner { + color: var( --jp-black ); + height: 20px; + width: 20px; + margin-left: calc( var( --spacing-base ) / 2 ); // 4px; + margin-right: 6px; + +} + .notice__actions { display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index cbbc9915d3e14..9eb794f6ecc3b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; +import FixerNotice from './fixer-notice'; import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,41 +61,43 @@ const ThreatActions = ( { } return ( -
- - { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> +
+ +
+ { threat.status === 'ignored' && ( - { threat.fixable && ( + ) } + { threat.status === 'current' && ( + <> - ) } - - ) } + { threat.fixable && ( + + ) } + + ) } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index bb52cfb174663..b7e8d6a7eaf74 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,8 +1,8 @@ import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; @@ -39,7 +39,7 @@ const ThreatFixConfirmation = ( { { siteCredentialsNeeded && userConnectionNeeded && ( - ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( - ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( - { - if ( fixerState.error ) { - return ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ); - } - if ( fixerState.stale ) { - return ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ); - } - if ( fixerState.inProgress && ! fixerState.stale ) { - return ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ); - } - return null; + status?: 'warning' | 'error' | 'success' | undefined; + title: string; + content: string; + handleConnectUser?: () => void; + userIsConnecting?: boolean; + credentialsRedirectUrl?: string; + credentialsIsFetching?: boolean; +} ): JSX.Element => { + return ( + +
+ { status === 'success' ? ( + + ) : ( + + ) } + + { title } + +
+ { content } +
+ { handleConnectUser && ( + + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); }; -export default ThreatNotices; +export default ThreatNotice; diff --git a/projects/js-packages/components/components/threats-data-views/stories/data.tsx b/projects/js-packages/components/components/threats-data-views/stories/data.tsx new file mode 100644 index 0000000000000..9563a7f594f58 --- /dev/null +++ b/projects/js-packages/components/components/threats-data-views/stories/data.tsx @@ -0,0 +1,126 @@ +export const data = [ + { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'delete' }, + fixer: { status: 'not_started' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << ; Default.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; -UserConnectedRequired.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; +AdditionalConnectionsNeeded.args = { + data: data, filters: [ { field: 'status', @@ -443,134 +194,9 @@ UserConnectedRequired.args = { ), }; -export const CredentialsRequired = args => ; -CredentialsRequired.args = { - data: [ - { - id: 185869885, - signature: 'EICAR_AV_Test', - title: 'Malicious code found in file: index.php', - description: - "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", - firstDetected: '2024-10-07T20:45:06.000Z', - fixedIn: null, - severity: 8, - fixable: { fixer: 'delete' }, - fixer: { status: 'not_started' }, - status: 'current', - filename: '/var/www/html/wp-content/index.php', - context: { - '1': 'echo << ; +UserConnectionNeeded.args = { + data: data, filters: [ { field: 'status', @@ -578,9 +204,30 @@ CredentialsRequired.args = { value: [ 'current' ], }, ], + isUserConnected: false, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + isThreatEligibleForFix: () => true, + onFixThreats: () => + alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onIgnoreThreats: () => + alert( 'Ignore threat action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert + onUnignoreThreats: () => + // eslint-disable-next-line no-alert + alert( + 'Unignore threat action callback triggered! This is handled by the component consumer.' + ), +}; + +export const CredentialsRequired = args => ; +CredentialsRequired.args = { + data: data, isUserConnected: true, hasConnectedOwner: true, + handleConnectUser: () => + alert( 'Connect user action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert credentials: false, + credentialsRedirectUrl: '#', isThreatEligibleForFix: () => true, onFixThreats: () => alert( 'Threat fix action callback triggered! This is handled by the component consumer.' ), // eslint-disable-line no-alert From b33d1d185120d9122d4f4bc4d1aa2b8744ff4c24 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 18:37:36 -0800 Subject: [PATCH 40/67] Add fixer notices --- .../threat-modal/connection-warning.tsx | 76 ------------- .../components/threat-modal/fixer-notice.tsx | 52 +++++++++ .../threat-modal/stories/index.stories.tsx | 96 ++++++++++++++++ .../threat-modal/styles.module.scss | 29 +++-- .../threat-modal/threat-actions.tsx | 56 ++++----- .../threat-modal/threat-fix-confirmation.tsx | 8 +- .../components/threat-modal/threat-notice.tsx | 107 +++++++++++++----- 7 files changed, 280 insertions(+), 144 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-modal/connection-warning.tsx create mode 100644 projects/js-packages/components/components/threat-modal/fixer-notice.tsx diff --git a/projects/js-packages/components/components/threat-modal/connection-warning.tsx b/projects/js-packages/components/components/threat-modal/connection-warning.tsx deleted file mode 100644 index a8ed47560e7de..0000000000000 --- a/projects/js-packages/components/components/threat-modal/connection-warning.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Text, Button } from '@automattic/jetpack-components'; -import { Notice } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; -import styles from './styles.module.scss'; - -/** - * ConnectionWarning component - * - * @param {object} props - The component props. - * @param {string} props.title - The title of the warning. - * @param {string} props.content - The content of the warning. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. - * - * @return {JSX.Element} The rendered ConnectionWarning component. - */ -const ConnectionWarning = ( { - title, - content, - handleConnectUser, - userIsConnecting, - credentialsRedirectUrl, - credentialsIsFetching, -}: { - title: string; - content: string; - handleConnectUser?: () => void; - userIsConnecting?: boolean; - credentialsRedirectUrl?: string; - credentialsIsFetching?: boolean; -} ): JSX.Element => { - return ( - -
- - - { title } - -
- { content } -
- { handleConnectUser && ( - - ) } - { credentialsRedirectUrl && ( - - ) } -
-
- } - /> - ); -}; - -export default ConnectionWarning; diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx new file mode 100644 index 0000000000000..17af4803c779a --- /dev/null +++ b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx @@ -0,0 +1,52 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.scss'; +import ThreatNotice from './threat-notice'; + +/** + * FixerNotice 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 FixerNotice = ( { + fixerState, +}: { + fixerState: { inProgress: boolean; error: boolean; stale: boolean }; +} ) => { + let status: 'error' | 'success' | undefined; + let title: string | undefined; + let content: string | undefined; + + if ( fixerState.error ) { + status = 'error'; + title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); + content = __( + 'Jetpack encountered a filesystem error when attempting to auto-fix this threat. Please try again later or contact support.', + 'jetpack' + ); + } else if ( fixerState.stale ) { + status = 'error'; + 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' + ); + } else if ( fixerState.inProgress ) { + status = 'success'; + title = __( 'An auto-fixer is in progress', 'jetpack' ); + content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); + } + + return title ? ( +
+ +
+ ) : null; +}; + +export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index 55b76371f1004..4f49f5316a246 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -155,6 +155,102 @@ CredentialsNeeded.args = { handleUnignoreThreatClick: () => {}, }; +export const InProgressFixer = Base.bind( {} ); +InProgressFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'in_progress' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const ErrorFixer = Base.bind( {} ); +ErrorFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { error: 'error' }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + +export const StaleFixer = Base.bind( {} ); +StaleFixer.args = { + threat: { + id: 185869885, + signature: 'EICAR_AV_Test', + title: 'Malicious code found in file: index.php', + description: + "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.", + firstDetected: '2024-10-07T20:45:06.000Z', + fixedIn: null, + severity: 8, + fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' }, + fixer: { status: 'in_progress', lastUpdated: new Date( '1999-01-01' ).toISOString() }, + status: 'current', + filename: '/var/www/html/wp-content/index.php', + context: { + '1': 'echo << {}, + credentials: [ { type: 'managed', role: 'main', still_valid: true } ], + credentialsRedirectUrl: '#', + handleFixThreatClick: () => {}, + handleIgnoreThreatClick: () => {}, + handleUnignoreThreatClick: () => {}, +}; + export const VulnerableExtension = Base.bind( {} ); VulnerableExtension.args = { threat: { diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index bd20ce0a775c2..138f112bb97ad 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -38,26 +38,39 @@ overflow-x: auto; } -.modal-actions { - display: flex; - justify-content: flex-end; - gap: calc( var( --spacing-base ) * 2 ); // 16px; +.modal-footer { padding-top: calc( var( --spacing-base ) * 3 ); // 24px border-top: 1px solid var( --jp-gray-0 ); + + .threat-actions { + display: flex; + justify-content: flex-end; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } +} + +.fixer-notice { + padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } .notice__title { display: flex; - - svg { - margin-right: calc( var( --spacing-base ) / 2 ); // 4px - } + gap: calc( var( --spacing-base ) / 2 ); // 4px p { font-weight: bold; } } +svg.spinner { + color: var( --jp-black ); + height: 20px; + width: 20px; + margin-left: calc( var( --spacing-base ) / 2 ); // 4px; + margin-right: 6px; + +} + .notice__actions { display: flex; gap: calc( var( --spacing-base ) * 2 ); // 16px; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index cbbc9915d3e14..9eb794f6ecc3b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,8 +2,8 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; +import FixerNotice from './fixer-notice'; import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; import { ThreatModalContext } from '.'; /** @@ -61,41 +61,43 @@ const ThreatActions = ( { } return ( -
- - { threat.status === 'ignored' && ( - - ) } - { threat.status === 'current' && ( - <> +
+ +
+ { threat.status === 'ignored' && ( - { threat.fixable && ( + ) } + { threat.status === 'current' && ( + <> - ) } - - ) } + { threat.fixable && ( + + ) } + + ) } +
); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index bb52cfb174663..b7e8d6a7eaf74 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,8 +1,8 @@ import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import ConnectionWarning from './connection-warning'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; +import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; @@ -39,7 +39,7 @@ const ThreatFixConfirmation = ( { { siteCredentialsNeeded && userConnectionNeeded && ( - ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( - ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( - { - if ( fixerState.error ) { - return ( - - { __( 'An error occurred auto-fixing this threat.', 'jetpack' ) } - - ); - } - if ( fixerState.stale ) { - return ( - - { __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) } - - ); - } - if ( fixerState.inProgress && ! fixerState.stale ) { - return ( - - { __( 'The auto-fixer is in progress.', 'jetpack' ) } - - ); - } - return null; + status?: 'warning' | 'error' | 'success' | undefined; + title: string; + content: string; + handleConnectUser?: () => void; + userIsConnecting?: boolean; + credentialsRedirectUrl?: string; + credentialsIsFetching?: boolean; +} ): JSX.Element => { + return ( + +
+ { status === 'success' ? ( + + ) : ( + + ) } + + { title } + +
+ { content } +
+ { handleConnectUser && ( + + ) } + { credentialsRedirectUrl && ( + + ) } +
+
+ } + /> + ); }; -export default ThreatNotices; +export default ThreatNotice; From 3541623ed617570fa0fbd8fdaa1bc5a75a9e54ee Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 18:55:32 -0800 Subject: [PATCH 41/67] Fix styling --- ...ixer-notice.tsx => fixer-state-notice.tsx} | 8 +++--- .../threat-modal/styles.module.scss | 26 ++++++++++++------- .../threat-modal/threat-actions.tsx | 4 +-- .../components/threat-modal/threat-notice.tsx | 4 ++- 4 files changed, 25 insertions(+), 17 deletions(-) rename projects/js-packages/components/components/threat-modal/{fixer-notice.tsx => fixer-state-notice.tsx} (88%) diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx similarity index 88% rename from projects/js-packages/components/components/threat-modal/fixer-notice.tsx rename to projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 17af4803c779a..4ade6c4f2b829 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -3,7 +3,7 @@ import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; /** - * FixerNotice component + * FixerStateNotice component * * @param {object} props - The component props. * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale). @@ -13,7 +13,7 @@ import ThreatNotice from './threat-notice'; * * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. */ -const FixerNotice = ( { +const FixerStateNotice = ( { fixerState, }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; @@ -26,7 +26,7 @@ const FixerNotice = ( { status = 'error'; title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); content = __( - 'Jetpack encountered a filesystem error when attempting to auto-fix this threat. Please try again later or contact support.', + 'Jetpack encountered a filesystem error while attempting to auto-fix this threat. Please try again later or contact support.', 'jetpack' ); } else if ( fixerState.stale ) { @@ -49,4 +49,4 @@ const FixerNotice = ( { ) : null; }; -export default FixerNotice; +export default FixerStateNotice; diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 138f112bb97ad..ccefd7984f5c7 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -53,12 +53,23 @@ padding-bottom: calc( var( --spacing-base ) * 3 ); // 24px } -.notice__title { - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px +.notice { + &__title { + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + + p { + font-weight: bold; + } + } + + &__actions { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px; + } - p { - font-weight: bold; + &__action { + margin-top: calc( var( --spacing-base ) * 2 ); // 16px; } } @@ -69,9 +80,4 @@ svg.spinner { margin-left: calc( var( --spacing-base ) / 2 ); // 4px; margin-right: 6px; -} - -.notice__actions { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px; } \ No newline at end of file diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 9eb794f6ecc3b..c7ade35499866 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -2,7 +2,7 @@ import { Button } from '@automattic/jetpack-components'; import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import React, { useCallback, useContext, useMemo } from 'react'; -import FixerNotice from './fixer-notice'; +import FixerStateNotice from './fixer-state-notice'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -62,7 +62,7 @@ const ThreatActions = ( { return (
- +
{ threat.status === 'ignored' && (
- { content } + { content }
{ handleConnectUser && (
- { content } + { content }
{ handleConnectUser && (
+ ); +}; + +export default ThreatIgnoreDetails; From 962a992b854503f35152b9691aa9be6c6a637882 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 19:53:39 -0800 Subject: [PATCH 47/67] Add modal fixer confirmation context --- .../components/threat-modal/index.tsx | 5 --- .../threat-modal/threat-actions.tsx | 22 ++++++------ .../threat-modal/threat-fix-confirmation.tsx | 7 ++-- .../components/threats-data-views/index.tsx | 35 ++++++------------- 4 files changed, 28 insertions(+), 41 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 9eb6d7f738f13..9d6acf1787036 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -8,7 +8,6 @@ import ThreatFixConfirmation from './threat-fix-confirmation'; interface ThreatModalContextType { closeModal: () => void; actionToConfirm: string | null; - setActionToConfirm: ( action: string ) => void; } export const ThreatModalContext = createContext< ThreatModalContextType | null >( null ); @@ -30,7 +29,6 @@ export const ThreatModalContext = createContext< ThreatModalContextType | null > * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function. * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function. * @param {string} props.actionToConfirm - The action to confirm. - * @param {Function} props.setActionToConfirm - The setActionToConfirm function. * * @return {JSX.Element} The threat modal. */ @@ -48,7 +46,6 @@ export default function ThreatModal( { handleIgnoreThreatClick, handleUnignoreThreatClick, actionToConfirm, - setActionToConfirm, ...modalProps }: { threat: Threat; @@ -64,7 +61,6 @@ export default function ThreatModal( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; actionToConfirm: string | null; - setActionToConfirm: ( action: string ) => void; } & React.ComponentProps< typeof Modal > ): JSX.Element { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; @@ -89,7 +85,6 @@ export default function ThreatModal( { value={ { closeModal: modalProps.onRequestClose, actionToConfirm, - setActionToConfirm, } } > void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ): JSX.Element => { - const { closeModal } = useContext( ThreatModalContext ); + const { closeModal, actionToConfirm } = useContext( ThreatModalContext ); const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); @@ -76,15 +76,17 @@ const ThreatActions = ( { ) } { threat.status === 'current' && ( <> - - { threat.fixable && ( + { [ 'all', 'ignore' ].includes( actionToConfirm ) && ( + + ) } + { threat.fixable && [ 'all', 'fix' ].includes( actionToConfirm ) && ( @@ -469,7 +458,7 @@ export default function ThreatsDataViews( { } return ( - + ); }, }, @@ -493,7 +482,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_IGNORE, label: __( 'Ignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatModal( items[ 0 ], false )(); + showThreatModal( items[ 0 ], 'ignore' )(); }, isEligible( item ) { if ( ! onIgnoreThreats ) { @@ -512,7 +501,7 @@ export default function ThreatsDataViews( { id: THREAT_ACTION_UNIGNORE, label: __( 'Unignore', 'jetpack' ), callback: ( items: Threat[] ) => { - showThreatModal( items[ 0 ], false )(); + showThreatModal( items[ 0 ], 'unignore' )(); }, isEligible( item ) { if ( ! onUnignoreThreats ) { @@ -596,9 +585,7 @@ export default function ThreatsDataViews( { handleIgnoreThreatClick={ onIgnoreThreats } handleUnignoreThreatClick={ onUnignoreThreats } onRequestClose={ hideThreatModal } - showThreatDetails={ showThreatDetails } - onShowThreatDetailsClick={ onShowThreatDetails } - onHideThreatDetailsClick={ onHideThreatDetails } + actionToConfirm={ actionToConfirm } /> ) : null } From 5860eca1af37255011c72fd03e4d9080cf96fb4c Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:25:16 -0800 Subject: [PATCH 48/67] Reorg --- .../components/threat-modal/fixer-notice.tsx | 46 ------------------- .../threat-modal/fixer-state-notice.tsx | 6 ++- .../threat-modal/threat-actions.tsx | 4 +- .../threat-modal/threat-fix-confirmation.tsx | 19 ++++++-- .../threat-modal/threat-fix-details.tsx | 21 ++------- .../threat-modal/threat-ignore-details.tsx | 7 ++- .../components/threat-modal/threat-notice.tsx | 8 ++++ 7 files changed, 39 insertions(+), 72 deletions(-) delete mode 100644 projects/js-packages/components/components/threat-modal/fixer-notice.tsx diff --git a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-notice.tsx deleted file mode 100644 index fa4dd5e4f87dc..0000000000000 --- a/projects/js-packages/components/components/threat-modal/fixer-notice.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import styles from './styles.module.scss'; -import ThreatNotice from './threat-notice'; - -/** - * FixerNotice 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 FixerNotice = ( { - fixerState, -}: { - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; -} ) => { - let status: 'error' | 'success' | undefined; - let title: string | undefined; - let content: string | undefined; - - if ( fixerState.error ) { - status = 'error'; - title = __( 'An error occurred auto-fixing this threat', 'jetpack' ); - content = __( 'Please try again later or contact support.', 'jetpack' ); - } else if ( fixerState.stale ) { - status = 'error'; - title = __( 'The auto-fixer is taking longer than expected', 'jetpack' ); - content = __( 'Please try again later or contact support.', 'jetpack' ); - } else if ( fixerState.inProgress ) { - status = 'success'; - title = __( 'An auto-fixer is in progress', 'jetpack' ); - content = __( 'Please wait while Jetpack auto-fixes the threat.', 'jetpack' ); - } - - return title ? ( -
- -
- ) : null; -}; - -export default FixerNotice; diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 4ade6c4f2b829..d48f12f999059 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,3 +1,4 @@ +import { type Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; @@ -6,6 +7,7 @@ import ThreatNotice from './threat-notice'; * FixerStateNotice component * * @param {object} props - The component props. + * @param {object} props.threat - The threat object containing notice details. * @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. @@ -14,8 +16,10 @@ import ThreatNotice from './threat-notice'; * @return {JSX.Element | null} The rendered fixer notice or null if no notice is available. */ const FixerStateNotice = ( { + threat, fixerState, }: { + threat: Threat; fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { let status: 'error' | 'success' | undefined; @@ -44,7 +48,7 @@ const FixerStateNotice = ( { return title ? (
- +
) : null; }; diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 28010f083c582..bba6b296823e3 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,13 +56,13 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status ) { + if ( ! threat?.status || threat.status === 'fixed' ) { return null; } return (
- +
{ threat.status === 'ignored' && ( - { isOpen ? ( - - ) : null } + { isOpen ? : null }
); }; From 344696a755b632b7bbb9a0d7037099f4e9010d84 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:47:06 -0800 Subject: [PATCH 50/67] Early return on ThreatActions for fixed threats --- .../components/components/threat-modal/threat-actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index c7ade35499866..d77610350adec 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,7 +56,7 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status ) { + if ( ! threat.status || threat.status === 'fixed' ) { return null; } From 242ca8f74c5904b17a769d15c94a4929d38d97ef Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 20:50:59 -0800 Subject: [PATCH 51/67] Make threat status check optional --- .../components/components/threat-modal/threat-actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index d77610350adec..33fb46fe55f5a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -56,7 +56,7 @@ const ThreatActions = ( { closeModal(); }, [ threat, handleUnignoreThreatClick, closeModal ] ); - if ( ! threat.status || threat.status === 'fixed' ) { + if ( ! threat?.status || threat.status === 'fixed' ) { return null; } From c01432f7e90197c3f59b6f966e68358fe1a8ae98 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 21:00:06 -0800 Subject: [PATCH 52/67] Fix stories --- .../components/threat-modal/stories/index.stories.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx index bf1eff8fe8f99..e4701970077d6 100644 --- a/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx +++ b/projects/js-packages/components/components/threat-modal/stories/index.stories.tsx @@ -11,10 +11,13 @@ const Base = args => { const [ isOpen, setIsOpen ] = useState( false ); const onClick = useCallback( () => setIsOpen( true ), [] ); const onRequestClose = useCallback( () => setIsOpen( false ), [] ); + return (
- { isOpen ? : null } + { isOpen ? ( + + ) : null }
); }; From 9093f0a7465fb56280e9793aa5b24e98f03878a4 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Wed, 20 Nov 2024 21:11:42 -0800 Subject: [PATCH 53/67] Fix tests --- .../threats-data-views/test/index.test.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx index 2e7dfea35d673..924398bdcf81f 100644 --- a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx +++ b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx @@ -45,9 +45,28 @@ const data = [ }, ]; +const mockProps = { + filters: [], + onChangeSelection: jest.fn(), + handleUpgradeClick: jest.fn(), + onFixThreats: jest.fn(), + onIgnoreThreats: jest.fn(), + onUnignoreThreats: jest.fn(), + isThreatEligibleForFix: jest.fn().mockReturnValue( true ), + isThreatEligibleForIgnore: jest.fn().mockReturnValue( true ), + isThreatEligibleForUnignore: jest.fn().mockReturnValue( true ), + isUserConnected: true, + hasConnectedOwner: true, + userIsConnecting: false, + handleConnectUser: jest.fn(), + credentials: [], + credentialsIsFetching: false, + credentialsRedirectUrl: '/redirect-url', +}; + describe( 'ThreatsDataViews', () => { it( 'renders threat data', () => { - render( ); + render( ); expect( screen.getByText( 'Malicious code found in file: index.php' ) ).toBeInTheDocument(); expect( screen.getByText( 'WooCommerce <= 3.2.3 - Authenticated PHP Object Injection' ) From a02bff67189489076f78df8b95e295d6b02f924b Mon Sep 17 00:00:00 2001 From: dkmyta Date: Thu, 21 Nov 2024 08:59:24 -0800 Subject: [PATCH 54/67] Remove jest --- .../threats-data-views/test/index.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx index 924398bdcf81f..c9c83afd24f68 100644 --- a/projects/js-packages/components/components/threats-data-views/test/index.test.tsx +++ b/projects/js-packages/components/components/threats-data-views/test/index.test.tsx @@ -47,18 +47,18 @@ const data = [ const mockProps = { filters: [], - onChangeSelection: jest.fn(), - handleUpgradeClick: jest.fn(), - onFixThreats: jest.fn(), - onIgnoreThreats: jest.fn(), - onUnignoreThreats: jest.fn(), - isThreatEligibleForFix: jest.fn().mockReturnValue( true ), - isThreatEligibleForIgnore: jest.fn().mockReturnValue( true ), - isThreatEligibleForUnignore: jest.fn().mockReturnValue( true ), + onChangeSelection: () => {}, + handleUpgradeClick: () => {}, + onFixThreats: () => {}, + onIgnoreThreats: () => {}, + onUnignoreThreats: () => {}, + isThreatEligibleForFix: () => true, + isThreatEligibleForIgnore: () => true, + isThreatEligibleForUnignore: () => true, isUserConnected: true, hasConnectedOwner: true, userIsConnecting: false, - handleConnectUser: jest.fn(), + handleConnectUser: () => {}, credentials: [], credentialsIsFetching: false, credentialsRedirectUrl: '/redirect-url', From 220d28cb0b9495aaee61afe8a15f0c50735fa25e Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 22 Nov 2024 09:39:12 -0700 Subject: [PATCH 55/67] Use custom button for threat details toggle (#40298) --- .../threat-modal/fixer-state-notice.tsx | 55 +++++++++++-------- .../threat-modal/styles.module.scss | 23 +++++--- .../threat-modal/threat-technical-details.tsx | 44 +++++++++------ 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 4ade6c4f2b829..4c8a8a5cb53db 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,4 +1,5 @@ import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; import styles from './styles.module.scss'; import ThreatNotice from './threat-notice'; @@ -18,29 +19,39 @@ const FixerStateNotice = ( { }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { - let status: 'error' | 'success' | undefined; - let title: string | undefined; - let content: string | undefined; + 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.error ) { - status = 'error'; - 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' - ); - } else if ( fixerState.stale ) { - status = 'error'; - 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' - ); - } else if ( fixerState.inProgress ) { - status = 'success'; - title = __( 'An auto-fixer is in progress', 'jetpack' ); - content = __( 'Please wait while Jetpack auto-fixes the threat.', '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 ? (
diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 4850724be6212..874a2285a098d 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -14,15 +14,24 @@ justify-content: flex-start; align-items: center; } +} - &__closed { - max-height: 0; - overflow: hidden; - } +.section__toggle { + border: none; + background: none; + padding: 0; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + align-items: center; + transition: text-underline-offset 0.2s; - &__open { - max-height: fit-content; - overflow: hidden; + &:hover { + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: calc( var( --spacing-base ) / 2 ); // 4px } } diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index bb52a38042c93..182bdd863e4b1 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,7 +1,7 @@ -import { Text, Button } from '@automattic/jetpack-components'; +import { Text } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import { chevronDown, chevronUp } from '@wordpress/icons'; +import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; import { useState, useCallback } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; @@ -29,25 +29,35 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = return (
- { __( 'The technical details', 'jetpack' ) } -
-
- { threat.filename && ( - <> - { __( 'Threat found in file:', 'jetpack' ) } -
{ threat.filename }
- - ) } - { threat.context && } - { threat.diff && } + > + + { open + ? __( 'Hide the technical details', 'jetpack' ) + : __( 'Show the technical details', 'jetpack' ) } + + +
+ { open && ( +
+ { threat.filename && ( + <> + { __( 'Threat found in file:', 'jetpack' ) } +
{ threat.filename }
+ + ) } + { threat.context && } + { threat.diff && } +
+ ) }
); }; From 896f7e0ad6b38e6d875543fad4554d9c27d2975f Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:00:40 -0800 Subject: [PATCH 56/67] Use Button and override internal styles --- .../threat-modal/styles.module.scss | 23 +++++++------------ .../threat-modal/threat-technical-details.tsx | 21 +++++++++-------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 874a2285a098d..2ecec5722de18 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -9,29 +9,22 @@ flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px - &__title { + &__content { display: flex; - justify-content: flex-start; + gap: calc( var( --spacing-base ) / 2 ); // 4px align-items: center; } } -.section__toggle { - border: none; - background: none; - padding: 0; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px - align-items: center; - transition: text-underline-offset 0.2s; +.section .section__toggle { + text-decoration: none; + + &:focus { + box-shadow: none; + } &:hover { text-decoration: underline; - text-decoration-thickness: 2px; - text-underline-offset: calc( var( --spacing-base ) / 2 ); // 4px } } diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 182bdd863e4b1..ba9e0a76ff9a9 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,4 +1,4 @@ -import { Text } from '@automattic/jetpack-components'; +import { Text, Button } from '@automattic/jetpack-components'; import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; @@ -29,19 +29,22 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = return (
- +
+ + { open + ? __( 'Hide the technical details', 'jetpack' ) + : __( 'Show the technical details', 'jetpack' ) } + + +
+
{ open && (
Date: Fri, 22 Nov 2024 09:04:48 -0800 Subject: [PATCH 57/67] Fix classes --- .../components/threat-modal/styles.module.scss | 12 ++++++------ .../threat-modal/threat-technical-details.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index 2ecec5722de18..d43fa45066aa9 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -8,12 +8,6 @@ display: flex; flex-direction: column; gap: calc( var( --spacing-base ) * 2 ); // 16px - - &__content { - display: flex; - gap: calc( var( --spacing-base ) / 2 ); // 4px - align-items: center; - } } .section .section__toggle { @@ -26,6 +20,12 @@ &:hover { text-decoration: underline; } + + &_content { + display: flex; + gap: calc( var( --spacing-base ) / 2 ); // 4px + align-items: center; + } } .title { diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index ba9e0a76ff9a9..dbb1e120d640a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -36,7 +36,7 @@ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element = aria-controls={ `threat-details-${ threat.id }` } onClick={ toggleOpen } > -
+
{ open ? __( 'Hide the technical details', 'jetpack' ) From 94ed99a29d2ae08197593f64b848df43d55e7102 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:07:56 -0800 Subject: [PATCH 58/67] Move fixerState comp to ThreatFixConfirmation --- .../components/components/threat-modal/index.tsx | 9 ++------- .../components/threat-modal/threat-fix-confirmation.tsx | 9 ++++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 7e9297e2e6086..404faf892a169 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -1,6 +1,6 @@ -import { type Threat, getFixerState } from '@automattic/jetpack-scan'; +import { type Threat } from '@automattic/jetpack-scan'; import { Modal } from '@wordpress/components'; -import { useMemo, createContext } from 'react'; +import { createContext } from 'react'; import Text from '../text'; import ThreatSeverityBadge from '../threat-severity-badge'; import styles from './styles.module.scss'; @@ -61,10 +61,6 @@ export default function ThreatModal( { const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner; const siteCredentialsNeeded = ! credentials || credentials.length === 0; - const fixerState = useMemo( () => { - return getFixerState( threat.fixer ); - }, [ threat.fixer ] ); - return ( void; userConnectionNeeded: boolean; userIsConnecting: boolean; @@ -33,6 +32,10 @@ const ThreatFixConfirmation = ( { handleIgnoreThreatClick?: ( threats: Threat[] ) => void; handleUnignoreThreatClick?: ( threats: Threat[] ) => void; } ) => { + const fixerState = useMemo( () => { + return getFixerState( threat.fixer ); + }, [ threat.fixer ] ); + return ( <> From 33268ee082fcfc3f07a36c2688bd9ed903174351 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 09:31:33 -0800 Subject: [PATCH 59/67] Rely heavily on context provider --- .../components/threat-modal/index.tsx | 36 +++++++---- .../threat-modal/threat-actions.tsx | 46 +++++--------- .../threat-modal/threat-fix-confirmation.tsx | 63 ++++--------------- .../threat-modal/threat-fix-details.tsx | 19 ++---- .../components/threat-modal/threat-notice.tsx | 35 +++++------ .../threat-modal/threat-summary.tsx | 36 +++++++---- .../threat-modal/threat-technical-details.tsx | 11 ++-- 7 files changed, 104 insertions(+), 142 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/index.tsx b/projects/js-packages/components/components/threat-modal/index.tsx index 404faf892a169..aa0b1577559af 100644 --- a/projects/js-packages/components/components/threat-modal/index.tsx +++ b/projects/js-packages/components/components/threat-modal/index.tsx @@ -7,6 +7,17 @@ import styles from './styles.module.scss'; 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 ); @@ -76,21 +87,20 @@ export default function ThreatModal( { - +
diff --git a/projects/js-packages/components/components/threat-modal/threat-actions.tsx b/projects/js-packages/components/components/threat-modal/threat-actions.tsx index 33fb46fe55f5a..08ab0d641a22b 100644 --- a/projects/js-packages/components/components/threat-modal/threat-actions.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-actions.tsx @@ -1,7 +1,7 @@ import { Button } from '@automattic/jetpack-components'; -import { type Threat, getDetailedFixerAction } from '@automattic/jetpack-scan'; +import { getFixerState, getDetailedFixerAction } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import React, { useCallback, useContext, useMemo } from 'react'; +import { useCallback, useContext, useMemo } from 'react'; import FixerStateNotice from './fixer-state-notice'; import styles from './styles.module.scss'; import { ThreatModalContext } from '.'; @@ -9,35 +9,23 @@ import { ThreatModalContext } from '.'; /** * ThreatActions component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing action details. - * @param {boolean} props.disabled - Whether the actions are disabled. - * @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. - * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat. - * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat. - * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat. - * * @return {JSX.Element | null} The rendered action buttons or null if no actions are available. */ -const ThreatActions = ( { - threat, - disabled, - fixerState, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, -}: { - threat: Threat; - disabled?: boolean; - fixerState: { inProgress: boolean; error: boolean; stale: boolean }; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; -} ): JSX.Element => { - const { closeModal } = useContext( ThreatModalContext ); +const ThreatActions = (): JSX.Element => { + const { + closeModal, + threat, + handleFixThreatClick, + handleIgnoreThreatClick, + handleUnignoreThreatClick, + userConnectionNeeded, + siteCredentialsNeeded, + } = useContext( ThreatModalContext ); + const disabled = userConnectionNeeded || siteCredentialsNeeded; + + const fixerState = useMemo( () => { + return getFixerState( threat.fixer ); + }, [ threat.fixer ] ); const detailedFixerAction = useMemo( () => getDetailedFixerAction( threat ), [ threat ] ); diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx index ca3bdc37956d6..97d3e4a3c968a 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-confirmation.tsx @@ -1,46 +1,24 @@ -import { type Threat, getFixerState } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; +import { useContext } from 'react'; import ThreatActions from './threat-actions'; import ThreatFixDetails from './threat-fix-details'; import ThreatNotice from './threat-notice'; import ThreatSummary from './threat-summary'; import ThreatTechnicalDetails from './threat-technical-details'; +import { ThreatModalContext } from '.'; -const ThreatFixConfirmation = ( { - threat, - handleUpgradeClick, - userConnectionNeeded, - userIsConnecting, - handleConnectUser, - siteCredentialsNeeded, - credentialsIsFetching, - credentialsRedirectUrl, - handleFixThreatClick, - handleIgnoreThreatClick, - handleUnignoreThreatClick, -}: { - threat: Threat; - handleUpgradeClick: () => void; - userConnectionNeeded: boolean; - userIsConnecting: boolean; - handleConnectUser: () => void; - siteCredentialsNeeded: boolean; - credentialsIsFetching: boolean; - credentialsRedirectUrl: string; - handleFixThreatClick?: ( threats: Threat[] ) => void; - handleIgnoreThreatClick?: ( threats: Threat[] ) => void; - handleUnignoreThreatClick?: ( threats: Threat[] ) => void; -} ) => { - const fixerState = useMemo( () => { - return getFixerState( threat.fixer ); - }, [ threat.fixer ] ); - +/** + * ThreatFixConfirmation component + * + * @return {JSX.Element} The rendered fix confirmation. + */ +const ThreatFixConfirmation = () => { + const { userConnectionNeeded, siteCredentialsNeeded } = useContext( ThreatModalContext ); return ( <> - - - + + + { siteCredentialsNeeded && userConnectionNeeded && ( ) } { ! siteCredentialsNeeded && userConnectionNeeded && ( @@ -61,8 +35,6 @@ const ThreatFixConfirmation = ( { 'A user connection provides Jetpack the access necessary to ignore and auto-fix threats on your site.', 'jetpack' ) } - handleConnectUser={ handleConnectUser } - userIsConnecting={ userIsConnecting } /> ) } { siteCredentialsNeeded && ! userConnectionNeeded && ( @@ -72,18 +44,9 @@ const ThreatFixConfirmation = ( { 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to ignore and auto-fix threats detected on your site.', 'jetpack' ) } - credentialsIsFetching={ credentialsIsFetching } - credentialsRedirectUrl={ credentialsRedirectUrl } /> ) } - + ); }; diff --git a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx index 72e2dbc3c1b2f..618ca87506309 100644 --- a/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-fix-details.tsx @@ -1,26 +1,19 @@ -import { Threat, getFixerDescription } from '@automattic/jetpack-scan'; +import { getFixerDescription } from '@automattic/jetpack-scan'; import { __, sprintf } from '@wordpress/i18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useContext } from 'react'; import ContextualUpgradeTrigger from '../contextual-upgrade-trigger'; import Text from '../text'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatFixDetails component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing fix details. - * @param {Function} props.handleUpgradeClick - Function to handle upgrade click events. - * * @return {JSX.Element | null} The rendered fix details or null if no fixable details are available. */ -const ThreatFixDetails = ( { - threat, - handleUpgradeClick, -}: { - threat: Threat; - handleUpgradeClick?: () => void; -} ): JSX.Element => { +const ThreatFixDetails = (): JSX.Element => { + const { threat, handleUpgradeClick } = useContext( ThreatModalContext ); + const title = useMemo( () => { if ( threat.status === 'fixed' ) { return __( 'How did Jetpack fix it?', 'jetpack' ); diff --git a/projects/js-packages/components/components/threat-modal/threat-notice.tsx b/projects/js-packages/components/components/threat-modal/threat-notice.tsx index fc4cdba5a6ccf..a0451e89f56b2 100644 --- a/projects/js-packages/components/components/threat-modal/threat-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-notice.tsx @@ -2,19 +2,17 @@ import { Text, Button } from '@automattic/jetpack-components'; import { Notice, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Icon, warning } from '@wordpress/icons'; +import { useContext } from 'react'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatNotice component * - * @param {object} props - The component props. - * @param {string} props.status - The status of the notice. - * @param {string} props.title - The title of the notice. - * @param {string} props.content - The content of the notice. - * @param {Function} props.handleConnectUser - Function to handle the user connection process. - * @param {boolean} props.userIsConnecting - Whether the user connection process is in progress. - * @param {boolean} props.credentialsIsFetching - Whether the credentials are being fetched. - * @param {string} props.credentialsRedirectUrl - The URL to redirect the user to set credentials. + * @param {object} props - The component props. + * @param {string} props.status - The status of the notice. + * @param {string} props.title - The title of the notice. + * @param {string} props.content - The content of the notice. * * @return {JSX.Element} The rendered ThreatNotice component. */ @@ -22,19 +20,20 @@ const ThreatNotice = ( { status = 'warning', title, content, - handleConnectUser, - userIsConnecting, - credentialsRedirectUrl, - credentialsIsFetching, }: { status?: 'warning' | 'error' | 'success' | undefined; title: string; content: string; - handleConnectUser?: () => void; - userIsConnecting?: boolean; - credentialsRedirectUrl?: string; - credentialsIsFetching?: boolean; } ): JSX.Element => { + const { + userConnectionNeeded, + userIsConnecting, + handleConnectUser, + siteCredentialsNeeded, + credentialsRedirectUrl, + credentialsIsFetching, + } = useContext( ThreatModalContext ); + return ( { content }
- { handleConnectUser && ( + { userConnectionNeeded && ( -
- ) } -
-); +/** + * ThreatSummary component + * + * @return {JSX.Element} The rendered threat summary. + */ +const ThreatSummary = (): JSX.Element => { + const { threat } = useContext( ThreatModalContext ); + + return ( +
+ { !! threat.description && { threat.description } } + { !! threat.source && ( +
+ +
+ ) } +
+ ); +}; export default ThreatSummary; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index dbb1e120d640a..92720ef715bb8 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -1,21 +1,20 @@ import { Text, Button } from '@automattic/jetpack-components'; -import { Threat } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { chevronDown, chevronUp, Icon } from '@wordpress/icons'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useContext } from 'react'; import DiffViewer from '../diff-viewer'; import MarkedLines from '../marked-lines'; import styles from './styles.module.scss'; +import { ThreatModalContext } from '.'; /** * ThreatTechnicalDetails component * - * @param {object} props - The component props. - * @param {object} props.threat - The threat object containing technical details. - * * @return {JSX.Element | null} The rendered technical details or null if no details are available. */ -const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => { +const ThreatTechnicalDetails = (): JSX.Element => { + const { threat } = useContext( ThreatModalContext ); + const [ open, setOpen ] = useState( false ); const toggleOpen = useCallback( () => { From 1e2333e486bd5c475a11d47f8cf819ee08de0ff2 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 09:37:20 -0800 Subject: [PATCH 60/67] Fix styles --- .../components/components/threat-modal/styles.module.scss | 6 +----- .../components/threat-modal/threat-technical-details.tsx | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/styles.module.scss b/projects/js-packages/components/components/threat-modal/styles.module.scss index d43fa45066aa9..1720d59b1f90c 100644 --- a/projects/js-packages/components/components/threat-modal/styles.module.scss +++ b/projects/js-packages/components/components/threat-modal/styles.module.scss @@ -13,15 +13,11 @@ .section .section__toggle { text-decoration: none; - &:focus { - box-shadow: none; - } - &:hover { text-decoration: underline; } - &_content { + &__content { display: flex; gap: calc( var( --spacing-base ) / 2 ); // 4px align-items: center; diff --git a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx index 92720ef715bb8..f7b14581f786f 100644 --- a/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx +++ b/projects/js-packages/components/components/threat-modal/threat-technical-details.tsx @@ -35,7 +35,7 @@ const ThreatTechnicalDetails = (): JSX.Element => { aria-controls={ `threat-details-${ threat.id }` } onClick={ toggleOpen } > -
+
{ open ? __( 'Hide the technical details', 'jetpack' ) From 538694fc62b6833277b41f40960505cdb52d6e20 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 10:18:40 -0800 Subject: [PATCH 61/67] Add support link --- .../threat-modal/fixer-state-notice.tsx | 34 +++++++++++++++---- .../components/threat-modal/threat-notice.tsx | 12 +++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx index 8d08e4914fca7..fa755769b10b6 100644 --- a/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx +++ b/projects/js-packages/components/components/threat-modal/fixer-state-notice.tsx @@ -1,3 +1,6 @@ +import { Button } from '@automattic/jetpack-components'; +import { CONTACT_SUPPORT_URL } from '@automattic/jetpack-scan'; +import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useMemo } from 'react'; import styles from './styles.module.scss'; @@ -19,14 +22,28 @@ const FixerStateNotice = ( { }: { fixerState: { inProgress: boolean; error: boolean; stale: boolean }; } ) => { + const getInterpolatedContent = (): JSX.Element => { + return createInterpolateElement( + __( 'Please try again or contact support.', 'jetpack' ), + { + supportLink: +
+ +
), }, { diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index 820b07916687b..f260b33bbf48a 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -1,15 +1,18 @@ @import '@wordpress/dataviews/build-style/style.css'; .threat__title { - color: var( --jp-gray-80 ); - font-weight: 510; - white-space: initial; + min-height: 24px; + max-width: fit-content; + display: flex; + align-items: center; + + .threat__title__link { + text-decoration: none; + } } .threat__description { - color: var( --jp-gray-80 ); font-size: 12px; - white-space: initial; } .threat__fixedOn, From 1716a45c38b3f56d84e78cc588435976ae8f6d4a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 13:57:34 -0800 Subject: [PATCH 65/67] Add slight padding to overflow issues --- .../components/components/threats-data-views/styles.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index f260b33bbf48a..bb992ea48cdac 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -5,6 +5,7 @@ max-width: fit-content; display: flex; align-items: center; + padding-left: calc( var( --spacing-base ) / 4 ); // 2px .threat__title__link { text-decoration: none; From aa0f8e571fc4591a44dbafce06d1543ac6ae9535 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 14:03:06 -0800 Subject: [PATCH 66/67] Add global override, over awkward title offset --- .../components/threats-data-views/styles.module.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index bb992ea48cdac..ac9cf71fb5008 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -1,11 +1,16 @@ @import '@wordpress/dataviews/build-style/style.css'; +:global { + .dataviews-view-list .dataviews-view-list__primary-field { + overflow: visible; + } +} + .threat__title { min-height: 24px; max-width: fit-content; display: flex; align-items: center; - padding-left: calc( var( --spacing-base ) / 4 ); // 2px .threat__title__link { text-decoration: none; From fbd709181d952ac88370b16df12e9ed7bc010644 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Mon, 25 Nov 2024 14:08:17 -0800 Subject: [PATCH 67/67] Remove unneeded styles while global override is in place --- .../components/threats-data-views/styles.module.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/js-packages/components/components/threats-data-views/styles.module.scss b/projects/js-packages/components/components/threats-data-views/styles.module.scss index ac9cf71fb5008..2b2e971d0b5c4 100644 --- a/projects/js-packages/components/components/threats-data-views/styles.module.scss +++ b/projects/js-packages/components/components/threats-data-views/styles.module.scss @@ -7,10 +7,7 @@ } .threat__title { - min-height: 24px; max-width: fit-content; - display: flex; - align-items: center; .threat__title__link { text-decoration: none;