diff --git a/.docker/config/cable.yml b/.docker/config/cable.yml new file mode 100644 index 00000000..20178b86 --- /dev/null +++ b/.docker/config/cable.yml @@ -0,0 +1,14 @@ +development: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: medicaid_gateway_development + +test: + adapter: async + url: redis://localhost:6379/1 + channel_prefix: medicaid_gateway_test + +production: + adapter: redis + url: redis://<%= ENV['REDIS_HOST_MEDICAID_GATEWAY'] %>:6379/1 + channel_prefix: medicaid_gateway_production diff --git a/.vscode/settings.json b/.vscode/settings.json index 13d7db93..8a5b228c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,8 @@ }, "[ruby]": { "editor.defaultFormatter": "castwide.solargraph" + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" } } diff --git a/Gemfile b/Gemfile index 1ab6bfd4..0d674dab 100644 --- a/Gemfile +++ b/Gemfile @@ -8,8 +8,8 @@ ruby '2.7.2' # Mount the Engines gem 'mitc_service', path: 'components/mitc_service' -gem 'aca_entities', git: 'https://github.com/ideacrew/aca_entities.git', branch: 'release_0.10.0' -gem 'event_source', git: 'https://github.com/ideacrew/event_source.git', branch: 'release_0.5.5' +gem 'aca_entities', git: 'https://github.com/ideacrew/aca_entities.git', branch: 'trunk' +gem 'event_source', git: 'https://github.com/ideacrew/event_source.git', branch: 'trunk' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem 'rails', '~> 6.1.3' @@ -31,6 +31,8 @@ gem 'jbuilder', '~> 2.7' gem 'bootstrap', '~> 5.1.0' # To prettify json payloads gem 'awesome_print' +# For interactive pieces +gem "stimulus_reflex", "~> 3.4" # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.4', require: false @@ -78,4 +80,4 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] group :production do gem 'eye', '0.10.0' gem 'unicorn', '~> 4.8' -end +end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 8279a1f4..2ffa3c09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/ideacrew/aca_entities.git - revision: acccd54c96e8d9ef48e239a56ecd2b6536e88ff2 - branch: release_0.10.0 + revision: abd4c4e4bed30c5c1008479545df41d4f9e494b9 + branch: trunk specs: aca_entities (0.9.0) deep_merge @@ -17,8 +17,8 @@ GIT GIT remote: https://github.com/ideacrew/event_source.git - revision: 4ad8aae6c76d0a0fd64907861c9a5f52c17bc47a - branch: release_0.5.5 + revision: 29393ebb04df8f57ccb536da4635087c8c8bde0b + branch: trunk specs: event_source (0.5.5) bunny (>= 2.14) @@ -162,6 +162,9 @@ GEM amq-protocol (~> 2.3, >= 2.3.1) sorted_set (~> 1, >= 1.0.2) byebug (11.1.3) + cable_ready (4.5.0) + rails (>= 5.2) + thread-local (>= 1.1.0) capybara (3.35.3) addressable mini_mime (>= 0.1.3) @@ -472,7 +475,7 @@ GEM sprockets-rails tilt semantic_range (3.0.0) - set (1.0.1) + set (1.0.2) shoulda-matchers (3.1.3) activesupport (>= 4.0.0) sinatra (2.1.0) @@ -492,7 +495,14 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) state_machines (0.5.0) + stimulus_reflex (3.4.1) + cable_ready (>= 4.5) + nokogiri + rack + rails (>= 5.2) + redis thor (1.1.0) + thread-local (1.1.0) tilt (2.0.10) timers (4.3.3) turbolinks (5.2.1) @@ -561,6 +571,7 @@ DEPENDENCIES sass-rails (>= 6) shoulda-matchers (~> 3) spring (~> 1.7.2) + stimulus_reflex (~> 3.4) turbolinks (~> 5) typhoeus tzinfo-data diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index cbd46a7a..d133e83a 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1 +1,26 @@ @import "bootstrap"; + +kbd { + -moz-background-clip: border; + -moz-background-inline-policy: continuous; + -moz-background-origin: padding; + background: #ffffff none repeat scroll 0 0; + border: none; + color: #000000; + padding: 2px 1px; + white-space: nowrap; +} + +.long-payload { + display: -webkit-box; + -webkit-line-clamp: 10; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.table-overrides { + table-layout: fixed; + width: 100%; + word-wrap: break-word; +} diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 8d6c2a1b..5bb35e00 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,6 +1,14 @@ # frozen_string_literal: true module ApplicationCable + + # stimulus reflex connection differentiator class Connection < ActionCable::Connection::Base + identified_by :session_id + + def connect + self.session_id = request.session.id + reject_unauthorized_connection unless session_id + end end end diff --git a/app/controllers/medicaid/applications_controller.rb b/app/controllers/medicaid/applications_controller.rb new file mode 100644 index 00000000..1275dd4e --- /dev/null +++ b/app/controllers/medicaid/applications_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Medicaid + # Determinations from MITC + class ApplicationsController < ActionController::Base + def show + @application = Medicaid::Application.find(params[:id]) + render layout: "application" + end + end +end \ No newline at end of file diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 0a35e3d2..91f74813 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -3,44 +3,49 @@ # ReportsController provides API access to reports class ReportsController < ApplicationController + def events + @start_on = start_on || session[:start] || Date.today + @end_on = end_on || session[:end] || Date.today + events = applications + transfers + inbound_transfers + checks + @events = events.map(&:to_event).sort_by { |event| event[:created_at] }.reverse + end + def medicaid_applications - range = range_from_params - applications = Medicaid::Application.where(created_at: range).or(updated_at: range) - render json: applications + render json: Medicaid::Application.where(created_at: range_from_params).or(updated_at: range_from_params) end def medicaid_application_check - @range = range_from_params - @applications = Medicaid::Application.where(created_at: @range).or(updated_at: @range) + @start_on = start_on || session[:ma_start] || Date.today + @end_on = end_on || session[:ma_end] || Date.today + @applications = applications end def account_transfers - @range = range_from_params - @transfers = Aces::Transfer.where(created_at: @range).or(updated_at: @range) + @start_on = start_on || session[:atp_start] || Date.today + @end_on = end_on || session[:atp_end] || Date.today + @transfers = transfers end def account_transfers_to_enroll - @range = range_from_params - @transfers = Aces::InboundTransfer.where(created_at: @range).or(updated_at: @range) + @start_on = start_on || session[:atp_start] || Date.today + @end_on = end_on || session[:atp_end] || Date.today + @transfers = inbound_transfers end def mec_checks - @range = range_from_params - @checks = Aces::MecCheck.where(created_at: @range).or(updated_at: @range) + @start_on = start_on || session[:mc_sent_start] || Date.today + @end_on = session[:mc_sent_end] || Date.today + @checks = checks end def transfer_summary - @range = range_from_params - @start_on = params.fetch(:start_on) if params.key?(:start_on) - @end_on = params.fetch(:end_on) if params.key?(:end_on) - at_sent = Aces::Transfer.where(created_at: @range).or(updated_at: @range) - @at_sent_total = at_sent.count - @at_sent_successful = at_sent.where(failure: nil).count + @start_on = start_on || session[:atp_start] || Date.today + @end_on = end_on || session[:atp_end] || Date.today + @at_sent_total = transfers.count + @at_sent_successful = transfers.where(failure: nil).count @at_sent_failure = @at_sent_total - @at_sent_successful - - at_received = Aces::InboundTransfer.where(created_at: @range).or(updated_at: @range) - @at_received_total = at_received.count - @at_received_successful = at_received.where(failure: nil).count + @at_received_total = inbound_transfers.count + @at_received_successful = inbound_transfers.where(failure: nil).count @at_received_failure = @at_received_total - @at_received_successful end @@ -51,4 +56,32 @@ def range_from_params end_on = params.key?(:end_on) ? Date.strptime(params.fetch(:end_on), "%m/%d/%Y") : Time.now.utc start_on.beginning_of_day..end_on.end_of_day end + + def start_on + Date.strptime(params.fetch(:start_on), "%m/%d/%Y") if params.key?(:start_on) + end + + def end_on + Date.strptime(params.fetch(:end_on), "%m/%d/%Y") if params.key?(:end_on) + end + + def range + @start_on.beginning_of_day..@end_on.end_of_day + end + + def applications + Medicaid::Application.where(created_at: range).or(updated_at: range) + end + + def transfers + Aces::Transfer.where(created_at: range).or(updated_at: range) + end + + def inbound_transfers + Aces::InboundTransfer.where(created_at: range).or(updated_at: range) + end + + def checks + Aces::MecCheck.where(created_at: range).or(updated_at: range) + end end diff --git a/app/javascript/controllers/application_controller.js b/app/javascript/controllers/application_controller.js new file mode 100644 index 00000000..38c19a4e --- /dev/null +++ b/app/javascript/controllers/application_controller.js @@ -0,0 +1,60 @@ +import { Controller } from 'stimulus' +import StimulusReflex from 'stimulus_reflex' + +/* This is your ApplicationController. + * All StimulusReflex controllers should inherit from this class. + * + * Example: + * + * import ApplicationController from './application_controller' + * + * export default class extends ApplicationController { ... } + * + * Learn more at: https://docs.stimulusreflex.com + */ +export default class extends Controller { + connect () { + StimulusReflex.register(this) + } + + /* Application-wide lifecycle methods + * + * Use these methods to handle lifecycle concerns for the entire application. + * Using the lifecycle is optional, so feel free to delete these stubs if you don't need them. + * + * Arguments: + * + * element - the element that triggered the reflex + * may be different than the Stimulus controller's this.element + * + * reflex - the name of the reflex e.g. "Example#demo" + * + * error/noop - the error message (for reflexError), otherwise null + * + * reflexId - a UUID4 or developer-provided unique identifier for each Reflex + */ + + beforeReflex (element, reflex, noop, reflexId) { + // document.body.classList.add('wait') + } + + reflexSuccess (element, reflex, noop, reflexId) { + // show success message + } + + reflexError (element, reflex, error, reflexId) { + // show error message + } + + reflexHalted (element, reflex, error, reflexId) { + // handle aborted Reflex action + } + + afterReflex (element, reflex, noop, reflexId) { + // document.body.classList.remove('wait') + } + + finalizeReflex (element, reflex, noop, reflexId) { + // all operations have completed, animation etc is now safe + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 00000000..dcb15acf --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,14 @@ +// Load all the controllers within this directory and all subdirectories. +// Controller files must be named *_controller.js. + +import { Application } from "stimulus" +import { definitionsFromContext } from "stimulus/webpack-helpers" +import StimulusReflex from 'stimulus_reflex' +import consumer from '../channels/consumer' +import controller from '../controllers/application_controller' + +const application = Application.start() +const context = require.context("controllers", true, /_controller\.js$/) +application.load(definitionsFromContext(context)) +StimulusReflex.initialize(application, { consumer, controller, isolate: true }) +StimulusReflex.debug = process.env.RAILS_ENV === 'development' diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 26530c6c..2747eca5 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -9,3 +9,5 @@ import "channels" Rails.start() Turbolinks.start() + +import "controllers" diff --git a/app/models/aces/inbound_transfer.rb b/app/models/aces/inbound_transfer.rb index e290466a..83276723 100644 --- a/app/models/aces/inbound_transfer.rb +++ b/app/models/aces/inbound_transfer.rb @@ -39,5 +39,8 @@ def to_event } end + def resubmittable? + payload.present? && ['Sent', 'Failed'].include?(result) + end end end diff --git a/app/operations/curam/check_payload.rb b/app/operations/curam/check_payload.rb index 9c98e103..dfd6a3a1 100644 --- a/app/operations/curam/check_payload.rb +++ b/app/operations/curam/check_payload.rb @@ -19,9 +19,9 @@ def call(id) protected - def find_transfer(application_id) - transfers = Aces::Transfer.where(application_identifier: application_id) - transfers.any? ? transfers.last : Failure(:no_transfers_found) + def find_transfer(transfer_id) + transfer = Aces::Transfer.find(transfer_id) + transfer || Failure(:no_transfers_found) end def build_check_request(transfer) diff --git a/app/reflexes/application_reflex.rb b/app/reflexes/application_reflex.rb new file mode 100644 index 00000000..4953b728 --- /dev/null +++ b/app/reflexes/application_reflex.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ApplicationReflex < StimulusReflex::Reflex + # Put application-wide Reflex behavior and callbacks in this file. + # + # Example: + # + # # If your ActionCable connection is: `identified_by :current_user` + # delegate :current_user, to: :connection + # + # Learn more at: https://docs.stimulusreflex.com/reflexes#reflex-classes +end diff --git a/app/reflexes/report_reflex.rb b/app/reflexes/report_reflex.rb new file mode 100644 index 00000000..d1a4e08f --- /dev/null +++ b/app/reflexes/report_reflex.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Update report pages based on user actions +class ReportReflex < ApplicationReflex + + def change_date + session_name = element.dataset[:session] + session[session_name] = Date.parse(element.value) + end + + def check_payload + Curam::CheckPayload.new.call(element.dataset[:id]) + end + + def resubmit_to_enroll + Transfers::ToEnroll.new.call(element.dataset[:payload], element.dataset[:id]) + end +end \ No newline at end of file diff --git a/app/reports/transfer_report.rb b/app/reports/transfer_report.rb index cfc6e6a9..15498b08 100644 --- a/app/reports/transfer_report.rb +++ b/app/reports/transfer_report.rb @@ -13,30 +13,54 @@ def self.run def self.run_report(start_on, end_on) timestamp = Time.zone.now.to_s range = start_on.beginning_of_day..end_on.end_of_day + + report_name = "transfer_report_#{timestamp}.csv" + FileUtils.touch(report_name) + CSV.open(report_name, "w") do |csv| + csv << headers + row = [range] + transfer_values(range) + + if MedicaidGatewayRegistry.feature_enabled?(:transfer_to_enroll) + inbound_vals = inbound_transfer_values(range) + row.insert(2, inbound_vals[:total]) + row << inbound_vals[:successful] + row << inbound_vals[:failure] + end + + csv << row + end + end + + def self.headers + if MedicaidGatewayRegistry.feature_enabled?(:transfer_to_enroll) + %w[DateRange Sent Received SentSuccesses SentFailures ReceivedSuccesses ReceivedFailures] + else + %w[DateRange Sent SentSuccesses SentFailures] + end + end + + def self.transfer_values(range) at_sent = Aces::Transfer.where(created_at: range).or(updated_at: range) at_sent_total = at_sent.count at_sent_successful = at_sent.where(failure: nil).count at_sent_failure = at_sent_total - at_sent_successful + [ + at_sent_total, + at_sent_successful, + at_sent_failure + ] + end + def self.inbound_transfer_values(range) at_received = Aces::InboundTransfer.where(created_at: range).or(updated_at: range) at_received_total = at_received.count at_received_successful = at_received.where(failure: nil).count at_received_failure = at_received_total - at_received_successful - - report_name = "transfer_report_#{timestamp}.csv" - FileUtils.touch(report_name) - CSV.open(report_name, "w") do |csv| - csv << %w[DateRange Sent Received SentSuccesses SentFailures ReceivedSuccesses ReceivedFailures] - csv << [ - range, - at_sent_total, - at_received_total, - at_sent_successful, - at_sent_failure, - at_received_successful, - at_received_failure - ] - end + { + total: at_received_total, + successful: at_received_successful, + failure: at_received_failure + } end end diff --git a/app/views/aces/inbound_transfers/show.html.erb b/app/views/aces/inbound_transfers/show.html.erb index 0a7c3772..cf667143 100644 --- a/app/views/aces/inbound_transfers/show.html.erb +++ b/app/views/aces/inbound_transfers/show.html.erb @@ -1,7 +1,7 @@
Updated At: <%= @transfer.created_at %>
Processed At: <%= @transfer.updated_at %>
-Transfer Status: <%= @transfer.failure.nil? ? 'Success' : 'Failure' %>
+Transfer Status: <%= @transfer.successful? ? 'Success' : 'Failure' %>
Ingestion Status: <%= @transfer.result %>
Application Identifier: <%= @transfer.application_identifier %>
Family Identifier: <%= @transfer.family_identifier %>
@@ -19,6 +19,9 @@ <%= @transfer.payload %> <% end %> - - -<%= link_to "See today's account transfers to Enroll", account_transfers_to_enroll_reports_path %> +<% unless @transfer.failure.blank? %> +Created At: <%= @transfer.created_at %>
Processed At: <%= @transfer.updated_at %>
-Transfer Status: <%= @transfer.failure.nil? ? 'Success' : 'Failure' %>
+Transfer Status: <%= @transfer.successful? ? 'Success' : 'Failure' %>
Ingestion Status: <%= @transfer.callback_status %>
Application Identifier: <%= @transfer.application_identifier %>
Family Identifier: <%= @transfer.family_identifier %>
@@ -23,4 +23,9 @@ <%= raw(ap(JSON.parse(@transfer.callback_payload))) %> <% end %> -<%= link_to "See today's account transfers", account_transfers_reports_path %> \ No newline at end of file +<% unless @transfer.failure.blank? %> +