Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFS-2336: First sketch of multi-provider search #442

Merged
merged 11 commits into from
Feb 12, 2025
1 change: 1 addition & 0 deletions app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ [email protected]
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=primary
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=deterministic
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=derivationsalt
SUPPORTED_PROVIDERS=pinwheel
6 changes: 5 additions & 1 deletion app/app/controllers/cbv/employer_searches_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Cbv::EmployerSearchesController < Cbv::BaseController

def show
@query = search_params[:query]
@employers = @query.blank? ? [] : fetch_employers(@query)
@employers = @query.blank? ? [] : provider_search(@query)
@has_pinwheel_account = @cbv_flow.pinwheel_accounts.any?
@selected_tab = search_params[:type] || "payroll"

Expand All @@ -20,6 +20,10 @@ def show

private

def provider_search(query = "")
ProviderSearchService.new(@cbv_flow.site_id).search(query)
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the memory implications of instantiating this every time? It's only really used for looking up some values specific to the partner agency configuration...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's only used in once place so it doesn't really matter right now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory implications are probably not too bad, since GC should clear it out automatically after the method runs. There is a tiny performance hit in allocating and then GCing it, but whatever, not worth worrying about.


def search_params
params.slice(:query, :type)
end
Expand Down
43 changes: 43 additions & 0 deletions app/app/services/argyle_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require "faraday"

class ArgyleService
ENVIRONMENTS = {
sandbox: {
base_url: "https://api-sandbox.argyle.com/v2",
api_key_id: ENV["ARGYLE_API_TOKEN_SANDBOX_ID"],
api_key_secret: ENV["ARGYLE_API_TOKEN_SANDBOX_SECRET"]
}
}

def initialize(environment, api_key_id = nil, api_key_secret = nil)
@api_key_id = api_key_id || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_id]
@api_key_secret = api_key_secret || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_secret]
@environment = ENVIRONMENTS.fetch(environment.to_sym) { |env| raise KeyError.new("ArgyleService unknown environment: #{env}") }

client_options = {
request: {
open_timeout: 5,
timeout: 5,
params_encoder: Faraday::FlatParamsEncoder
},
url: @environment[:base_url]
}
@http = Faraday.new(client_options) do |conn|
conn.set_basic_auth @api_key_id, @api_key_secret
conn.response :raise_error
conn.response :json, content_type: "application/json"
conn.response :logger,
Rails.logger,
headers: true,
bodies: true,
log_level: :debug
end
end

# Fetch all Argyle items
def items(query = nil)
@http.get("items", { q: query }).body
end
end
68 changes: 68 additions & 0 deletions app/app/services/provider_search_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class ProviderSearchService
PROVIDER_RESULT = Struct.new(:provider_name, :provider_options, :name, :logo_url, keyword_init: true)

SUPPORTED_PROVIDERS = (ENV["SUPPORTED_PROVIDERS"] || "pinwheel")&.split(",")&.map(&:to_sym)

def initialize(client_agency_id)
client_agency_config = site_config[client_agency_id]

@providers = SUPPORTED_PROVIDERS.map do |provider|
case provider
when :pinwheel
PinwheelAdapter.new(client_agency_config.pinwheel_environment)
when :argyle
ArgyleAdapter.new(client_agency_config.argyle_environment)
end
end
end

def search(query = "")
@providers.map { |provider| provider.query(query) }.flatten
end

private

def site_config
Rails.application.config.sites
end

class PinwheelAdapter
def initialize(environment)
@pinwheel = PinwheelService.new(environment)
end

def query(query)
@pinwheel.fetch_items(q: query)["data"].map do |result|
PROVIDER_RESULT.new(
provider_name: :pinwheel,
provider_options: {
response_type: result["response_type"],
provider_id: result["id"]
},
name: result["name"],
logo_url: result["logo_url"]
)
end
end
end

class ArgyleAdapter
def initialize(environment)
@argyle = ArgyleService.new(environment)
end

