Skip to content

Conversation

Initsogar
Copy link
Contributor

@Initsogar Initsogar commented Sep 27, 2025

Resolves DOTDASH-529

Proposed Changes

  • Extract threat-fixing logic into a reusable useFixThreats hook.
  • Refactor single and bulk threat modals to use the shared hook.
  • Fix error handling that wasn't working properly (UI showing success when API failed).
    • Added gcTime: 0 to prevent stale "fixed" status persisting after mutations fail.
  • Update fetchFixThreatsStatus return type to use correct FixThreatsStatusResponse definition.
  • Add unit tests for useFixThreats hook

Bonus:

  • Updated bulk fix threat modal pluralization

Demo

Before After
demo-03.mp4
demo01.mp4

Why are these changes being made?

The previous implementation of the individual threat-fixing was only using a mutation to trigger the fix, but doesn't track the progress. After implementing the bulk threat fix modal, we identified an opportunity to reuse the same endpoints to initiate the fix and also to track the progress.

This refactor centralizes the logic in a reusable hook with proper state management and error handling for both implementations.

Testing Instructions

  1. Navigate to /v2/sites/{site-slug}/scan in the Hosting Dashboard
  2. Test single threat fixing:
    • Click the “Fix threat” action on any individual threat.
    • Click on “Fix threat” in the modal.
    • Ensure it keeps on isBusy state while polling the fix status.
    • Ensure the modal closes and the snackbar appears when it succeeds (fixed or not_fixed).
  3. Test bulk threat fixing:
    • Select multiple threats and click “Fix threats”.
    • Click on “Fix threats” in the modal.
    • Ensure it keeps on isBusy state while polling the fix status.
    • Ensure the modal closes and the snackbar appears when it succeeds (fixed or not_fixed).

Unit tests

Run the comprehensive test suite with coverage:

# Run hook tests with coverage
yarn test-client dashboard/sites/scan/test/use-fix-threats.test.tsx --coverage --collectCoverageFrom="**/hooks/use-fix-threats.ts"

(Use `node --trace-deprecation ...` to show where the warning was created)
 PASS  client/dashboard/sites/scan/test/use-fix-threats.test.tsx
--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files           |     100 |      100 |     100 |     100 |
 use-fix-threats.ts |     100 |      100 |     100 |     100 |
--------------------|---------|----------|---------|---------|-------------------

Test Suites: 1 passed, 1 total
Tests:       13 passed, 13 total
Snapshots:   0 total
Time:        1.251 s
Ran all test suites matching /dashboard\/sites\/scan\/test\/use-fix-threats.test.tsx/i.

Or just the tests without checking coverage:

# Run all scan tests
yarn test-client dashboard/sites/scan

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

@Initsogar Initsogar self-assigned this Sep 27, 2025
Copy link

github-actions bot commented Sep 27, 2025

@Initsogar Initsogar changed the title Update/hd scan fix threat progress Scan Dashboard: implement threat fix progress tracking with reusable hook Sep 27, 2025
Comment on lines -20 to -26
return {
...result,
threats: Object.entries( result.threats ).map( ( [ id, threat ] ) => ( {
...threat,
id,
} ) ),
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Centralized the custom mapping on the useFixThreats hook instead

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit confused, as this is apparently the opposite of what you suggested last week (p1758823754745099?thread_ts=1758820334.569579&cid=C09AAF76EQZ-slack-C09AAF76EQZ).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My expectation was to use the same query hook (fixThreatsStatusQuery) across different components, such as the Fix Threat modal once you added the Bulk Fix modal. Since I centralized all of that logic into a single hook, we can call it directly there. This also made it easier to handle the transformation logic in one place while keeping the query return type as the raw FixThreatsStatusResponse. It also simplified tests because we can mock the fetcher without introducing intermediate “transformation” types in the fixThreatsStatusQuery hook.

siteId: number,
threatIds: number[]
): Promise< SiteScan > {
): Promise< FixThreatsStatusResponse > {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced SiteScan with the new FixThreatsStatusResponse because the responses are not the same.

return;
if ( status.allFixed ) {
createSuccessNotice(
_n( 'Threat fixed.', 'All threats were successfully fixed.', bulkFixableThreats.length ),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added pluralization because there could be a case where only 1 threat is available for bulk fixing:

CleanShot 2025-09-28 at 14 26 15@2x

@Initsogar Initsogar requested a review from myhro September 28, 2025 19:27
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Sep 28, 2025
@Initsogar Initsogar marked this pull request as ready for review September 28, 2025 19:27
@Initsogar Initsogar requested a review from a team as a code owner September 28, 2025 19:27
@matticbot
Copy link
Contributor

matticbot commented Sep 29, 2025

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • help-center
  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug update/hd-scan-fix-threat-progress on your sandbox.

Copy link
Contributor

@myhro myhro left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, Rafa. I left a couple questions, but they are not blockers. LGTM. ✅

id: Number( id ),
} ) );
},
gcTime: 0, // Always fetch fresh status data, never use cached results
Copy link
Contributor

Choose a reason for hiding this comment

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

Using meta: { persist: false } wasn't enough? Maybe we should drop it from there as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it wasn’t enough 😢. We were still getting stale cached results, which caused the snackbar to appear immediately on subsequent fix attempts. Setting the garbage collection time to 0 clears the results from memory as soon as they’re no longer needed, so if you unmount the modal and try again, it now works properly.

I removed the meta: { persist: false } in 13d3522 as it is no longer needed.

Copy link
Member

Choose a reason for hiding this comment

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

if you unmount the modal and try again, it now works properly

If you want to force a fetch when the hook is re-mounted maybe the refetchOnMount: 'always' option will work? https://tanstack.com/query/latest/docs/framework/react/reference/useQuery#:~:text=in%20the%20background-,refetchOnMount,-%3A%20boolean%20%7C%20%22always%22%20%7C%20((query

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi 👋

This specific query hook is trying to fetch the progress of a threat that is being fixed. If for some reason the process fails and the user wants to try again, the query still gets the latest status from cache and reports it immediately without actually trying to fetch a new result.

I believe the refetchOnMount just attempts to fetch but still returns what's in the cache first. Am I right?

Copy link
Member

Choose a reason for hiding this comment

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

The hook returns a isFetchedAfterMount boolean, is that what you need?

It might not be 😅 I'm not familiar with the scan code, so hopefully I'm suggesting things that don't make sense. But React Query does cover a lot of use cases.

Comment on lines -20 to -26
return {
...result,
threats: Object.entries( result.threats ).map( ( [ id, threat ] ) => ( {
...threat,
id,
} ) ),
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit confused, as this is apparently the opposite of what you suggested last week (p1758823754745099?thread_ts=1758820334.569579&cid=C09AAF76EQZ-slack-C09AAF76EQZ).

@Initsogar Initsogar merged commit 690d06a into trunk Sep 29, 2025
12 checks passed
@Initsogar Initsogar deleted the update/hd-scan-fix-threat-progress branch September 29, 2025 19:12
@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Sep 29, 2025
@a8ci18n
Copy link

a8ci18n commented Sep 29, 2025

This Pull Request is now available for translation here: https://translate.wordpress.com/deliverables/20323842

Hi @Initsogar, could you please edit the description of this PR and add a screenshot for our translators? Ideally it'd include all of the following strings:

  • Threat fixed.
  • Failed to fix threat. Please contact support.

Thank you in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants