Skip to content

Commit 35a418f

Browse files
authored
Protect: Ship the Protect page design updates, bulk threat actions and on-by-default (#54055)
* Protect: record Tracks events on the Protect dashboard Page views for each tab, Scan now, the Get Scan links, the Monitor connect link and every security setting toggle. Events reuse the Protect plugin, RSM Scan and Jetpack Settings names where the action is the same. * Protect: address review feedback on dashboard Tracks events Record nothing unless PHP allowed Tracks, since another plugin's w.js would otherwise send the queue. Record setting events outside the save's try block, send the page-view section only for a known Settings card, and send link clicks with use_beacon so they survive the navigation. * Protect: record Tracks events for threats on the dashboard Opening a threat records the Protect plugin's <kind>_threat_open event. Fix, ignore and unignore, and the threat tables' search, layout, page and filter changes, record the Scan page's jetpack_scan_* events. * Protect: pass useRef an initial value, as React 19's types require * Protect: record the page view before the threat report's early return Hooks must run in the same order on every render. * Protect: test leaving the threat report, failed unignores and layout changes * Protect: record every dashboard event through one Tracks client Move this branch's events onto trunk's data/tracks.ts and drop data/analytics.ts. The consent flag now gates the upsell events too, the user is identified once, and blog_id comes from script data. PHP loads w.js and checks consent in one place, and prints only the flag instead of the connection state. * Protect: record Scan now from the shared startScan, so the header button counts too * Protect: Turn the Protect page on for connected sites Mark the protect-dashboard module Auto Activate: Yes, so it turns on when a site connects and, through activate_new_modules(), when a connected site updates from a release without it. Bump First Introduced to 16.4-a.4 so sites on 16.4-a.1 or a.3, which shipped the module off, get it too. Keep it out of the defaults while the standalone Jetpack Protect plugin is active, since the page would take over that plugin's own until JETPACK-3062. * Protect: Hide the Protect page while the Jetpack Protect plugin is active Make the module unavailable while that plugin is active, instead of only leaving it out of the defaults, so installing the plugin after the page is on no longer takes over the plugin's page. Jetpack keeps an unavailable module in its stored active list, so keep it a default for upgrades and connections too: the page then appears once the plugin is gone. The module file skips Dashboard::init() while hidden, since activation loads it. * Protect Dashboard: Show a threat's row as busy while Auto-fix runs The threats table now reads the per-threat action state the inspector already uses. While a fix, ignore or unignore runs, the row shows a spinner and status in place of its date, mutes its other cells, and offers only View. A deleting row keeps its actions so its open Delete modal stays mounted. * Protect Dashboard: Rename brute force protection to Login protection The Login protection card still said "Brute force protection" in its Settings subheading and "Enable brute force protection" on the Overview toggle. Use the Login protection name throughout, and drop the subheading that would repeat the card title. * Protect Dashboard: Show threat row actions without hovering DataViews' table layout hides primary row actions until the row is hovered or focused, so in a list of threats there was no way to tell which ones could be auto-fixed without hovering over each one. Keep them visible on every row in the Scan, report and History tables. * Protect Dashboard: Add a Jetpack changelog entry and tighten wording Auto-fix and Unignore only show on rows where they apply, so say the buttons show without hovering rather than on every row. * Protect Dashboard: Keep focus in a threat's row when its actions lock Starting Auto-fix, Ignore or Unignore removes the clicked button, and sometimes the row's actions menu, so keyboard focus fell to the page. Move it to the row's threat button instead. * My Jetpack: Show the firewall as on in the Protect card whenever it runs The card's Auto-Firewall row was only On with automatic rules, so a free site whose firewall is running read Inactive while the Protect page read On. Read the firewall module instead, and explain the automatic rules in the row's popover. * Protect Dashboard: Test that a row refocus leaves kept focus alone * My Jetpack: Decide the Protect card's firewall state in one place The tooltip hook now supplies copy per state instead of re-deriving it, and the tests cover the disconnected and plugin-inactive rows. * Protect: Test the hidden Protect page is stored but stays out of the way Cover the module file skipping Dashboard::init() while the Jetpack Protect plugin is active, activation storing the hidden module until that plugin is gone, and the re-added default still honoring the flag. Describe the plugin handling in the class docblock. * My Jetpack: Skip the firewall popover when the Protect data fails to load A failed request left the row on Inactive with copy blaming the host's firewall. Also cover a disconnected site on such a host. * Protect Dashboard: Keep the Scan card's height when threats appear The safe state's big shield made the card 116px taller than a one-threat list, so the card shrank when threats appeared. Shrink the shield and type, and give the safe and error rows the height of the stats row plus a one-threat list. * Protect Dashboard: Test that a busy row shows its status, not its date * Protect: Initialize the defaults before the try block for Phan * Protect Dashboard: Keep the Scan card's header while a scan runs The running scan replaced the whole card, dropping the header and footer, so the card jumped by a header's height when a scan started or finished. Show it in the same 227px row as the safe and error states instead. * Protect Dashboard: Test that a running scan keeps the Scan card's header * Jetpack: Combine the Protect page changelog entries into one * Protect Dashboard: Drop the Jetpack plugin changelog entry The Protect page's Jetpack plugin entries are being combined separately. * My Jetpack: Pin the firewall popover's Tracks props in tests Also cover automatic rules left on with the firewall off, and mark the card's data prop as optional since a failed request leaves it unset. * Protect Dashboard: Match the safe-state shield to the design's 39x48 * Protect dashboard: Rename the Scan card and show its last scan in a badge Match the Overview design: the card is titled "Scan results", with "Last scan 5 hours ago" in a gray draft badge beside the title. * Protect Dashboard: Match the page header to the design Show a "Jetpack / Protect" breadcrumb in place of the title and description, as Boost does, and make the header's scan button a minimal "Scan site" button. The threat report's trail starts at Jetpack too. Without My Jetpack the dashboard keeps its plain title. * Protect Dashboard: Name the page region and test the header wiring AdminPage gains an ariaLabel prop, since admin-ui's Page only names its region from a string title and the Protect header now shows a breadcrumb instead. The stage tests render the header so the breadcrumb, the plain-title fallback and the report trail are covered. * Protect dashboard: Check the last-scan badge keeps its exact time The badge now renders through Badge instead of Text, so assert the <time> element still carries its datetime and hover title. * Protect Dashboard: Assert the current breadcrumb in the trail tests * Protect dashboard: Restructure the Firewall card to match Firewall v2 The Overview card now has a primary Run test button in its header, the requests blocked in the last 30 days with a View log button, and a "No requests blocked yet" empty state in place of the zeros. The IP list toggles live on the Settings tab only; the card keeps a single row of Edit block list, Edit allow list and Configure Firewall links, with each list's count as a badge. * Protect dashboard: Keep the Firewall card's test counts across tab switches The card unmounts on a tab switch, so it came back with page load's counts, and a site that had just logged its first block was told "No requests blocked yet" again. A test's refreshed counts are now kept per page-load state. The empty state's focus handoff now only fires when this test's refresh leaves the empty state, and only when focus was lost with the button, so it no longer pulls focus back from wherever the user moved it. * Protect dashboard: Show a test's counts on a Firewall card that came back mid-test The refreshed counts were read once on mount, so returning to the Overview while a test was still running showed page load's counts. The card now subscribes to them. Also restores a test for the upsell shown once the Settings tab turns automatic rules off. * Protect dashboard: Use createStore for the Firewall card's counts The scan and history stores already use the shared helper, so the card no longer needs its own listener set. * Remove the Firewall IP list link from the ESLint exclude list * Protect Dashboard: Match the Scan card's safe state to the design (#54074) * Protect Dashboard: Match the Scan card's safe state to the design Title and one line of what was checked, with no scan button in the card; scans start from the page header. Scan sites read "No threats found", free sites "No vulnerabilities found" with the check named as one for known vulnerabilities. * Protect Dashboard: Address review on the Scan card's safe state Drop "in the latest scan", since the plugin and theme counts are read when the page loads, not from the scan. Test that the header button still holds a free site to one check a day now that the card no longer says when the next one runs, and fix the card's docblock. * Protect Dashboard: Test that Scan sites skip the once-a-day limit The card's old rows were the only check that a recently scanned Scan site could start another scan; the header button is now the only way. * Protect Dashboard: Add the Review and fix threats toolbar to the Scan card's threat list (#54079) * Protect Dashboard: Add a toolbar to the Scan card's threat list Put the list under a Review and fix threats heading that collapses it, with search, severity and type filters, view options, and row selection with bulk Auto-fix and Ignore on Scan sites. Each threat now takes one line, with its severity beside it. * Protect Dashboard: Match the threat list to the design on a real site Make the threat column DataViews' title field, so each checkbox is named for its threat, and keep only the kind bold. Size the heading like body text, hide the idle bulk footer, drop the raw severity filter in favor of levels, and say so when a whole batch fails. Pin the safe state to the new one-threat height. * Protect Dashboard: Harden bulk threat actions from review Keep checked rows when one row is acted on, and drop threats that leave the list from the selection. Report fixes still running apart from failed ones, survive a few missed status checks, and run at most five requests at once. Arrow keys no longer open threats in a collapsed list. * Protect Dashboard: Throttle bulk requests, not whole fixes Mark every threat in a batch busy before any request goes out, so a queued one can't be acted on twice, and cap in-flight requests rather than fixes, so one slow fix doesn't hold up the rest. Losing track of a started fix now reports it as still running; a client error fails it. * Protect Dashboard: Bound fixes by time and keep the batch path private A rate limit or timeout no longer ends a fix as failed, and a fix gives up after about five minutes however long its checks waited in the bulk queue. Only bulk actions can skip the busy check, and the throttle test now covers fixes, whose polls must not hold a request slot. * Protect Dashboard: Name the fix timeout for what it is * Protect Dashboard: Use the renamed Scan site button in Scan now tests * Protect: skip DataViews' page clamp in table Tracks events DataViews moves back to the last page itself when a table shrinks past it, which recorded jetpack_scan_page_change and view_change nobody clicked. Also test the fix that never finishes, which records jetpack_scan_fix_threat_failed. * Jetpack: Fold the merged Protect page entries into the combined entry * Protect Dashboard: Expect the renamed Scan results heading in a test * Protect Dashboard: Format the merged threat row actions * Protect: cover History's page clamp, pin the fix timeout test Run the page-clamp test against both threat tables, and check the timeout test ends on the polling cap, not an early error. * Protect Dashboard: Align the Tracks tests with the bundled Scan changes
1 parent 35897bf commit 35a418f

94 files changed

Lines changed: 3288 additions & 893 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
AdminPage: Add an ariaLabel prop that names the page region when the header shows breadcrumbs instead of a title.

‎projects/js-packages/components/components/admin-page/index.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const AdminPage: FC< AdminPageProps > = ( {
3636
actions,
3737
globalControls,
3838
breadcrumbs,
39+
ariaLabel,
3940
tabs,
4041
showBottomBorder = true,
4142
unwrapped = false,
@@ -85,6 +86,7 @@ const AdminPage: FC< AdminPageProps > = ( {
8586
visual={ logo || <JetpackLogo showText={ false } height={ 20 } /> }
8687
breadcrumbs={ breadcrumbs }
8788
title={ title }
89+
ariaLabel={ ariaLabel }
8890
subTitle={ subTitle }
8991
actions={
9092
globalControls ? (

‎projects/js-packages/components/components/admin-page/test/component.tsx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ describe( 'AdminPage', () => {
3636
expect( screen.queryAllByRole( 'separator' ) ).toHaveLength( separators );
3737
} );
3838

39+
it( 'names the page region from the aria label when breadcrumbs replace the title', () => {
40+
render(
41+
<AdminPage breadcrumbs={ <nav /> } ariaLabel="Protect" showFooter={ false }>
42+
Content
43+
</AdminPage>
44+
);
45+
46+
expect( screen.getByRole( 'region', { name: 'Protect' } ) ).toBeInTheDocument();
47+
} );
48+
3949
it( 'leaves the shared REST client alone when no API root or nonce is passed', () => {
4050
const setApiRoot = jest.spyOn( restApi, 'setApiRoot' );
4151
const setApiNonce = jest.spyOn( restApi, 'setApiNonce' );

‎projects/js-packages/components/components/admin-page/types.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ export type AdminPageProps = {
5151
*/
5252
breadcrumbs?: ReactNode;
5353

54+
/**
55+
* Accessible name of the page region. Defaults to `title`, so pass it when the header shows `breadcrumbs` instead.
56+
*/
57+
ariaLabel?: string;
58+
5459
/**
5560
* Tab navigation displayed below the title/tagline in the unified header.
5661
*/

‎projects/packages/my-jetpack/_inc/components/info-popover/index.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { FC, ReactNode } from 'react';
99
import './style.scss';
1010

1111
export interface InfoPopoverProps {
12-
/** What the popover explains, e.g. "Auto-Firewall"; names the trigger for assistive tech. */
12+
/** What the popover explains, e.g. "Firewall"; names the trigger for assistive tech. */
1313
label: string;
1414
title: ReactNode;
1515
text: ReactNode;
@@ -61,7 +61,7 @@ export const InfoPopover: FC< InfoPopoverProps > = ( {
6161
aria-label={
6262
triggerLabel ??
6363
sprintf(
64-
/* translators: %s is the name of the feature or stat the popover explains, e.g. "Auto-Firewall". */
64+
/* translators: %s is the name of the feature or stat the popover explains, e.g. "Firewall". */
6565
__( 'More about %s', 'jetpack-my-jetpack' ),
6666
label
6767
)

‎projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx‎

Lines changed: 0 additions & 114 deletions
This file was deleted.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { __ } from '@wordpress/i18n';
2+
import {
3+
protectCardShieldInactive as ShieldInactive,
4+
protectCardShieldOff as ShieldOff,
5+
protectCardShieldSuccess as ShieldSuccess,
6+
} from '../../../assets/inline-svgs';
7+
import useProduct from '../../../data/products/use-product';
8+
import useMyJetpackConnection from '../../../hooks/use-my-jetpack-connection';
9+
import { ProtectInfoPopover } from './info-popover';
10+
import { useProtectTooltipCopy } from './use-protect-tooltip-copy';
11+
import type { FirewallState } from './use-protect-tooltip-copy';
12+
import type { FC } from 'react';
13+
14+
interface FirewallStatusProps {
15+
// Undefined when the Protect data request fails.
16+
data?: ProtectData;
17+
}
18+
19+
export const FirewallStatus: FC< FirewallStatusProps > = ( { data } ) => {
20+
const slug = 'protect';
21+
const { detail } = useProduct( slug );
22+
const { isPluginActive = false, hasPaidPlanForProduct = false } = detail || {};
23+
const { isSiteConnected } = useMyJetpackConnection();
24+
const { firewallTooltips } = useProtectTooltipCopy( data );
25+
const { waf_enabled: isWafEnabled, waf_supported: wafSupported } = data?.wafConfig || {};
26+
const isRunning = isPluginActive && isSiteConnected;
27+
28+
// The firewall module decides On, as on the Protect page; automatic rules only add to it.
29+
let status: FirewallState = 'off';
30+
if ( isRunning && ! wafSupported ) {
31+
status = 'inactive';
32+
} else if ( isRunning && isWafEnabled ) {
33+
status = 'active';
34+
}
35+
36+
const display: Record< FirewallState, { text: string; alt: string; icon: string } > = {
37+
active: {
38+
text: __( 'On', 'jetpack-my-jetpack' ),
39+
alt: __( 'Shield icon - Firewall Status: On', 'jetpack-my-jetpack' ),
40+
icon: ShieldSuccess,
41+
},
42+
inactive: {
43+
text: __( 'Inactive', 'jetpack-my-jetpack' ),
44+
alt: __( 'Shield icon - Firewall Status: Inactive', 'jetpack-my-jetpack' ),
45+
icon: ShieldInactive,
46+
},
47+
off: {
48+
text: __( 'Off', 'jetpack-my-jetpack' ),
49+
alt: __( 'Shield icon - Firewall Status: Off', 'jetpack-my-jetpack' ),
50+
icon: ShieldOff,
51+
},
52+
};
53+
const { text, alt, icon } = display[ status ];
54+
// Disconnected, or the firewall's data failed to load: there is no state to explain.
55+
const tooltip = isRunning && data?.wafConfig ? firewallTooltips[ status ] : null;
56+
57+
return (
58+
<>
59+
<div className="value-section__heading">
60+
{ __( 'Firewall', 'jetpack-my-jetpack' ) }
61+
{ tooltip && (
62+
<ProtectInfoPopover
63+
label={ __( 'Firewall', 'jetpack-my-jetpack' ) }
64+
title={ tooltip.title }
65+
text={ tooltip.text }
66+
tracksEventProps={ {
67+
// Kept from the old Auto-Firewall row so existing Tracks reports carry on.
68+
location: 'auto-firewall',
69+
status,
70+
has_paid_plan: hasPaidPlanForProduct,
71+
} }
72+
/>
73+
) }
74+
</div>
75+
<div className="value-section__data">
76+
<div>
77+
<img className="value-section__status-icon" src={ icon } alt={ alt } />
78+
</div>
79+
<div className="value-section__status-text">{ text }</div>
80+
</div>
81+
</>
82+
);
83+
};

‎projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { QUERY_GET_PROTECT_DATA_KEY, REST_API_GET_PROTECT_DATA } from '../../../
22
import useProduct from '../../../data/products/use-product';
33
import useSimpleQuery from '../../../data/use-simple-query';
44
import LoadingBlock from '../../loading-block';
5-
import { AutoFirewallStatus } from './auto-firewall-status';
5+
import { FirewallStatus } from './firewall-status';
66
import { ProtectInfoPopover } from './info-popover';
77
import { LoginsBlockedStatus } from './logins-blocked-status';
88
import { ScanAndThreatStatus } from './scan-threats-status';
@@ -55,11 +55,11 @@ const ProtectValueSection = () => {
5555
<ScanAndThreatStatus data={ protectData } />
5656
) }
5757
</div>
58-
<div className="value-section__auto-firewall">
58+
<div className="value-section__firewall">
5959
{ isLoading ? (
6060
<LoadingBlock width="100%" height="20px" />
6161
) : (
62-
<AutoFirewallStatus data={ protectData } />
62+
<FirewallStatus data={ protectData } />
6363
) }
6464
</div>
6565
<div className="value-section__logins-blocked">

0 commit comments

Comments
 (0)