refactor(operation_log): store data in jsonb instead of files

This commit is contained in:
Paul Chavard 2022-11-18 11:31:23 +01:00
parent e4bf1bd2db
commit c0fd080d0e
4 changed files with 50 additions and 20 deletions

View file

@ -765,8 +765,8 @@ class Dossier < ApplicationRecord
def expired_keep_track_and_destroy! def expired_keep_track_and_destroy!
transaction do transaction do
DeletedDossier.create_from_dossier(self, :expired) DeletedDossier.create_from_dossier(self, :expired)
dossier_operation_logs.destroy_all
log_automatic_dossier_operation(:supprimer, self) log_automatic_dossier_operation(:supprimer, self)
dossier_operation_logs.purge_discarded
destroy! destroy!
end end
true true
@ -1164,7 +1164,7 @@ class Dossier < ApplicationRecord
def purge_discarded def purge_discarded
transaction do transaction do
DeletedDossier.create_from_dossier(self, hidden_by_reason) DeletedDossier.create_from_dossier(self, hidden_by_reason)
dossier_operation_logs.not_deletion.destroy_all dossier_operation_logs.purge_discarded
destroy destroy
end end
end end

View file

@ -37,10 +37,29 @@ class DossierOperationLog < ApplicationRecord
belongs_to :bill_signature, optional: true belongs_to :bill_signature, optional: true
scope :not_deletion, -> { where.not(operation: operations.fetch(:supprimer)) } scope :not_deletion, -> { where.not(operation: operations.fetch(:supprimer)) }
scope :with_data, -> { where.not(data: nil) }
scope :brouillon_expired, -> { where(dossier: Dossier.brouillon_expired).not_deletion } scope :brouillon_expired, -> { where(dossier: Dossier.brouillon_expired).not_deletion }
scope :en_construction_expired, -> { where(dossier: Dossier.en_construction_expired).not_deletion } scope :en_construction_expired, -> { where(dossier: Dossier.en_construction_expired).not_deletion }
scope :termine_expired, -> { where(dossier: Dossier.termine_expired).not_deletion } scope :termine_expired, -> { where(dossier: Dossier.termine_expired).not_deletion }
def move_to_cold_storage!
if data.present?
serialized.attach(
io: StringIO.new(data.to_json),
filename: "operation-#{digest}.json",
content_type: 'application/json',
# we don't want to run virus scanner on this file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
update!(data: nil)
end
end
def self.purge_discarded
not_deletion.destroy_all
with_data.each(&:move_to_cold_storage!)
end
def self.create_and_serialize(params) def self.create_and_serialize(params)
dossier = params.fetch(:dossier) dossier = params.fetch(:dossier)
@ -59,24 +78,17 @@ class DossierOperationLog < ApplicationRecord
executed_at: Time.zone.now, executed_at: Time.zone.now,
automatic_operation: !!params[:automatic_operation]) automatic_operation: !!params[:automatic_operation])
serialized = { data = {
operation: operation_log.operation, operation: operation_log.operation,
dossier_id: operation_log.dossier_id, dossier_id: operation_log.dossier_id,
author: self.serialize_author(params[:author]), author: self.serialize_author(params[:author]),
subject: self.serialize_subject(params[:subject], operation_log.operation), subject: self.serialize_subject(params[:subject], operation_log.operation),
automatic_operation: operation_log.automatic_operation?, automatic_operation: operation_log.automatic_operation?,
executed_at: operation_log.executed_at.iso8601 executed_at: operation_log.executed_at.iso8601
}.compact.to_json }.compact
operation_log.digest = Digest::SHA256.hexdigest(serialized) operation_log.data = data
operation_log.digest = Digest::SHA256.hexdigest(data.to_json)
operation_log.serialized.attach(
io: StringIO.new(serialized),
filename: "operation-#{operation_log.digest}.json",
content_type: 'application/json',
# we don't want to run virus scanner on this file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
operation_log.save! operation_log.save!
end end

View file

@ -201,14 +201,21 @@ class PiecesJustificativesService
def self.operation_logs_and_signature_ids(dossiers) def self.operation_logs_and_signature_ids(dossiers)
dol_id_dossier_id_bill_id = DossierOperationLog dol_id_dossier_id_bill_id = DossierOperationLog
.where(dossier: dossiers, data: nil)
.pluck(:bill_signature_id, :id, :dossier_id)
dol_id_data_bill_id = DossierOperationLog
.where(dossier: dossiers) .where(dossier: dossiers)
.pluck(:id, :dossier_id, :bill_signature_id) .with_data
.pluck(:bill_signature_id, :id, :dossier_id, :data, :digest, :created_at)
dol_id_dossier_id = dol_id_dossier_id_bill_id dol_id_dossier_id = dol_id_dossier_id_bill_id
.map { |dol_id, dossier_id, _| [dol_id, dossier_id] } .map { |_, dol_id, dossier_id| [dol_id, dossier_id] }
.to_h .to_h
bill_ids = dol_id_dossier_id_bill_id.map(&:third).uniq.compact bill_ids = (dol_id_dossier_id_bill_id + dol_id_data_bill_id)
.map(&:first)
.uniq
.compact
serialized_dols = ActiveStorage::Attachment serialized_dols = ActiveStorage::Attachment
.includes(:blob) .includes(:blob)
@ -217,6 +224,17 @@ class PiecesJustificativesService
dossier_id = dol_id_dossier_id[a.record_id] dossier_id = dol_id_dossier_id[a.record_id]
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a) ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
end end
serialized_dols += dol_id_data_bill_id.map do |_, id, dossier_id, data, digest, created_at|
a = ActiveStorage::FakeAttachment.new(
file: StringIO.new(data.to_json),
filename: "operation-#{digest}.json",
name: 'serialized',
id: id,
created_at: created_at,
record_type: 'DossierOperationLog'
)
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
end
[serialized_dols, bill_ids] [serialized_dols, bill_ids]
end end

View file

@ -1043,7 +1043,7 @@ describe Dossier do
describe '#accepter!' do describe '#accepter!' do
let(:dossier) { create(:dossier, :en_instruction, :with_individual) } let(:dossier) { create(:dossier, :en_instruction, :with_individual) }
let(:last_operation) { dossier.dossier_operation_logs.last } let(:last_operation) { dossier.dossier_operation_logs.last }
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) } let(:operation_serialized) { last_operation.data }
let!(:instructeur) { create(:instructeur) } let!(:instructeur) { create(:instructeur) }
let!(:now) { Time.zone.parse('01/01/2100') } let!(:now) { Time.zone.parse('01/01/2100') }
let(:attestation) { Attestation.new } let(:attestation) { Attestation.new }
@ -1105,7 +1105,7 @@ describe Dossier do
describe '#passer_en_instruction!' do describe '#passer_en_instruction!' do
let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) } let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) }
let(:last_operation) { dossier.dossier_operation_logs.last } let(:last_operation) { dossier.dossier_operation_logs.last }
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) } let(:operation_serialized) { last_operation.data }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
before { dossier.passer_en_instruction!(instructeur: instructeur) } before { dossier.passer_en_instruction!(instructeur: instructeur) }
@ -1123,7 +1123,7 @@ describe Dossier do
describe '#passer_automatiquement_en_instruction!' do describe '#passer_automatiquement_en_instruction!' do
let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) } let(:dossier) { create(:dossier, :en_construction, en_construction_close_to_expiration_notice_sent_at: Time.zone.now) }
let(:last_operation) { dossier.dossier_operation_logs.last } let(:last_operation) { dossier.dossier_operation_logs.last }
let(:operation_serialized) { JSON.parse(last_operation.serialized.download) } let(:operation_serialized) { last_operation.data }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
before { dossier.passer_automatiquement_en_instruction! } before { dossier.passer_automatiquement_en_instruction! }
@ -1300,7 +1300,7 @@ describe Dossier do
it { expect(dossier.attestation).to be_nil } it { expect(dossier.attestation).to be_nil }
it { expect(dossier.termine_close_to_expiration_notice_sent_at).to be_nil } it { expect(dossier.termine_close_to_expiration_notice_sent_at).to be_nil }
it { expect(last_operation.operation).to eq('repasser_en_instruction') } it { expect(last_operation.operation).to eq('repasser_en_instruction') }
it { expect(JSON.parse(last_operation.serialized.download)['author']['email']).to eq(instructeur.email) } it { expect(last_operation.data['author']['email']).to eq(instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_revert_to_instruction).with(dossier) } it { expect(DossierMailer).to have_received(:notify_revert_to_instruction).with(dossier) }
after { Timecop.return } after { Timecop.return }