Skip to content

Commit

Permalink
Merge branch 'main' into TIMO/readme-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
millerti committed Sep 30, 2024
2 parents 7ea5f4b + 35ab18f commit 881cabe
Show file tree
Hide file tree
Showing 35 changed files with 1,345 additions and 239 deletions.
3 changes: 3 additions & 0 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ brew "tfenv"
# AWS command-line utilities necessary for deploying and operations
brew "awscli"
cask "session-manager-plugin"

# necessary for file encryption
brew "gpg"
2 changes: 1 addition & 1 deletion app/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ gem "sprockets-rails"
gem "pg", "~> 1.1"

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", "~> 5.0"
gem "puma", "~> 6.4.3"

# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"
Expand Down
6 changes: 3 additions & 3 deletions app/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ GEM
psych (5.1.2)
stringio
public_suffix (6.0.1)
puma (5.6.8)
puma (6.4.3)
nio4r (~> 2.0)
racc (1.8.1)
rack (2.2.9)
Expand Down Expand Up @@ -510,7 +510,7 @@ GEM
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
webrick (1.8.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand Down Expand Up @@ -557,7 +557,7 @@ DEPENDENCIES
pg (~> 1.1)
pg-aws_rds_iam (~> 0.5.0)
premailer-rails
puma (~> 5.0)
puma (~> 6.4.3)
rails (~> 7.1.3, >= 7.1.3.4)
rails-controller-testing
rails-erd (~> 1.7)
Expand Down
2 changes: 1 addition & 1 deletion app/app/components/pinwheel_data_point_component.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<%= render(TableRowComponent.new(label: @field[:label], value: @field[:value], highlight: @highlight)) %>
<%= render(TableRowComponent.new(@field[:label], @field[:value], highlight: @highlight)) %>
5 changes: 3 additions & 2 deletions app/app/components/table_row_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<tr class="<%= @highlight && "cbv-row-highlight" %>">
<td><%= @label %></td>
<td><%= @value %></td>
<% @cells.each do |cell| %>
<td><%= cell %></td>
<% end %>
</tr>
5 changes: 2 additions & 3 deletions app/app/components/table_row_component.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# frozen_string_literal: true

class TableRowComponent < ViewComponent::Base
def initialize(label:, value:, highlight: false)
@label = label
@value = value
def initialize(*cells, highlight: false)
@cells = cells
@highlight = highlight
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def create

if @cbv_flow_invitation.errors.any?
error_count = @cbv_flow_invitation.errors.size
error_header = "#{"error".pluralize(error_count)} occurred"
error_header = "#{helpers.pluralize(error_count, 'error')} occurred"

# Collect error messages without attribute names
error_messages = @cbv_flow_invitation.errors.messages.values.flatten.map { |msg| "<li>#{msg}</li>" }.join
Expand Down
8 changes: 6 additions & 2 deletions app/app/controllers/cbv/summaries_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def show
cbv_flow_id: @cbv_flow.id
})

render pdf: "#{@cbv_flow.id}", layout: "pdf", locals: { is_caseworker: false }, footer: { right: "Income Verification Report | Page [page] of [topage]", font_size: 10 }
render pdf: "#{@cbv_flow.id}",
layout: "pdf",
locals: { is_caseworker: Rails.env.development? && params[:is_caseworker] },
footer: { right: "Income Verification Report | Page [page] of [topage]", font_size: 10 }
end
end
end
Expand Down Expand Up @@ -100,6 +103,7 @@ def transmit_to_caseworker
# Generate PDF
pdf_service = PdfService.new
@pdf_output = pdf_service.generate(
renderer: self,
template: "cbv/summaries/show",
variables: {
is_caseworker: true,
Expand All @@ -108,7 +112,7 @@ def transmit_to_caseworker
employments: @employments,
incomes: @incomes,
identities: @identities,
payments_grouped_by_employer: summarize_by_employer(@payments, @employments, @incomes, @identities),
payments_grouped_by_employer: summarize_by_employer(@payments, @employments, @incomes, @identities, @cbv_flow.pinwheel_accounts),
has_consent: has_consent
}
)
Expand Down
18 changes: 8 additions & 10 deletions app/app/controllers/concerns/cbv/reports_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Cbv::ReportsHelper
include Cbv::PaymentsHelper

def payments_grouped_by_employer
summarize_by_employer(@payments, @employments, @incomes, @identities)
summarize_by_employer(@payments, @employments, @incomes, @identities, @cbv_flow.pinwheel_accounts)
end

