mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +02:00
Show UI warning to user when they need provider data but have not setup Synth yet (#1926)
* Simplify provider concerns * Update tests * Add UI warning for missing Synth key if family requires external data
This commit is contained in:
parent
624faa10d0
commit
fa0248056d
22 changed files with 184 additions and 136 deletions
|
@ -1,6 +1,4 @@
|
|||
class Account::DataEnricher
|
||||
include Providable
|
||||
|
||||
attr_reader :account
|
||||
|
||||
def initialize(account)
|
||||
|
@ -37,7 +35,7 @@ class Account::DataEnricher
|
|||
|
||||
candidates.each do |entry|
|
||||
begin
|
||||
info = self.class.synth_provider.enrich_transaction(entry.name).info
|
||||
info = entry.fetch_enrichment_info
|
||||
|
||||
next unless info.present?
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Account::Entry < ApplicationRecord
|
||||
include Monetizable
|
||||
include Monetizable, Provided
|
||||
|
||||
monetize :amount
|
||||
|
||||
|
|
11
app/models/account/entry/provided.rb
Normal file
11
app/models/account/entry/provided.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Account::Entry::Provided
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Synthable
|
||||
|
||||
def fetch_enrichment_info
|
||||
return nil unless synth_client.present?
|
||||
|
||||
synth_client.enrich_transaction(name).info
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
# `Providable` serves as an extension point for integrating multiple providers.
|
||||
# For an example of a multi-provider, multi-concept implementation,
|
||||
# see: https://github.com/maybe-finance/maybe/pull/561
|
||||
|
||||
module Providable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def security_prices_provider
|
||||
synth_provider
|
||||
end
|
||||
|
||||
def security_info_provider
|
||||
synth_provider
|
||||
end
|
||||
|
||||
def exchange_rates_provider
|
||||
synth_provider
|
||||
end
|
||||
|
||||
def git_repository_provider
|
||||
Provider::Github.new
|
||||
end
|
||||
|
||||
def synth_provider
|
||||
api_key = self_hosted? ? Setting.synth_api_key : ENV["SYNTH_API_KEY"]
|
||||
api_key.present? ? Provider::Synth.new(api_key) : nil
|
||||
end
|
||||
|
||||
private
|
||||
def self_hosted?
|
||||
Rails.application.config.app_mode.self_hosted?
|
||||
end
|
||||
end
|
||||
end
|
37
app/models/concerns/synthable.rb
Normal file
37
app/models/concerns/synthable.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
module Synthable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def synth_usage
|
||||
synth_client&.usage
|
||||
end
|
||||
|
||||
def synth_overage?
|
||||
synth_usage&.usage&.utilization.to_i >= 100
|
||||
end
|
||||
|
||||
def synth_healthy?
|
||||
synth_client&.healthy?
|
||||
end
|
||||
|
||||
def synth_client
|
||||
api_key = ENV.fetch("SYNTH_API_KEY", Setting.synth_api_key)
|
||||
|
||||
return nil unless api_key.present?
|
||||
|
||||
Provider::Synth.new(api_key)
|
||||
end
|
||||
end
|
||||
|
||||
def synth_client
|
||||
self.class.synth_client
|
||||
end
|
||||
|
||||
def synth_usage
|
||||
self.class.synth_usage
|
||||
end
|
||||
|
||||
def synth_overage?
|
||||
self.class.synth_overage?
|
||||
end
|
||||
end
|
|
@ -1,19 +1,18 @@
|
|||
module ExchangeRate::Provided
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Providable
|
||||
include Synthable
|
||||
|
||||
class_methods do
|
||||
def provider_healthy?
|
||||
exchange_rates_provider.present? && exchange_rates_provider.healthy?
|
||||
def provider
|
||||
synth_client
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_rates_from_provider(from:, to:, start_date:, end_date: Date.current, cache: false)
|
||||
return [] unless exchange_rates_provider.present?
|
||||
return [] unless provider.present?
|
||||
|
||||
response = exchange_rates_provider.fetch_exchange_rates \
|
||||
response = provider.fetch_exchange_rates \
|
||||
from: from,
|
||||
to: to,
|
||||
start_date: start_date,
|
||||
|
@ -38,9 +37,9 @@ module ExchangeRate::Provided
|
|||
end
|
||||
|
||||
def fetch_rate_from_provider(from:, to:, date:, cache: false)
|
||||
return nil unless exchange_rates_provider.present?
|
||||
return nil unless provider.present?
|
||||
|
||||
response = exchange_rates_provider.fetch_exchange_rate \
|
||||
response = provider.fetch_exchange_rate \
|
||||
from: from,
|
||||
to: to,
|
||||
date: date
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Family < ApplicationRecord
|
||||
include Providable, Plaidable, Syncable, AutoTransferMatchable
|
||||
include Synthable, Plaidable, Syncable, AutoTransferMatchable
|
||||
|
||||
DATE_FORMATS = [
|
||||
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
||||
|
@ -92,22 +92,25 @@ class Family < ApplicationRecord
|
|||
).link_token
|
||||
end
|
||||
|
||||
def synth_usage
|
||||
self.class.synth_provider&.usage
|
||||
end
|
||||
|
||||
def synth_overage?
|
||||
self.class.synth_provider&.usage&.utilization.to_i >= 100
|
||||
end
|
||||
|
||||
def synth_valid?
|
||||
self.class.synth_provider&.healthy?
|
||||
end
|
||||
|
||||
def subscribed?
|
||||
stripe_subscription_status == "active"
|
||||
end
|
||||
|
||||
def requires_data_provider?
|
||||
# If family has any trades, they need a provider for historical prices
|
||||
return true if trades.any?
|
||||
|
||||
# If family has any accounts not denominated in the family's currency, they need a provider for historical exchange rates
|
||||
return true if accounts.where.not(currency: self.currency).any?
|
||||
|
||||
# If family has any entries in different currencies, they need a provider for historical exchange rates
|
||||
uniq_currencies = entries.pluck(:currency).uniq
|
||||
return true if uniq_currencies.count > 1
|
||||
return true if uniq_currencies.first != self.currency
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def primary_user
|
||||
users.order(:created_at).first
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Security < ApplicationRecord
|
||||
include Providable
|
||||
include Provided
|
||||
|
||||
before_save :upcase_ticker
|
||||
|
||||
|
@ -9,21 +9,6 @@ class Security < ApplicationRecord
|
|||
validates :ticker, presence: true
|
||||
validates :ticker, uniqueness: { scope: :exchange_operating_mic, case_sensitive: false }
|
||||
|
||||
class << self
|
||||
def provider
|
||||
security_prices_provider
|
||||
end
|
||||
|
||||
def search(query)
|
||||
security_prices_provider.search_securities(
|
||||
query: query[:search],
|
||||
dataset: "limited",
|
||||
country_code: query[:country],
|
||||
exchange_operating_mic: query[:exchange_operating_mic]
|
||||
).securities.map { |attrs| new(**attrs) }
|
||||
end
|
||||
end
|
||||
|
||||
def current_price
|
||||
@current_price ||= Security::Price.find_price(security: self, date: Date.current)
|
||||
return nil if @current_price.nil?
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
module Security::Price::Provided
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Providable
|
||||
include Synthable
|
||||
|
||||
class_methods do
|
||||
private
|
||||
def provider
|
||||
synth_client
|
||||
end
|
||||
|
||||
private
|
||||
def fetch_price_from_provider(security:, date:, cache: false)
|
||||
return nil unless security_prices_provider.present?
|
||||
return nil unless provider.present?
|
||||
return nil unless security.has_prices?
|
||||
|
||||
response = security_prices_provider.fetch_security_prices \
|
||||
response = provider.fetch_security_prices \
|
||||
ticker: security.ticker,
|
||||
mic_code: security.exchange_mic,
|
||||
start_date: date,
|
||||
|
@ -31,11 +34,11 @@ module Security::Price::Provided
|
|||
end
|
||||
|
||||
def fetch_prices_from_provider(security:, start_date:, end_date:, cache: false)
|
||||
return [] unless security_prices_provider.present?
|
||||
return [] unless provider.present?
|
||||
return [] unless security
|
||||
return [] unless security.has_prices?
|
||||
|
||||
response = security_prices_provider.fetch_security_prices \
|
||||
response = provider.fetch_security_prices \
|
||||
ticker: security.ticker,
|
||||
mic_code: security.exchange_mic,
|
||||
start_date: start_date,
|
||||
|
|
26
app/models/security/provided.rb
Normal file
26
app/models/security/provided.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
module Security::Provided
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Synthable
|
||||
|
||||
class_methods do
|
||||
def provider
|
||||
synth_client
|
||||
end
|
||||
|
||||
def search_provider(query)
|
||||
response = provider.search_securities(
|
||||
query: query[:search],
|
||||
dataset: "limited",
|
||||
country_code: query[:country],
|
||||
exchange_operating_mic: query[:exchange_operating_mic]
|
||||
)
|
||||
|
||||
if response.success?
|
||||
response.securities.map { |attrs| new(**attrs) }
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -75,10 +75,7 @@ class TradeImport < Import
|
|||
return internal_security if internal_security.present?
|
||||
|
||||
# If security prices provider isn't properly configured or available, create with nil exchange_operating_mic
|
||||
provider = Security.security_prices_provider
|
||||
unless provider.present? && provider.respond_to?(:search_securities)
|
||||
return Security.find_or_create_by!(ticker: ticker, exchange_operating_mic: nil)
|
||||
end
|
||||
return Security.find_or_create_by!(ticker: ticker, exchange_operating_mic: nil) unless Security.provider.present?
|
||||
|
||||
# Cache provider responses so that when we're looping through rows and importing,
|
||||
# we only hit our provider for the unique combinations of ticker / exchange_operating_mic
|
||||
|
@ -86,18 +83,10 @@ class TradeImport < Import
|
|||
@provider_securities_cache ||= {}
|
||||
|
||||
provider_security = @provider_securities_cache[cache_key] ||= begin
|
||||
response = provider.search_securities(
|
||||
Security.search_provider(
|
||||
query: ticker,
|
||||
exchange_operating_mic: exchange_operating_mic
|
||||
)
|
||||
|
||||
if !response || !response.success? || !response.securities || response.securities.empty?
|
||||
nil
|
||||
else
|
||||
response.securities.first
|
||||
end
|
||||
rescue => e
|
||||
nil
|
||||
).first
|
||||
end
|
||||
|
||||
return Security.find_or_create_by!(ticker: ticker, exchange_operating_mic: nil) if provider_security.nil?
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
module Upgrader::Provided
|
||||
extend ActiveSupport::Concern
|
||||
include Providable
|
||||
|
||||
class_methods do
|
||||
private
|
||||
def fetch_latest_upgrade_candidates_from_provider
|
||||
git_repository_provider.fetch_latest_upgrade_candidates
|
||||
end
|
||||
|
||||
def git_repository_provider
|
||||
Provider::Github.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue