Skip to content

Commit

Permalink
Ndbex/95814 add va notify webhook (#21065)
Browse files Browse the repository at this point in the history
* level set

* level set with master

* removing yarn.lock

* FD

* pulling in main

* Levelset

* state fix

* Added feature flag for the dependents action needed email callback

* Decided to go the simpler route and not implement a custom callback at this time

* Setup email failure job

* Added in a flipper feature falg and check for it in the senf_failure_email method in dependency claim

* All tests are passing! Huzzahgit add .

* Change to the file name of the job spec

* Fix as flipper was calling a failure when calling send_failure_email

* codeowners file change

* meant to be sidekiq and not it's spec

* whoops

* more codeowners

* Called personlisation from the claim.send_failure_email method

* disabled :dependents_failure_callback_email flipper in before block

* Removed callback_options from the submit_central_form686c job

* Added context to the flipper

* Rubocop

* Refactor for allow(Flipper)

---------

Co-authored-by: micahaspyr <[email protected]>
  • Loading branch information
micahaspyr and micah-frazier-oddball-io authored Mar 7, 2025
1 parent 7076695 commit 84264e4
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 73 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ app/sidekiq/central_mail/submit_form4142_job.rb @department-of-veterans-affairs/
app/sidekiq/copay_notifications @department-of-veterans-affairs/vsa-debt-resolution @department-of-veterans-affairs/backend-review-group
app/sidekiq/cypress_viewport_updater @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
app/sidekiq/delete_old_pii_logs_job.rb @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
app/sidekiq/dependents/form_686c_674_failure_email_job.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/education_form @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/education_form/templates/10203.erb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/backend-review-group
app/sidekiq/education_form/templates/1990.erb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/backend-review-group
Expand Down Expand Up @@ -1386,6 +1387,7 @@ spec/sidekiq/education_form @department-of-veterans-affairs/my-education-benefit
spec/sidekiq/education_form/create_daily_spool_files.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/govcio-vfep-codereviewers
spec/sidekiq/education_form/create_daily_excel_files_spec.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/delete_old_pii_logs_job_spec.rb @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
spec/sidekiq/dependents/form686c674_failure_email_job_spec.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/evss/dependents_application_job_spec.rb @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/evss/disability_compensation_form @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/evss/disability_compensation_form/form526_document_upload_failure_email_spec.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
Expand Down
24 changes: 14 additions & 10 deletions app/models/saved_claim/dependency_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ def to_pdf(form_id: FORM)
def send_failure_email(email) # rubocop:disable Metrics/MethodLength
# if the claim is both a 686c and a 674, send a combination email.
# otherwise, check to see which individual type it is and send the corresponding email.
personalisation = {
'first_name' => parsed_form.dig('veteran_information', 'full_name', 'first')&.upcase.presence,
'date_submitted' => Time.zone.today.strftime('%B %d, %Y'),
'confirmation_number' => confirmation_number
}
template_id = if submittable_686? && submittable_674?
Settings.vanotify.services.va_gov.template_id.form21_686c_674_action_needed_email
elsif submittable_686?
Expand All @@ -171,17 +176,16 @@ def send_failure_email(email) # rubocop:disable Metrics/MethodLength
Rails.logger.error('Email template cannot be assigned for SavedClaim', saved_claim_id: id)
nil
end

if email.present? && template_id.present?
VANotify::EmailJob.perform_async(
email,
template_id,
{
'first_name' => parsed_form.dig('veteran_information', 'full_name', 'first')&.upcase.presence,
'date_submitted' => Time.zone.today.strftime('%B %d, %Y'),
'confirmation_number' => confirmation_number
}
)
if Flipper.enabled?(:dependents_failure_callback_email)
Dependents::Form686c674FailureEmailJob.perform_async(id, email, template_id, personalisation)
else
VANotify::EmailJob.perform_async(
email,
template_id,
personalisation
)
end
end
end

Expand Down
47 changes: 47 additions & 0 deletions app/sidekiq/dependents/form_686c_674_failure_email_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require 'va_notify/service'

class Dependents::Form686c674FailureEmailJob
include Sidekiq::Job

FORM_ID = '686C-674'
FORM_ID_674 = '21-674'
STATSD_KEY_PREFIX = 'api.dependents.form_686c_674_failure_email_job'
ZSF_DD_TAG_FUNCTION = '686c_674_failure_email_queuing'

sidekiq_options retry: 16

sidekiq_retries_exhausted do |msg, ex|
Rails.logger.error('Form686c674FailureEmailJob exhausted all retries',
{
saved_claim_id: msg['args'].first,
error_message: ex.message
})
end

def perform(claim_id, email, template_id, personalisation)
@claim = SavedClaim::DependencyClaim.find(claim_id)
va_notify_client.send_email(email_address: email,
template_id:,
personalisation:)
rescue => e
Rails.logger.warn('Form686c674FailureEmailJob failed, retrying send...', { claim_id:, error: e })
end

private

def va_notify_client
@va_notify_client ||= VaNotify::Service.new(Settings.vanotify.services.va_gov.api_key, callback_options)
end

def callback_options
{
callback_metadata: {
notification_type: 'error',
form_id: @claim.form_id,
statsd_tags: { service: 'dependent-change', function: ZSF_DD_TAG_FUNCTION }
}
}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SubmitCentralForm686cJob
FORM_ID = '686C-674'
FORM_ID_674 = '21-674'
STATSD_KEY_PREFIX = 'worker.submit_686c_674_backup_submission'
ZSF_DD_TAG_FUNCTION = 'submit_686c_674_backup_submission'
# retry for 2d 1h 47m 12s
# https://github.com/sidekiq/sidekiq/wiki/Error-Handling
RETRY = 16
Expand Down
3 changes: 3 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ features:
dependents_trigger_action_needed_email:
actor_type: user
description: Set whether to enable VANotify email to Veteran for Dependents Backup Path failure exhaustion
dependents_failure_callback_email:
actor_type: user
description: Enables the dependents action needed email callback to be used when an action needed email is triggered
disability_526_form4142_polling_records:
actor_type: user
description: enables creation of, and tracking of, sent form 4142 documents, from the 526 flow, to the Lighthouse Benefits Intake API
Expand Down
173 changes: 173 additions & 0 deletions spec/sidekiq/dependents/form686c674_failure_email_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Dependents::Form686c674FailureEmailJob, type: :job do
let(:job) { described_class.new }
let(:claim_id) { 123 }
let(:email) { '[email protected]' }
let(:template_id) { 'test-template-id' }
let(:claim) do
instance_double(
SavedClaim::DependencyClaim,
form_id: described_class::FORM_ID,
confirmation_number: 'ABCD1234',
parsed_form: {
'veteran_information' => {
'full_name' => {
'first' => 'John'
}
}
}
)
end
let(:personalisation) do
{
'first_name' => 'JOHN',
'date_submitted' => 'January 01, 2023',
'confirmation_number' => 'ABCD1234'
}
end
let(:va_notify_client) { instance_double(VaNotify::Service) }

describe '#perform' do
before do
allow(SavedClaim::DependencyClaim).to receive(:find).with(claim_id).and_return(claim)
allow(VaNotify::Service).to receive(:new).and_return(va_notify_client)
allow(va_notify_client).to receive(:send_email)
allow(Rails.logger).to receive(:warn)
end

it 'sends an email with the correct parameters' do
expect(va_notify_client).to receive(:send_email).with(
email_address: email,
template_id:,
personalisation: {
'first_name' => 'JOHN',
'date_submitted' => 'January 01, 2023',
'confirmation_number' => 'ABCD1234'
}
)

job.perform(claim_id, email, template_id, personalisation)
end

context 'when an error occurs' do
before do
allow(va_notify_client).to receive(:send_email).and_raise(StandardError.new('Test error'))
end

it 'logs the error and allows retry' do
expect(Rails.logger).to receive(:warn).with(
'Form686c674FailureEmailJob failed, retrying send...',
{ claim_id:, error: instance_of(StandardError) }
)

# Should not raise error
expect { job.perform(claim_id, email, template_id, personalisation) }.not_to raise_error
end
end
end

describe 'sidekiq_retries_exhausted' do
it 'logs an error when retries are exhausted' do
msg = { 'args' => [claim_id] }
ex = StandardError.new('Test exhausted error')

expect(Rails.logger).to receive(:error).with(
'Form686c674FailureEmailJob exhausted all retries',
{
saved_claim_id: claim_id,
error_message: 'Test exhausted error'
}
)

described_class.sidekiq_retries_exhausted_block.call(msg, ex)
end
end

describe '#va_notify_client' do
before do
allow(SavedClaim::DependencyClaim).to receive(:find).with(claim_id).and_return(claim)
allow(VaNotify::Service).to receive(:new).and_return(va_notify_client)

vanotify = double('vanotify')
services = double('services')
va_gov = double('va_gov')

allow(Settings).to receive(:vanotify).and_return(vanotify)
allow(vanotify).to receive(:services).and_return(services)
allow(services).to receive(:va_gov).and_return(va_gov)
allow(va_gov).to receive(:api_key).and_return('test-api-key')
end

it 'initializes VaNotify::Service with correct parameters' do
expected_callback_options = {
callback_metadata: {
notification_type: 'error',
form_id: described_class::FORM_ID,
statsd_tags: { service: 'dependent-change', function: described_class::ZSF_DD_TAG_FUNCTION }
}
}

expect(VaNotify::Service).to receive(:new).with('test-api-key', expected_callback_options)

# Just allow the job to execute, which should create the client
allow(va_notify_client).to receive(:send_email)
job.perform(claim_id, email, template_id, personalisation)
end
end

describe '#personalisation' do
before do
allow(SavedClaim::DependencyClaim).to receive(:find).with(claim_id).and_return(claim)
today = double('today')
allow(Time.zone).to receive(:today).and_return(today)
allow(today).to receive(:strftime).with('%B %d, %Y').and_return('January 01, 2023')
# Create the service but don't set expectations on send_email yet
allow(VaNotify::Service).to receive(:new).and_return(va_notify_client)
end

it 'sends correct personalisation data in the email' do
# Instead of directly calling the private method, test what gets sent to va_notify_client
expect(va_notify_client).to receive(:send_email).with(
email_address: email,
template_id:,
personalisation:
)

job.perform(claim_id, email, template_id, {
'first_name' => 'JOHN',
'date_submitted' => 'January 01, 2023',
'confirmation_number' => 'ABCD1234'
})
end

context 'when first name is nil' do
let(:claim) do
instance_double(
SavedClaim::DependencyClaim,
form_id: described_class::FORM_ID,
confirmation_number: 'ABCD1234',
parsed_form: {
'veteran_information' => {
'full_name' => {
'first' => nil
}
}
}
)
end

it 'handles nil first_name gracefully' do
expect(va_notify_client).to receive(:send_email).with(
email_address: email,
template_id:,
personalisation: hash_including('first_name' => nil)
)
personalisation['first_name'] = nil
job.perform(claim_id, email, template_id, personalisation)
end
end
end
end
Loading

0 comments on commit 84264e4

Please sign in to comment.