From 7572620a53f217b2641789309f68c0d45a86cc28 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 25 Oct 2024 10:41:06 -0700 Subject: [PATCH] Rebase --- .../components/threats-data-views/index.tsx | 236 ++++++++---------- .../threats-data-views/test/index.test.tsx | 4 +- .../components/threats-data-views/types.d.ts | 27 +- .../components/threats-data-views/utils.ts | 8 +- .../scan/changelog/add-threat-types | 4 - .../js-packages/scan/src/types/threat.d.ts | 59 ----- 6 files changed, 124 insertions(+), 214 deletions(-) delete mode 100644 projects/js-packages/scan/changelog/add-threat-types delete mode 100644 projects/js-packages/scan/src/types/threat.d.ts 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 0ed7a6565bcb8..35191a4f8f97f 100644 --- a/projects/js-packages/components/components/threats-data-views/index.tsx +++ b/projects/js-packages/components/components/threats-data-views/index.tsx @@ -4,6 +4,7 @@ import { } from '@wordpress/components'; import { Action, + ActionButton, DataViews, Field, FieldType, @@ -14,15 +15,15 @@ import { type View, } from '@wordpress/dataviews'; import { dateI18n } from '@wordpress/date'; -import { __, sprintf, _x } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useCallback, useMemo, useState } from 'react'; -import React from 'react'; import Badge from '../badge'; +import ThreatSeverityBadge from '../threat-severity-badge'; import { THREAT_STATUSES, THREAT_TYPES } from './constants'; import FixerStatusIcon, { FixerStatusBadge } from './fixer-status'; import styles from './styles.module.scss'; -import { DataViewsThreat, ThreatsDataViewsActionCallback } from './types'; +import { type Threat } from './types'; import { getThreatIcon, getThreatSubtitle, getThreatType } from './utils'; /** @@ -40,7 +41,7 @@ export function ThreatsStatusFilter( { }: { filters: Filter[]; onChange: ( newValue: string ) => void; - data: DataViewsThreat[]; + data: Threat[]; } ): JSX.Element { const activeCount = useMemo( () => data.filter( item => item.status === 'current' ).length, @@ -109,15 +110,16 @@ export function ThreatsStatusFilter( { * * @param {object} props - Component props. * @param {Array} props.data - Threats data. - * @param {Array} props.filters - Initial DataViews filters. + * @param {Array} props.filters - Initial DataView filters. * @param {Function} props.onChangeSelection - Callback function run when an item is selected. - * @param {Function} props.onFixThreat - Threat fix action callback. - * @param {Function} props.onIgnoreThreat - Threat ignore action callback. - * @param {Function} props.onUnignoreThreat - Threat unignore action callback. + * @param {Function} props.onFixThreats - Threat fix action callback. + * @param {Function} props.onIgnoreThreats - Threat ignore action callback. + * @param {Function} props.onUnignoreThreats - Threat unignore action callback. * @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. - * @return {JSX.Element} The component. + * + * @return {JSX.Element} The ThreatsDataViews component. */ export default function ThreatsDataViews( { data, @@ -126,19 +128,19 @@ export default function ThreatsDataViews( { isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, - onFixThreat, - onIgnoreThreat, - onUnignoreThreat, + onFixThreats, + onIgnoreThreats, + onUnignoreThreats, }: { - data: DataViewsThreat[]; + data: Threat[]; filters?: Filter[]; onChangeSelection?: ( selectedItemIds: string[] ) => void; - isThreatEligibleForFix?: ( threat: DataViewsThreat ) => boolean; - isThreatEligibleForIgnore?: ( threat: DataViewsThreat ) => boolean; - isThreatEligibleForUnignore?: ( threat: DataViewsThreat ) => boolean; - onFixThreat?: ThreatsDataViewsActionCallback; - onIgnoreThreat?: ThreatsDataViewsActionCallback; - onUnignoreThreat?: ThreatsDataViewsActionCallback; + isThreatEligibleForFix?: ( threat: Threat ) => boolean; + isThreatEligibleForIgnore?: ( threat: Threat ) => boolean; + isThreatEligibleForUnignore?: ( threat: Threat ) => boolean; + onFixThreats?: ActionButton< Threat >[ 'callback' ]; + onIgnoreThreats?: ActionButton< Threat >[ 'callback' ]; + onUnignoreThreats?: ActionButton< Threat >[ 'callback' ]; } ): JSX.Element { const baseView = { sort: { @@ -152,7 +154,7 @@ export default function ThreatsDataViews( { }; /** - * DataViews default layouts. + * DataView default layouts. * * This property provides layout information about the view types that are active. If empty, enables all layout types (see “Layout Types”) with empty layout data. * @@ -164,14 +166,6 @@ export default function ThreatsDataViews( { fields: [ 'severity', 'threat', 'auto-fix' ], layout: { primaryField: 'severity', - combinedFields: [ - { - id: 'threat', - label: __( 'Threat', 'jetpack' ), - children: [ 'subtitle', 'title', 'description' ], - direction: 'vertical', - }, - ], }, }, list: { @@ -185,7 +179,7 @@ export default function ThreatsDataViews( { }; /** - * DataViews view object - configures how the dataset is visible to the user. + * DataView view object - configures how the dataset is visible to the user. * * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object */ @@ -195,24 +189,32 @@ export default function ThreatsDataViews( { } ); /** - * Compute values based on the threats data. + * Compute values from the provided threats data. * * @member {object} extensions - List of unique threat extensions. * @member {object} signatures - List of unique threat signatures. * @member {Array} dataFields - List of unique fields. */ - const { extensions, signatures, dataFields } = useMemo( () => { + const { + extensions, + signatures, + dataFields, + }: { + extensions: { value: string; label: string }[]; + signatures: { value: string; label: string }[]; + dataFields: string[]; + } = useMemo( () => { return data.reduce( ( acc, threat ) => { // Extensions - if ( threat?.extension ) { + if ( threat.extension ) { if ( ! acc.extensions.find( ( { value } ) => value === threat.extension.slug ) ) { acc.extensions.push( { value: threat.extension.slug, label: threat.extension.name } ); } } // Signatures - if ( threat?.signature ) { + if ( threat.signature ) { if ( ! acc.signatures.find( ( { value } ) => value === threat.signature ) ) { acc.signatures.push( { value: threat.signature, label: threat.signature } ); } @@ -233,61 +235,67 @@ export default function ThreatsDataViews( { return acc; }, { - extensions: [] as { value: string; label: string }[], - signatures: [] as { value: string; label: string }[], - dataFields: [] as string[], + extensions: [], + signatures: [], + dataFields: [], } ); }, [ data ] ); /** - * DataViews fields - describes the visible items for each record in the dataset. + * DataView fields - describes the visible items for each record in the dataset. * * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object */ const fields = useMemo( () => { - const result: Field< DataViewsThreat >[] = [ + const result: Field< Threat >[] = [ { - id: 'title', - label: __( 'Title', 'jetpack' ), + id: 'threat', + label: __( 'Threat', 'jetpack' ), enableGlobalSearch: true, enableHiding: false, - render( { item }: { item: DataViewsThreat } ) { - if ( view.type === 'list' ) { - return item.title; - } - return { item.title }; + getValue( { item }: { item: Threat } ) { + return item.title + item.description; + }, + render( { item }: { item: Threat } ) { + return ( +
+
+ + { getThreatSubtitle( item ) } +
+
{ item.title }
+
{ item.description }
+
+ ); }, }, { - id: 'description', - label: __( 'Description', 'jetpack' ), + id: 'title', + label: __( 'Title', 'jetpack' ), enableGlobalSearch: true, enableHiding: false, - render( { item }: { item: DataViewsThreat } ) { - return { item.description }; - }, }, { id: 'icon', label: __( 'Icon', 'jetpack' ), - getValue( { item }: { item: DataViewsThreat } ) { + enableHiding: false, + getValue( { item }: { item: Threat } ) { return getThreatType( item ); }, - render( { item }: { item: DataViewsThreat } ) { + render( { item }: { item: Threat } ) { return ( -
+
); }, - enableHiding: false, }, { id: 'status', label: __( 'Status', 'jetpack' ), elements: THREAT_STATUSES, - getValue( { item }: { item: DataViewsThreat } ) { + getValue( { item }: { item: Threat } ) { if ( ! item.status ) { return 'current'; } @@ -295,11 +303,11 @@ export default function ThreatsDataViews( { THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? item.status ); }, - render( { item }: { item: DataViewsThreat } ) { + render( { item }: { item: Threat } ) { if ( item.status ) { const status = THREAT_STATUSES.find( ( { value } ) => value === item.status ); if ( status ) { - return { status?.label }; + return { status.label }; } } return { __( 'Active', 'jetpack' ) }; @@ -310,7 +318,7 @@ export default function ThreatsDataViews( { label: __( 'Extension', 'jetpack' ), enableGlobalSearch: true, elements: extensions, - getValue( { item }: { item: DataViewsThreat } ) { + getValue( { item }: { item: Threat } ) { return item.extension ? item.extension.slug : ''; }, }, @@ -318,17 +326,17 @@ export default function ThreatsDataViews( { id: 'type', label: __( 'Category', 'jetpack' ), elements: THREAT_TYPES, - getValue( { item }: { item: DataViewsThreat } ) { - if ( 'signature' in item && item.signature === 'Vulnerable.WP.Core' ) { + getValue( { item }: { item: Threat } ) { + if ( item.signature === 'Vulnerable.WP.Core' ) { return 'core'; } - if ( 'extension' in item && item.extension ) { + if ( item.extension ) { return item.extension.type; } - if ( 'filename' in item && item.filename ) { + if ( item.filename ) { return 'file'; } - if ( 'table' in item && item.table ) { + if ( item.table ) { return 'database'; } @@ -338,19 +346,8 @@ export default function ThreatsDataViews( { { id: 'subtitle', label: __( 'Affected Item', 'jetpack' ), - getValue( { item }: { item: DataViewsThreat } ) { - return getThreatSubtitle( item ); - }, - render( { item }: { item: DataViewsThreat } ) { - if ( view.type === 'table' ) { - return ( -
- - { getThreatSubtitle( item ) } -
- ); - } - + enableHiding: false, + getValue( { item }: { item: Threat } ) { return getThreatSubtitle( item ); }, }, @@ -361,7 +358,7 @@ export default function ThreatsDataViews( { label: __( 'Signature', 'jetpack' ), elements: signatures, enableGlobalSearch: true, - getValue( { item }: { item: DataViewsThreat } ) { + getValue( { item }: { item: Threat } ) { return item.signature || ''; }, }, @@ -372,30 +369,12 @@ export default function ThreatsDataViews( { { id: 'severity', label: __( 'Severity', 'jetpack' ), - getValue( { item }: { item: DataViewsThreat } ) { + type: 'integer' as FieldType, + getValue( { item }: { item: Threat } ) { return item.severity ?? 0; }, - render( { item }: { item: DataViewsThreat } ) { - let text = _x( 'Low', 'Severity label for issues rated below 3.', 'jetpack' ); - let variant: 'danger' | 'warning' | undefined; - - if ( item.severity >= 5 ) { - text = _x( - 'Critical', - 'Severity label for issues rated 5 or higher.', - 'jetpack' - ); - variant = 'danger'; - } else if ( item.severity >= 3 && item.severity < 5 ) { - text = _x( - 'High', - 'Severity label for issues rated between 3 and 5.', - 'jetpack' - ); - variant = 'warning'; - } - - return { text }; + render( { item }: { item: Threat } ) { + return ; }, }, ] @@ -406,10 +385,11 @@ export default function ThreatsDataViews( { id: 'auto-fix', label: __( 'Auto-fix', 'jetpack' ), enableHiding: false, - getValue( { item }: { item: DataViewsThreat } ) { - return item.fixable ? 'Yes' : ''; + type: 'integer' as FieldType, + getValue( { item }: { item: Threat } ) { + return item.fixable ? 1 : 0; }, - render( { item }: { item: DataViewsThreat } ) { + render( { item }: { item: Threat } ) { if ( ! item.fixable ) { return null; } @@ -433,15 +413,15 @@ export default function ThreatsDataViews( { id: 'first-detected', label: __( 'First Detected', 'jetpack' ), type: 'datetime' as FieldType, - getValue( { item }: { item: DataViewsThreat } ) { - return new Date( item.firstDetected ); + getValue( { item }: { item: Threat } ) { + return item.firstDetected ? new Date( item.firstDetected ) : null; }, - render( { item }: { item: DataViewsThreat } ) { - return ( + render( { item }: { item: Threat } ) { + return item.firstDetected ? ( { dateI18n( 'F j Y', item.firstDetected, false ) } - ); + ) : null; }, }, ] @@ -452,15 +432,15 @@ export default function ThreatsDataViews( { id: 'fixed-on', label: __( 'Fixed On', 'jetpack' ), type: 'datetime' as FieldType, - getValue( { item }: { item: DataViewsThreat } ) { - return new Date( item.firstDetected ); + getValue( { item }: { item: Threat } ) { + return item.fixedOn ? new Date( item.fixedOn ) : null; }, - render( { item }: { item: DataViewsThreat } ) { - return ( + render( { item }: { item: Threat } ) { + return item.fixedOn ? ( - { dateI18n( 'F j Y', item.firstDetected, false ) } + { dateI18n( 'F j Y', item.fixedOn, false ) } - ); + ) : null; }, }, ] @@ -471,21 +451,22 @@ export default function ThreatsDataViews( { }, [ extensions, signatures, dataFields, view ] ); /** - * DataViews actions - collection of operations that can be performed upon each record. + * DataView actions - collection of operations that can be performed upon each record. * * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#actions-object */ const actions = useMemo( () => { - const result: Action< DataViewsThreat >[] = []; + const result: Action< Threat >[] = []; if ( dataFields.includes( 'fixable' ) ) { result.push( { id: 'fix', label: __( 'Auto-Fix', 'jetpack' ), isPrimary: true, - callback: onFixThreat, + supportsBulk: true, + callback: onFixThreats, isEligible( item ) { - if ( ! onFixThreat ) { + if ( ! onFixThreats ) { return false; } if ( isThreatEligibleForFix ) { @@ -493,7 +474,6 @@ export default function ThreatsDataViews( { } return !! item.fixable; }, - icon: 'check', } ); } @@ -503,9 +483,9 @@ export default function ThreatsDataViews( { label: __( 'Ignore', 'jetpack' ), isPrimary: true, isDestructive: true, - callback: onIgnoreThreat, + callback: onIgnoreThreats, isEligible( item ) { - if ( ! onIgnoreThreat ) { + if ( ! onIgnoreThreats ) { return false; } if ( isThreatEligibleForIgnore ) { @@ -513,7 +493,6 @@ export default function ThreatsDataViews( { } return item.status === 'current'; }, - icon: 'unseen', } ); } @@ -523,9 +502,9 @@ export default function ThreatsDataViews( { label: __( 'Unignore', 'jetpack' ), isPrimary: true, isDestructive: true, - callback: onUnignoreThreat, + callback: onUnignoreThreats, isEligible( item ) { - if ( ! onUnignoreThreat ) { + if ( ! onUnignoreThreats ) { return false; } if ( isThreatEligibleForUnignore ) { @@ -533,16 +512,15 @@ export default function ThreatsDataViews( { } return item.status === 'ignored'; }, - icon: 'seen', } ); } return result; }, [ dataFields, - onFixThreat, - onIgnoreThreat, - onUnignoreThreat, + onFixThreats, + onIgnoreThreats, + onUnignoreThreats, isThreatEligibleForFix, isThreatEligibleForIgnore, isThreatEligibleForUnignore, @@ -567,11 +545,11 @@ export default function ThreatsDataViews( { }, [] ); /** - * DataViews getItemId function - returns the unique ID for each record in the dataset. + * DataView getItemId function - returns the unique ID for each record in the dataset. * * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function */ - const getItemId = useCallback( ( item: DataViewsThreat ) => item.id.toString(), [] ); + const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] ); /** * Callback function to handle the status change filter. 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 25317d79e09f5..eb6ef8b6581bc 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 @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; import ThreatsDataViews from '..'; -import { DataViewsThreat } from '../types'; +import { Threat } from '../types'; const data = [ { @@ -24,7 +24,7 @@ const data = [ }, source: null, }, -] as DataViewsThreat[]; +] as Threat[]; describe( 'ThreatsDataViews', () => { it( 'renders threat data', () => { diff --git a/projects/js-packages/components/components/threats-data-views/types.d.ts b/projects/js-packages/components/components/threats-data-views/types.d.ts index 2b6991b5cc181..b0344a5978fb4 100644 --- a/projects/js-packages/components/components/threats-data-views/types.d.ts +++ b/projects/js-packages/components/components/threats-data-views/types.d.ts @@ -2,7 +2,7 @@ export type ThreatStatus = 'fixed' | 'ignored' | 'current'; export type ThreatFixType = 'replace' | 'delete' | 'update' | string; -export type DataViewsThreat = { +export type Threat = { /** The threat's unique ID. */ id: number; @@ -39,20 +39,12 @@ export type DataViewsThreat = { } | false; - /** If available, the threat's latest fixer status. */ - fixer?: ThreatFixStatus; + /** The fixer status. */ + fixer: ThreatFixStatus; /** The threat's source. */ source?: string; - /** The threat's affected extension. */ - extension?: { - name: string; - slug: string; - type: 'plugin' | 'theme' | 'core'; - version: string; - }; - /** The threat's context. */ context?: Record< string, unknown > | null; @@ -67,12 +59,15 @@ export type DataViewsThreat = { /** The diff showing the threat's modified file contents. */ diff?: string; -}; -export type ThreatsDataViewsActionCallback = ( - items: Threat[], - context: { registry: unknown; onActionPerformed?: ( threats: DataViewsThreat[] ) => void } -) => void; + /** The affected extension. */ + extension?: { + slug: string; + name: string; + version: string; + type: 'plugin' | 'theme' | 'core'; + }; +}; export type FixerStatus = 'not_started' | 'in_progress' | 'fixed' | 'not_fixed'; diff --git a/projects/js-packages/components/components/threats-data-views/utils.ts b/projects/js-packages/components/components/threats-data-views/utils.ts index 0e16958658b2a..bfe4e9db55246 100644 --- a/projects/js-packages/components/components/threats-data-views/utils.ts +++ b/projects/js-packages/components/components/threats-data-views/utils.ts @@ -1,7 +1,7 @@ import { code, color, grid, plugins, shield, wordpress } from '@wordpress/icons'; -import { DataViewsThreat, ThreatFixStatus } from './types'; +import { type Threat, type ThreatFixStatus } from './types'; -export const getThreatIcon = ( threat: DataViewsThreat ) => { +export const getThreatIcon = ( threat: Threat ) => { const type = getThreatType( threat ); switch ( type ) { @@ -20,7 +20,7 @@ export const getThreatIcon = ( threat: DataViewsThreat ) => { } }; -export const getThreatType = ( threat: DataViewsThreat ) => { +export const getThreatType = ( threat: Threat ) => { if ( threat.signature === 'Vulnerable.WP.Core' ) { return 'core'; } @@ -37,7 +37,7 @@ export const getThreatType = ( threat: DataViewsThreat ) => { return null; }; -export const getThreatSubtitle = ( threat: DataViewsThreat ) => { +export const getThreatSubtitle = ( threat: Threat ) => { const type = getThreatType( threat ); switch ( type ) { diff --git a/projects/js-packages/scan/changelog/add-threat-types b/projects/js-packages/scan/changelog/add-threat-types deleted file mode 100644 index e549d3e8a3f87..0000000000000 --- a/projects/js-packages/scan/changelog/add-threat-types +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add threat TypeScript types diff --git a/projects/js-packages/scan/src/types/threat.d.ts b/projects/js-packages/scan/src/types/threat.d.ts deleted file mode 100644 index 757503972fa0c..0000000000000 --- a/projects/js-packages/scan/src/types/threat.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type ThreatStatus = 'fixed' | 'ignored' | 'current'; - -export type ThreatFixType = 'replace' | 'delete' | 'update' | string; - -export type Threat = { - /** The threat's unique ID. */ - id: number; - - /** The threat's signature. */ - signature: string; - - /** The threat's title. */ - title: string; - - /** The threat's description. */ - description: string; - - /** The threat's current status. */ - status: ThreatStatus; - - /** The threat's severity level (0-10). */ - severity: number; - - /** The date the threat was first detected on the site, in YYYY-MM-DDTHH:MM:SS.000Z format. */ - firstDetected: string; - - /** The version the threat is fixed in. */ - fixedIn?: string | null; - - /** The date the threat was fixed, in YYYY-MM-DDTHH:MM:SS.000Z format. */ - fixedOn?: string | null; - - /** The fixable details. */ - fixable: - | { - fixer: ThreatFixType; - target?: string | null; - extensionStatus?: string | null; - } - | false; - - /** The threat's source. */ - source?: string; - - /** The threat's context. */ - context?: Record< string, unknown > | null; - - /** The name of the affected file. */ - filename: string | null; - - /** The rows affected by the database threat. */ - rows?: unknown; - - /** The table name of the database threat. */ - table?: string; - - /** The diff showing the threat's modified file contents. */ - diff?: string; -};