def set_employments(account_id = nil)
Expand All @@ -23,26 +23,24 @@ def total_gross_income
@payments.reduce(0) { |sum, payment| sum + payment[:gross_pay_amount] }
end

def summarize_by_employer(payments, employments, incomes, identities)
payments
.each_with_object({}) do |payment, hash|
account_id = payment[:account_id]
pinwheel_account = PinwheelAccount.find_by_pinwheel_account_id(account_id)
def summarize_by_employer(payments, employments, incomes, identities, pinwheel_accounts)
pinwheel_accounts
.each_with_object({}) do |pinwheel_account, hash|
account_id = pinwheel_account.pinwheel_account_id
has_income_data = pinwheel_account.job_succeeded?("income")
has_employment_data = pinwheel_account.job_succeeded?("employment")
has_identity_data = pinwheel_account.job_succeeded?("identity")
account_payments = payments.filter { |payment| payment[:account_id] == account_id }
hash[account_id] ||= {
total: 0,
payments: [],
total: account_payments.sum { |payment| payment[:gross_pay_amount] },
payments: account_payments,
has_income_data: has_income_data,
has_employment_data: has_employment_data,
has_identity_data: has_identity_data,
income: has_income_data && incomes.find { |income| income["account_id"] == account_id },
employment: has_employment_data && employments.find { |employment| employment["account_id"] == account_id },
identity: has_identity_data && identities.find { |identity| identity["account_id"] == account_id }
}
hash[account_id][:total] += payment[:gross_pay_amount]
hash[account_id][:payments] << payment
end
end

Expand Down
27 changes: 27 additions & 0 deletions app/app/controllers/webhooks/pinwheel/events_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def create
PinwheelAccount
.create_with(cbv_flow: @cbv_flow, supported_jobs: supported_jobs)
.find_or_create_by(pinwheel_account_id: params["payload"]["account_id"])
track_account_created_event(@cbv_flow, params["payload"]["platform_name"])
end

if PinwheelAccount::EVENTS_MAP.keys.include?(params["event"])
Expand All @@ -26,6 +27,8 @@ def create
end

if pinwheel_account.has_fully_synced?
track_account_synced_event(@cbv_flow, pinwheel_account)

PaystubsChannel.broadcast_to(@cbv_flow, {
event: "cbv.payroll_data_available",
account_id: params["payload"]["account_id"]
Expand All @@ -50,6 +53,30 @@ def authorize_webhook
end
end

def track_account_synced_event(cbv_flow, pinwheel_account)
NewRelicEventTracker.track("PinwheelAccountSyncFinished", {
cbv_flow_id: cbv_flow.id,
identity_success: pinwheel_account.job_succeeded?("identity"),
identity_supported: pinwheel_account.supported_jobs.include?("identity"),
income_success: pinwheel_account.job_succeeded?("income"),
income_supported: pinwheel_account.supported_jobs.include?("income"),
paystubs_success: pinwheel_account.job_succeeded?("paystubs"),
paystubs_supported: pinwheel_account.supported_jobs.include?("paystubs"),
employment_success: pinwheel_account.job_succeeded?("employment"),
employment_supported: pinwheel_account.supported_jobs.include?("employment"),
sync_duration_seconds: Time.now - pinwheel_account.created_at
})
rescue => ex
Rails.logger.error "Unable to track NewRelic event (PinwheelAccountSyncFinished): #{ex}"
end

def track_account_created_event(cbv_flow, platform_name)
NewRelicEventTracker.track("PinwheelAccountCreated", {
cbv_flow_id: cbv_flow.id,
platform_name: platform_name
})
end

def set_cbv_flow
@cbv_flow = CbvFlow.find_by_pinwheel_end_user_id(params["payload"]["end_user_id"])
end
Expand Down
35 changes: 26 additions & 9 deletions app/app/models/cbv_flow_invitation.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
class CbvFlowInvitation < ApplicationRecord
EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP
# We're opting not to use URI::MailTo::EMAIL_REGEXP
# https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
#
# EXCERPT: This requirement is a willful violation of RFC 5322, which defines a syntax for email addresses
# that is simultaneously too strict (before the "@" character), too vague (after the "@" character),
# and too lax (allowing comments, whitespace characters, and quoted strings in manners unfamiliar to most users)
# to be of practical use here.
EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z\d\-]+\z/i

# Massachusetts: 7 digits
MA_AGENCY_ID_REGEX = /\A\d{7}\z/
Expand All @@ -13,6 +20,12 @@ class CbvFlowInvitation < ApplicationRecord
# New York City: 2 uppercase letters, followed by 5 digits, followed by 1 uppercase letter
NYC_CLIENT_ID_REGEX = /\A[A-Z]{2}\d{5}[A-Z]\z/

# Invitation validity time zone
INVITATION_VALIDITY_TIME_ZONE = "America/New_York"

# Paystub report range
PAYSTUB_REPORT_RANGE = 90.days

belongs_to :user
has_many :cbv_flows

Expand All @@ -25,18 +38,17 @@ class CbvFlowInvitation < ApplicationRecord
validates :first_name, presence: true
validates :last_name, presence: true
validates :email_address, format: { with: EMAIL_REGEX, message: :invalid_format }
validates :snap_application_date, presence: true

# MA specific validations
validates :agency_id_number, format: { with: MA_AGENCY_ID_REGEX, message: :invalid_format }, if: :ma_site?
validates :beacon_id, format: { with: MA_BEACON_ID_REGEX, message: :invalid_format }, if: :ma_site?
validate :ma_snap_application_date_not_more_than_1_year_ago, if: :ma_site?
validate :ma_snap_application_date_not_in_future
validate :ma_snap_application_date_not_in_future, if: :ma_site?


# NYC specific validations
validates :case_number, presence: true, format: { with: NYC_CASE_NUMBER_REGEX, message: :invalid_format }, if: :nyc_site?
validates :client_id_number, format: { with: NYC_CLIENT_ID_REGEX, message: :invalid_format }, if: -> { nyc_site? && client_id_number.present? }
validates :case_number, format: { with: NYC_CASE_NUMBER_REGEX, message: :invalid_format }, if: :nyc_site?
validates :client_id_number, format: { with: NYC_CLIENT_ID_REGEX, message: :invalid_format }, if: :nyc_site?
validate :nyc_snap_application_date_not_more_than_30_days_ago, if: :nyc_site?
validate :nyc_snap_application_date_not_in_future, if: :nyc_site?

Expand All @@ -54,9 +66,6 @@ class CbvFlowInvitation < ApplicationRecord
auth_token: :string
)

