-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Decision Reviews Engine: Duplicate controllers and routes (#19789)
* Duplicate main form endpoints - also contestable issues endpoints -v1 and v2 * Fix linting errors * Duplicate final endpoints - evidence - notification callbacks * Fix shared example constant collision --------- Co-authored-by: dfong-adh <[email protected]>
- Loading branch information
Showing
22 changed files
with
2,085 additions
and
3 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
modules/decision_reviews/app/controllers/decision_reviews/v1/appeals_base_controller.rb
This file contains 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,48 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'caseflow/service' | ||
require 'decision_reviews/v1/service' | ||
|
||
module DecisionReviews | ||
module V1 | ||
class AppealsBaseController < ApplicationController | ||
include FailedRequestLoggable | ||
before_action { authorize :appeals, :access? } | ||
|
||
private | ||
|
||
def decision_review_service | ||
DecisionReviews::V1::Service.new | ||
end | ||
|
||
def request_body_hash | ||
@request_body_hash ||= get_hash_from_request_body | ||
end | ||
|
||
def get_hash_from_request_body | ||
# rubocop:disable Style/ClassEqualityComparison | ||
# testing string b/c NullIO class doesn't always exist | ||
raise request_body_is_not_a_hash_error if request.body.class.name == 'Puma::NullIO' | ||
# rubocop:enable Style/ClassEqualityComparison | ||
|
||
body = JSON.parse request.body.string | ||
raise request_body_is_not_a_hash_error unless body.is_a?(Hash) | ||
|
||
body | ||
rescue JSON::ParserError | ||
raise request_body_is_not_a_hash_error | ||
end | ||
|
||
def request_body_is_not_a_hash_error | ||
DecisionReviewV1::ServiceException.new key: 'DR_REQUEST_BODY_IS_NOT_A_HASH' | ||
end | ||
|
||
def request_body_debug_data | ||
{ | ||
request_body_class_name: request.try(:body).class.name, | ||
request_body_string: request.try(:body).try(:string) | ||
} | ||
end | ||
end | ||
end | ||
end |
94 changes: 94 additions & 0 deletions
94
...ision_reviews/app/controllers/decision_reviews/v1/decision_review_evidences_controller.rb
This file contains 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,94 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'decision_reviews/v1/logging_utils' | ||
require 'common/pdf_helpers' | ||
|
||
# Notice of Disagreement evidence submissions | ||
module DecisionReviews | ||
module V1 | ||
class DecisionReviewEvidencesController < ApplicationController | ||
include FormAttachmentCreate | ||
include DecisionReviews::V1::LoggingUtils | ||
service_tag 'evidence-upload' | ||
|
||
FORM_ATTACHMENT_MODEL = DecisionReviewEvidenceAttachment | ||
|
||
private | ||
|
||
def serializer_klass | ||
DecisionReviewEvidenceAttachmentSerializer | ||
end | ||
|
||
# This method, declared in `FormAttachmentCreate`, is responsible for uploading file data to S3. | ||
def save_attachment_to_cloud! | ||
# `form_attachment` is declared in `FormAttachmentCreate`, included above. | ||
form_attachment_guid = form_attachment&.guid | ||
password = filtered_params[:password] | ||
|
||
log_params = { | ||
form_attachment_guid:, | ||
encrypted: password.present? | ||
} | ||
|
||
# Unlock pdf with hexapdf instead of using pdftk | ||
if password.present? | ||
unlocked_pdf = unlock_pdf(filtered_params[:file_data], password) | ||
form_attachment.set_file_data!(unlocked_pdf) | ||
else | ||
super | ||
end | ||
|
||
log_formatted(**common_log_params.merge(params: log_params, is_success: true)) | ||
rescue => e | ||
log_formatted(**common_log_params.merge(params: log_params, is_success: false, response_error: e)) | ||
raise e | ||
end | ||
|
||
def common_log_params | ||
{ | ||
key: :evidence_upload_to_s3, | ||
form_id: get_form_id_from_request_headers, | ||
user_uuid: current_user.uuid, | ||
downstream_system: 'AWS S3' | ||
} | ||
end | ||
|
||
def unlock_pdf(file, password) | ||
tmpf = Tempfile.new(['decrypted_form_attachment', '.pdf']) | ||
::Common::PdfHelpers.unlock_pdf(file.tempfile.path, password, tmpf) | ||
tmpf.rewind | ||
|
||
file.tempfile.unlink | ||
file.tempfile = tmpf | ||
file | ||
end | ||
|
||
def get_form_id_from_request_headers | ||
# 'Source-App-Name', which specifies the form from which evidence was submitted, is taken from `window.appName`, | ||
# which is taken from the `entryName` in the manifest.json files for each form. See: | ||
# - vets-website/src/platform/utilities/api/index.js (apiRequest) | ||
# - vets-website/src/platform/startup/setup.js (setUpCommonFunctionality) | ||
# - vets-website/src/platform/startup/index.js (startApp) | ||
# - vets-api/lib/source_app_middleware.rb | ||
source_app_name = request.env['SOURCE_APP'] | ||
# The higher-level review form (996) is not included in this list because it does not permit evidence uploads. | ||
form_id = { | ||
'10182-board-appeal' => '10182', | ||
'995-supplemental-claim' => '995' | ||
}[source_app_name] | ||
|
||
if form_id.present? | ||
form_id | ||
else | ||
# If, for some odd reason, the `entryName`s are changed in these manifest.json files (or if the HLR form | ||
# begins accepting additional evidence), we will trigger a DataDog alert hinging on the StatsD metric below. | ||
# Upon receiving this alert, we can update the form_id hash above. | ||
StatsD.increment('decision_review.evidence_upload_to_s3.unexpected_form_id') | ||
# In this situation, there is no good reason to block the Veteran from uploading their evidence to S3, | ||
# so we return the unexpected `source_app_name` to be logged by `log_formatted` above. | ||
source_app_name | ||
end | ||
end | ||
end | ||
end | ||
end |
107 changes: 107 additions & 0 deletions
107
.../app/controllers/decision_reviews/v1/decision_review_notification_callbacks_controller.rb
This file contains 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,107 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'decision_reviews/v1/logging_utils' | ||
|
||
module DecisionReviews | ||
module V1 | ||
class DecisionReviewNotificationCallbacksController < ApplicationController | ||
include ActionController::HttpAuthentication::Token::ControllerMethods | ||
include DecisionReviews::V1::LoggingUtils | ||
|
||
service_tag 'appeal-application' | ||
|
||
skip_before_action :verify_authenticity_token, only: [:create] | ||
skip_before_action :authenticate, only: [:create] | ||
skip_after_action :set_csrf_header, only: [:create] | ||
before_action :authenticate_header, only: [:create] | ||
|
||
STATSD_KEY_PREFIX = 'api.decision_review.notification_callback' | ||
|
||
DELIVERED_STATUS = 'delivered' | ||
|
||
APPEAL_TYPE_TO_SERVICE_MAP = { | ||
'HLR' => 'higher-level-review', | ||
'NOD' => 'board-appeal', | ||
'SC' => 'supplemental-claims' | ||
}.freeze | ||
|
||
VALID_FUNCTION_TYPES = %w[form evidence secondary_form].freeze | ||
|
||
def create | ||
return render json: nil, status: :not_found unless enabled? | ||
|
||
payload = JSON.parse(request.body.string) | ||
status = payload['status']&.downcase | ||
reference = payload['reference'] | ||
|
||
StatsD.increment("#{STATSD_KEY_PREFIX}.received", tags: { status: }) | ||
send_silent_failure_avoided_metric(reference) if status == DELIVERED_STATUS | ||
|
||
DecisionReviewNotificationAuditLog.create!(notification_id: payload['id'], reference:, status:, payload:) | ||
|
||
log_formatted(**log_params(payload, true)) | ||
render json: { message: 'success' } | ||
rescue => e | ||
log_formatted(**log_params(payload, false), params: { exception_message: e.message }) | ||
render json: { message: 'failed' } | ||
end | ||
|
||
private | ||
|
||
def log_params(payload, is_success) | ||
{ | ||
key: :decision_review_notification_callback, | ||
form_id: '995', | ||
user_uuid: nil, | ||
upstream_system: 'VANotify', | ||
body: payload.merge('to' => '<FILTERED>'), # scrub PII from logs | ||
is_success:, | ||
params: { | ||
notification_id: payload['id'], | ||
status: payload['status'] | ||
} | ||
} | ||
end | ||
|
||
def send_silent_failure_avoided_metric(reference) | ||
service_name, function_type = parse_reference_value(reference) | ||
tags = ["service:#{service_name}", "function: #{function_type} submission to Lighthouse"] | ||
StatsD.increment('silent_failure_avoided', tags:) | ||
rescue => e | ||
Rails.logger.error('Failed to send silent_failure_avoided metric', params: { reference:, message: e.message }) | ||
end | ||
|
||
def parse_reference_value(reference) | ||
appeal_type, function_type = reference.split('-') | ||
raise 'Invalid function_type' unless VALID_FUNCTION_TYPES.include? function_type | ||
|
||
[APPEAL_TYPE_TO_SERVICE_MAP.fetch(appeal_type.upcase), function_type] | ||
end | ||
|
||
def authenticate_header | ||
authenticate_user_with_token || authenticity_error | ||
end | ||
|
||
def authenticate_user_with_token | ||
authenticate_with_http_token do |token| | ||
is_authenticated = token == bearer_token_secret | ||
Rails.logger.info('DecisionReviewNotificationCallbacksController callback received', is_authenticated:) | ||
|
||
is_authenticated | ||
end | ||
end | ||
|
||
def authenticity_error | ||
render json: { message: 'Invalid credentials' }, status: :unauthorized | ||
end | ||
|
||
def bearer_token_secret | ||
Settings.nod_vanotify_status_callback.bearer_token | ||
end | ||
|
||
def enabled? | ||
Flipper.enabled? :nod_callbacks_endpoint | ||
end | ||
end | ||
end | ||
end |
50 changes: 50 additions & 0 deletions
50
...app/controllers/decision_reviews/v1/higher_level_reviews/contestable_issues_controller.rb
This file contains 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,50 @@ | ||
# frozen_string_literal: true | ||
|
||
module DecisionReviews | ||
module V1 | ||
module HigherLevelReviews | ||
class ContestableIssuesController < AppealsBaseController | ||
service_tag 'higher-level-review' | ||
|
||
def index | ||
ci = decision_review_service | ||
.get_higher_level_review_contestable_issues(user: current_user, benefit_type: params[:benefit_type]) | ||
.body | ||
render json: merge_legacy_appeals(ci) | ||
rescue => e | ||
log_exception_to_personal_information_log( | ||
e, | ||
error_class: "#{self.class.name}#index exception #{e.class} (HLR_V1)", | ||
benefit_type: params[:benefit_type] | ||
) | ||
raise | ||
end | ||
|
||
private | ||
|
||
def merge_legacy_appeals(contestable_issues) | ||
# Fetch Legacy Appels and combine with CIs | ||
ci_la = nil | ||
begin | ||
la = decision_review_service | ||
.get_legacy_appeals(user: current_user) | ||
.body | ||
# punch in an empty LA section if no LAs for user to distinguish no LAs from a LA call fail | ||
la['data'] = [{ type: 'legacyAppeal', attributes: { issues: [] } }] if la['data'].empty? | ||
ci_la = { data: contestable_issues['data'] + la['data'] } | ||
rescue => e | ||
# If LA fails keep going Legacy Appeals are not critical, return original contestable_issues | ||
log_exception_to_personal_information_log( | ||
e, | ||
error_class: "#{self.class.name}#index exception #{e.class} (HLR_V1_LEGACY_APPEALS)", | ||
benefit_type: params[:benefit_type] | ||
) | ||
contestable_issues | ||
else | ||
ci_la | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
65 changes: 65 additions & 0 deletions
65
...s/decision_reviews/app/controllers/decision_reviews/v1/higher_level_reviews_controller.rb
This file contains 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,65 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'decision_reviews/saved_claim/service' | ||
|
||
module DecisionReviews | ||
module V1 | ||
class HigherLevelReviewsController < AppealsBaseController | ||
include DecisionReviews::SavedClaim::Service | ||
service_tag 'higher-level-review' | ||
|
||
def show | ||
render json: decision_review_service.get_higher_level_review(params[:id]).body | ||
rescue => e | ||
log_exception_to_personal_information_log( | ||
e, error_class: error_class(method: 'show', exception_class: e.class), id: params[:id] | ||
) | ||
raise | ||
end | ||
|
||
def create | ||
hlr_response_body = decision_review_service | ||
.create_higher_level_review(request_body: request_body_hash, user: @current_user) | ||
.body | ||
submitted_appeal_uuid = hlr_response_body.dig('data', 'id') | ||
ActiveRecord::Base.transaction do | ||
AppealSubmission.create!(user_uuid: @current_user.uuid, user_account: @current_user.user_account, | ||
type_of_appeal: 'HLR', submitted_appeal_uuid:) | ||
|
||
store_saved_claim(claim_class: ::SavedClaim::HigherLevelReview, form: request_body_hash.to_json, | ||
guid: submitted_appeal_uuid) | ||
|
||
# Clear in-progress form since submit was successful | ||
InProgressForm.form_for_user('20-0996', current_user)&.destroy! | ||
end | ||
render json: hlr_response_body | ||
rescue => e | ||
::Rails.logger.error( | ||
message: "Exception occurred while submitting Higher Level Review: #{e.message}", | ||
backtrace: e.backtrace | ||
) | ||
|
||
handle_personal_info_error(e) | ||
end | ||
|
||
private | ||
|
||
def error_class(method:, exception_class:) | ||
"#{self.class.name}##{method} exception #{exception_class} (HLR_V1)" | ||
end | ||
|
||
def handle_personal_info_error(e) | ||
request = begin | ||
{ body: request_body_hash } | ||
rescue | ||
request_body_debug_data | ||
end | ||
|
||
log_exception_to_personal_information_log( | ||
e, error_class: error_class(method: 'create', exception_class: e.class), request: | ||
) | ||
raise | ||
end | ||
end | ||
end | ||
end |
22 changes: 22 additions & 0 deletions
22
.../controllers/decision_reviews/v1/notice_of_disagreements/contestable_issues_controller.rb
This file contains 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,22 @@ | ||
# frozen_string_literal: true | ||
|
||
module DecisionReviews | ||
module V1 | ||
module NoticeOfDisagreements | ||
class ContestableIssuesController < AppealsBaseController | ||
service_tag 'board-appeal' | ||
|
||
def index | ||
render json: decision_review_service | ||
.get_notice_of_disagreement_contestable_issues(user: current_user) | ||
.body | ||
rescue => e | ||
log_exception_to_personal_information_log e, | ||
error_class: | ||
"#{self.class.name}#index exception #{e.class} (NOD_V1)" | ||
raise | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.