Skip to content

Conversation

@JadeCara
Copy link
Contributor

@JadeCara JadeCara commented Oct 16, 2025

Issue: ENG-1578

Description Of Changes

🎯 Purpose
As a privacy admin I want to take bulk actions on many privacy requests at once, so that I can quickly approve requests or deny requests.

Details

We really want to support the use case where a privacy admin applies some search criteria, then selects DSRS that meet that criteria and applies a bulk action. This will aid us in better supporting the bulk closure of duplicate requests and unverified requests.

This PR adds a _validate_privacy_request_for_bulk_operation function to the privacy request service that all bulk functions can use and adds a new bulk function finalize_privacy_requests to the service as well. This is part of the effort to provide bulk DSR functionality for users. The endpoint that uses this function will be in the next PR (#6776).

Code Changes

  • added _validate_privacy_request_for_bulk_operation and finalize_privacy_requests to src/fides/service/privacy_request/privacy_request_service.py
  • added tests for both to tests/service/test_privacy_request_service.py

Steps to Confirm

  1. all tests should pass. This is not hooked up to an endpoint yet.

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
      • If a downgrade migration is not possible for this change, please call this out in the PR description!
    • No migrations
  • Documentation:
    • Documentation complete, PR opened in fidesdocs
    • Documentation issue created in fidesdocs
    • If there are any new client scopes created as part of the pull request, remember to update public-facing documentation that references our scope registry
    • No documentation updates required

@vercel
Copy link

vercel bot commented Oct 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
fides-plus-nightly Ready Ready Preview Comment Oct 23, 2025 6:07pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
fides-privacy-center Ignored Ignored Oct 23, 2025 6:07pm

@JadeCara JadeCara changed the title Eng 1578 be support bulk dsr operations finalize service [ENG-1578] Support bulk dsr operations: finalize Oct 16, 2025
@codecov
Copy link

codecov bot commented Oct 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.40%. Comparing base (4240eb4) to head (6afe17c).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6775   +/-   ##
=======================================
  Coverage   87.39%   87.40%           
=======================================
  Files         518      518           
  Lines       33821    33846   +25     
  Branches     3892     3892           
=======================================
+ Hits        29559    29584   +25     
  Misses       3407     3407           
  Partials      855      855           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JadeCara JadeCara marked this pull request as ready for review October 16, 2025 20:45
@JadeCara JadeCara requested a review from a team as a code owner October 16, 2025 20:46
@JadeCara JadeCara requested review from johnewart and removed request for a team October 16, 2025 20:46
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Summary

Added bulk finalization support for privacy requests by introducing a shared validation helper _validate_privacy_request_for_bulk_operation that consolidates common validation logic (request existence, deletion status) used across bulk operations. The new finalize_privacy_requests method allows bulk finalization of privacy requests in requires_manual_finalization status, setting finalization metadata and queuing them for processing.

Key changes:

  • Extracted common validation logic into _validate_privacy_request_for_bulk_operation helper
  • Refactored approve_privacy_requests and deny_privacy_requests to use shared validation
  • Added finalize_privacy_requests method with status validation and queue integration
  • Comprehensive test coverage including edge cases (not found, deleted, wrong status, exceptions)

Issues found:

  • Database state persisted before queue operation in finalize_privacy_requests, which could leave inconsistent state if queueing fails

Confidence Score: 4/5

  • Safe to merge with one logical issue to consider
  • Code follows existing patterns, includes excellent test coverage, and successfully refactors duplicate validation logic. Score reduced by 1 due to database save occurring before queue operation which could leave inconsistent state on queue failure (though this may match existing behavior in similar methods)
  • Pay close attention to src/fides/service/privacy_request/privacy_request_service.py lines 680-688 regarding database persistence ordering

Important Files Changed

File Analysis

Filename Score Overview
src/fides/service/privacy_request/privacy_request_service.py 4/5 Added _validate_privacy_request_for_bulk_operation helper and finalize_privacy_requests method. Refactored existing bulk operations to use shared validation. Minor issue: database saved before queue operation could leave inconsistent state on queue failure.
tests/service/test_privacy_request_service.py 5/5 Comprehensive test coverage for validation helper and finalize functionality. Tests cover not found, deleted, wrong status, success, mixed results, and exception handling scenarios.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service as PrivacyRequestService
    participant Validator as _validate_privacy_request_for_bulk_operation
    participant DB as Database
    participant Queue as queue_privacy_request
    
    Client->>Service: finalize_privacy_requests(request_ids, user_id)
    
    loop For each request_id
        Service->>Validator: _validate_privacy_request_for_bulk_operation(request_id)
        Validator->>DB: get_privacy_request(request_id)
        
        alt Request not found
            Validator-->>Service: BulkUpdateFailed (not found)
        else Request deleted
            Validator-->>Service: BulkUpdateFailed (deleted)
        else Valid request
            Validator-->>Service: PrivacyRequest
            
            alt Status != requires_manual_finalization
                Service-->>Service: Add to failed list
            else Valid status
                Service->>DB: Set finalized_at, finalized_by
                Service->>DB: save()
                
                Service->>Queue: queue_privacy_request(id, from_step=finalization)
                
                alt Queue success
                    Service-->>Service: Add to succeeded list
                else Queue failure (Exception)
                    Service-->>Service: Add to failed list
                end
            end
        end
    end
    
    Service-->>Client: BulkReviewResponse(succeeded, failed)
Loading

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +680 to +688
privacy_request.finalized_at = datetime.utcnow()
privacy_request.finalized_by = user_id
privacy_request.save(db=self.db)

# Queue the privacy request for finalization
queue_privacy_request(
privacy_request_id=privacy_request.id,
from_step=CurrentStep.finalization.value,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: database persisted before queue operation - if queue_privacy_request fails, finalized_at and finalized_by are already saved, leaving the privacy request in an inconsistent state

consider either:

  1. saving after successful queue (but may need transaction management)
  2. adding rollback logic in the exception handler
  3. documenting this behavior as acceptable if the queue operation is expected to be highly reliable
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/fides/service/privacy_request/privacy_request_service.py
Line: 680:688

Comment:
**logic:** database persisted before queue operation - if `queue_privacy_request` fails, `finalized_at` and `finalized_by` are already saved, leaving the privacy request in an inconsistent state

consider either:
1. saving after successful queue (but may need transaction management)
2. adding rollback logic in the exception handler
3. documenting this behavior as acceptable if the queue operation is expected to be highly reliable

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This follows the existing pattern in approve_privacy_requests (line 555 → 569) and deny_privacy_requests (line 618 → 630). This code is consistent with the codebase, but may be something we want to review in the future. The exception handler will catch queue failures and add them to the failed list, which is appropriate behavior. If this needs to change, it should be a separate refactoring PR for all three methods.

if privacy_request.deleted_at is not None:
return BulkUpdateFailed(
message="Cannot transition status for a deleted request",
data=PrivacyRequestResponse.model_validate(privacy_request).model_dump(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to include the whole model in this response, or just the ID? (Might it contain data we don't want to send?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the pattern the other bulk operations were following. If we want to change it for all the operations its probably more of a refactor than just pulling out the common functionality - not that I am against the refactor, but its just out of scope for this ticket I think.

Copy link
Collaborator

@johnewart johnewart Oct 23, 2025

Choose a reason for hiding this comment

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

Sounds good, more a question than a need to change it - but I agree we should be consistent

data={"privacy_request_id": request_id},
)
)
result = self._validate_privacy_request_for_bulk_operation(request_id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it might be better for _validate_privacy_request... to raise an exception if something is wrong and return a well-known-object when it returns correctly rather than checking isinstance - thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep love it, update incoming!

data={"privacy_request_id": request_id},
)
)
result = self._validate_privacy_request_for_bulk_operation(request_id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question here


return BulkReviewResponse(succeeded=succeeded, failed=failed)

def finalize_privacy_requests(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this a "private-ish" method? (Inasmuch as anything in Python is private) Also, where does this get called? I see tests for it but that seems to be the only place it's invoked; is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is being called in the api in the next PR - I was following the pattern of the other bulk functionality here. I think having 1 private and the other bulk functions not being private would be odd. I can agree it should be in line for a refactor, but that should be its own PR (as mentioned in the response to the greptile comment below)

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 Just wanted to make sure it wasn't on accident

except ObjectDeletedError:
pass

@pytest.fixture
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a more general observation about the way we test (in lots of our fixtures); this doesn't really need to be persisted, does it? It should be safe to assume the DB works and we're not doing anything but creating, storing and then deleting it

assert privacy_request.location is None
assert privacy_request.status == PrivacyRequestStatus.pending

def test_validate_privacy_request_for_bulk_operation_not_found(
Copy link
Collaborator

Choose a reason for hiding this comment

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

These seem to be covered by the tests below, aren't they? (in that the correct thing happens when there are non-existent, deleted, or valid requests)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removing :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

😄

Copy link
Collaborator

@johnewart johnewart left a comment

Choose a reason for hiding this comment

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

👍 Thanks!

@johnewart johnewart added this pull request to the merge queue Oct 23, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Oct 24, 2025
@JadeCara JadeCara added this pull request to the merge queue Oct 24, 2025
Merged via the queue into main with commit cefe861 Oct 24, 2025
68 checks passed
@JadeCara JadeCara deleted the ENG-1578-be-support-bulk-dsr-operations-finalize-service branch October 24, 2025 15:15
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.

3 participants