diff --git a/app/controllers/frontend_log_controller.rb b/app/controllers/frontend_log_controller.rb index ef4ea773cd7..dc570ccefb9 100644 --- a/app/controllers/frontend_log_controller.rb +++ b/app/controllers/frontend_log_controller.rb @@ -1,6 +1,4 @@ class FrontendLogController < ApplicationController - class FrontendError < StandardError; end - respond_to :json skip_before_action :verify_authenticity_token @@ -10,11 +8,10 @@ class FrontendError < StandardError; end # In rare circumstances, these writes can clobber other, more important writes. before_action :skip_session_commit - FRONTEND_ERROR_EVENT = 'Frontend Error'.freeze - # Please try to keep this list alphabetical as well! # rubocop:disable Layout/LineLength EVENT_MAP = { + 'Frontend Error' => FrontendErrorLogger.method(:track_error), 'IdV: consent checkbox toggled' => :idv_consent_checkbox_toggled, 'IdV: download personal key' => :idv_personal_key_downloaded, 'IdV: location submitted' => :idv_in_person_location_submitted, @@ -34,15 +31,11 @@ class FrontendError < StandardError; end 'Sign In: IdV requirements accordion clicked' => :sign_in_idv_requirements_accordion_clicked, 'User prompted before navigation' => :user_prompted_before_navigation, 'User prompted before navigation and still on page' => :user_prompted_before_navigation_and_still_on_page, - }.transform_values { |method| AnalyticsEvents.instance_method(method) }.freeze + }.freeze # rubocop:enable Layout/LineLength def create - if error_event? - NewRelic::Agent.notice_error(FrontendError.new, custom_params: log_params[:payload].to_h) - else - frontend_logger.track_event(log_params[:event], log_params[:payload].to_h) - end + frontend_logger.track_event(log_params[:event], log_params[:payload].to_h) render json: { success: true }, status: :ok end @@ -69,10 +62,6 @@ def valid_event? log_params[:event].present? end - def error_event? - log_params[:event] == FRONTEND_ERROR_EVENT - end - def valid_payload? params[:payload].nil? || !log_params[:payload].nil? end diff --git a/app/services/frontend_error_logger.rb b/app/services/frontend_error_logger.rb new file mode 100644 index 00000000000..1d15be0b986 --- /dev/null +++ b/app/services/frontend_error_logger.rb @@ -0,0 +1,7 @@ +class FrontendErrorLogger + class FrontendError < StandardError; end + + def self.track_error(name:, message:, stack:) + NewRelic::Agent.notice_error(FrontendError.new, custom_params: { name:, message:, stack: }) + end +end diff --git a/app/services/frontend_logger.rb b/app/services/frontend_logger.rb index 6155253e9a1..1e5034d003a 100644 --- a/app/services/frontend_logger.rb +++ b/app/services/frontend_logger.rb @@ -1,14 +1,27 @@ class FrontendLogger attr_reader :analytics, :event_map + # @param [Analytics] analytics + # @param [Hash{String=>Symbol,#call}] event_map map of string event names to method names + # on Analytics, or a custom implementation that's callable (like a Proc or Method) def initialize(analytics:, event_map:) @analytics = analytics @event_map = event_map end + # Logs an event and converts the payload to the correct keyword args + # @param [String] name + # @param [Hash] attributes payload with string keys def track_event(name, attributes) - if (analytics_method = event_map[name]) - analytics.send(analytics_method.name, **hash_from_method_kwargs(attributes, analytics_method)) + analytics_method = event_map[name] + + if analytics_method.is_a?(Symbol) + analytics.send( + analytics_method, + **hash_from_kwargs(attributes, AnalyticsEvents.instance_method(analytics_method)), + ) + elsif analytics_method.respond_to?(:call) + analytics_method.call(**hash_from_kwargs(attributes, analytics_method)) else analytics.track_event("Frontend: #{name}", attributes) end @@ -16,12 +29,17 @@ def track_event(name, attributes) private - def hash_from_method_kwargs(hash, method) - method_kwargs(method).index_with { |key| hash[key.to_s] } + # @param [Hash] hash + # @param [Proc,Method] callable + # @return [Hash] + def hash_from_kwargs(hash, callable) + kwargs(callable).index_with { |key| hash[key.to_s] } end - def method_kwargs(method) - method. + # @param [Proc,Method] callable + # @return [Array] the names of the kwargs for the callable (both optional and required) + def kwargs(callable) + callable. parameters. map { |type, name| name if [:key, :keyreq].include?(type) }. compact diff --git a/spec/controllers/frontend_log_controller_spec.rb b/spec/controllers/frontend_log_controller_spec.rb index d8e4b48a68c..7b052939644 100644 --- a/spec/controllers/frontend_log_controller_spec.rb +++ b/spec/controllers/frontend_log_controller_spec.rb @@ -174,7 +174,7 @@ context 'for an error event' do let(:params) do { - 'event' => described_class::FRONTEND_ERROR_EVENT, + 'event' => 'Frontend Error', 'payload' => { 'name' => 'name', 'message' => 'message', @@ -186,7 +186,7 @@ it 'notices the error to NewRelic instead of analytics logger' do expect(fake_analytics).not_to receive(:track_event) expect(NewRelic::Agent).to receive(:notice_error).with( - described_class::FrontendError.new, + FrontendErrorLogger::FrontendError.new, custom_params: { name: 'name', message: 'message', diff --git a/spec/services/frontend_logger_spec.rb b/spec/services/frontend_logger_spec.rb index 01bffa7ce85..577e7c5aa03 100644 --- a/spec/services/frontend_logger_spec.rb +++ b/spec/services/frontend_logger_spec.rb @@ -16,7 +16,10 @@ def example_method_handler(ok:, **rest) let(:event_map) do { - 'method' => ExampleAnalyticsEvents.instance_method(:example_method_handler), + 'method' => analytics.method(:example_method_handler), + 'proc' => lambda do |ok:, other:| + analytics.track_event('some customized event', 'ok' => ok, 'other' => other, 'custom' => 1) + end, } end let(:logger) { described_class.new(analytics: analytics, event_map: event_map) } @@ -46,5 +49,17 @@ def example_method_handler(ok:, **rest) expect(analytics).to have_logged_event('example', ok: true, rest: {}) end end + + context 'with proc handler' do + let(:name) { 'proc' } + + it 'calls the method and passes analytics and attributes' do + call + + expect(analytics).to have_logged_event( + 'some customized event', 'ok' => true, 'other' => true, 'custom' => 1 + ) + end + end end end