Skip to content

Protect Dashboard: Add the Scan history tab - #53196

Merged
enejb merged 18 commits into
trunkfrom
add/protect-dashboard-history
Oct 9, 2026
Merged

enejb merged 18 commits into
trunkfrom
add/protect-dashboard-history

Conversation

@enejb

@enejb enejb commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Fixes JETPACK-2931

Part of JETPACK-2880

Proposed changes

All of this is behind the jetpack-protect-dashboard flag and the protect-dashboard module.

History tab (sites with a Jetpack Scan plan)

  • New History tab with a "Scan history" card: Fixed (n) and Ignored (n) lists, laid out like the Scan list (severity, threat, "Fixed on" / "Detected on"). The column heading names the list ("Fixed threats" / "Ignored threats").
  • Choosing a fixed threat opens a read-only details sidebar with a Fixed badge and the fix date. Ignored threats open Scan's own details sidebar, so they can be unignored from there.
  • New GET jetpack/v4/protect-dashboard/history (manage_options); returns 403 no_scan_plan without a Scan plan. It shares one cached WordPress.com /scan/history fetch with the existing ignored-threats endpoint (Scan::get_history_threats()), so the History tab makes one request instead of two. The cache is cleared when a scan starts, a threat is ignored or unignored, or a fix completes.
  • Without a Scan plan there is no History tab, and ?tab=history falls back to Overview.

Scan card and threat lists

  • The Scan card's header has a subheading that says what kind of scanning the site gets ("Daily malware and vulnerability checks…" with Scan, "Daily vulnerability checks…" without). The "Active" and "Vulnerability checks only" badges are gone, and the icon is now the shield.
  • The Active/Ignored filter moved off the Overview card. Its footer has View scan history (opens History on Fixed) and, when there are ignored threats, View N ignored threats (opens History on Ignored).
  • Threat rows use DataViews row actions, like the Forms dashboard: View (and Auto-fix / Unignore where they apply) on hover, everything else in the ⋮ menu.
  • Delete unused plugins and themes: a threat in a plugin that isn't active, or a theme that is neither the active theme nor its parent, offers Delete plugin / Delete theme (row menu and details sidebar), for users who can delete plugins or themes. Plugins go to core's own "Are you sure?" delete screen. Core deletes themes without asking, so the dashboard confirms first. Not offered on multisite, where another site may still use the plugin or theme.
  • The threat details sidebar closes on Escape and has a 360px minimum width beside the page (782px and up).
  • Card headers keep one height with or without a button, so switching tabs doesn't shift the layout.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

On a connected site running this branch:

  1. wp companion feature-flag enable jetpack-protect-dashboard (or the jetpack_feature_flag_enabled_jetpack-protect-dashboard filter), then wp jetpack module activate protect-dashboard. If the module is not found, run wp eval 'Jetpack_Options::delete_option("available_modules");' first.
  2. Site without Scan: go to Jetpack > Protect. Only Overview and Settings show; admin.php?page=jetpack-protect&p=%2F%3Ftab%3Dhistory lands on Overview; GET /wp-json/jetpack/v4/protect-dashboard/history returns 403 no_scan_plan. The Scan card's subheading reads "Daily vulnerability checks for WordPress, plugins and themes."
  3. Site with Scan (or a Security bundle): a History tab appears. Fixed and Ignored lists show with counts; a fixed row's View opens a sidebar with a Fixed badge; an ignored row's View opens Scan's sidebar with Unignore it.
  4. Row actions: hover a threat on Overview: View appears, the ⋮ menu lists the rest. Press Escape with the sidebar open: it closes (with the ⋮ menu open, Escape closes only the menu).
  5. Ignore → History: ignore a threat on Overview. The footer shows View 1 ignored threat; it opens History on Ignored with that threat listed.
  6. Delete: install and deactivate a plugin with a known vulnerability (e.g. Contact Form 7 5.3.1) and run a scan. Its threat offers Delete plugin, which opens core's delete confirmation. For a theme, install an old vulnerable theme without activating it: Delete theme asks first, then deletes it. Neither option appears for an active plugin, the active theme or its parent, or on multisite.
  7. Fix → History: auto-fix a fixable threat, then open History: the threat is listed under Fixed (no reload needed).

Screenshots

Captured on a live Jurassic Ninja site with a Jetpack Security plan, at 1440×900.

Overview: Scan card Row actions on hover
Scan card on Overview Row hover shows View and the actions menu
Threat details History: Fixed
Threat details inspector History tab, Fixed list
History: Ignored (empty)
History tab, Ignored list empty state

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the add/protect-dashboard-history branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/protect-dashboard-history

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Feature] Protect Dashboard [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ labels Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control

jp-launch-control Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 7 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/protect/src/class-dashboard-threats.php 90/130 (69.23%) -7.04% 12 💔
projects/packages/protect/routes/dashboard/inspector.tsx 0/11 (0.00%) 0.00% 8 💔
projects/packages/protect/routes/dashboard/sections/scan/threat-actions.ts 27/53 (50.94%) -1.14% 3 ❤️‍🩹
projects/packages/protect/routes/dashboard/sections/history/index.tsx 2/3 (66.67%) -33.33% 1 ❤️‍🩹
projects/packages/protect/routes/dashboard/sections/scan/scan-card.tsx 5/61 (8.20%) 3.11% 0 💚

9 files are newly checked for coverage. Only the first 5 are listed here.

File Coverage
projects/packages/protect/routes/dashboard/sections/history/history-panel.tsx 0/9 (0.00%) 💔
projects/packages/protect/routes/dashboard/sections/history/inspector.tsx 0/7 (0.00%) 💔
projects/packages/protect/routes/dashboard/sections/scan/threat-row-actions.tsx 0/19 (0.00%) 💔
projects/packages/protect/routes/dashboard/sections/scan/delete-software.tsx 1/16 (6.25%) 💔
projects/packages/protect/routes/dashboard/sections/history/history-list.tsx 3/29 (10.34%) 💔

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@enejb
enejb force-pushed the add/protect-dashboard-history branch 2 times, most recently from 14d2903 to 952095c Compare October 6, 2026 01:11
@enejb
enejb force-pushed the add/protect-dashboard-foundation branch 3 times, most recently from b531356 to 6567e4b Compare October 8, 2026 14:23
Base automatically changed from add/protect-dashboard-foundation to trunk October 8, 2026 15:34
@enejb
enejb force-pushed the add/protect-dashboard-history branch from 8c42593 to 0535e45 Compare October 8, 2026 23:40
@enejb
enejb requested a balanced review from Copilot October 8, 2026 23:40
@enejb
enejb marked this pull request as ready for review October 8, 2026 23:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Fix results remain stale in both cache layers, and multisite plugin deletion uses the wrong admin context.

7 open findings
What changed in this PR

Adds a Scan-plan-only History tab to the flag-gated Protect dashboard, backed by a cached WordPress.com history endpoint.

Changes:

  • Adds the History REST endpoint, tab, filters, table, and inspector.
  • Shares Scan history caching between fixed and ignored threats.
  • Refactors threat actions and adds plugin/theme deletion controls.
File Description
projects/​plugins/​jetpack/​changelog/​add-protect-dashboard-history Adds the Jetpack release note.
projects/​packages/​protect/​src/​sections/​class-scan.php Shares and invalidates the history cache.
projects/​packages/​protect/​src/​sections/​class-history.php Adds History state and REST route.
projects/​packages/​protect/​src/​class-dashboard-threats.php Adds software deletion links.
projects/​packages/​protect/​routes/​dashboard/​test/​use-close-on-escape.test.tsx Tests Escape-key behavior.
projects/​packages/​protect/​routes/​dashboard/​stage.tsx Generalizes tab navigation.
projects/​packages/​protect/​routes/​dashboard/​sections/​types.ts Exposes generalized tab navigation.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​types.ts Adds deletion actions to threat types.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​threats-list.tsx Extracts reusable threat fields and views.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​threat-row-actions.tsx Defines DataViews row actions.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​threat-details.tsx Shows fixed status and deletion controls.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​threat-actions.ts Prevents duplicate threat actions.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​store.ts Generalizes URL search-parameter state.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​settings-card.tsx Updates the Scan icon.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​scan-card.tsx Links Scan to History and revises presentation.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​labels.ts Adds deletion labels.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​inspector.tsx Uses generalized search parameters.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​index.tsx Passes tab navigation into Scan.
projects/​packages/​protect/​routes/​dashboard/​sections/​scan/​delete-software.tsx Adds deletion confirmations and controls.
projects/​packages/​protect/​routes/​dashboard/​sections/​inspector-params.ts Registers the History inspector parameter.
projects/​packages/​protect/​routes/​dashboard/​sections/​history/​store.ts Fetches and stores history data.
projects/​packages/​protect/​routes/​dashboard/​sections/​history/​inspector.tsx Adds read-only history threat details.
projects/​packages/​protect/​routes/​dashboard/​sections/​history/​index.tsx Registers the plan-gated History tab.
projects/​packages/​protect/​routes/​dashboard/​sections/​history/​history-panel.tsx Renders loading, error, and list states.
projects/​packages/​protect/​routes/​dashboard/​sections/​history/​history-list.tsx Renders fixed and ignored threat tables.
projects/​packages/​protect/​routes/​dashboard/​route.scss Adjusts card headers and inspector width.
projects/​packages/​protect/​routes/​dashboard/​inspector.tsx Adds Escape-key inspector closing.
projects/​packages/​protect/​routes/​dashboard/​components/​use-close-on-escape.ts Implements Escape-key handling.
projects/​packages/​protect/​routes/​dashboard/​components/​tab-link.tsx Generalizes dashboard tab links.
projects/​packages/​protect/​routes/​dashboard/​components/​card.tsx Adds card descriptions and header sizing.
projects/​packages/​protect/​changelog/​add-protect-dashboard-history Adds the package release note.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +25 to +27
export function loadHistory(): Promise< void > {
if ( isLoading || historyStore.get().threats ) {
return Promise.resolve();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5406e5b. Opening the History tab now always refetches (loadHistory( true )), keeping the current list on screen while it does, so a threat fixed from Overview shows up without a reload. The details sidebar still only fetches when nothing is loaded. Paired with the server-side cache clear on a completed fix (see the class-scan.php thread).

Comment on lines +70 to +74
id: 'delete-plugin',
label: softwareLabel( 'delete' ),
isEligible: item => item.extension?.type === 'plugins' && hasLink( 'delete' )( item ),
// WordPress asks to confirm on the page this opens.
callback: link( 'delete' ),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented in the PR description and changelog entries (5406e5b): the delete behaviour, its confirmation (core's own screen for plugins, a dashboard confirmation for themes, since core deletes themes without asking), and testing steps 6–7. I kept it in this PR since it's part of the same Scan/History pass; happy to split it out if a reviewer prefers.

$actions['delete'] = add_query_arg(
'_wpnonce',
wp_create_nonce( 'bulk-plugins' ),
self_admin_url( 'plugins.php?action=delete-selected&checked[]=' . rawurlencode( $file ) )

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, and it goes a bit further: on multisite a plugin inactive on this site can still be active on another, so offering delete from one site's dashboard is unsafe even with the right admin URL. Fixed in 5406e5b by not offering plugin delete on multisite at all, matching what this PR already did for themes.

Comment on lines +66 to +69
public static function get_history() {
if ( ! Dashboard::has_scan_plan() ) {
return new WP_Error( 'no_scan_plan', __( 'Scan history needs a Jetpack Scan plan.', 'jetpack-protect-pkg' ), array( 'status' => 403 ) );
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 5406e5b: History_Section_Test::test_history_needs_a_scan_plan checks the 403 no_scan_plan response even when the history cache is already filled (the gate runs before the cache), and Scan_Section_Test::test_ignored_threats_come_from_the_shared_history checks the shared cache is split correctly. Removing the plan gate or the ignored filter fails exactly these tests. I didn't add a plan-enabled delegation test: Plan::has_required_plan() memoizes in a function-level static, so it can't be flipped within one PHPUnit process without changing that package.

Comment on lines +270 to +271
$threats = Dashboard_Threats::format_all( (array) ( $body->threats ?? array() ) );
set_transient( self::HISTORY_CACHE, $threats, 5 * MINUTE_IN_SECONDS );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5406e5b: HISTORY_CACHE is now cleared when the fix POST reports fixed immediately and when a polled fix status first reports fixed.

/**
* Call `onClose` when Escape is pressed anywhere on the page.
*
* Skips presses a menu, listbox or dialog handled or had focus for, so Escape closes those first.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded in 5406e5b: "Ignores Escape inside a menu, listbox or dialog, or when one already handled it, so that closes first."

Significance: patch
Type: other

Protect: Add a Scan history tab to the Protect dashboard for sites with Jetpack Scan.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this one as is. The entry is Type: other, which is Jetpack's non-user-facing bucket ("This section will not be copied to readme.txt"), so it doesn't reach the readme or release notes. It matches the earlier flag-gated dashboard PRs, e.g. #53195's add-protect-dashboard-scan entry. The user-facing entry can land when the flag ships.

@enejb
enejb merged commit b426b22 into trunk Oct 9, 2026
94 of 95 checks passed
@enejb
enejb deleted the add/protect-dashboard-history branch October 9, 2026 00:23
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] In Progress labels Oct 9, 2026
@github-actions github-actions Bot added this to the jetpack/16.4 milestone Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Protect Dashboard [Package] My Jetpack [Package] Protect [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants