Skip to content

Commit

Permalink
Merge branch 'master' into API-42308-bgs-service-standardization
Browse files Browse the repository at this point in the history
  • Loading branch information
stiehlrod authored Dec 20, 2024
2 parents c9ba99a + 2913119 commit 07cd5e6
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 97 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ lib/caseflow @department-of-veterans-affairs/lighthouse-banana-peels @department
lib/central_mail @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/chip @department-of-veterans-affairs/vsa-healthcare-health-quest-1-backend @department-of-veterans-affairs/patient-check-in @department-of-veterans-affairs/backend-review-group
lib/claim_letters @department-of-veterans-affairs/benefits-management-tools-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/claim_documents/monitor.rb @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group
lib/clamav @department-of-veterans-affairs/backend-review-group
lib/common/client/base.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
lib/common/client/concerns/mhv_fhir_session_client.rb @department-of-veterans-affairs/vfs-mhv-medical-records @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
Expand Down Expand Up @@ -1419,6 +1420,7 @@ spec/lib/carma @department-of-veterans-affairs/vfs-10-10 @department-of-veterans
spec/lib/caseflow @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/central_mail @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/chip @department-of-veterans-affairs/vsa-healthcare-health-quest-1-backend @department-of-veterans-affairs/patient-check-in @department-of-veterans-affairs/backend-review-group
spec/lib/claim_documents/monitor_spec.rb @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group
spec/lib/claim_status_tool @department-of-veterans-affairs/benefits-management-tools-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/common/client/concerns/mhv_fhir_session_client_spec.rb @department-of-veterans-affairs/vfs-mhv-medical-records @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/lib/common/client/concerns/mhv_jwt_session_client_spec.rb @department-of-veterans-affairs/vfs-mhv-medical-records @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
Expand Down Expand Up @@ -2127,6 +2129,7 @@ spec/support/vcr_cassettes/spec/support @department-of-veterans-affairs/octo-ide
spec/support/vcr_cassettes/staccato @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/token_validation @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/travel_pay @department-of-veterans-affairs/travel-pay-integration @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/uploads/validate_document.yml @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group
spec/spupport/vcr_cassettes/user/get_facilities_empty.yml @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/va_forms @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/va_notify @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,8 @@ GEM
ruby-rc4
ttfunk
pg (1.5.9)
pg_query (5.1.0)
google-protobuf (>= 3.22.3)
pg_query (6.0.0)
google-protobuf (>= 3.25.3)
pg_search (2.3.7)
activerecord (>= 6.1)
activesupport (>= 6.1)
Expand Down
66 changes: 56 additions & 10 deletions app/controllers/v0/claim_documents_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,46 @@

require 'pension_burial/tag_sentry'
require 'lgy/tag_sentry'
require 'claim_documents/monitor'
require 'lighthouse/benefits_intake/service'
require 'pdf_utilities/datestamp_pdf'

module V0
class ClaimDocumentsController < ApplicationController
service_tag 'claims-shared'
skip_before_action(:authenticate)
before_action :load_user

def create
Rails.logger.info "Creating PersistentAttachment FormID=#{form_id}"
uploads_monitor.track_document_upload_attempt(form_id, current_user)

attachment = klass.new(form_id:)
@attachment = klass&.new(form_id:)
# add the file after so that we have a form_id and guid for the uploader to use
attachment.file = unlock_file(params['file'], params['password'])
@attachment.file = unlock_file(params['file'], params['password'])

raise Common::Exceptions::ValidationErrors, attachment unless attachment.valid?
if %w[21P-527EZ 21P-530 21P-530V2].include?(form_id) &&
Flipper.enabled?(:document_upload_validation_enabled) && !stamped_pdf_valid?

attachment.save
raise Common::Exceptions::ValidationErrors, @attachment
end

raise Common::Exceptions::ValidationErrors, @attachment unless @attachment.valid?

Rails.logger.info "Success creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment.id}"
@attachment.save

render json: PersistentAttachmentSerializer.new(attachment)
uploads_monitor.track_document_upload_success(form_id, @attachment.id, current_user)

render json: PersistentAttachmentSerializer.new(@attachment)
rescue => e
Rails.logger.error "Error creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment.id} #{e}"
uploads_monitor.track_document_upload_failed(form_id, @attachment&.id, current_user, e)
raise e
end

private

def klass
case form_id
when '21P-527EZ', '21P-530EZ'
when '21P-527EZ', '21P-530EZ', '21P-530V2'
PensionBurial::TagSentry.tag_sentry
PersistentAttachments::PensionBurial
when '21-686C', '686C-674'
Expand All @@ -47,7 +57,7 @@ def form_id
end

def unlock_file(file, file_password)
return file unless File.extname(file) == '.pdf' && file_password
return file unless File.extname(file) == '.pdf' && file_password.present?

pdftk = PdfForms.new(Settings.binaries.pdftk)
tmpf = Tempfile.new(['decrypted_form_attachment', '.pdf'])
Expand All @@ -69,5 +79,41 @@ def unlock_file(file, file_password)
file.tempfile = tmpf
file
end

# rubocop:disable Metrics/MethodLength
def stamped_pdf_valid?
extension = File.extname(@attachment&.file&.id)
allowed_types = PersistentAttachment::ALLOWED_DOCUMENT_TYPES

if allowed_types.exclude?(extension)
raise Common::Exceptions::UnprocessableEntity.new(
detail: I18n.t('errors.messages.extension_allowlist_error', extension:, allowed_types:),
source: 'PersistentAttachment.stamped_pdf_valid?'
)
elsif @attachment&.file&.size&.< PersistentAttachment::MINIMUM_FILE_SIZE
raise Common::Exceptions::UnprocessableEntity.new(
detail: 'File size must not be less than 1.0 KB',
source: 'PersistentAttachment.stamped_pdf_valid?'
)
end

document = PDFUtilities::DatestampPdf.new(@attachment.to_pdf).run(text: 'VA.GOV', x: 5, y: 5)
intake_service.valid_document?(document:)
rescue BenefitsIntake::Service::InvalidDocumentError => e
@attachment.errors.add(:attachment, e.message)
false
rescue PdfForms::PdftkError
@attachment.errors.add(:attachment, 'File is corrupt and cannot be uploaded')
false
end
# rubocop:enable Metrics/MethodLength

def intake_service
@intake_service ||= BenefitsIntake::Service.new
end

def uploads_monitor
@uploads_monitor ||= ClaimDocuments::Monitor.new
end
end
end
27 changes: 18 additions & 9 deletions app/models/health_care_application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class HealthCareApplication < ApplicationRecord
FORM_ID = '10-10EZ'
ACTIVEDUTY_ELIGIBILITY = 'TRICARE'
DISABILITY_THRESHOLD = 50
DD_ZSF_TAGS = [
'service:healthcare-application',
'function: 10-10EZ async form submission'
].freeze
LOCKBOX = Lockbox.new(key: Settings.lockbox.master_key, encode: true)

attr_accessor :user, :async_compatible, :google_analytics_client_id, :form
Expand Down Expand Up @@ -255,15 +259,14 @@ def log_sync_submission_failure
end

def log_async_submission_failure
log_zero_silent_failures
log_zero_silent_failures unless Flipper.enabled?(:hca_zero_silent_failures)
StatsD.increment("#{HCA::Service::STATSD_KEY_PREFIX}.failed_wont_retry")
StatsD.increment("#{HCA::Service::STATSD_KEY_PREFIX}.failed_wont_retry_short_form") if short_form?
log_submission_failure_details
end

def log_zero_silent_failures
tags = ['service:healthcare-application', 'function: 10-10EZ async form submission']
StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
StatsD.increment('silent_failure_avoided_no_confirmation', tags: DD_ZSF_TAGS)
end

def log_submission_failure_details
Expand Down Expand Up @@ -292,13 +295,19 @@ def send_failure_email
api_key = Settings.vanotify.services.health_apps_1010.api_key

salutation = first_name ? "Dear #{first_name}," : ''
metadata =
{
callback_metadata: {
notification_type: 'error',
form_number: FORM_ID,
statsd_tags: DD_ZSF_TAGS
}
}

VANotify::EmailJob.perform_async(
email,
template_id,
{ 'salutation' => salutation },
api_key
)
params = [email, template_id, { 'salutation' => salutation }, api_key]
params << metadata if Flipper.enabled?(:hca_zero_silent_failures)

VANotify::EmailJob.perform_async(*params)
StatsD.increment("#{HCA::Service::STATSD_KEY_PREFIX}.submission_failure_email_sent")
rescue => e
log_exception_to_sentry(e)
Expand Down
3 changes: 3 additions & 0 deletions app/models/persistent_attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
class PersistentAttachment < ApplicationRecord
include SetGuid

ALLOWED_DOCUMENT_TYPES = %w[.pdf .jpg .jpeg .png].freeze
MINIMUM_FILE_SIZE = 1.kilobyte.freeze

has_kms_key
has_encrypted :file_data, key: :kms_key, **lockbox_options
belongs_to :saved_claim, inverse_of: :persistent_attachments, optional: true
Expand Down
7 changes: 7 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ features:
disability_compensation_staging_lighthouse_brd:
actor_type: user
description: Switches to Lighthouse Staging BRD Service. NEVER ENABLE IN PRODUCTION.
document_upload_validation_enabled:
actor_type: user
description: Enables stamped PDF validation on document upload
enable_in_development: true
hca_browser_monitoring_enabled:
actor_type: user
description: Enables browser monitoring for the health care application.
Expand Down Expand Up @@ -128,6 +132,9 @@ features:
hca_retrieve_facilities_without_repopulating:
actor_type: user
description: Constrain facilities endpoint to only return existing facilities values - even if the table is empty, do not rerun the Job to populate it.
hca_zero_silent_failures:
actor_type: user
description: Pass callback metadata to vanotify sidekiq job
cg1010_oauth_2_enabled:
actor_type: user
description: Use OAuth 2.0 Authentication for 10-10CG Form Mulesoft integration.
Expand Down
48 changes: 48 additions & 0 deletions lib/claim_documents/monitor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require 'logging/monitor'

module ClaimDocuments
##
# Monitor functions for Rails logging and StatsD
# @todo abstract, split logging for controller and sidekiq
#
class Monitor < ::Logging::Monitor
# statsd key for document uploads
DOCUMENT_STATS_KEY = 'api.claim_documents'

def initialize
super('claim_documents')
end

def track_document_upload_attempt(form_id, current_user)
additional_context = {
user_account_uuid: current_user&.user_account_uuid,
tags: ["form_id:#{form_id}"]
}
track_request('info', "Creating PersistentAttachment FormID=#{form_id}", "#{DOCUMENT_STATS_KEY}.attempt",
**additional_context)
end

def track_document_upload_success(form_id, attachment_id, current_user)
additional_context = {
attachment_id:,
user_account_uuid: current_user&.user_account_uuid,
tags: ["form_id:#{form_id}"]
}
track_request('info', "Success creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment_id}",
"#{DOCUMENT_STATS_KEY}.success", **additional_context)
end

def track_document_upload_failed(form_id, attachment_id, current_user, e)
additional_context = {
attachment_id:,
user_account_uuid: current_user&.user_account_uuid,
tags: ["form_id:#{form_id}"],
message: e&.message
}
track_request('error', "Error creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment_id} #{e}",
"#{DOCUMENT_STATS_KEY}.failure", **additional_context)
end
end
end
Binary file added spec/fixtures/files/tiny.pdf
Binary file not shown.
50 changes: 50 additions & 0 deletions spec/lib/claim_documents/monitor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require 'rails_helper'
require 'claim_documents/monitor'

RSpec.describe ClaimDocuments::Monitor do
let(:service) { OpenStruct.new(uuid: 'uuid') }
let(:monitor) { described_class.new }
let(:document_stats_key) { described_class::DOCUMENT_STATS_KEY }
let(:form_id) { 'ABC123' }
let(:attachment_id) { '12345' }
let(:current_user) { create(:user) }
let(:error) { StandardError.new('An error occurred') }

describe '#track_document_upload_attempt' do
it 'logs an upload attempt' do
expect(StatsD).to receive(:increment).with("#{document_stats_key}.attempt", tags: ["form_id:#{form_id}"])
expect(Rails.logger).to receive(:info).with(
"Creating PersistentAttachment FormID=#{form_id}",
hash_including(user_account_uuid: current_user.user_account_uuid, statsd: "#{document_stats_key}.attempt")
)

monitor.track_document_upload_attempt(form_id, current_user)
end
end

describe '#track_document_upload_success' do
it 'logs a successful upload' do
expect(StatsD).to receive(:increment).with("#{document_stats_key}.success", tags: ["form_id:#{form_id}"])
expect(Rails.logger).to receive(:info).with(
"Success creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment_id}",
hash_including(user_account_uuid: current_user.user_account_uuid, statsd: "#{document_stats_key}.success")
)

monitor.track_document_upload_success(form_id, attachment_id, current_user)
end
end

describe '#track_document_upload_failed' do
it 'logs a failed upload' do
expect(StatsD).to receive(:increment).with("#{document_stats_key}.failure", tags: ["form_id:#{form_id}"])
expect(Rails.logger).to receive(:error).with(
"Error creating PersistentAttachment FormID=#{form_id} AttachmentID=#{attachment_id} #{error}",
hash_including(user_account_uuid: current_user.user_account_uuid, statsd: "#{document_stats_key}.failure")
)

monitor.track_document_upload_failed(form_id, attachment_id, current_user, error)
end
end
end
Loading

0 comments on commit 07cd5e6

Please sign in to comment.