Skip to content

Commit

Permalink
Use react router for threat history, separate scan and history root c…
Browse files Browse the repository at this point in the history
…omponents, minor UI adjustments
  • Loading branch information
nateweller committed Jul 12, 2024
1 parent be29dc8 commit e2c9aee
Show file tree
Hide file tree
Showing 36 changed files with 869 additions and 530 deletions.
1 change: 0 additions & 1 deletion projects/plugins/protect/src/class-jetpack-protect.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
20 changes: 3 additions & 17 deletions projects/plugins/protect/src/class-rest-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
},
)
);
}

/**
Expand Down Expand Up @@ -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 );
}

Expand Down
18 changes: 4 additions & 14 deletions projects/plugins/protect/src/class-scan-history.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -204,31 +203,22 @@ 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 ) ) {
return $history;
}

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' );
Expand Down
5 changes: 2 additions & 3 deletions projects/plugins/protect/src/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
} ),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -39,9 +40,10 @@ const AdminPage = ( { children } ) => {
setTimeout( () => {
refreshPlan();
refreshStatus( true );
refreshScanHistory();
}, 5000 );
}
}, [ refreshPlan, refreshStatus, startScanOptimistically ] );
}, [ refreshPlan, refreshStatus, refreshScanHistory, startScanOptimistically ] );

/*
* Show interstital page when
Expand All @@ -57,7 +59,7 @@ const AdminPage = ( { children } ) => {
{ notice.message && <Notice floating={ true } dismissable={ true } { ...notice } /> }
<Container horizontalSpacing={ 0 }>
<Tabs className={ styles.navigation }>
<Tab link="/" label={ __( 'Scan', 'jetpack-protect' ) } />
<Tab link="/scan" label={ __( 'Scan', 'jetpack-protect' ) } />
<Tab
link="/firewall"
label={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { STORE_ID } from '../../state/store';

const useRegistrationWatcher = () => {
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
Expand Down
24 changes: 24 additions & 0 deletions projects/plugins/protect/src/js/components/button-group/index.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<WordPressButtonGroup className={ styles[ 'button-group' ] } { ...props }>
{ children }
</WordPressButtonGroup>
);
}

ButtonGroup.Button = props => <Button { ...props } />;

export default ButtonGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable react/react-in-jsx-scope */
import { Button } from '@automattic/jetpack-components';
import React from 'react';
import ButtonGroup from '../index.jsx';

export default {
title: 'Plugins/Protect/Button Group',
component: ButtonGroup,
argTypes: {},
};

const Template = args => (
<ButtonGroup { ...args }>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ButtonGroup>
);
export const Default = Template.bind( {} );
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
97 changes: 47 additions & 50 deletions projects/plugins/protect/src/js/components/paid-accordion/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 && (
<Text className={ styles[ 'accordion-header-status' ] }>
{ sprintf(
/* translators: %s: First detected date */
__( 'Threat found %s', 'jetpack-protect' ),
dateI18n( 'M j, Y', detectedAt )
) }
{ 'fixed' === status && (
<>
<span className={ styles[ 'accordion-header-status-separator' ] }></span>
<span className={ styles[ 'is-fixed' ] }>
{ sprintf(
/* translators: %s: Fixed on date */
__( 'Threat fixed %s', 'jetpack-protect' ),
dateI18n( 'M j, Y', fixedOn )
) }
</span>
</>
) }
</Text>
) }
{ ( 'fixed' === status || 'ignored' === status ) && (
<StatusBadge status={ 'fixed' === status ? 'fixed' : 'ignored' } />
) }
</>
);
};

const StatusBadge = ( { status } ) => (
<div className={ `${ styles[ 'status-badge' ] } ${ styles[ status ] }` }>
{ 'fixed' === status
? __( 'Fixed', 'jetpack-protect' )
: __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) }
</div>
);

export const PaidAccordionItem = ( {
id,
title,
Expand All @@ -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,
Expand All @@ -46,49 +84,6 @@ export const PaidAccordionItem = ( {

const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] );

const FixDetails = ( { date, isFixed } ) => (
<span className={ styles[ isFixed ? 'is-fixed' : 'is-ignored' ] }>
{ isFixed
? sprintf(
/* translators: %s: Fixed on date */
__( 'Threat fixed %s', 'jetpack-protect' ),
dateI18n( 'M j, Y', date )
)
: __( 'Threat ignored', 'jetpack-protect' ) }
</span>
);

const ScanHistoryDetails = ( { viewingHistory, detectedAt, fixedAt } ) => {
if ( ! viewingHistory ) {
return null;
}

return (
<>
{ detectedAt && (
<Text className={ styles[ 'accordion-header-status' ] }>
{ sprintf(
/* translators: %s: First detected date */
__( 'Threat found %s', 'jetpack-protect' ),
dateI18n( 'M j, Y', detectedAt )
) }
<span className={ styles[ 'accordion-header-status-separator' ] }></span>
<FixDetails date={ fixedAt || detectedAt } isFixed={ !! fixedAt } />
</Text>
) }
<StatusBadge status={ fixedAt ? 'fixed' : 'ignored' } />
</>
);
};

const StatusBadge = ( { status } ) => (
<div className={ `${ styles[ 'status-badge' ] } ${ styles[ status ] }` }>
{ 'fixed' === status
? __( 'Fixed', 'jetpack-protect' )
: __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) }
</div>
);

return (
<div className={ styles[ 'accordion-item' ] }>
<button className={ styles[ 'accordion-header' ] } onClick={ handleClick }>
Expand All @@ -103,11 +98,13 @@ export const PaidAccordionItem = ( {
>
{ title }
</Text>
<ScanHistoryDetails
viewingHistory={ viewingScanHistory }
detectedAt={ firstDetected }
fixedAt={ fixedOn }
/>
{ ( 'fixed' === status || 'ignored' === status ) && (
<ScanHistoryDetails
detectedAt={ firstDetected }
status={ status }
fixedAt={ fixedOn }
/>
) }
</div>
<div>
<ThreatSeverityBadge severity={ severity } />
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<svg width="80" height="96" viewBox="0 0 80 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M40 0.00634766L80 17.7891V44.2985C80 66.8965 65.1605 88.2927 44.2352 95.0425C41.4856 95.9295 38.5144 95.9295 35.7648 95.0425C14.8395 88.2927 0 66.8965 0 44.2985V17.7891L40 0.00634766Z"
fill="#069E08"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M60.9 33.6909L35.375 67.9124L19.2047 55.9263L22.7848 51.1264L34.1403 59.5436L56.0851 30.122L60.9 33.6909Z"
fill="white"
/>
</svg>
);
}
Loading

0 comments on commit e2c9aee

Please sign in to comment.