@automattic/jetpack-scan 1.3.0 → 1.4.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.
- package/CHANGELOG.md +21 -0
- package/package.json +22 -25
- package/src/components/threat-severity-badge/index.tsx +8 -4
- package/src/components/threats-data-views/constants.ts +6 -6
- package/src/components/threats-data-views/index.tsx +243 -84
- package/src/components/threats-data-views/styles.module.scss +23 -0
- package/build/components/index.d.ts +0 -4
- package/build/components/index.js +0 -4
- package/build/components/threat-fixer-button/index.d.ts +0 -16
- package/build/components/threat-fixer-button/index.js +0 -56
- package/build/components/threat-modal/fixer-state-notice.d.ts +0 -19
- package/build/components/threat-modal/fixer-state-notice.js +0 -44
- package/build/components/threat-modal/index.d.ts +0 -52
- package/build/components/threat-modal/index.js +0 -49
- package/build/components/threat-modal/threat-actions.d.ts +0 -7
- package/build/components/threat-modal/threat-actions.js +0 -43
- package/build/components/threat-modal/threat-fix-confirmation.d.ts +0 -7
- package/build/components/threat-modal/threat-fix-confirmation.js +0 -21
- package/build/components/threat-modal/threat-fix-details.d.ts +0 -7
- package/build/components/threat-modal/threat-fix-details.js +0 -41
- package/build/components/threat-modal/threat-notice.d.ts +0 -16
- package/build/components/threat-modal/threat-notice.js +0 -26
- package/build/components/threat-modal/threat-summary.d.ts +0 -7
- package/build/components/threat-modal/threat-summary.js +0 -16
- package/build/components/threat-modal/threat-technical-details.d.ts +0 -7
- package/build/components/threat-modal/threat-technical-details.js +0 -31
- package/build/components/threat-severity-badge/index.d.ts +0 -4
- package/build/components/threat-severity-badge/index.js +0 -13
- package/build/components/threat-severity-badge/test/index.test.d.ts +0 -1
- package/build/components/threat-severity-badge/test/index.test.js +0 -9
- package/build/components/threats-data-views/constants.d.ts +0 -42
- package/build/components/threats-data-views/constants.js +0 -37
- package/build/components/threats-data-views/index.d.ts +0 -29
- package/build/components/threats-data-views/index.js +0 -410
- package/build/components/threats-data-views/test/index.test.d.ts +0 -1
- package/build/components/threats-data-views/test/index.test.js +0 -51
- package/build/components/threats-data-views/threats-status-toggle-group-control.d.ts +0 -15
- package/build/components/threats-data-views/threats-status-toggle-group-control.js +0 -97
- package/build/constants/index.d.ts +0 -3
- package/build/constants/index.js +0 -3
- package/build/index.d.ts +0 -4
- package/build/index.js +0 -4
- package/build/index.test.d.ts +0 -1
- package/build/index.test.js +0 -4
- package/build/types/fixers.d.ts +0 -34
- package/build/types/fixers.js +0 -1
- package/build/types/index.d.ts +0 -3
- package/build/types/index.js +0 -3
- package/build/types/status.d.ts +0 -42
- package/build/types/status.js +0 -1
- package/build/types/threats.d.ts +0 -50
- package/build/types/threats.js +0 -1
- package/build/utils/index.d.ts +0 -15
- package/build/utils/index.js +0 -125
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0] - 2026-05-11
|
|
9
|
+
### Added
|
|
10
|
+
- ThreatsDataViews: Add an optional `empty` prop that's forwarded to the underlying `DataViews` so consumers can render their own empty-state node instead of DataViews' built-in "no items" body. [#48458]
|
|
11
|
+
- ThreatsDataViews: Accept `RenderFixModal`, `RenderIgnoreModal`, and `RenderUnignoreModal` props so consumers can route row actions through DataViews-managed confirmation modals. [#48458]
|
|
12
|
+
- ThreatsDataViews: Add an optional `onTrackEvent` callback for DataViews-canonical view transition events. [#48458]
|
|
13
|
+
- ThreatsDataViews: Add an optional `persistKey` prop to persist filters, sort, search, pagination, and layout in local storage. [#48458]
|
|
14
|
+
- ThreatsDataViews: Add an optional `RenderViewModal` prop for always-available threat detail views. [#48458]
|
|
15
|
+
- ThreatsDataViews: Add an optional `showStatusFilter` prop so consumers that already filter by status can hide the in-table active/history toggle. [#48458]
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Badge: Migrated usages to @wordpress/ui Badge. [#48156]
|
|
19
|
+
- Internal: No longer require automattic/jetpack-changelogger as a per-project dev dependency. [#48225]
|
|
20
|
+
- Scan Package: Switch the package to source exports so consumers resolve `@automattic/jetpack-scan` directly to `./src/index.ts`. [#48458]
|
|
21
|
+
- Update package dependencies. [#47907]
|
|
22
|
+
- Update package dependencies. [#48106]
|
|
23
|
+
- Update package dependencies. [#48126]
|
|
24
|
+
- Update package dependencies. [#48141]
|
|
25
|
+
- Update package dependencies. [#48346]
|
|
26
|
+
- ThreatsDataViews: Anchor the DataViews empty body to `calc(100vh - 320px)` so consumers get a full-height empty state without wiring a custom flex chain. [#48458]
|
|
27
|
+
|
|
8
28
|
## [1.3.0] - 2026-04-11
|
|
9
29
|
### Changed
|
|
10
30
|
- Set `.repository.url` in `package.json` to the mirror repo rather than the monorepo. Required for enabling provenance. [#47149]
|
|
@@ -175,6 +195,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
175
195
|
### Removed
|
|
176
196
|
- Updated dependencies. [#39754]
|
|
177
197
|
|
|
198
|
+
[1.4.0]: https://github.com/Automattic/jetpack-scan/compare/v1.3.0...v1.4.0
|
|
178
199
|
[1.3.0]: https://github.com/Automattic/jetpack-scan/compare/v1.2.2...v1.3.0
|
|
179
200
|
[1.2.2]: https://github.com/Automattic/jetpack-scan/compare/v1.2.1...v1.2.2
|
|
180
201
|
[1.2.1]: https://github.com/Automattic/jetpack-scan/compare/v1.2.0...v1.2.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-scan",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A JS client for consuming Jetpack Scan services",
|
|
6
6
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/scan/#readme",
|
|
@@ -13,41 +13,38 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "GPL-2.0-or-later",
|
|
15
15
|
"author": "Automattic",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"*.css",
|
|
18
|
+
"*.scss"
|
|
19
|
+
],
|
|
16
20
|
"type": "module",
|
|
17
21
|
"exports": {
|
|
18
|
-
".":
|
|
19
|
-
"types": "./build/index.d.ts",
|
|
20
|
-
"default": "./build/index.js"
|
|
21
|
-
}
|
|
22
|
+
".": "./src/index.ts"
|
|
22
23
|
},
|
|
23
|
-
"main": "./build/index.js",
|
|
24
|
-
"types": "./build/index.d.ts",
|
|
25
24
|
"scripts": {
|
|
26
|
-
"build": "pnpm run clean && pnpm run compile-ts",
|
|
27
|
-
"clean": "rm -rf build/",
|
|
28
|
-
"compile-ts": "tsgo --pretty",
|
|
29
25
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
30
26
|
"test-coverage": "pnpm run test --coverage",
|
|
31
27
|
"typecheck": "tsgo --noEmit"
|
|
32
28
|
},
|
|
33
29
|
"dependencies": {
|
|
34
|
-
"@automattic/jetpack-api": "^1.0.
|
|
35
|
-
"@automattic/jetpack-base-styles": "^1.
|
|
36
|
-
"@automattic/jetpack-components": "^1.
|
|
37
|
-
"@wordpress/api-fetch": "7.
|
|
38
|
-
"@wordpress/components": "32.
|
|
39
|
-
"@wordpress/dataviews": "
|
|
40
|
-
"@wordpress/date": "5.
|
|
41
|
-
"@wordpress/element": "6.
|
|
42
|
-
"@wordpress/i18n": "6.
|
|
43
|
-
"@wordpress/icons": "12.
|
|
44
|
-
"@wordpress/theme": "0.
|
|
45
|
-
"@wordpress/
|
|
30
|
+
"@automattic/jetpack-api": "^1.0.24",
|
|
31
|
+
"@automattic/jetpack-base-styles": "^1.2.1",
|
|
32
|
+
"@automattic/jetpack-components": "^1.11.0",
|
|
33
|
+
"@wordpress/api-fetch": "7.44.0",
|
|
34
|
+
"@wordpress/components": "32.6.0",
|
|
35
|
+
"@wordpress/dataviews": "14.1.0",
|
|
36
|
+
"@wordpress/date": "5.44.0",
|
|
37
|
+
"@wordpress/element": "6.44.0",
|
|
38
|
+
"@wordpress/i18n": "6.17.0",
|
|
39
|
+
"@wordpress/icons": "12.2.0",
|
|
40
|
+
"@wordpress/theme": "0.11.0",
|
|
41
|
+
"@wordpress/ui": "0.11.0",
|
|
42
|
+
"@wordpress/url": "4.44.0",
|
|
46
43
|
"debug": "4.4.3"
|
|
47
44
|
},
|
|
48
45
|
"devDependencies": {
|
|
49
|
-
"@storybook/addon-docs": "10.3.
|
|
50
|
-
"@storybook/react": "10.3.
|
|
46
|
+
"@storybook/addon-docs": "10.3.5",
|
|
47
|
+
"@storybook/react": "10.3.5",
|
|
51
48
|
"@testing-library/dom": "10.4.1",
|
|
52
49
|
"@testing-library/react": "16.3.2",
|
|
53
50
|
"@types/jest": "30.0.0",
|
|
@@ -56,7 +53,7 @@
|
|
|
56
53
|
"jest": "30.3.0",
|
|
57
54
|
"react": "^18.2.0",
|
|
58
55
|
"react-dom": "^18.2.0",
|
|
59
|
-
"storybook": "10.3.
|
|
56
|
+
"storybook": "10.3.5",
|
|
60
57
|
"typescript": "5.9.3"
|
|
61
58
|
},
|
|
62
59
|
"peerDependencies": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Badge } from '@automattic/jetpack-components';
|
|
2
1
|
import { _x } from '@wordpress/i18n';
|
|
2
|
+
import { Badge } from '@wordpress/ui';
|
|
3
3
|
|
|
4
4
|
const ThreatSeverityBadge = ( { severity } ) => {
|
|
5
5
|
if ( severity >= 5 ) {
|
|
6
6
|
return (
|
|
7
|
-
<Badge
|
|
7
|
+
<Badge intent="high">
|
|
8
8
|
{ _x( 'Critical', 'Severity label for issues rated 5 or higher.', 'jetpack-scan' ) }
|
|
9
9
|
</Badge>
|
|
10
10
|
);
|
|
@@ -12,13 +12,17 @@ const ThreatSeverityBadge = ( { severity } ) => {
|
|
|
12
12
|
|
|
13
13
|
if ( severity >= 3 && severity < 5 ) {
|
|
14
14
|
return (
|
|
15
|
-
<Badge
|
|
15
|
+
<Badge intent="medium">
|
|
16
16
|
{ _x( 'High', 'Severity label for issues rated between 3 and 5.', 'jetpack-scan' ) }
|
|
17
17
|
</Badge>
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
return
|
|
21
|
+
return (
|
|
22
|
+
<Badge intent="draft">
|
|
23
|
+
{ _x( 'Low', 'Severity label for issues rated below 3.', 'jetpack-scan' ) }
|
|
24
|
+
</Badge>
|
|
25
|
+
);
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
export default ThreatSeverityBadge;
|
|
@@ -7,12 +7,11 @@ import {
|
|
|
7
7
|
wordpress as coreIcon,
|
|
8
8
|
} from '@wordpress/icons';
|
|
9
9
|
|
|
10
|
-
export const THREAT_STATUSES: { value: string; label: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
];
|
|
10
|
+
export const THREAT_STATUSES: { value: string; label: string; intent?: 'medium' | 'stable' }[] = [
|
|
11
|
+
{ value: 'current', label: __( 'Active', 'jetpack-scan' ), intent: 'medium' },
|
|
12
|
+
{ value: 'fixed', label: __( 'Fixed', 'jetpack-scan' ), intent: 'stable' },
|
|
13
|
+
{ value: 'ignored', label: __( 'Ignored', 'jetpack-scan' ) },
|
|
14
|
+
];
|
|
16
15
|
|
|
17
16
|
export const THREAT_TYPES = [
|
|
18
17
|
{ value: 'plugins', label: __( 'Plugin', 'jetpack-scan' ) },
|
|
@@ -47,3 +46,4 @@ export const THREAT_FIELD_AUTO_FIX = 'auto-fix';
|
|
|
47
46
|
export const THREAT_ACTION_FIX = 'fix';
|
|
48
47
|
export const THREAT_ACTION_IGNORE = 'ignore';
|
|
49
48
|
export const THREAT_ACTION_UNIGNORE = 'unignore';
|
|
49
|
+
export const THREAT_ACTION_VIEW = 'view';
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { Badge } from '@automattic/jetpack-components';
|
|
2
1
|
import {
|
|
3
2
|
type Action,
|
|
4
3
|
type ActionButton,
|
|
5
4
|
type Field,
|
|
6
5
|
type FieldTypeName,
|
|
7
6
|
type Filter,
|
|
7
|
+
type RenderModalProps,
|
|
8
8
|
type SortDirection,
|
|
9
|
-
type SupportedLayouts,
|
|
10
9
|
type View,
|
|
11
10
|
DataViews,
|
|
12
11
|
filterSortAndPaginate,
|
|
@@ -14,13 +13,23 @@ import {
|
|
|
14
13
|
import { dateI18n } from '@wordpress/date';
|
|
15
14
|
import { __ } from '@wordpress/i18n';
|
|
16
15
|
import { Icon } from '@wordpress/icons';
|
|
17
|
-
import {
|
|
16
|
+
import { Badge } from '@wordpress/ui';
|
|
17
|
+
import {
|
|
18
|
+
useCallback,
|
|
19
|
+
useEffect,
|
|
20
|
+
useMemo,
|
|
21
|
+
useRef,
|
|
22
|
+
useState,
|
|
23
|
+
type ReactElement,
|
|
24
|
+
type ReactNode,
|
|
25
|
+
} from 'react';
|
|
18
26
|
import { ThreatSeverityBadge, getThreatType, type Threat } from '@automattic/jetpack-scan';
|
|
19
27
|
import ThreatFixerButton from '../threat-fixer-button/index.tsx';
|
|
20
28
|
import {
|
|
21
29
|
THREAT_ACTION_FIX,
|
|
22
30
|
THREAT_ACTION_IGNORE,
|
|
23
31
|
THREAT_ACTION_UNIGNORE,
|
|
32
|
+
THREAT_ACTION_VIEW,
|
|
24
33
|
THREAT_FIELD_AUTO_FIX,
|
|
25
34
|
THREAT_FIELD_DESCRIPTION,
|
|
26
35
|
THREAT_FIELD_EXTENSION,
|
|
@@ -44,16 +53,32 @@ import ThreatsStatusToggleGroupControl from './threats-status-toggle-group-contr
|
|
|
44
53
|
/**
|
|
45
54
|
* DataViews component for displaying security threats.
|
|
46
55
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
56
|
+
* Each row action (Auto-fix / Ignore / Unignore) supports two wiring shapes:
|
|
57
|
+
* pass a callback (`onFixThreats` etc.) for the existing fire-and-forget
|
|
58
|
+
* behaviour, or pass a React component via the matching `Render*Modal` prop
|
|
59
|
+
* to open a confirmation modal — DataViews renders it inline when the
|
|
60
|
+
* action is invoked, and consumers receive `{ items, closeModal }` from
|
|
61
|
+
* `RenderModalProps< Threat >`. Render-modal props take precedence when
|
|
62
|
+
* both are supplied for the same action.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} props - Component props.
|
|
65
|
+
* @param {Array} props.data - Threats data.
|
|
66
|
+
* @param {Array} props.filters - Initial DataView filters.
|
|
67
|
+
* @param {Function} props.onChangeSelection - Callback function run when an item is selected.
|
|
68
|
+
* @param {Function} props.onFixThreats - Threat fix action callback (used when no `RenderFixModal`).
|
|
69
|
+
* @param {Function} props.onIgnoreThreats - Threat ignore action callback (used when no `RenderIgnoreModal`).
|
|
70
|
+
* @param {Function} props.onUnignoreThreats - Threat unignore action callback (used when no `RenderUnignoreModal`).
|
|
71
|
+
* @param {Function} props.RenderFixModal - Optional component rendered as the fix-action modal.
|
|
72
|
+
* @param {Function} props.RenderIgnoreModal - Optional component rendered as the ignore-action modal.
|
|
73
|
+
* @param {Function} props.RenderUnignoreModal - Optional component rendered as the unignore-action modal.
|
|
74
|
+
* @param {Function} props.RenderViewModal - Optional component rendered as the view-details modal. Unlike the fix / ignore / unignore actions, this one is always eligible for any row.
|
|
75
|
+
* @param {Function} props.isThreatEligibleForFix - Function to determine if a threat is eligible for fixing.
|
|
76
|
+
* @param {Function} props.isThreatEligibleForIgnore - Function to determine if a threat is eligible for ignoring.
|
|
77
|
+
* @param {Function} props.isThreatEligibleForUnignore - Function to determine if a threat is eligible for unignoring.
|
|
78
|
+
* @param {ReactNode} [props.empty] - Empty-state node forwarded to DataViews when `data` is empty. Defaults to DataViews' built-in "no items" body.
|
|
79
|
+
* @param {boolean} [props.showStatusFilter] - Whether to render the active/historic status toggle above the table. Defaults to `true`. Set to `false` when the consumer already filters the dataset by status outside the component (e.g. page-level tabs).
|
|
80
|
+
* @param {Function} [props.onTrackEvent] - Optional callback that receives DataViews-canonical event names (`view_change`, `filter_change`, `search`, `page_change`, `layout_changed`) on the matching view transitions. Consumers prefix and forward to their own analytics client.
|
|
81
|
+
* @param {string} [props.persistKey] - Optional `localStorage` key. When set, the component hydrates its initial view state from `localStorage[persistKey]` and writes back on every change. Use stable, namespaced keys (e.g. `jetpack-scan:active-threats:view`) so consumer panels don't collide. Quietly no-ops when `localStorage` is unavailable.
|
|
57
82
|
*
|
|
58
83
|
* @return {JSX.Element} The ThreatsDataViews component.
|
|
59
84
|
*/
|
|
@@ -67,6 +92,14 @@ export default function ThreatsDataViews( {
|
|
|
67
92
|
onFixThreats,
|
|
68
93
|
onIgnoreThreats,
|
|
69
94
|
onUnignoreThreats,
|
|
95
|
+
RenderFixModal,
|
|
96
|
+
RenderIgnoreModal,
|
|
97
|
+
RenderUnignoreModal,
|
|
98
|
+
RenderViewModal,
|
|
99
|
+
empty,
|
|
100
|
+
showStatusFilter = true,
|
|
101
|
+
onTrackEvent,
|
|
102
|
+
persistKey,
|
|
70
103
|
}: {
|
|
71
104
|
data: Threat[];
|
|
72
105
|
filters?: Filter[];
|
|
@@ -77,6 +110,14 @@ export default function ThreatsDataViews( {
|
|
|
77
110
|
onFixThreats?: ( threats: Threat[] ) => void;
|
|
78
111
|
onIgnoreThreats?: ActionButton< Threat >[ 'callback' ];
|
|
79
112
|
onUnignoreThreats?: ActionButton< Threat >[ 'callback' ];
|
|
113
|
+
RenderFixModal?: ( props: RenderModalProps< Threat > ) => ReactElement;
|
|
114
|
+
RenderIgnoreModal?: ( props: RenderModalProps< Threat > ) => ReactElement;
|
|
115
|
+
RenderUnignoreModal?: ( props: RenderModalProps< Threat > ) => ReactElement;
|
|
116
|
+
RenderViewModal?: ( props: RenderModalProps< Threat > ) => ReactElement;
|
|
117
|
+
empty?: ReactNode;
|
|
118
|
+
showStatusFilter?: boolean;
|
|
119
|
+
onTrackEvent?: ( event: string, properties?: Record< string, unknown > ) => void;
|
|
120
|
+
persistKey?: string;
|
|
80
121
|
} ): JSX.Element {
|
|
81
122
|
const baseView = {
|
|
82
123
|
sort: {
|
|
@@ -96,7 +137,7 @@ export default function ThreatsDataViews( {
|
|
|
96
137
|
*
|
|
97
138
|
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#defaultlayouts-record-string-view
|
|
98
139
|
*/
|
|
99
|
-
const defaultLayouts
|
|
140
|
+
const defaultLayouts = {
|
|
100
141
|
table: {
|
|
101
142
|
...baseView,
|
|
102
143
|
fields: [ THREAT_FIELD_SEVERITY, THREAT_FIELD_TYPE, THREAT_FIELD_AUTO_FIX ],
|
|
@@ -121,13 +162,40 @@ export default function ThreatsDataViews( {
|
|
|
121
162
|
/**
|
|
122
163
|
* DataView view object - configures how the dataset is visible to the user.
|
|
123
164
|
*
|
|
165
|
+
* When `persistKey` is supplied, the initial view hydrates from
|
|
166
|
+
* `localStorage[persistKey]` so reloads, tab changes, and drill-ins
|
|
167
|
+
* preserve the user's filters / sort / pagination / layout. Falls
|
|
168
|
+
* back to the default table view on parse failure or first load.
|
|
169
|
+
*
|
|
124
170
|
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#view-object
|
|
125
171
|
*/
|
|
126
|
-
const [ view, setView ] = useState< View >( {
|
|
127
|
-
type: 'table',
|
|
128
|
-
|
|
172
|
+
const [ view, setView ] = useState< View >( () => {
|
|
173
|
+
const fallback: View = { type: 'table', ...defaultLayouts.table };
|
|
174
|
+
if ( ! persistKey || typeof window === 'undefined' ) {
|
|
175
|
+
return fallback;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const stored = window.localStorage.getItem( persistKey );
|
|
179
|
+
if ( stored ) {
|
|
180
|
+
return JSON.parse( stored ) as View;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// localStorage may be disabled (privacy mode, full disk) — no-op.
|
|
184
|
+
}
|
|
185
|
+
return fallback;
|
|
129
186
|
} );
|
|
130
187
|
|
|
188
|
+
useEffect( () => {
|
|
189
|
+
if ( ! persistKey || typeof window === 'undefined' ) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
window.localStorage.setItem( persistKey, JSON.stringify( view ) );
|
|
194
|
+
} catch {
|
|
195
|
+
// localStorage may be disabled (privacy mode, full disk) — no-op.
|
|
196
|
+
}
|
|
197
|
+
}, [ persistKey, view ] );
|
|
198
|
+
|
|
131
199
|
/**
|
|
132
200
|
* Compute values from the provided threats data.
|
|
133
201
|
*
|
|
@@ -253,10 +321,10 @@ export default function ThreatsDataViews( {
|
|
|
253
321
|
if ( item.status ) {
|
|
254
322
|
const status = THREAT_STATUSES.find( ( { value } ) => value === item.status );
|
|
255
323
|
if ( status ) {
|
|
256
|
-
return <Badge
|
|
324
|
+
return <Badge intent={ status.intent }>{ status.label }</Badge>;
|
|
257
325
|
}
|
|
258
326
|
}
|
|
259
|
-
return <Badge
|
|
327
|
+
return <Badge intent="medium">{ __( 'Active', 'jetpack-scan' ) }</Badge>;
|
|
260
328
|
},
|
|
261
329
|
},
|
|
262
330
|
{
|
|
@@ -419,56 +487,109 @@ export default function ThreatsDataViews( {
|
|
|
419
487
|
const result: Action< Threat >[] = [];
|
|
420
488
|
|
|
421
489
|
if ( dataFields.includes( 'fixable' ) ) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
490
|
+
const isEligible = ( item: Threat ) => {
|
|
491
|
+
if ( ! RenderFixModal && ! onFixThreats ) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
if ( isThreatEligibleForFix ) {
|
|
495
|
+
return isThreatEligibleForFix( item );
|
|
496
|
+
}
|
|
497
|
+
return !! item.fixable;
|
|
498
|
+
};
|
|
499
|
+
if ( RenderFixModal ) {
|
|
500
|
+
result.push( {
|
|
501
|
+
id: THREAT_ACTION_FIX,
|
|
502
|
+
label: __( 'Auto-fix', 'jetpack-scan' ),
|
|
503
|
+
isPrimary: true,
|
|
504
|
+
modalHeader: __( 'Fix threat', 'jetpack-scan' ),
|
|
505
|
+
RenderModal: RenderFixModal,
|
|
506
|
+
isEligible,
|
|
507
|
+
} );
|
|
508
|
+
} else {
|
|
509
|
+
result.push( {
|
|
510
|
+
id: THREAT_ACTION_FIX,
|
|
511
|
+
label: __( 'Auto-fix', 'jetpack-scan' ),
|
|
512
|
+
isPrimary: true,
|
|
513
|
+
callback: onFixThreats,
|
|
514
|
+
isEligible,
|
|
515
|
+
} );
|
|
516
|
+
}
|
|
437
517
|
}
|
|
438
518
|
|
|
439
519
|
if ( dataFields.includes( 'status' ) ) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
520
|
+
const isEligible = ( item: Threat ) => {
|
|
521
|
+
if ( ! RenderIgnoreModal && ! onIgnoreThreats ) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
if ( isThreatEligibleForIgnore ) {
|
|
525
|
+
return isThreatEligibleForIgnore( item );
|
|
526
|
+
}
|
|
527
|
+
return item.status === 'current';
|
|
528
|
+
};
|
|
529
|
+
if ( RenderIgnoreModal ) {
|
|
530
|
+
result.push( {
|
|
531
|
+
id: THREAT_ACTION_IGNORE,
|
|
532
|
+
label: __( 'Ignore', 'jetpack-scan' ),
|
|
533
|
+
isPrimary: true,
|
|
534
|
+
modalHeader: __( 'Ignore threat', 'jetpack-scan' ),
|
|
535
|
+
RenderModal: RenderIgnoreModal,
|
|
536
|
+
isEligible,
|
|
537
|
+
} );
|
|
538
|
+
} else {
|
|
539
|
+
result.push( {
|
|
540
|
+
id: THREAT_ACTION_IGNORE,
|
|
541
|
+
label: __( 'Ignore', 'jetpack-scan' ),
|
|
542
|
+
isPrimary: true,
|
|
543
|
+
callback: onIgnoreThreats,
|
|
544
|
+
isEligible,
|
|
545
|
+
} );
|
|
546
|
+
}
|
|
455
547
|
}
|
|
456
548
|
|
|
457
549
|
if ( dataFields.includes( 'status' ) ) {
|
|
550
|
+
const isEligible = ( item: Threat ) => {
|
|
551
|
+
if ( ! RenderUnignoreModal && ! onUnignoreThreats ) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
if ( isThreatEligibleForUnignore ) {
|
|
555
|
+
return isThreatEligibleForUnignore( item );
|
|
556
|
+
}
|
|
557
|
+
return item.status === 'ignored';
|
|
558
|
+
};
|
|
559
|
+
if ( RenderUnignoreModal ) {
|
|
560
|
+
result.push( {
|
|
561
|
+
id: THREAT_ACTION_UNIGNORE,
|
|
562
|
+
label: __( 'Unignore', 'jetpack-scan' ),
|
|
563
|
+
isPrimary: true,
|
|
564
|
+
modalHeader: __( 'Unignore threat', 'jetpack-scan' ),
|
|
565
|
+
RenderModal: RenderUnignoreModal,
|
|
566
|
+
isEligible,
|
|
567
|
+
} );
|
|
568
|
+
} else {
|
|
569
|
+
result.push( {
|
|
570
|
+
id: THREAT_ACTION_UNIGNORE,
|
|
571
|
+
label: __( 'Unignore', 'jetpack-scan' ),
|
|
572
|
+
isPrimary: true,
|
|
573
|
+
callback: onUnignoreThreats,
|
|
574
|
+
isEligible,
|
|
575
|
+
} );
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// View details — always-eligible row action that opens the
|
|
580
|
+
// supplied `RenderViewModal`. Unlike fix / ignore / unignore, it
|
|
581
|
+
// is NOT gated by threat status or capability, so the user can
|
|
582
|
+
// always drill in for the full file context, fix description,
|
|
583
|
+
// and metadata.
|
|
584
|
+
if ( RenderViewModal ) {
|
|
458
585
|
result.push( {
|
|
459
|
-
id:
|
|
460
|
-
label: __( '
|
|
461
|
-
isPrimary:
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
if ( isThreatEligibleForUnignore ) {
|
|
468
|
-
return isThreatEligibleForUnignore( item );
|
|
469
|
-
}
|
|
470
|
-
return item.status === 'ignored';
|
|
471
|
-
},
|
|
586
|
+
id: THREAT_ACTION_VIEW,
|
|
587
|
+
label: __( 'View details', 'jetpack-scan' ),
|
|
588
|
+
isPrimary: false,
|
|
589
|
+
modalHeader: __( 'Threat details', 'jetpack-scan' ),
|
|
590
|
+
modalSize: 'large',
|
|
591
|
+
RenderModal: RenderViewModal,
|
|
592
|
+
isEligible: () => true,
|
|
472
593
|
} );
|
|
473
594
|
}
|
|
474
595
|
|
|
@@ -478,6 +599,10 @@ export default function ThreatsDataViews( {
|
|
|
478
599
|
onFixThreats,
|
|
479
600
|
onIgnoreThreats,
|
|
480
601
|
onUnignoreThreats,
|
|
602
|
+
RenderFixModal,
|
|
603
|
+
RenderIgnoreModal,
|
|
604
|
+
RenderUnignoreModal,
|
|
605
|
+
RenderViewModal,
|
|
481
606
|
isThreatEligibleForFix,
|
|
482
607
|
isThreatEligibleForIgnore,
|
|
483
608
|
isThreatEligibleForUnignore,
|
|
@@ -493,13 +618,42 @@ export default function ThreatsDataViews( {
|
|
|
493
618
|
}, [ data, view, fields ] );
|
|
494
619
|
|
|
495
620
|
/**
|
|
496
|
-
* Callback function to update the view state.
|
|
621
|
+
* Callback function to update the view state. When `onTrackEvent` is
|
|
622
|
+
* supplied, diff the previous view against the new one and fire the
|
|
623
|
+
* matching DataViews-canonical event names so consumer analytics can
|
|
624
|
+
* track which dimension actually changed (search vs filter vs page
|
|
625
|
+
* vs layout). The generic `view_change` event always fires last so
|
|
626
|
+
* consumers can choose between the granular events and an "anything
|
|
627
|
+
* changed" hook.
|
|
497
628
|
*
|
|
498
629
|
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-dataviews/#onchangeview-function
|
|
499
630
|
*/
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
631
|
+
const previousViewRef = useRef< View >( view );
|
|
632
|
+
const onChangeView = useCallback(
|
|
633
|
+
( newView: View ) => {
|
|
634
|
+
if ( onTrackEvent ) {
|
|
635
|
+
const previous = previousViewRef.current;
|
|
636
|
+
if ( previous.search !== newView.search ) {
|
|
637
|
+
onTrackEvent( 'search', { has_query: !! newView.search } );
|
|
638
|
+
}
|
|
639
|
+
if ( previous.type !== newView.type ) {
|
|
640
|
+
onTrackEvent( 'layout_changed', { layout: newView.type } );
|
|
641
|
+
}
|
|
642
|
+
if ( previous.page !== newView.page ) {
|
|
643
|
+
onTrackEvent( 'page_change', { page: newView.page } );
|
|
644
|
+
}
|
|
645
|
+
if (
|
|
646
|
+
JSON.stringify( previous.filters ?? [] ) !== JSON.stringify( newView.filters ?? [] )
|
|
647
|
+
) {
|
|
648
|
+
onTrackEvent( 'filter_change' );
|
|
649
|
+
}
|
|
650
|
+
onTrackEvent( 'view_change' );
|
|
651
|
+
}
|
|
652
|
+
previousViewRef.current = newView;
|
|
653
|
+
setView( newView );
|
|
654
|
+
},
|
|
655
|
+
[ onTrackEvent ]
|
|
656
|
+
);
|
|
503
657
|
|
|
504
658
|
/**
|
|
505
659
|
* DataView getItemId function - returns the unique ID for each record in the dataset.
|
|
@@ -509,23 +663,28 @@ export default function ThreatsDataViews( {
|
|
|
509
663
|
const getItemId = useCallback( ( item: Threat ) => item.id.toString(), [] );
|
|
510
664
|
|
|
511
665
|
return (
|
|
512
|
-
<
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
666
|
+
<div className={ styles[ 'threats-data-views' ] }>
|
|
667
|
+
<DataViews
|
|
668
|
+
actions={ actions }
|
|
669
|
+
data={ processedData }
|
|
670
|
+
defaultLayouts={ defaultLayouts }
|
|
671
|
+
fields={ fields }
|
|
672
|
+
getItemId={ getItemId }
|
|
673
|
+
onChangeSelection={ onChangeSelection }
|
|
674
|
+
onChangeView={ onChangeView }
|
|
675
|
+
paginationInfo={ paginationInfo }
|
|
676
|
+
view={ view }
|
|
677
|
+
empty={ empty }
|
|
678
|
+
header={
|
|
679
|
+
showStatusFilter ? (
|
|
680
|
+
<ThreatsStatusToggleGroupControl
|
|
681
|
+
data={ data }
|
|
682
|
+
view={ view }
|
|
683
|
+
onChangeView={ onChangeView }
|
|
684
|
+
/>
|
|
685
|
+
) : undefined
|
|
686
|
+
}
|
|
687
|
+
/>
|
|
688
|
+
</div>
|
|
530
689
|
);
|
|
531
690
|
}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
@import "@wordpress/theme/design-tokens.css";
|
|
2
2
|
@import "@wordpress/dataviews/build-style/style.css";
|
|
3
3
|
|
|
4
|
+
// Wrapper carries the flex chain forward to `<DataViews>` (whose root
|
|
5
|
+
// `.dataviews-wrapper` is `height: 100%`) and scopes the empty-state
|
|
6
|
+
// override below — `:global(.dataviews-no-results)` only matches inside
|
|
7
|
+
// this wrapper because the outer class is module-hashed.
|
|
8
|
+
.threats-data-views {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex: 1 1 auto;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
min-block-size: 0;
|
|
13
|
+
|
|
14
|
+
// DataViews ships `.dataviews-no-results` with `flex-grow: 1`, so it
|
|
15
|
+
// fills its parent — but only if that parent has a defined height.
|
|
16
|
+
// Anchor the empty body to the viewport (minus admin bar + page
|
|
17
|
+
// header + tab row + footer + a small breathing margin) so the empty
|
|
18
|
+
// state always reads as full-height and the surrounding page chain
|
|
19
|
+
// gets pushed to the bottom of the viewport even when DataViews is
|
|
20
|
+
// the only thing in the panel. Min-height (not height) so a populated
|
|
21
|
+
// table still scrolls naturally.
|
|
22
|
+
:global(.dataviews-no-results) {
|
|
23
|
+
min-block-size: calc(100vh - 320px);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
4
27
|
.threat__title {
|
|
5
28
|
color: var(--jp-gray-80);
|
|
6
29
|
font-weight: 510;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { default as ThreatFixerButton } from './threat-fixer-button/index.tsx';
|
|
2
|
-
export { default as ThreatModal } from './threat-modal/index.tsx';
|
|
3
|
-
export { default as ThreatSeverityBadge } from './threat-severity-badge/index.tsx';
|
|
4
|
-
export { default as ThreatsDataViews } from './threats-data-views/index.tsx';
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { default as ThreatFixerButton } from './threat-fixer-button/index.js';
|
|
2
|
-
export { default as ThreatModal } from './threat-modal/index.js';
|
|
3
|
-
export { default as ThreatSeverityBadge } from './threat-severity-badge/index.js';
|
|
4
|
-
export { default as ThreatsDataViews } from './threats-data-views/index.js';
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type Threat } from '@automattic/jetpack-scan';
|
|
2
|
-
/**
|
|
3
|
-
* Threat Fixer Button component.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} props - Component props.
|
|
6
|
-
* @param {object} props.threat - The threat.
|
|
7
|
-
* @param {Function} props.onClick - The onClick function.
|
|
8
|
-
* @param {string} props.className - The className.
|
|
9
|
-
*
|
|
10
|
-
* @return {JSX.Element} The component.
|
|
11
|
-
*/
|
|
12
|
-
export default function ThreatFixerButton({ threat, className, onClick }: {
|
|
13
|
-
threat: Threat;
|
|
14
|
-
onClick: (items: Threat[]) => void;
|
|
15
|
-
className?: string;
|
|
16
|
-
}): JSX.Element;
|