From e2c9aee1be31915d1eb284e68c34de464b7d7bf0 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Fri, 12 Jul 2024 16:01:51 -0600 Subject: [PATCH] Use react router for threat history, separate scan and history root components, minor UI adjustments --- .../protect/src/class-jetpack-protect.php | 1 - .../protect/src/class-rest-controller.php | 20 +- .../protect/src/class-scan-history.php | 18 +- projects/plugins/protect/src/js/api.js | 5 +- .../src/js/components/admin-page/index.jsx | 8 +- .../admin-page/use-registration-watcher.js | 3 +- .../src/js/components/button-group/index.jsx | 24 ++ .../button-group/stories/index.stories.jsx | 18 ++ .../button-group/styles.module.scss | 22 ++ .../js/components/paid-accordion/index.jsx | 97 +++---- .../components/protect-check-icon/index.tsx | 25 ++ .../src/js/components/scan-button/index.jsx | 34 +++ .../src/js/components/summary/index.jsx | 180 ++---------- .../js/components/summary/styles.module.scss | 32 --- .../src/js/components/threats-list/empty.jsx | 37 ++- .../src/js/components/threats-list/index.jsx | 46 +-- .../js/components/threats-list/navigation.jsx | 18 +- .../js/components/threats-list/paid-list.jsx | 22 +- .../threats-list/styles.module.scss | 13 +- .../threats-list/use-threats-list.js | 19 +- projects/plugins/protect/src/js/global.d.ts | 1 + .../src/js/hooks/use-protect-data/index.js | 44 ++- .../src/js/hooks/use-scan-history/index.js | 74 ----- projects/plugins/protect/src/js/index.tsx | 8 +- .../src/js/routes/scan/history/index.jsx | 267 ++++++++++++++++++ .../js/routes/scan/history/status-filters.jsx | 38 +++ .../js/routes/scan/history/styles.module.scss | 65 +++++ .../protect/src/js/routes/scan/index.jsx | 76 +---- .../js/routes/scan/scan-section-header.tsx | 46 +++ .../routes/scan/scan-section-navigation.jsx | 36 +++ .../src/js/routes/scan/styles.module.scss | 45 +++ .../plugins/protect/src/js/state/actions.js | 27 +- .../plugins/protect/src/js/state/reducers.js | 13 +- .../plugins/protect/src/js/state/selectors.js | 1 - .../plugins/protect/src/js/styles.module.scss | 9 + .../src/models/class-history-model.php | 7 - 36 files changed, 869 insertions(+), 530 deletions(-) create mode 100644 projects/plugins/protect/src/js/components/button-group/index.jsx create mode 100644 projects/plugins/protect/src/js/components/button-group/stories/index.stories.jsx create mode 100644 projects/plugins/protect/src/js/components/button-group/styles.module.scss create mode 100644 projects/plugins/protect/src/js/components/protect-check-icon/index.tsx create mode 100644 projects/plugins/protect/src/js/components/scan-button/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/summary/styles.module.scss create mode 100644 projects/plugins/protect/src/js/global.d.ts delete mode 100644 projects/plugins/protect/src/js/hooks/use-scan-history/index.js create mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx create mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-section-header.tsx create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-section-navigation.jsx diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 854978215ee46..26730756a952c 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -212,7 +212,6 @@ public function initial_state() { 'apiNonce' => wp_create_nonce( 'wp_rest' ), 'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ), 'status' => Status::get_status( $refresh_status_from_wpcom ), - 'viewingScanHistory' => false, 'scanHistory' => Scan_History::get_scan_history( $refresh_status_from_wpcom ), 'installedPlugins' => Plugins_Installer::get_plugins(), 'installedThemes' => Sync_Functions::get_themes(), diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 0e11a3d08bd24..6da77263e808c 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -203,25 +203,13 @@ public static function register_rest_endpoints() { 'jetpack-protect/v1', 'scan-history', array( - 'methods' => \WP_REST_Server::EDITABLE, + 'methods' => \WP_REST_Server::READABLE, 'callback' => __CLASS__ . '::api_get_scan_history', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ) ); - - register_rest_route( - 'jetpack-protect/v1', - 'clear-scan-history-cache', - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::api_clear_scan_history_cache', - 'permission_callback' => function () { - return current_user_can( 'manage_options' ); - }, - ) - ); } /** @@ -437,12 +425,10 @@ public static function api_complete_onboarding_steps( $request ) { /** * Return Scan History for the API endpoint * - * @param WP_REST_Request $request The request object. - * * @return WP_REST_Response */ - public static function api_get_scan_history( $request ) { - $scan_history = Scan_History::get_scan_history( false, $request['filter'] ); + public static function api_get_scan_history() { + $scan_history = Scan_History::get_scan_history( false ); return rest_ensure_response( $scan_history, 200 ); } diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index b671c83c30bf0..afe66592a30d4 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -113,11 +113,10 @@ public static function delete_option() { /** * Gets the current history of the Jetpack Protect checks * - * @param bool $refresh_from_wpcom Refresh the local plan and history cache from wpcom. - * @param array $filter The filter to apply to the data. + * @param bool $refresh_from_wpcom Refresh the local plan and history cache from wpcom. * @return History_Model|bool */ - public static function get_scan_history( $refresh_from_wpcom = false, $filter = null ) { + public static function get_scan_history( $refresh_from_wpcom = false ) { $has_required_plan = Plan::has_required_plan(); if ( ! $has_required_plan ) { return false; @@ -142,7 +141,7 @@ public static function get_scan_history( $refresh_from_wpcom = false, $filter = ) ); } else { - $history = self::normalize_api_data( $history, $filter ); + $history = self::normalize_api_data( $history ); } self::$history = $history; @@ -204,20 +203,15 @@ public static function fetch_from_api() { * Formats the payload from the Scan API into an instance of History_Model. * * @param object $scan_data The data returned by the scan API. - * @param array $filter The filter to apply to the data. * @return History_Model */ - private static function normalize_api_data( $scan_data, $filter ) { + private static function normalize_api_data( $scan_data ) { $history = new History_Model(); $history->num_threats = 0; $history->num_core_threats = 0; $history->num_plugins_threats = 0; $history->num_themes_threats = 0; - if ( $filter ) { - $history->filter = $filter; - } - $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { @@ -225,10 +219,6 @@ private static function normalize_api_data( $scan_data, $filter ) { } foreach ( $scan_data->threats as $threat ) { - if ( ! in_array( $threat->status, $history->filter, true ) ) { - continue; - } - if ( isset( $threat->extension->type ) ) { if ( 'plugin' === $threat->extension->type ) { self::handle_extension_threats( $threat, $history, 'plugin' ); diff --git a/projects/plugins/protect/src/js/api.js b/projects/plugins/protect/src/js/api.js index 3b75d4246b604..30ab7a200896b 100644 --- a/projects/plugins/protect/src/js/api.js +++ b/projects/plugins/protect/src/js/api.js @@ -46,11 +46,10 @@ const API = { data: { step_ids: stepIds }, } ), - fetchScanHistory: $filter => + fetchScanHistory: () => apiFetch( { path: 'jetpack-protect/v1/scan-history', - method: 'POST', - data: { filter: $filter }, + method: 'GET', } ), }; diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx index f11e152256988..6a0e509d7c743 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -20,7 +20,8 @@ const AdminPage = ( { children } ) => { const { isSeen: wafSeen } = useWafData(); const notice = useSelect( select => select( STORE_ID ).getNotice() ); - const { refreshPlan, startScanOptimistically, refreshStatus } = useDispatch( STORE_ID ); + const { refreshPlan, startScanOptimistically, refreshStatus, refreshScanHistory } = + useDispatch( STORE_ID ); const { adminUrl } = window.jetpackProtectInitialState || {}; const { run, isRegistered, hasCheckoutStarted } = useProductCheckoutWorkflow( { productSlug: JETPACK_SCAN_SLUG, @@ -39,9 +40,10 @@ const AdminPage = ( { children } ) => { setTimeout( () => { refreshPlan(); refreshStatus( true ); + refreshScanHistory(); }, 5000 ); } - }, [ refreshPlan, refreshStatus, startScanOptimistically ] ); + }, [ refreshPlan, refreshStatus, refreshScanHistory, startScanOptimistically ] ); /* * Show interstital page when @@ -57,7 +59,7 @@ const AdminPage = ( { children } ) => { { notice.message && } - + { const { isRegistered } = useConnection(); - const { refreshStatus } = useDispatch( STORE_ID ); + const { refreshStatus, refreshScanHistory } = useDispatch( STORE_ID ); const status = useSelect( select => select( STORE_ID ).getStatus() ); useEffect( () => { if ( isRegistered && ! status.status ) { refreshStatus(); + refreshScanHistory(); } // We don't want to run the effect if status changes. Only on changes on isRegistered. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/projects/plugins/protect/src/js/components/button-group/index.jsx b/projects/plugins/protect/src/js/components/button-group/index.jsx new file mode 100644 index 0000000000000..8b8d716c75c79 --- /dev/null +++ b/projects/plugins/protect/src/js/components/button-group/index.jsx @@ -0,0 +1,24 @@ +import { Button } from '@automattic/jetpack-components'; +import { ButtonGroup as WordPressButtonGroup } from '@wordpress/components'; +import React from 'react'; +import styles from './styles.module.scss'; + +/** + * Button Group + * + * @param {object} props - Component props. + * @param { React.ReactNode } props.children - Component children. + * + * @returns { React.ReactNode } The Button Group component. + */ +function ButtonGroup( { children, ...props } ) { + return ( + + { children } + + ); +} + +ButtonGroup.Button = props => + + +); +export const Default = Template.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/button-group/styles.module.scss b/projects/plugins/protect/src/js/components/button-group/styles.module.scss new file mode 100644 index 0000000000000..975da1372a711 --- /dev/null +++ b/projects/plugins/protect/src/js/components/button-group/styles.module.scss @@ -0,0 +1,22 @@ +.button-group { + :global .components-button { + position: relative; + box-shadow: inset 0 0 0 1.5px var( --jp-gray ); + + &:first-child { + border-radius: var(--jp-border-radius) 0 0 var(--jp-border-radius); + } + + &:last-child { + border-radius: 0 var(--jp-border-radius) var(--jp-border-radius) 0; + } + + &:hover { + z-index: 1; + } + + + .components-button { + margin-left: -1.5px; + } + } +} diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx index 1839ef6c26a8b..b922738dd6c83 100644 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx @@ -5,13 +5,51 @@ import { sprintf, __ } from '@wordpress/i18n'; import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons'; import clsx from 'clsx'; import React, { useState, useCallback, useContext } from 'react'; -import useScanHistory from '../../hooks/use-scan-history'; import { STORE_ID } from '../../state/store'; import ThreatSeverityBadge from '../severity'; import styles from './styles.module.scss'; const PaidAccordionContext = React.createContext(); +const ScanHistoryDetails = ( { detectedAt, fixedOn, status } ) => { + return ( + <> + { detectedAt && ( + + { sprintf( + /* translators: %s: First detected date */ + __( 'Threat found %s', 'jetpack-protect' ), + dateI18n( 'M j, Y', detectedAt ) + ) } + { 'fixed' === status && ( + <> + + + { sprintf( + /* translators: %s: Fixed on date */ + __( 'Threat fixed %s', 'jetpack-protect' ), + dateI18n( 'M j, Y', fixedOn ) + ) } + + + ) } + + ) } + { ( 'fixed' === status || 'ignored' === status ) && ( + + ) } + + ); +}; + +const StatusBadge = ( { status } ) => ( +
+ { 'fixed' === status + ? __( 'Fixed', 'jetpack-protect' ) + : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } +
+); + export const PaidAccordionItem = ( { id, title, @@ -23,12 +61,12 @@ export const PaidAccordionItem = ( { firstDetected, fixedOn, onOpen, + status, } ) => { const accordionData = useContext( PaidAccordionContext ); const open = accordionData?.open === id; const setOpen = accordionData?.setOpen; const threatsAreFixing = useSelect( select => select( STORE_ID ).getThreatsAreFixing() ); - const { viewingScanHistory } = useScanHistory(); const bodyClassNames = clsx( styles[ 'accordion-body' ], { [ styles[ 'accordion-body-open' ] ]: open, @@ -46,49 +84,6 @@ export const PaidAccordionItem = ( { const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - const FixDetails = ( { date, isFixed } ) => ( - - { isFixed - ? sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', date ) - ) - : __( 'Threat ignored', 'jetpack-protect' ) } - - ); - - const ScanHistoryDetails = ( { viewingHistory, detectedAt, fixedAt } ) => { - if ( ! viewingHistory ) { - return null; - } - - return ( - <> - { detectedAt && ( - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', detectedAt ) - ) } - - - - ) } - - - ); - }; - - const StatusBadge = ( { status } ) => ( -
- { 'fixed' === status - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
- ); - return (
diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx new file mode 100644 index 0000000000000..3727aa3eae3a0 --- /dev/null +++ b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx @@ -0,0 +1,25 @@ +import { type JSX } from 'react'; + +/** + * Protect Shield and Checkmark SVG Icon + * + * @returns {JSX.Element} Protect Shield and Checkmark SVG Icon + */ +export default function ProtectCheck(): JSX.Element { + return ( + + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx new file mode 100644 index 0000000000000..95b1541af8f46 --- /dev/null +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -0,0 +1,34 @@ +import { Button } from '@automattic/jetpack-components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import React from 'react'; +import { STORE_ID } from '../../state/store'; + +/** + * Scan Button Component + * + * @param {object} props - The component props. + * @returns {React.ReactElement} Button that triggers a scan on click. + */ +export default function ScanButton( { ...props } ) { + const { scan } = useDispatch( STORE_ID ); + const scanIsEnqueuing = useSelect( select => select( STORE_ID ).getScanIsEnqueuing(), [] ); + + const handleScanClick = () => { + return event => { + event.preventDefault(); + scan(); + }; + }; + + return ( + + ); +} diff --git a/projects/plugins/protect/src/js/components/summary/index.jsx b/projects/plugins/protect/src/js/components/summary/index.jsx index fd416021cc138..d9a0d3623e57a 100644 --- a/projects/plugins/protect/src/js/components/summary/index.jsx +++ b/projects/plugins/protect/src/js/components/summary/index.jsx @@ -1,171 +1,49 @@ -import { - Container, - Col, - Text, - Title, - getIconBySlug, - Button, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; import React, { useState } from 'react'; import useProtectData from '../../hooks/use-protect-data'; -import useScanHistory from '../../hooks/use-scan-history'; -import { STORE_ID } from '../../state/store'; +import ScanSectionHeader from '../../routes/scan/scan-section-header'; import OnboardingPopover from '../onboarding-popover'; -import styles from './styles.module.scss'; const Summary = () => { const [ isSm ] = useBreakpointMatch( 'sm' ); - const { - filter, - viewingScanHistory, - allScanHistoryIsLoading, - ignoredScanHistoryIsLoading, - fixedScanHistoryIsLoading, - toggleAllScanHistory, - toggleIgnoredScanHistory, - toggleFixedScanHistory, - handleHistoryClick, - handleCurrentClick, - } = useScanHistory(); const { numThreats, lastChecked, hasRequiredPlan } = useProtectData(); - const scanIsEnqueuing = useSelect( select => select( STORE_ID ).getScanIsEnqueuing() ); - const { scan } = useDispatch( STORE_ID ); - const Icon = getIconBySlug( 'protect' ); // Popover anchors const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const handleScanClick = () => { - return event => { - event.preventDefault(); - scan(); - }; - }; - - const renderScanOptions = () => ( - <> - -