@automattic/jetpack-scan 0.5.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/build/components/index.d.ts +4 -0
  3. package/build/components/index.js +4 -0
  4. package/build/components/threat-fixer-button/index.d.ts +16 -0
  5. package/build/components/threat-fixer-button/index.js +56 -0
  6. package/build/components/threat-modal/fixer-state-notice.d.ts +19 -0
  7. package/build/components/threat-modal/fixer-state-notice.js +44 -0
  8. package/build/components/threat-modal/index.d.ts +51 -0
  9. package/build/components/threat-modal/index.js +45 -0
  10. package/build/components/threat-modal/threat-actions.d.ts +7 -0
  11. package/build/components/threat-modal/threat-actions.js +40 -0
  12. package/build/components/threat-modal/threat-fix-confirmation.d.ts +7 -0
  13. package/build/components/threat-modal/threat-fix-confirmation.js +19 -0
  14. package/build/components/threat-modal/threat-fix-details.d.ts +7 -0
  15. package/build/components/threat-modal/threat-fix-details.js +40 -0
  16. package/build/components/threat-modal/threat-notice.d.ts +16 -0
  17. package/build/components/threat-modal/threat-notice.js +23 -0
  18. package/build/components/threat-modal/threat-summary.d.ts +7 -0
  19. package/build/components/threat-modal/threat-summary.js +16 -0
  20. package/build/components/threat-modal/threat-technical-details.d.ts +7 -0
  21. package/build/components/threat-modal/threat-technical-details.js +26 -0
  22. package/build/components/threat-severity-badge/index.d.ts +4 -0
  23. package/build/components/threat-severity-badge/index.js +13 -0
  24. package/build/components/threat-severity-badge/test/index.test.d.ts +1 -0
  25. package/build/components/threat-severity-badge/test/index.test.js +9 -0
  26. package/build/components/threats-data-views/constants.d.ts +33 -0
  27. package/build/components/threats-data-views/constants.js +37 -0
  28. package/build/components/threats-data-views/index.d.ts +29 -0
  29. package/build/components/threats-data-views/index.js +412 -0
  30. package/build/components/threats-data-views/test/index.test.d.ts +1 -0
  31. package/build/components/threats-data-views/test/index.test.js +51 -0
  32. package/build/components/threats-data-views/threats-status-toggle-group-control.d.ts +15 -0
  33. package/build/components/threats-data-views/threats-status-toggle-group-control.js +95 -0
  34. package/build/index.d.ts +4 -3
  35. package/build/index.js +4 -3
  36. package/build/types/index.d.ts +3 -3
  37. package/build/types/index.js +3 -3
  38. package/build/types/status.d.ts +1 -1
  39. package/build/types/threats.d.ts +1 -1
  40. package/build/utils/index.d.ts +2 -2
  41. package/build/utils/index.js +7 -9
  42. package/package.json +19 -14
  43. package/src/components/index.js +4 -0
  44. package/src/components/threat-fixer-button/index.tsx +101 -0
  45. package/src/components/threat-fixer-button/styles.module.scss +16 -0
  46. package/src/components/threat-modal/fixer-state-notice.tsx +63 -0
  47. package/src/components/threat-modal/index.tsx +109 -0
  48. package/src/components/threat-modal/styles.module.scss +82 -0
  49. package/src/components/threat-modal/threat-actions.tsx +93 -0
  50. package/src/components/threat-modal/threat-fix-confirmation.tsx +54 -0
  51. package/src/components/threat-modal/threat-fix-details.tsx +64 -0
  52. package/src/components/threat-modal/threat-notice.tsx +84 -0
  53. package/src/components/threat-modal/threat-summary.tsx +29 -0
  54. package/src/components/threat-modal/threat-technical-details.tsx +65 -0
  55. package/src/components/threat-severity-badge/index.tsx +24 -0
  56. package/src/components/threat-severity-badge/styles.module.scss +27 -0
  57. package/src/components/threat-severity-badge/test/index.test.jsx +9 -0
  58. package/src/components/threats-data-views/constants.ts +49 -0
  59. package/src/components/threats-data-views/index.tsx +533 -0
  60. package/src/components/threats-data-views/styles.module.scss +40 -0
  61. package/src/components/threats-data-views/test/index.test.jsx +56 -0
  62. package/src/components/threats-data-views/threats-status-toggle-group-control.tsx +158 -0
  63. package/src/index.ts +4 -3
  64. package/src/types/index.ts +3 -3
  65. package/src/types/status.ts +1 -1
  66. package/src/types/threats.ts +1 -1
  67. package/src/utils/index.ts +3 -3
  68. /package/src/{index.test.ts → index.test.js} +0 -0
