Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Adds ScanReport component
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { __ } from '@wordpress/i18n';
import {
code as fileIcon,
color as themeIcon,
plugins as pluginIcon,
shield as shieldIcon,
wordpress as coreIcon,
} from '@wordpress/icons';

export const TYPES = [
{ value: 'core', label: __( 'WordPress', 'jetpack-components' ) },
{ value: 'plugins', label: __( 'Plugin', 'jetpack-components' ) },
{ value: 'themes', label: __( 'Theme', 'jetpack-components' ) },
{ value: 'files', label: __( 'Files', 'jetpack-components' ) },
];

export const ICONS = {
plugins: pluginIcon,
themes: themeIcon,
core: coreIcon,
files: fileIcon,
default: shieldIcon,
};

export const FIELD_ICON = 'icon';
export const FIELD_TYPE = 'type';
export const FIELD_NAME = 'name';
export const FIELD_STATUS = 'status';
export const FIELD_UPDATE = 'update';
export const FIELD_VERSION = 'version';
197 changes: 197 additions & 0 deletions projects/js-packages/components/components/scan-report/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { type ScanReportExtension } from '@automattic/jetpack-scan';
import { Tooltip } from '@wordpress/components';
import {
type SupportedLayouts,
type View,
type Field,
DataViews,
filterSortAndPaginate,
} from '@wordpress/dataviews';
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import { useCallback, useMemo, useState } from 'react';
import ShieldIcon from '../shield-icon';
import {
FIELD_NAME,
FIELD_VERSION,
FIELD_ICON,
FIELD_STATUS,
FIELD_TYPE,
TYPES,
ICONS,
} from './constants';
import styles from './styles.module.scss';

