This repository is currently being migrated. It's locked while the migration is in progress.
-
Notifications
You must be signed in to change notification settings - Fork 94
API 55333 POA slack alerts #27776
Merged
Merged
API 55333 POA slack alerts #27776
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
158d641
add alerts for silent failures
siddharthalamsal 55c1284
Merge branch 'master' into api-55333-POA-alerts
siddharthalamsal 69330ae
copilot changes
siddharthalamsal 4fa56a8
Merge branch 'master' into api-55333-POA-alerts
siddharthalamsal 34ddfc8
SlackNotifier module
siddharthalamsal e523994
refactor
siddharthalamsal 5fe4c3e
remove duplicate method calls
siddharthalamsal a895e73
update logging
siddharthalamsal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,11 +10,10 @@ module ClaimsApi | |
| module V2 | ||
| module Veterans | ||
| class PowerOfAttorney::RequestController < ClaimsApi::V2::Veterans::PowerOfAttorney::BaseController | ||
| include ClaimsApi::V2::PowerOfAttorneyRequests::IndexValidation | ||
| include ClaimsApi::V2::PowerOfAttorneyRequests::CreateValidation | ||
|
|
||
| FORM_NUMBER = 'POA_REQUEST' | ||
| MAX_PAGE_SIZE = 100 | ||
| MAX_PAGE_NUMBER = 100 | ||
| DEFAULT_PAGE_SIZE = 10 | ||
| DEFAULT_PAGE_NUMBER = 1 | ||
|
|
||
| # POST /power-of-attorney-requests | ||
| def index | ||
|
|
@@ -202,25 +201,39 @@ def process_poa_decision(decision:, proc_id:, representative_id:, poa_code:, met | |
|
|
||
| @json_body, type = result | ||
| validate_mapped_data!(veteran.participant_id, type, poa_code) | ||
| # build headers | ||
| build_decision_headers(veteran, claimant) | ||
| save_and_submit_poa_record(poa_code:, representative_id:, type:) | ||
| rescue => e | ||
| log_decision_failure(proc_id, e) | ||
| raise | ||
| end | ||
| # rubocop:enable Metrics/ParameterLists | ||
|
|
||
| def build_decision_headers(veteran, claimant) | ||
| @claimant_icn = claimant.icn.presence || claimant.mpi.icn if claimant | ||
| build_auth_headers(veteran) | ||
| end | ||
|
|
||
| def save_and_submit_poa_record(poa_code:, representative_id:, type:) | ||
| attrs = decide_request_attributes(poa_code:, decide_form_attributes: form_attributes) | ||
| # save record | ||
| power_of_attorney = ClaimsApi::PowerOfAttorney.create!(attrs) | ||
|
|
||
| claims_v2_logging('process_poa_decision', | ||
| message: 'Record saved, sending to POA Form Builder Job') | ||
| ClaimsApi::V2::PoaFormBuilderJob.perform_async(power_of_attorney.id, type, | ||
| 'post', representative_id) | ||
|
|
||
| power_of_attorney # return to the decide method for the response | ||
| rescue => e | ||
| power_of_attorney | ||
| end | ||
|
|
||
| def log_decision_failure(proc_id, error) | ||
| claims_v2_logging('process_poa_decision', | ||
| message: "Failed to save power of attorney record. Error: #{e}") | ||
| raise e | ||
| level: :error, | ||
| message: "Failed to save power of attorney record. Error: #{error}") | ||
| request_slack_alert('POA Decide Request', | ||
| "Failed to process POA decision for id: #{params[:id]}, " \ | ||
| "procId: #{proc_id} in #{Rails.env}: #{error.message}") | ||
| end | ||
| # rubocop:enable Metrics/ParameterLists | ||
|
|
||
| def validate_mapped_data!(veteran_participant_id, type, poa_code) | ||
| claims_v2_logging('process_poa_decision', | ||
|
|
@@ -242,7 +255,12 @@ def validate_mapped_data!(veteran_participant_id, type, poa_code) | |
|
|
||
| def log_and_raise_decision_error_message! | ||
| claims_v2_logging('process_poa_decision', | ||
| message: 'Encountered issues validating the mapped data') | ||
| level: :error, | ||
| message: "Encountered issues validating the mapped data for POA id: #{params[:id]}: " \ | ||
| "#{@claims_api_forms_validation_errors}") | ||
| request_slack_alert('POA Decide Request', | ||
| "Validation errors in mapped data for POA id: #{params[:id]} in #{Rails.env}: " \ | ||
| "#{@claims_api_forms_validation_errors}") | ||
|
|
||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'An error occurred while processing this decision. Please try again later.' | ||
|
|
@@ -297,180 +315,8 @@ def find_poa_request!(lighthouse_id) | |
| request | ||
| end | ||
|
|
||
| def validate_country_code | ||
| vet_cc = form_attributes.dig('veteran', 'address', 'countryCode') | ||
| claimant_cc = form_attributes.dig('claimant', 'address', 'countryCode') | ||
|
|
||
| if ClaimsApi::BRD::COUNTRY_CODES[vet_cc.to_s.upcase].blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'The country provided is not valid.' | ||
| ) | ||
| end | ||
|
|
||
| if claimant_cc.present? && ClaimsApi::BRD::COUNTRY_CODES[claimant_cc.to_s.upcase].blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'The country provided is not valid.' | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_phone_country_code | ||
| %w[veteran claimant].each do |key| | ||
| phone = form_attributes.dig(key, 'phone') | ||
| next if phone.blank? | ||
|
|
||
| validate_phone_details(phone, key) | ||
| end | ||
| end | ||
|
|
||
| def validate_phone_details(phone, key) | ||
| return if phone['phoneNumber'].blank? | ||
|
|
||
| validate_phone_and_country_code_combination_not_valid!(phone, key) | ||
| validate_domestic_country_code_on_international_number!(phone, key) | ||
| end | ||
|
|
||
| def validate_phone_and_country_code_combination_not_valid!(phone_data, key) | ||
| phone_number = phone_data['phoneNumber']&.gsub(/\D/, '') | ||
| country_code = phone_data['countryCode'] | ||
|
|
||
| if phone_number.length > 7 && country_code.blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "The #{key}'s international phone number requires a countryCode." | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_domestic_country_code_on_international_number!(phone_data, key) | ||
| phone_number = phone_data['phoneNumber']&.gsub(/\D/, '') | ||
| country_code = phone_data['countryCode']&.gsub(/\D/, '') | ||
|
|
||
| if phone_number.length > 7 && country_code == '1' | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "The #{key}'s countryCode is for a domestic phone number." | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_accredited_representative(poa_code) | ||
| @representative = ::Veteran::Service::Representative.where('? = ANY(poa_codes)', | ||
| poa_code).order(created_at: :desc).first | ||
| # there must be a representative to appoint. This representative can be an accredited attorney, claims agent, | ||
| # or representative. | ||
| if @representative.nil? | ||
| raise ::Common::Exceptions::ResourceNotFound.new( | ||
| detail: "Could not find an Accredited Representative with poa code: #{poa_code}" | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_accredited_organization(poa_code) | ||
| # organization is not required. An attorney or claims agent appointment request would not have an accredited | ||
| # organization to associate with. | ||
| @organization = ::Veteran::Service::Organization.find_by(poa: poa_code) | ||
| end | ||
|
|
||
| def build_bgs_attributes(form_attributes) | ||
| bgs_form_attributes = form_attributes.deep_merge(veteran_data) | ||
| bgs_form_attributes.deep_merge!(claimant_data) if user_profile&.status == :ok | ||
| bgs_form_attributes.deep_merge!(representative_data) | ||
| bgs_form_attributes.deep_merge!(organization_data) if @organization | ||
|
|
||
| bgs_form_attributes | ||
| end | ||
|
|
||
| def validate_filter!(filter) | ||
| return nil if filter.blank? | ||
|
|
||
| valid_filters = %w[status state city country] | ||
|
|
||
| invalid_filters = filter.keys - valid_filters | ||
|
|
||
| if invalid_filters.any? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "Invalid filter(s): #{invalid_filters.join(', ')}" | ||
| ) | ||
| end | ||
|
|
||
| validate_statuses!(filter['status']) | ||
| end | ||
|
|
||
| def validate_statuses!(statuses) | ||
| return nil if statuses.blank? | ||
|
|
||
| unless statuses.is_a?(Array) | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'filter status must be an array' | ||
| ) | ||
| end | ||
|
|
||
| valid_statuses = ManageRepresentativeService::ALL_STATUSES | ||
|
|
||
| if statuses.any? { |status| valid_statuses.exclude?(status.upcase) } | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "Status(es) must be one of: #{valid_statuses.join(', ')}" | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_page_size_and_number_params | ||
| return if use_defaults? | ||
|
|
||
| valid_page_param?('size') if params[:page][:size] | ||
| valid_page_param?('number') if params[:page][:number] | ||
|
|
||
| @page_size_param = params[:page][:size] ? params[:page][:size].to_i : DEFAULT_PAGE_SIZE | ||
| @page_number_param = params[:page][:number] ? params[:page][:number].to_i : DEFAULT_PAGE_NUMBER | ||
|
|
||
| verify_under_max_values! | ||
| end | ||
|
|
||
| def use_defaults? | ||
| if params[:page].blank? | ||
| @page_size_param = DEFAULT_PAGE_SIZE | ||
| @page_number_param = DEFAULT_PAGE_NUMBER | ||
|
|
||
| true | ||
| end | ||
| end | ||
|
|
||
| def verify_under_max_values! | ||
| if @page_size_param && @page_size_param > MAX_PAGE_SIZE | ||
| raise_param_exceeded_warning = true | ||
| include_page_size_msg = true | ||
| end | ||
| if @page_number_param && @page_number_param > MAX_PAGE_NUMBER | ||
| raise_param_exceeded_warning = true | ||
| include_page_number_msg = true | ||
| end | ||
| if raise_param_exceeded_warning.present? | ||
| build_params_error_msg(include_page_size_msg, | ||
| include_page_number_msg) | ||
| end | ||
| end | ||
|
|
||
| def valid_page_param?(key) | ||
| param_val = params[:page][:"#{key}"] | ||
| return true if param_val.is_a?(String) && param_val.match?(/^\d+?$/) && param_val | ||
|
|
||
| raise ::Common::Exceptions::BadRequest.new( | ||
| detail: "The page[#{key}] param value #{params[:page][:"#{key}"]} is invalid" | ||
| ) | ||
| end | ||
|
|
||
| def build_params_error_msg(include_page_size_msg, include_page_number_msg) | ||
| if include_page_size_msg.present? && include_page_number_msg.present? | ||
| msg = "Both the maximum page size param value of #{MAX_PAGE_SIZE} has been exceeded and " \ | ||
| "the maximum page number param value of #{MAX_PAGE_NUMBER} has been exceeded." | ||
| elsif include_page_size_msg.present? | ||
| msg = "The maximum page size param value of #{MAX_PAGE_SIZE} has been exceeded." | ||
| elsif include_page_number_msg.present? | ||
| msg = "The maximum page number param value of #{MAX_PAGE_NUMBER} has been exceeded." | ||
| end | ||
|
|
||
| raise ::Common::Exceptions::BadRequest.new( | ||
| detail: msg | ||
| ) | ||
| def normalize(item) | ||
| item.to_s.strip.downcase | ||
| end | ||
|
|
||
| def verify_poa_codes_data(poa_codes) | ||
|
|
@@ -486,8 +332,13 @@ def page_number_to_index(number) | |
| number - 1 | ||
| end | ||
|
|
||
| def normalize(item) | ||
| item.to_s.strip.downcase | ||
| def build_bgs_attributes(form_attributes) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is showing up as new code in the git diff, but it originally was in this file (see removed chunk above), i moved it to the new file to reduces total lines, but then that file had too many lines so i moved it back 🙃 |
||
| bgs_form_attributes = form_attributes.deep_merge(veteran_data) | ||
| bgs_form_attributes.deep_merge!(claimant_data) if user_profile&.status == :ok | ||
| bgs_form_attributes.deep_merge!(representative_data) | ||
| bgs_form_attributes.deep_merge!(organization_data) if @organization | ||
|
|
||
| bgs_form_attributes | ||
| end | ||
|
|
||
| def veteran_data | ||
|
|
||
23 changes: 23 additions & 0 deletions
23
modules/claims_api/app/controllers/concerns/claims_api/slack_notifier.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module ClaimsApi | ||
| module SlackNotifier | ||
| extend ActiveSupport::Concern | ||
|
|
||
| private | ||
|
|
||
| def request_slack_alert(source, message) | ||
| webhook_url = Settings.claims_api.slack.webhook_url.to_s | ||
| return if webhook_url.blank? | ||
|
|
||
| slack_client = SlackNotify::Client.new(webhook_url:, | ||
| channel: '#api-benefits-claims-alerts', | ||
| username: "Failed #{source}") | ||
| slack_client.notify(message) | ||
| rescue => e | ||
| ClaimsApi::Logger.log('request_slack_alert', | ||
| level: :error, | ||
| message: "Failed to send Slack alert: #{e.message}") | ||
| end | ||
| end | ||
| end |
88 changes: 88 additions & 0 deletions
88
...pi/app/controllers/concerns/claims_api/v2/power_of_attorney_requests/create_validation.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require 'brd/brd' | ||
|
|
||
| module ClaimsApi | ||
| module V2 | ||
| module PowerOfAttorneyRequests | ||
| module CreateValidation | ||
| extend ActiveSupport::Concern | ||
|
|
||
| private | ||
|
|
||
| def validate_country_code | ||
| vet_cc = form_attributes.dig('veteran', 'address', 'countryCode') | ||
| claimant_cc = form_attributes.dig('claimant', 'address', 'countryCode') | ||
|
|
||
| if ClaimsApi::BRD::COUNTRY_CODES[vet_cc.to_s.upcase].blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'The country provided is not valid.' | ||
| ) | ||
| end | ||
|
|
||
| if claimant_cc.present? && ClaimsApi::BRD::COUNTRY_CODES[claimant_cc.to_s.upcase].blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: 'The country provided is not valid.' | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_phone_country_code | ||
| %w[veteran claimant].each do |key| | ||
| phone = form_attributes.dig(key, 'phone') | ||
| next if phone.blank? | ||
|
|
||
| validate_phone_details(phone, key) | ||
| end | ||
| end | ||
|
|
||
| def validate_phone_details(phone, key) | ||
| return if phone['phoneNumber'].blank? | ||
|
|
||
| validate_phone_and_country_code_combination_not_valid!(phone, key) | ||
| validate_domestic_country_code_on_international_number!(phone, key) | ||
| end | ||
|
|
||
| def validate_phone_and_country_code_combination_not_valid!(phone_data, key) | ||
| phone_number = phone_data['phoneNumber']&.gsub(/\D/, '') | ||
| country_code = phone_data['countryCode'] | ||
|
|
||
| if phone_number.length > 7 && country_code.blank? | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "The #{key}'s international phone number requires a countryCode." | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_domestic_country_code_on_international_number!(phone_data, key) | ||
| phone_number = phone_data['phoneNumber']&.gsub(/\D/, '') | ||
| country_code = phone_data['countryCode']&.gsub(/\D/, '') | ||
|
|
||
| if phone_number.length > 7 && country_code == '1' | ||
| raise ::Common::Exceptions::UnprocessableEntity.new( | ||
| detail: "The #{key}'s countryCode is for a domestic phone number." | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_accredited_representative(poa_code) | ||
| @representative = ::Veteran::Service::Representative.where('? = ANY(poa_codes)', | ||
| poa_code).order(created_at: :desc).first | ||
| # there must be a representative to appoint. This representative can be an accredited attorney, claims agent, | ||
| # or representative. | ||
| if @representative.nil? | ||
| raise ::Common::Exceptions::ResourceNotFound.new( | ||
| detail: "Could not find an Accredited Representative with poa code: #{poa_code}" | ||
| ) | ||
| end | ||
| end | ||
|
|
||
| def validate_accredited_organization(poa_code) | ||
| # organization is not required. An attorney or claims agent appointment request would not have an accredited | ||
| # organization to associate with. | ||
| @organization = ::Veteran::Service::Organization.find_by(poa: poa_code) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not actually gettin the text in the error log I was hoping for. I think we need an update to the
log_and_raise_decision_error_messagebit for theJsonSchema::JsonApiMissingAttribute. to include the error messages text so wee can see that.Something along these lines (pseudo code here just FYI)
I'm not a huge fan of passing in the instance variable for the errors, seems odd but I think we need to send in the errors since it would be one or the other. This would look like this which I think helps us understand the error more, otherwise we know it is a 422, but have no idea why.