@@ -0,0 +1,533 @@
1
+ import { Badge } from '@automattic/jetpack-components';
2
+ import {
3
+ type Action,
4
+ type ActionButton,
5
+ type Field,
6
+ type FieldType,
7
+ type Filter,
8
+ type SortDirection,
9
+ type SupportedLayouts,
10
+ type View,
11
+ DataViews,
12
+ filterSortAndPaginate,
13
+ } from '@wordpress/dataviews';
14
+ import { dateI18n } from '@wordpress/date';
15
+ import { __ } from '@wordpress/i18n';
16
+ import { Icon } from '@wordpress/icons';
17
+ import { useCallback, useMemo, useState } from 'react';
18
+ import { ThreatSeverityBadge, getThreatType, type Threat } from '@automattic/jetpack-scan';
19
+ import ThreatFixerButton from '../threat-fixer-button/index.tsx';
20
+ import {
21
+ THREAT_ACTION_FIX,
22
+ THREAT_ACTION_IGNORE,
23
+ THREAT_ACTION_UNIGNORE,
24
+ THREAT_FIELD_AUTO_FIX,
25
+ THREAT_FIELD_DESCRIPTION,
26
+ THREAT_FIELD_EXTENSION,
27
+ THREAT_FIELD_FIRST_DETECTED,
28
+ THREAT_FIELD_FIXED_ON,
29
+ THREAT_FIELD_ICON,
30
+ THREAT_FIELD_PLUGIN,
31
+ THREAT_FIELD_SEVERITY,
32
+ THREAT_FIELD_SIGNATURE,
33
+ THREAT_FIELD_STATUS,
34
+ THREAT_FIELD_THEME,
35
+ THREAT_FIELD_TITLE,
36
+ THREAT_FIELD_TYPE,
37
+ THREAT_ICONS,
38
+ THREAT_STATUSES,
39
+ THREAT_TYPES,
40
+ } from './constants.ts';
41
+ import styles from './styles.module.scss';
42
+ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-control.tsx';
43
+
44
+ /**
45
+ * DataViews component for displaying security threats.
46
+ *
47
+ * @param {object} props - Component props.
48
+ * @param {Array} props.data - Threats data.
49
+ * @param {Array} props.filters - Initial DataView filters.
50
+ * @param {Function} props.onChangeSelection - Callback function run when an item is selected.
51
+ * @param {Function} props.onFixThreats - Threat fix action callback.
52
+ * @param {Function} props.onIgnoreThreats - Threat ignore action callback.
53
+ * @param {Function} props.onUnignoreThreats - Threat unignore action callback.
54
+ * @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing.
55
+ * @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring.
56
+ * @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring.
57
+ *
58
+ * @return {JSX.Element} The ThreatsDataViews component.
59
+ */
60
+ export default function ThreatsDataViews( {
61
+ data,
62
+ filters,
63
+ onChangeSelection,
64
+ isThreatEligibleForFix,
65
+ isThreatEligibleForIgnore,
66
+ isThreatEligibleForUnignore,
67
+ onFixThreats,
68
+ onIgnoreThreats,
69
+ onUnignoreThreats,
70
+ }: {
71
+ data: Threat[];
72
+ filters?: Filter[];
73
+ onChangeSelection?: ( selectedItemIds: string[] ) => void;
74
+ isThreatEligibleForFix?: ( threat: Threat ) => boolean;
75
+ isThreatEligibleForIgnore?: ( threat: Threat ) => boolean;
76
+ isThreatEligibleForUnignore?: ( threat: Threat ) => boolean;
77
+ onFixThreats?: ( threats: Threat[] ) => void;
78
+ onIgnoreThreats?: ActionButton< Threat >[ 'callback' ];
79
+ onUnignoreThreats?: ActionButton< Threat >[ 'callback' ];
80
+ } ): JSX.Element {
81
+ const baseView = {
82
+ sort: {
83
+ field: 'severity',
84
+ direction: 'desc' as SortDirection,
85
+ },
86
+ search: '',
87
+ filters: filters || [],
88
+ page: 1,
89
+ perPage: 20,
90
+ };
91
+
92
+ /**
93
+ * DataView default layouts.
94
+ *
95
+ * 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.
96
+ *
97
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view
98
+ */
99
+ const defaultLayouts: SupportedLayouts = {
100
+ table: {
101
+ ...baseView,
102
+ fields: [ THREAT_FIELD_SEVERITY, THREAT_FIELD_TYPE, THREAT_FIELD_AUTO_FIX ],
103
+ titleField: THREAT_FIELD_TITLE,
104
+ descriptionField: THREAT_FIELD_DESCRIPTION,
105
+ showMedia: false,
106
+ },
107
+ list: {
108
+ ...baseView,
109
+ fields: [
110
+ THREAT_FIELD_SEVERITY,
111
+ THREAT_FIELD_TYPE,
112
+ THREAT_FIELD_EXTENSION,
113
+ THREAT_FIELD_SIGNATURE,
114
+ ],
115
+ titleField: THREAT_FIELD_TITLE,
116
+ mediaField: THREAT_FIELD_ICON,
117
+ showMedia: true,
118
+ },
119
+ };
120
+
121
+ /**
122
+ * DataView view object - configures how the dataset is visible to the user.
123
+ *
124
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object
125
+ */
126
+ const [ view, setView ] = useState< View >( {
127
+ type: 'table',
128
+ ...defaultLayouts.table,
129
+ } );
130
+
131
+ /**
132
+ * Compute values from the provided threats data.
133
+ *
134
+ * @member {object[]} themes - List of unique themes included in the threats data.
135
+ * @member {object[]} plugins - plugins included in the threats data.
136
+ * @member {object[]} signatures - List of unique threat signatures.
137
+ * @member {string[]} dataFields - List of unique fields.
138
+ */
139
+ const {
140
+ themes,
141
+ plugins,
142
+ signatures,
143
+ dataFields,
144
+ }: {
145
+ themes: { value: string; label: string }[];
146
+ plugins: { value: string; label: string }[];
147
+ signatures: { value: string; label: string }[];
148
+ dataFields: string[];
149
+ } = useMemo( () => {
150
+ return data.reduce(
151
+ ( acc, threat ) => {
152
+ // Extensions (Themes and Plugins)
153
+ if ( threat.extension ) {
154
+ switch ( threat.extension.type ) {
155
+ case 'themes':
156
+ if ( ! acc.themes.find( ( { value } ) => value === threat.extension.slug ) ) {
157
+ acc.themes.push( { value: threat.extension.slug, label: threat.extension.name } );
158
+ }
159
+ break;
160
+ case 'plugins':
161
+ if ( ! acc.plugins.find( ( { value } ) => value === threat.extension.slug ) ) {
162
+ acc.plugins.push( { value: threat.extension.slug, label: threat.extension.name } );
163
+ }
164
+ break;
165
+ default:
166
+ break;
167
+ }
168
+ }
169
+
170
+ // Signatures
171
+ if ( threat.signature ) {
172
+ if ( ! acc.signatures.find( ( { value } ) => value === threat.signature ) ) {
173
+ acc.signatures.push( { value: threat.signature, label: threat.signature } );
174
+ }
175
+ }
176
+
177
+ // Fields
178
+ const fields = Object.keys( threat );
179
+ fields.forEach( field => {
180
+ if (
181
+ ! acc.dataFields.includes( field ) &&
182
+ threat[ field ] !== null &&
183
+ threat[ field ] !== undefined
184
+ ) {
185
+ acc.dataFields.push( field );
186
+ }
187
+ } );
188
+
189
+ return acc;
190
+ },
191
+ {
192
+ themes: [],
193
+ plugins: [],
194
+ signatures: [],
195
+ dataFields: [],
196
+ }
197
+ );
198
+ }, [ data ] );
199
+
200
+ /**
201
+ * DataView fields - describes the visible items for each record in the dataset.
202
+ *
203
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#fields-object
204
+ */
205
+ const fields = useMemo( () => {
206
+ const result: Field< Threat >[] = [
207
+ {
208
+ id: THREAT_FIELD_TITLE,
209
+ label: __( 'Threat', 'jetpack-scan' ),
210
+ enableGlobalSearch: true,
211
+ enableHiding: false,
212
+ render: ( { item }: { item: Threat } ) => (
213
+ <div className={ styles.threat__title }>{ item.title }</div>
214
+ ),
215
+ },
216
+ {
217
+ id: THREAT_FIELD_DESCRIPTION,
218
+ label: __( 'Description', 'jetpack-scan' ),
219
+ enableGlobalSearch: true,
220
+ enableHiding: false,
221
+ render: ( { item }: { item: Threat } ) => (
222
+ <div className={ styles.threat__description }>{ item.description }</div>
223
+ ),
224
+ },
225
+ {
226
+ id: THREAT_FIELD_ICON,
227
+ label: __( 'Icon', 'jetpack-scan' ),
228
+ enableHiding: false,
229
+ getValue( { item }: { item: Threat } ) {
230
+ return getThreatType( item );
231
+ },
232
+ render( { item }: { item: Threat } ) {
233
+ return (
234
+ <div className={ styles.threat__media }>
235
+ <Icon icon={ THREAT_ICONS[ getThreatType( item ) ] } size={ 20 } />
236
+ </div>
237
+ );
238
+ },
239
+ },
240
+ {
241
+ id: THREAT_FIELD_STATUS,
242
+ label: __( 'Status', 'jetpack-scan' ),
243
+ elements: THREAT_STATUSES,
244
+ getValue( { item }: { item: Threat } ) {
245
+ if ( ! item.status ) {
246
+ return 'current';
247
+ }
248
+ return (
249
+ THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? item.status
250
+ );
251
+ },
252
+ render( { item }: { item: Threat } ) {
253
+ if ( item.status ) {
254
+ const status = THREAT_STATUSES.find( ( { value } ) => value === item.status );
255
+ if ( status ) {
256
+ return <Badge variant={ status?.variant }>{ status.label }</Badge>;
257
+ }
258
+ }
259
+ return <Badge variant="warning">{ __( 'Active', 'jetpack-scan' ) }</Badge>;
260
+ },
261
+ },
262
+ {
263
+ id: THREAT_FIELD_TYPE,
264
+ label: __( 'Type', 'jetpack-scan' ),
265
+ elements: THREAT_TYPES,
266
+ getValue( { item }: { item: Threat } ) {
267
+ switch ( getThreatType( item ) ) {
268
+ case 'core':
269
+ return __( 'WordPress', 'jetpack-scan' );
270
+ case 'plugins':
271
+ return __( 'Plugin', 'jetpack-scan' );
272
+ case 'themes':
273
+ return __( 'Theme', 'jetpack-scan' );
274
+ case 'file':
275
+ return __( 'File', 'jetpack-scan' );
276
+ default:
277
+ return __( 'Unknown', 'jetpack-scan' );
278
+ }
279
+ },
280
+ },
281
+ {
282
+ id: THREAT_FIELD_EXTENSION,
283
+ label: __( 'Extension', 'jetpack-scan' ),
284
+ enableGlobalSearch: true,
285
+ enableHiding: true,
286
+ getValue( { item }: { item: Threat } ) {
287
+ return item.extension ? item.extension.slug : '';
288
+ },
289
+ render( { item }: { item: Threat } ) {
290
+ return item.extension ? item.extension.name : '';
291
+ },
292
+ },
293
+ {
294
+ id: THREAT_FIELD_PLUGIN,
295
+ label: __( 'Plugin', 'jetpack-scan' ),
296
+ enableGlobalSearch: true,
297
+ enableHiding: false,
298
+ elements: plugins,
299
+ getValue( { item }: { item: Threat } ) {
300
+ return item.extension ? item.extension.slug : '';
301
+ },
302
+ },
303
+ {
304
+ id: THREAT_FIELD_THEME,
305
+ label: __( 'Theme', 'jetpack-scan' ),
306
+ enableGlobalSearch: true,
307
+ enableHiding: false,
308
+ elements: themes,
309
+ getValue( { item }: { item: Threat } ) {
310
+ return item.extension ? item.extension.slug : '';
311
+ },
312
+ },
313
+ ...( dataFields.includes( 'severity' )
314
+ ? [
315
+ {
316
+ id: THREAT_FIELD_SEVERITY,
317
+ label: __( 'Severity', 'jetpack-scan' ),
318
+ type: 'integer' as FieldType,
319
+ getValue( { item }: { item: Threat } ) {
320
+ return item.severity ?? 0;
321
+ },
322
+ render( { item }: { item: Threat } ) {
323
+ return <ThreatSeverityBadge severity={ item.severity } />;
324
+ },
325
+ },
326
+ ]
327
+ : [] ),
328
+ ...( dataFields.includes( 'signature' )
329
+ ? [
330
+ {
331
+ id: THREAT_FIELD_SIGNATURE,
332
+ label: __( 'Signature', 'jetpack-scan' ),
333
+ elements: signatures,
334
+ enableGlobalSearch: true,
335
+ getValue( { item }: { item: Threat } ) {
336
+ return item.signature || '';
337
+ },
338
+ },
339
+ ]
340
+ : [] ),
341
+ ...( dataFields.includes( 'firstDetected' )
342
+ ? [
343
+ {
344
+ id: THREAT_FIELD_FIRST_DETECTED,
345
+ label: __( 'First Detected', 'jetpack-scan' ),
346
+ type: 'datetime' as FieldType,
347
+ getValue( { item }: { item: Threat } ) {
348
+ return item.firstDetected ? new Date( item.firstDetected ) : null;
349
+ },
350
+ render( { item }: { item: Threat } ) {
351
+ return item.firstDetected ? (
352
+ <span className={ styles.threat__firstDetected }>
353
+ { dateI18n( 'F j Y', item.firstDetected, false ) }
354
+ </span>
355
+ ) : null;
356
+ },
357
+ },
358
+ ]
359
+ : [] ),
360
+ ...( dataFields.includes( 'fixedOn' )
361
+ ? [
362
+ {
363
+ id: THREAT_FIELD_FIXED_ON,
364
+ label: __( 'Fixed On', 'jetpack-scan' ),
365
+ type: 'datetime' as FieldType,
366
+ getValue( { item }: { item: Threat } ) {
367
+ return item.fixedOn ? new Date( item.fixedOn ) : null;
368
+ },
369
+ render( { item }: { item: Threat } ) {
370
+ return item.fixedOn ? (
371
+ <span className={ styles.threat__fixedOn }>
372
+ { dateI18n( 'F j Y', item.fixedOn, false ) }
373
+ </span>
374
+ ) : null;
375
+ },
376
+ },
377
+ ]
378
+ : [] ),
379
+ ...( dataFields.includes( 'fixable' )
380
+ ? [
381
+ {
382
+ id: THREAT_FIELD_AUTO_FIX,
383
+ label: __( 'Auto-fix', 'jetpack-scan' ),
384
+ enableHiding: false,
385
+ elements: [
386
+ {
387
+ value: 'yes',
388
+ label: __( 'Yes', 'jetpack-scan' ),
389
+ },
390
+ {
391
+ value: 'no',
392
+ label: __( 'No', 'jetpack-scan' ),
393
+ },
394
+ ],
395
+ getValue( { item }: { item: Threat } ) {
396
+ return item.fixable ? 'yes' : 'no';
397
+ },
398
+ render( { item }: { item: Threat } ) {
399
+ if ( ! item.fixable ) {
400
+ return null;
401
+ }
402
+
403
+ return <ThreatFixerButton threat={ item } onClick={ onFixThreats } />;
404
+ },
405
+ },
406
+ ]
407
+ : [] ),
408
+ ];
409
+
410
+ return result;
411
+ }, [ dataFields, plugins, themes, signatures, onFixThreats ] );
412
+
413
+ /**
414
+ * DataView actions - collection of operations that can be performed upon each record.
415
+ *
416
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#actions-object
417
+ */
418
+ const actions = useMemo( () => {
419
+ const result: Action< Threat >[] = [];
420
+
421
+ if ( dataFields.includes( 'fixable' ) ) {
422
+ result.push( {
423
+ id: THREAT_ACTION_FIX,
424
+ label: __( 'Auto-fix', 'jetpack-scan' ),
425
+ isPrimary: true,
426
+ callback: onFixThreats,
427
+ isEligible( item ) {
428
+ if ( ! onFixThreats ) {
429
+ return false;
430
+ }
431
+ if ( isThreatEligibleForFix ) {
432
+ return isThreatEligibleForFix( item );
433
+ }
434
+ return !! item.fixable;
435
+ },
436
+ } );
437
+ }
438
+
439
+ if ( dataFields.includes( 'status' ) ) {
440
+ result.push( {
441
+ id: THREAT_ACTION_IGNORE,
442
+ label: __( 'Ignore', 'jetpack-scan' ),
443
+ isPrimary: true,
444
+ isDestructive: true,
445
+ callback: onIgnoreThreats,
446
+ isEligible( item ) {
447
+ if ( ! onIgnoreThreats ) {
448
+ return false;
449
+ }
450
+ if ( isThreatEligibleForIgnore ) {
451
+ return isThreatEligibleForIgnore( item );
452
+ }
453
+ return item.status === 'current';
454
+ },
455
+ } );
456
+ }
457
+
458
+ if ( dataFields.includes( 'status' ) ) {
459
+ result.push( {
460
+ id: THREAT_ACTION_UNIGNORE,
461
+ label: __( 'Unignore', 'jetpack-scan' ),
462
+ isPrimary: true,
463
+ isDestructive: true,
464
+ callback: onUnignoreThreats,
465
+ isEligible( item ) {
466
+ if ( ! onUnignoreThreats ) {
467
+ return false;
468
+ }
469
+ if ( isThreatEligibleForUnignore ) {
470
+ return isThreatEligibleForUnignore( item );
471
+ }
472
+ return item.status === 'ignored';
473
+ },
474
+ } );
475
+ }
476
+
477
+ return result;
478
+ }, [
479
+ dataFields,
480
+ onFixThreats,
481
+ onIgnoreThreats,
482
+ onUnignoreThreats,
483
+ isThreatEligibleForFix,
484
+ isThreatEligibleForIgnore,
485
+ isThreatEligibleForUnignore,
486
+ ] );
487
+
488
+ /**
489
+ * Apply the view settings (i.e. filters, sorting, pagination) to the dataset.
490
+ *
491
+ * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts
492
+ */
493
+ const { data: processedData, paginationInfo } = useMemo( () => {
494
+ return filterSortAndPaginate( data, view, fields );
495
+ }, [ data, view, fields ] );
496
+
497
+ /**
498
+ * Callback function to update the view state.
499
+ *
500
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function
501
+ */
502
+ const onChangeView = useCallback( ( newView: View ) => {
503
+ setView( newView );
504
+ }, [] );
505
+
506
+ /**
507
+ * DataView getItemId function - returns the unique ID for each record in the dataset.
508
+ *
509
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#getitemid-function
510
+ */
511
+ const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] );
512
+
513
+ return (
514
+ <DataViews
515
+ actions={ actions }
516
+ data={ processedData }
517
+ defaultLayouts={ defaultLayouts }
518
+ fields={ fields }
519
+ getItemId={ getItemId }
520
+ onChangeSelection={ onChangeSelection }
521
+ onChangeView={ onChangeView }
522
+ paginationInfo={ paginationInfo }
523
+ view={ view }
524
+ header={
525
+ <ThreatsStatusToggleGroupControl
526
+ data={ data }
527
+ view={ view }
528
+ onChangeView={ onChangeView }
529
+ />
530
+ }
531
+ />
532
+ );
533
+ }
@@ -0,0 +1,40 @@
1
+ @import '@wordpress/dataviews/build-style/style.css';
2
+
3
+ .threat__title {
4
+ color: var( --jp-gray-80 );
5
+ font-weight: 510;
6
+ white-space: initial;
7
+ }
8
+
9
+ .threat__description {
10
+ color: var( --jp-gray-80 );
11
+ font-size: 12px;
12
+ white-space: initial;
13
+ }
14
+
15
+ .threat__fixedOn,
16
+ .threat__firstDetected {
17
+ white-space: nowrap;
18
+ }
19
+
20
+ .threat__fixedOn {
21
+ color: var( --jp-green-70 );
22
+ }
23
+
24
+ .threat__media {
25
+ width: 100%;
26
+ height: 100%;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ background-color: #EDFFEE;
31
+ border-color: #EDFFEE;
32
+
33
+ svg {
34
+ fill: var( --jp-black );
35
+ }
36
+ }
37
+
38
+ .toggle-group-control {
39
+ min-width: 300px;
40
+ }
@@ -0,0 +1,56 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import ThreatsDataViews from '../index.tsx';
3
+
4
+ const data = [
5
+ // Scan API Data
6
+ {
7
+ id: 185869885,
8
+ signature: 'EICAR_AV_Test',
9
+ title: 'Malicious code found in file: index.php',
10
+ description:
11
+ "This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.",
12
+ firstDetected: '2024-10-07T20:45:06.000Z',
13
+ fixedIn: null,
14
+ fixedOn: '2024-10-07T20:45:06.000Z',
15
+ fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' },
16
+ fixer: {
17
+ status: 'in_progress',
18
+ startedAt: '2024-10-07T20:45:06.000Z',
19
+ lastUpdated: '2024-10-07T20:45:06.000Z',
20
+ },
21
+ severity: 8,
22
+ status: 'current',
23
+ filename: '/var/www/html/wp-content/index.php',
24
+ context: {
25
+ 1: 'echo <<<HTML',
26
+ 2: 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
27
+ 3: 'HTML;',
28
+ marks: {},
29
+ },
30
+ },
31
+ // Protect Report Data
32
+ {
33
+ id: '1d0470df-4671-47ac-8d87-a165e8f7d502',
34
+ title: 'WooCommerce <= 3.2.3 - Authenticated PHP Object Injection',
35
+ description:
36
+ 'Versions 3.2.3 and earlier are affected by an issue where cached queries within shortcodes could lead to object injection. This is related to the recent WordPress 4.8.3 security release.This issue can only be exploited by users who can edit content and add shortcodes, but we still recommend all users running WooCommerce 3.x upgrade to 3.2 to mitigate this issue.',
37
+ source: 'https://wpscan.com/vulnerability/1d0470df-4671-47ac-8d87-a165e8f7d502',
38
+ extension: {
39
+ name: 'WooCommerce',
40
+ slug: 'woocommerce',
41
+ version: '3.2.3',
42
+ type: 'plugins',
43
+ },
44
+ fixedIn: '3.2.4',
45
+ },
46
+ ];
47
+
48
+ describe( 'ThreatsDataViews', () => {
49
+ it( 'renders threat data', () => {
50
+ render( <ThreatsDataViews data={ data } /> );
51
+ expect( screen.getByText( 'Malicious code found in file: index.php' ) ).toBeInTheDocument();
52
+ expect(
53
+ screen.getByText( 'WooCommerce <= 3.2.3 - Authenticated PHP Object Injection' )
54
+ ).toBeInTheDocument();
55
+ } );
56
+ } );