INVITATION_VALIDITY_TIME_ZONE = "America/New_York"
PAYSTUB_REPORT_RANGE = 90.days

scope :unstarted, -> { left_outer_joins(:cbv_flows).where(cbv_flows: { id: nil }) }

# Invitations are valid until 11:59pm Eastern Time on the (e.g.) 14th day
Expand Down Expand Up @@ -105,7 +114,15 @@ def parse_snap_application_date
new_date_format = Date.strptime(raw_snap_application_date.to_s, "%m/%d/%Y")
self.snap_application_date = new_date_format
rescue Date::Error => e
errors.add(:snap_application_date, :invalid_date)
case site_id
when "ma"
error = :ma_invalid_date
when "nyc"
error = :nyc_invalid_date
else
error = :invalid_date
end
errors.add(:snap_application_date, error)
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion app/app/services/data_retention_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def redact_incomplete_cbv_flows
.includes(:cbv_flow_invitation)
.find_each do |cbv_flow|
invitation_redact_at = cbv_flow.cbv_flow_invitation.expires_at + REDACT_UNUSED_INVITATIONS_AFTER
next unless Time.now.after?(invitation_redact_at)

cbv_flow.redact! if Time.now.after?(invitation_redact_at)
cbv_flow.redact!
cbv_flow.cbv_flow_invitation.redact! if cbv_flow.cbv_flow_invitation.present?
end
end
Expand Down
5 changes: 2 additions & 3 deletions app/app/services/pdf_service.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require "pdf-reader"

class PdfService
include ApplicationHelper
# Represents the result of PDF generation
class PdfGenerationResult
attr_reader :content, :html, :page_count, :file_size
Expand All @@ -14,8 +13,8 @@ def initialize(content, html, page_count, file_size)
end
end

def generate(template:, variables: {})
html_content = ApplicationController.renderer.render_to_string(
def generate(renderer:, template:, variables: {})
html_content = renderer.render_to_string(
template: template,
formats: [ :pdf ],
layout: "layouts/pdf",
Expand Down
2 changes: 2 additions & 0 deletions app/app/services/session_invalidation_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def initialize(user, session_id)
end

def valid?
return false unless @user.present?

(@user.invalidated_session_ids || {}).exclude?(@session_id)
end

Expand Down
Loading

0 comments on commit 881cabe

Please sign in to comment.