def query(query)
@argyle.items(query)["results"].map do |result|
PROVIDER_RESULT.new(
provider_name: :argyle,
provider_options: {
response_type: result["kind"],
provider_id: result["id"]
},
name: result["name"],
logo_url: result["logo_url"]
)
end
end
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now after doing all of this I wonder if it makes sense just to have the services themselves assume a common interface. I don't know if you'd enforce it. There's no reason why PROVIDER_RESULT can't be used in the service classes themselves? 🤔

end
12 changes: 6 additions & 6 deletions app/app/views/cbv/employer_searches/_employer.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
<div class="usa-card usa-card--flag usa-card--media-right flex-1">
<div class="usa-card__container">
<div class="usa-card__header">
<h3 class="usa-card__heading"><%= employer["name"] %></h3>
<h3 class="usa-card__heading"><%= employer.name %></h3>
</div>
<div class="display-none usa-card__media usa-card__media--inset">
<% if employer['logo_url'] %>
<% if employer.logo_url %>
<div class="usa-card__img">
<img
src="<%= employer["logo_url"] %>"
src="<%= employer.logo_url %>"
alt="A placeholder image"
>
</div>
Expand All @@ -24,10 +24,10 @@
<div class="usa-card__footer">
<button
data-action="click->cbv-employer-search#select"
data-id="<%= employer["id"] %>"
data-response-type="<%= employer["response_type"] %>"
data-id="<%= employer.provider_options["provider_id"] %>"
data-response-type="<%= employer.provider_options["response_type"] %>"
data-is-default-option="false"
data-name="<%= employer["name"] %>"
data-name="<%= employer.name %>"
data-cbv-employer-search-target="employerButton"
class="usa-button usa-button--outline"
type="button"
Expand Down
6 changes: 6 additions & 0 deletions app/config/site-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
logo_square_path: hra_logo_square.png
pinwheel:
environment: <%= ENV["NYC_PINWHEEL_ENVIRONMENT"] %>
argyle:
environment: <%= ENV["SANDBOX_ARGYLE_ENVIRONMENT"] %>
transmission_method: shared_email
transmission_method_configuration:
email: <%= ENV['NYC_HRA_EMAIL'] %>
Expand All @@ -28,6 +30,8 @@
caseworker_feedback_form: https://forms.office.com/r/rfV04qjG9A
pinwheel:
environment: <%= ENV["MA_PINWHEEL_ENVIRONMENT"] %>
argyle:
environment: <%= ENV["SANDBOX_ARGYLE_ENVIRONMENT"] %>
transmission_method: s3
transmission_method_configuration:
bucket: <%= ENV['MA_DTA_S3_BUCKET'] %>
Expand All @@ -53,6 +57,8 @@
caseworker_feedback_form: https://docs.google.com/forms/d/e/1FAIpQLSfrUiz0oWE5jbXjPfl-idQQGPgxKplqFtcKq08UOhTaEa2k6A/viewform
pinwheel:
environment: <%= ENV["SANDBOX_PINWHEEL_ENVIRONMENT"] %>
argyle:
environment: <%= ENV["SANDBOX_ARGYLE_ENVIRONMENT"] %>
transmission_method: shared_email
transmission_method_configuration:
email: <%= ENV['SLACK_TEST_EMAIL'] %>
Expand Down
2 changes: 2 additions & 0 deletions app/lib/site_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Site
pay_income_days
pinwheel_api_token
pinwheel_environment
argyle_environment
staff_portal_enabled
sso
transmission_method
Expand All @@ -50,6 +51,7 @@ def initialize(yaml)
@logo_square_path = yaml["logo_square_path"]
@pay_income_days = yaml["pay_income_days"]
@pinwheel_environment = yaml["pinwheel"]["environment"] || "sandbox"
@argyle_environment = yaml["argyle"]["environment"] || "sandbox"
@transmission_method = yaml["transmission_method"]
@transmission_method_configuration = yaml["transmission_method_configuration"]
@staff_portal_enabled = yaml["staff_portal_enabled"]
Expand Down
6 changes: 4 additions & 2 deletions app/spec/lib/site_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
- id: foo
agency_name: Foo Agency Name
pinwheel:
api_token: foo
environment: foo
argyle:
environment: foo
- id: bar
agency_name: Bar Agency Name
pinwheel:
api_token: bar
environment: bar
argyle:
environment: foo
YAML

before do
Expand Down
Loading