/**
* DataViews component for displaying a scan report.
*
* @param {object} props - Component props.
* @param {Array} props.data - Scan report data.
* @param {Function} props.onChangeSelection - Callback function run when an item is selected.
*
* @return {JSX.Element} The ScanReport component.
*/
export default function ScanReport( { data, onChangeSelection } ): JSX.Element {
const baseView = {
search: '',
filters: [],
page: 1,
perPage: 20,
};

/**
* 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.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view
*/
const defaultLayouts: SupportedLayouts = {
table: {
...baseView,
fields: [ FIELD_STATUS, FIELD_TYPE, FIELD_NAME, FIELD_VERSION ],
layout: {
primaryField: FIELD_STATUS,
},
},
list: {
...baseView,
fields: [ FIELD_STATUS, FIELD_VERSION ],
layout: {
primaryField: FIELD_NAME,
mediaField: FIELD_ICON,
},
},
};

/**
* 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
*/
const [ view, setView ] = useState< View >( {
type: 'table',
...defaultLayouts.table,
} );

/**
* 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 iconHeight = 20;
const result: Field< ScanReportExtension >[] = [
{
id: FIELD_STATUS,
label: __( 'Status', 'jetpack-components' ),
render( { item }: { item: ScanReportExtension } ) {
let variant: 'info' | 'warning' | 'success' = 'info';
let text = __(
'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.',
'jetpack-components'
);

if ( item.checked ) {
if ( item.threats.length > 0 ) {
variant = 'warning';
text = __( 'Threat detected.', 'jetpack-components' );
} else {
variant = 'success';
text = __( 'No known threats found that affect this version.', 'jetpack-components' );
}
}

return (
<Tooltip className={ styles.tooltip } text={ text }>
<div className={ styles.icon }>
<ShieldIcon variant={ variant } height={ iconHeight } />
</div>
</Tooltip>
);
},
},
{
id: FIELD_TYPE,
label: __( 'Type', 'jetpack-components' ),
elements: TYPES,
},
{
id: FIELD_NAME,
label: __( 'Name', 'jetpack-components' ),
enableGlobalSearch: true,
getValue( { item }: { item: ScanReportExtension } ) {
return item.name ? item.name : '';
},
},
{
id: FIELD_VERSION,
label: __( 'Version', 'jetpack-components' ),
enableGlobalSearch: true,
getValue( { item }: { item: ScanReportExtension } ) {
return item.version ? item.version : '';
},
},
...( view.type === 'list'
? [
{
id: FIELD_ICON,
label: __( 'Icon', 'jetpack-components' ),
enableSorting: false,
enableHiding: false,
getValue( { item }: { item: ScanReportExtension } ) {
return ICONS[ item.type ] || '';
},
render( { item }: { item: ScanReportExtension } ) {
return (
<div className={ styles.threat__media }>
<Icon icon={ ICONS[ item.type ] } />
</div>
);
},
},
]
: [] ),
];

return result;
}, [ view ] );

/**
* Apply the view settings (i.e. filters, sorting, pagination) to the dataset.
*
* @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts
*/
const { data: processedData, paginationInfo } = useMemo( () => {
return filterSortAndPaginate( data, view, fields );
}, [ data, view, fields ] );

/**
* Callback function to update the view state.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function
*/
const onChangeView = useCallback( ( newView: View ) => {
setView( newView );
}, [] );

/**
* 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: ScanReportExtension ) => item.id.toString(), [] );

return (
<DataViews
data={ processedData }
defaultLayouts={ defaultLayouts }
fields={ fields }
getItemId={ getItemId }
onChangeSelection={ onChangeSelection }
onChangeView={ onChangeView }
paginationInfo={ paginationInfo }
view={ view }
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import ScanReport from '..';

export default {
title: 'JS Packages/Components/Scan Report',
component: ScanReport,
parameters: {
backgrounds: {
default: 'light',
values: [ { name: 'light', value: 'white' } ],
},
},
decorators: [
Story => (
<div style={ { maxWidth: '100%', backgroundColor: 'white' } }>
<Story />
</div>
),
],
};

export const Default = args => <ScanReport { ...args } />;
Default.args = {
data: [
{
id: 1,
name: 'WordPress',
slug: null,
version: '6.7.1',
threats: [],
checked: true,
type: 'core',
},
{
id: 2,
name: 'Jetpack',
slug: 'jetpack/jetpack.php',
version: '14.1-a.7',
threats: [],
checked: false,
type: 'plugins',
},
{
id: 3,
name: 'Twenty Fifteen',
slug: 'twentyfifteen',
version: '1.1',
threats: [
{
id: 198352527,
signature: 'Vulnerable.WP.Extension',
description: 'Vulnerable WordPress extension',
severity: 3,
},
],
checked: true,
type: 'themes',
},
{
id: 4,
threats: [
{
id: 198352406,
signature: 'EICAR_AV_Test_Suspicious',
title: 'Malicious code found in file: jptt_eicar.php',
severity: 1,
},
],
checked: true,
type: 'files',
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '@wordpress/dataviews/build-style/style.css';

.threat__media {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #EDFFEE;
border-color: #EDFFEE;

svg {
fill: var( --jp-black );
}
}

.tooltip {
max-width: 240px;
border-radius: 4px;
text-align: left;
}
14 changes: 9 additions & 5 deletions projects/js-packages/components/components/shield-icon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';

const COLORS = {
error: '#D63638',
warning: '#F0B849',
success: '#069E08',
default: '#1d2327',
info: '#A7AAAD',
success: '#069E08',
warning: '#F0B849',
error: '#D63638',
};

/**
Expand Down Expand Up @@ -32,11 +33,11 @@ export default function ShieldIcon( {
}: {
className?: string;
contrast?: string;
fill?: 'default' | 'success' | 'warning' | 'error' | string;
fill?: 'default' | 'info' | 'success' | 'warning' | 'error' | string;
height?: number;
icon?: 'success' | 'error';
outline?: boolean;
variant: 'default' | 'success' | 'warning' | 'error';
variant: 'default' | 'info' | 'success' | 'warning' | 'error';
} ): JSX.Element {
const shieldFill = COLORS[ fill ] || fill || COLORS[ variant ];
const iconFill = outline ? shieldFill : contrast;
Expand All @@ -60,6 +61,9 @@ export default function ShieldIcon( {
}
fill={ shieldFill }
/>
{ 'info' === iconVariant && (
<path d="M33.7 69H41.5V45.6H33.7V69ZM33.7 37.8H41.5V30H33.7V37.8Z" fill={ iconFill } />
) }
{ 'success' === iconVariant && (
<path
fillRule="evenodd"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ export default {
control: {
type: 'select',
},
options: [ 'default', 'success', 'warning', 'error' ],
options: [ 'default', 'info', 'success', 'warning', 'error' ],
},
icon: {
control: {
type: 'select',
},
options: [ 'success', 'error' ],
options: [ 'info', 'success', 'error' ],
},
fill: {
control: 'color',
Expand All @@ -39,12 +39,14 @@ export const Variants = () => {
<div style={ { display: 'flex', flexDirection: 'column', gap: '8px' } }>
<div style={ { display: 'flex', gap: '8px' } }>
<ShieldIcon variant="default" />
<ShieldIcon variant="info" />
<ShieldIcon variant="success" />
<ShieldIcon variant="warning" />
<ShieldIcon variant="error" />
</div>
<div style={ { display: 'flex', gap: '8px' } }>
<ShieldIcon variant="default" outline />
<ShieldIcon variant="info" outline />
<ShieldIcon variant="success" outline />
<ShieldIcon variant="warning" outline />
<ShieldIcon variant="error" outline />
Expand Down
Loading
Loading