Skip to content

Commit 1366106

Browse files
authored
Merge pull request #5884 from nhsuk/background-processing-of-parent-removal
Process bulk removal of parents from an import as a background task
2 parents 7a5aabf + caf83a2 commit 1366106

13 files changed

Lines changed: 271 additions & 95 deletions

app/components/app_import_status_component.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def status_text
2020
"in_re_review" => "Review",
2121
"committing" => "Importing",
2222
"processed" => "Completed",
23+
"removing_parent_relationships" => "Completed",
2324
"partially_processed" => "Partially completed",
2425
"low_pds_match_rate" => "Failed",
2526
"cancelled" => "Cancelled"
@@ -36,6 +37,7 @@ def status_color
3637
"in_re_review" => "blue",
3738
"committing" => "blue",
3839
"processed" => "green",
40+
"removing_parent_relationships" => "green",
3941
"partially_processed" => "green",
4042
"low_pds_match_rate" => "red",
4143
"cancelled" => "grey"

app/components/app_import_summary_component.html.erb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,16 @@
5555
<% end %>
5656
<% end %>
5757
<% end %>
58+
59+
<% if Flipper.enabled?(:import_bulk_remove_parents) &&
60+
import.is_a?(PatientImport) &&
61+
import.parent_relationships.present? %>
62+
<% if import.removing_parent_relationships? %>
63+
<p class="nhsuk-u-secondary-text-colour">Parent-child relationships are currently being removed from this import</p>
64+
<% elsif import.processed? && import.parent_relationships.any? %>
65+
<p>
66+
<%= link_to "Remove all parent-child relationships from import", imports_bulk_remove_parents_path(import.class.name.underscore, import.id) %>
67+
</p>
68+
<% end %>
69+
<% end %>
5870
<% end %>

app/controllers/imports/bulk_remove_parents_controller.rb

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,7 @@ def set_import
5151
end
5252

5353
def set_consents
54-
@consents =
55-
Consent
56-
.includes(patient: { parent_relationships: :parent })
57-
.joins(patient: :parent_relationships)
58-
.merge(@import.patients)
59-
.merge(@import.parent_relationships)
60-
.where("consents.parent_id = parent_relationships.parent_id")
61-
.not_invalidated
54+
@consents = @import.parent_relationship_consents
6255
end
6356

6457
def success_flash_text

app/forms/bulk_remove_parents_form.rb

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class BulkRemoveParentsForm
44
include ActiveModel::Model
55
include ActiveModel::Attributes
66

7+
BATCH_SIZE = 100
8+
79
attr_accessor :current_user
810
attr_reader :import, :consents
911

@@ -24,15 +26,18 @@ def initialize(import:, consents:, current_user:, **attributes)
2426

2527
def save!
2628
return false unless valid?
27-
28-
if remove_option == "unconsented_only"
29-
import.destroy_parent_relationships_without_consent!(consents)
30-
else
31-
import.destroy_parent_relationships_and_invalidate_consents!(
32-
current_user,
33-
consents
34-
)
35-
end
29+
import.update!(status: :removing_parent_relationships)
30+
31+
import
32+
.parent_relationship_ids
33+
.each_slice(BATCH_SIZE) do |batch_ids|
34+
BulkRemoveParentRelationshipsJob.perform_later(
35+
import.to_global_id.to_s,
36+
batch_ids,
37+
current_user.id,
38+
remove_option
39+
)
40+
end
3641

3742
true
3843
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
class BulkRemoveParentRelationshipsJob < ApplicationJob
4+
queue_as :imports
5+
6+
def perform(
7+
import_global_id,
8+
parent_relationship_ids_batch,
9+
user_id,
10+
remove_option
11+
)
12+
import = GlobalID::Locator.locate(import_global_id)
13+
user = User.find(user_id)
14+
15+
ActiveRecord::Base.transaction do
16+
parent_relationships =
17+
import
18+
.parent_relationships
19+
.where(id: parent_relationship_ids_batch)
20+
.includes(:parent, :patient)
21+
22+
consents =
23+
import.parent_relationship_consents(scope: parent_relationships)
24+
25+
parent_relationships_to_remove =
26+
if remove_option == "unconsented_only"
27+
consented_pairs = consents.pluck(:patient_id, :parent_id).to_set
28+
29+
parent_relationships.reject do |pr|
30+
consented_pairs.include?([pr.patient_id, pr.parent_id])
31+
end
32+
else
33+
parent_relationships
34+
end
35+
36+
invalidate_consents!(user, consents) if remove_option == "all"
37+
38+
parents_to_check = parent_relationships_to_remove.map(&:parent)
39+
patient_ids = parent_relationships_to_remove.map(&:patient_id)
40+
41+
parent_relationships_to_remove.each(&:destroy!)
42+
43+
parents_to_check.each do |parent|
44+
next if parent.destroyed?
45+
next if parent.parent_relationships.exists?
46+
next if parent.consents.exists?
47+
48+
parent.destroy!
49+
end
50+
51+
StatusUpdaterJob.perform_bulk(patient_ids.zip)
52+
end
53+
54+
mark_complete_if_finished(import, remove_option)
55+
end
56+
57+
private
58+
59+
def invalidate_consents!(user, consents)
60+
timestamp = Time.current.to_fs(:long)
61+
invalidation_note =
62+
"Consent invalidated on #{timestamp} " \
63+
"because #{user.full_name} removed all parent-child relationships from an import."
64+
65+
consents.update_all(notes: invalidation_note, invalidated_at: Time.current)
66+
end
67+
68+
def mark_complete_if_finished(import, remove_option)
69+
return unless import.remaining_parent_relationships(remove_option:).empty?
70+
71+
import.update!(status: :processed)
72+
end
73+
end

app/models/concerns/csv_importable.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ module CSVImportable
3838
)
3939
end
4040
scope :status_for_imported_records,
41-
-> { where(status: %i[processed partially_processed]) }
41+
-> do
42+
where(
43+
status: %i[
44+
processed
45+
partially_processed
46+
removing_parent_relationships
47+
]
48+
)
49+
end
4250

4351
enum :status,
4452
{
@@ -52,7 +60,8 @@ module CSVImportable
5260
in_re_review: 7,
5361
committing: 8,
5462
partially_processed: 9,
55-
cancelled: 10
63+
cancelled: 10,
64+
removing_parent_relationships: 11
5665
},
5766
default: :pending_import,
5867
validate: true

app/models/patient_import.rb

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -128,61 +128,23 @@ def commit_changesets(changesets)
128128
end
129129
end
130130

131-
def destroy_parent_relationships_and_invalidate_consents!(
132-
current_user,
133-
consents
134-
)
135-
ActiveRecord::Base.transaction do
136-
parent_relationships =
137-
self.parent_relationships.includes(:parent, :patient)
138-
139-
timestamp = Time.current.to_fs(:long)
140-
user_name = current_user&.full_name || "system"
141-
invalidation_note =
142-
"Consent invalidated on #{timestamp} " \
143-
"because #{user_name} removed all parent-child relationships from an import."
144-
145-
# Invalidate consents first before destroying parent relationships, which
146-
# will make consents return an empty set.
147-
consents.update_all(
148-
notes: invalidation_note,
149-
invalidated_at: Time.current
150-
)
151-
152-
patient_ids_to_update = consents.map(&:patient_id).uniq
153-
154-
parents_to_check = parent_relationships.map(&:parent)
155-
156-
parent_relationships.destroy_all
157-
158-
parents_to_check.each do |parent|
159-
if !parent.destroyed? && parent.parent_relationships.empty? &&
160-
parent.consents.empty?
161-
parent.destroy!
162-
end
163-
end
164-
165-
StatusUpdaterJob.perform_bulk(patient_ids_to_update.zip)
131+
def remaining_parent_relationships(remove_option:)
132+
if remove_option == "unconsented_only"
133+
parent_relationships -
134+
parent_relationship_consents.map(&:parent_relationship)
135+
else
136+
parent_relationships
166137
end
167138
end
168139

169-
def destroy_parent_relationships_without_consent!(consents)
170-
ActiveRecord::Base.transaction do
171-
parent_relationships_without_consents =
172-
parent_relationships.includes(:parent, :patient) -
173-
consents.map(&:parent_relationship).uniq
174-
175-
parents_to_check = parent_relationships_without_consents.map(&:parent)
176-
177-
parent_relationships_without_consents.each(&:destroy!)
178-
179-
parents_to_check.each do |parent|
180-
if !parent.destroyed? && parent.parent_relationships.empty? &&
181-
parent.consents.empty?
182-
parent.destroy!
183-
end
184-
end
185-
end
140+
def parent_relationship_consents(scope: parent_relationships)
141+
Consent
142+
.includes(patient: { parent_relationships: :parent })
143+
.joins(patient: :parent_relationships)
144+
.merge(patients)
145+
.merge(scope)
146+
.where("consents.parent_id = parent_relationships.parent_id")
147+
.not_invalidated
186148
end
187149

188150
private

app/views/imports/bulk_remove_parents/new.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
<%= render AppPaginationComponent.new(pagy: @pagy) %>
5959

60-
<%= f.govuk_collection_radio_buttons :remove_option, %w[unconsented_only all], :itself %>
60+
<%= f.govuk_collection_radio_buttons :remove_option, %w[unconsented_only all], :itself, legend: { text: "Which relationships do you want to remove?" } %>
6161
<% end %>
6262

6363
<div class="nhsuk-button-group">

app/views/imports/show.html.erb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
) %>
6969
<% end %>
7070

71-
<% if import.processed? || import.partially_processed? %>
71+
<% if import.processed? || import.partially_processed? || import.removing_parent_relationships? %>
7272

7373
<% if @cancelled.present? %>
7474
<h3 class="nhsuk-u-reading-width">
@@ -132,12 +132,4 @@
132132
</h3>
133133
<%= render AppVaccinationRecordTableComponent.new(@vaccination_records, current_user:, pagy: @pagy) %>
134134
<% end %>
135-
136-
<% if Flipper.enabled?(:import_bulk_remove_parents) &&
137-
import.is_a?(PatientImport) && import.processed? &&
138-
import.parent_relationships.present? %>
139-
<p>
140-
<%= link_to "Remove all parent-child relationships from import", imports_bulk_remove_parents_path(import.class.name.underscore, import.id) %>
141-
</p>
142-
<% end %>
143135
<% end %>

config/locales/en.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,8 +691,8 @@ en:
691691
cancel: Cancel
692692
create:
693693
success_flash:
694-
unconsented_only: Parent–child relationships without a submitted consent response have been removed.
695-
all: All parent-child relationships included in this import have been removed.
694+
unconsented_only: Parent–child relationships without a submitted consent response are being removed.
695+
all: All parent-child relationships included in this import are being removed.
696696
mailers:
697697
consent_form_mailer:
698698
reasons_for_refusal:

0 commit comments

Comments
 (0)