diff --git a/projects/packages/protect-models/changelog/update-protect-threats-data b/projects/packages/protect-models/changelog/update-protect-threats-data new file mode 100644 index 0000000000000..1cb2d3079cb4d --- /dev/null +++ b/projects/packages/protect-models/changelog/update-protect-threats-data @@ -0,0 +1,4 @@ +Significance: major +Type: changed + +Changed the formatting of threat data. diff --git a/projects/packages/protect-status/changelog/update-protect-threats-data b/projects/packages/protect-status/changelog/update-protect-threats-data new file mode 100644 index 0000000000000..1cb2d3079cb4d --- /dev/null +++ b/projects/packages/protect-status/changelog/update-protect-threats-data @@ -0,0 +1,4 @@ +Significance: major +Type: changed + +Changed the formatting of threat data. diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index d560cff20ef86..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; -import { ExtensionStatus } from '../../types/scans'; -import { Threat, ThreatStatus } from '../../types/threats'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx index b9fac81d88775..42c5f32a8b6de 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/history/index.jsx @@ -1,16 +1,13 @@ -import { AdminSectionHero, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; +import { AdminSectionHero, Container, Col, H3, Text } from '@automattic/jetpack-components'; +import { __, sprintf } from '@wordpress/i18n'; import { Navigate, useParams } from 'react-router-dom'; import AdminPage from '../../../components/admin-page'; import ErrorScreen from '../../../components/error-section'; import ProtectCheck from '../../../components/protect-check-icon'; import ScanFooter from '../../../components/scan-footer'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; +import useHistoryQuery from '../../../data/scan/use-history-query'; import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; import ScanSectionHeader from '../scan-section-header'; import ScanHistoryDataView from './scan-history-data-view'; import styles from './styles.module.scss'; @@ -22,276 +19,64 @@ const ScanHistoryRoute = () => { const { hasPlan } = usePlan(); const { filter = 'all' } = useParams(); - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); + const { data: status } = useHistoryQuery(); // Threat history is only available for paid plans. if ( ! hasPlan ) { return ; } - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - return ( - { error ? ( + { status.error ? ( ) : ( - - - - - - { list.length > 0 ? ( -
-
- { getTitle() } -
- -
- ) : ( -
- -

- { __( "Don't worry about a thing", 'jetpack-protect' ) } -

- - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
- ) } - -
+ { status.threats.length > 0 ? ( +
+ +
+ ) : ( +
+ +

+ { __( "Don't worry about a thing", 'jetpack-protect' ) } +

+ + { sprintf( + /* translators: %s: Filter type */ + __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), + 'all' === filter ? ' ' : ` ${ filter } ` + ) } + +
+ ) } ) }
diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 3f49e4ff55151..eb6b790e82280 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -12,7 +12,6 @@ import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-s import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import onboardingSteps from './onboarding-steps'; import ScanResultsDataView from './scan-results-data-view'; @@ -123,13 +122,12 @@ const ScanInProgressSection = ( { currentProgress } ) => { */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { lastChecked } = useProtectData(); const { data: status } = useScanStatusQuery( { usePolling: true } ); let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active';