diff --git a/Makefile b/Makefile index a2aba78f2db..4ee599d6467 100644 --- a/Makefile +++ b/Makefile @@ -309,6 +309,7 @@ public/api/_analytics-events.json: .yardoc .yardoc/objects/root.dat .yardoc .yardoc/objects/root.dat: app/services/analytics_events.rb bundle exec yard doc \ + --no-progress \ --fail-on-warning \ --type-tag identity.idp.previous_event_name:"Previous Event Name" \ --no-output \ diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index c413a64e7f2..46d183386ef 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -125,7 +125,7 @@ def account_reset_cancel_token_validation( # @param [String] user_id # @param [Integer, nil] account_age_in_days number of days since the account was confirmed # @param [Time] account_confirmed_at date that account creation was confirmed - # (rounded) or nil if the account was not confirmed + # (rounded) or nil if the account was not confirmed # @param [Hash] mfa_method_counts Hash of MFA method with the number of that method on the account # @param [Boolean] identity_verified if the deletion occurs on a verified account # @param [Hash] errors Errors resulting from form validation @@ -244,7 +244,7 @@ def account_visit # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [String] user_id User the email is linked to # @param [Boolean] from_select_email_flow Whether email was added as part of partner email - # selection. + # selection. # A user has clicked the confirmation link in an email def add_email_confirmation( user_id:, @@ -270,7 +270,7 @@ def add_email_confirmation( # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [String] domain_name Domain name of email address submitted # @param [Boolean] in_select_email_flow Whether email is being added as part of partner email - # selection. + # selection. # Tracks request for adding new emails to an account def add_email_request( success:, @@ -513,7 +513,7 @@ def edit_password_visit(required_password_change: false, **extra) # @param [Boolean] sp_request_url_present if was an SP request URL in the session # @param [Boolean] remember_device if the remember device cookie was present # @param [Boolean, nil] new_device Whether the user is authenticating from a new device. Nil if - # the attempt was unsuccessful, since it cannot be known whether it's a new device. + # the attempt was unsuccessful, since it cannot be known whether it's a new device. # Tracks authentication attempts at the email/password screen def email_and_password_auth( success:, @@ -1086,7 +1086,7 @@ def idv_camera_info_error(error:, **_extra) # @param ["hybrid","standard"] flow_path Document capture user flow # @param [Array] camera_info Information on the users cameras max resolution - # as captured by the browser + # as captured by the browser def idv_camera_info_logged(flow_path:, camera_info:, **_extra) track_event( :idv_camera_info_logged, flow_path: flow_path, camera_info: camera_info @@ -1454,7 +1454,7 @@ def idv_doc_auth_exception_visited(step_name:, remaining_submit_attempts:, **ext # @param [String] side the side of the image submission # @param [Integer] submit_attempts Times that user has tried submitting (previously called - # "attempts") + # "attempts") # @param [Integer] remaining_submit_attempts (previously called "remaining_attempts") # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] liveness_checking_required Whether or not the selfie is required @@ -2984,7 +2984,7 @@ def idv_in_person_prepare_visited(flow_path:, opted_in_to_in_person_proofing:, * # @param [String] analytics_id # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing - # address page visited + # address page visited def idv_in_person_proofing_address_visited( flow_path:, step:, @@ -6442,7 +6442,7 @@ def phone_change_viewed # @param [Boolean] success # @param [Integer] phone_configuration_id - # tracks a phone number deletion event + # Tracks a phone number deletion event def phone_deletion(success:, phone_configuration_id:, **extra) track_event( 'Phone Number Deletion: Submitted', @@ -6485,7 +6485,7 @@ def piv_cac_delete_submitted( # @param [Hash] errors Errors resulting from form validation # @param [String, nil] key_id PIV/CAC key_id from PKI service # @param [Boolean] new_device Whether the user is authenticating from a new device - # tracks piv cac login event + # Tracks piv cac login event def piv_cac_login(success:, errors:, key_id:, new_device:, **extra) track_event( :piv_cac_login, @@ -7043,17 +7043,17 @@ def security_event_received( ) end - # tracks if the session is kept alive + # Tracks if the session is kept alive def session_kept_alive track_event('Session Kept Alive') end - # tracks if the session timed out + # Tracks if the session timed out def session_timed_out track_event('Session Timed Out') end - # tracks when a user's session is timed out + # Tracks when a user's session is timed out def session_total_duration_timeout track_event('User Maximum Session Length Exceeded') end @@ -7064,7 +7064,7 @@ def sign_in_notification_timeframe_expired_absent end # @param [String] flash - # tracks when a user visits the sign in page + # Tracks when a user visits the sign in page def sign_in_page_visit(flash:, **extra) track_event('Sign in page visited', flash:, **extra) end @@ -7080,7 +7080,7 @@ def sign_in_security_check_failed_visited # @param [Boolean] new_user Whether this is an incomplete user (no associated MFA methods) # @param [Boolean] has_other_auth_methods Whether the user has other authentication methods # @param [Integer] phone_configuration_id Phone configuration associated with request - # tracks when a user opts into SMS + # Tracks when a user opts into SMS def sms_opt_in_submitted( success:, errors:, @@ -7105,7 +7105,7 @@ def sms_opt_in_submitted( # @param [Boolean] new_user # @param [Boolean] has_other_auth_methods # @param [Integer] phone_configuration_id - # tracks when a user visits the sms opt in page + # Tracks when a user visits the sms opt in page def sms_opt_in_visit( new_user:, has_other_auth_methods:, diff --git a/lib/analytics_events_documenter.rb b/lib/analytics_events_documenter.rb index 6200eab1216..f14f1b794c1 100644 --- a/lib/analytics_events_documenter.rb +++ b/lib/analytics_events_documenter.rb @@ -129,6 +129,16 @@ def missing_documentation errors << "#{error_prefix} #{tag.name} missing types" if !tag.types end + description = method_description(method_object) + if description.present? && !method_object.docstring.match?(/\A[A-Z]/) + indented_description = description.lines.map { |line| " #{line.chomp}" }.join("\n") + + errors << <<~MSG + #{error_prefix} method description starts with lowercase, check indentation: + #{indented_description} + MSG + end + errors end end @@ -156,7 +166,7 @@ def as_json { event_name: extract_event_name(method_object), previous_event_names: method_object.tags(PREVIOUS_EVENT_NAME_TAG).map(&:text), - description: method_object.docstring.presence, + description: method_description(method_object), attributes: attributes, method_name: method_object.name, source_line: method_object.line, @@ -168,6 +178,12 @@ def as_json { events: events_json_summary } end + # Strips Rubocop directives from description text + # @return [String, nil] + def method_description(method_object) + method_object.docstring.to_s.gsub(/^rubocop.+$/, '').presence&.chomp + end + private # Naive attempt to pull tracked event string or symbol from source code diff --git a/spec/lib/analytics_events_documenter_spec.rb b/spec/lib/analytics_events_documenter_spec.rb index 81c2c24ef78..d6dc013fbf7 100644 --- a/spec/lib/analytics_events_documenter_spec.rb +++ b/spec/lib/analytics_events_documenter_spec.rb @@ -204,6 +204,43 @@ def some_event(*) end end + context 'param description gets munged into method descripion' do + let(:source_code) { <<~RUBY } + class AnalyticsEvents + # @param val [String] some value that + # does things and this should be part of the param + # Some Event + def some_event(val:, **extra) + track_event('Some Event', val:, **extra) + end + end + RUBY + + it 'errors' do + expect(documenter.missing_documentation.first) + .to include('method description starts with lowercase, check indentation') + end + end + + context 'rubocop comment around params description' do + let(:source_code) { <<~RUBY } + class AnalyticsEvents + # @param val [String] some value that + # does things and this should be part of the param + # Some Event + # rubocop:disable Layout/LineLength + def some_event(val:, **extra) + track_event('Some Event', val:, **extra) + end + # rubocop:enable Layout/LineLength + end + RUBY + + it 'ignores rubocop lines' do + expect(documenter.missing_documentation).to be_empty + end + end + describe '#as_json' do let(:source_code) { <<~RUBY } class AnalyticsEvents @@ -211,9 +248,11 @@ class AnalyticsEvents # @param [Integer] count number of attempts # The event that does something with stuff # @option extra [String] 'DocumentName' the document name + # rubocop:disable Layout/LineLength def some_event(success:, count:, **extra) track_event('Some Event', **extra) end + # rubocop:enable Layout/LineLength # @identity.idp.previous_event_name The Old Other Event # @identity.idp.previous_event_name Even Older Other Event @@ -223,7 +262,7 @@ def other_event end RUBY - it 'is a JSON representation of params for each event' do + it 'is a JSON representation of params for each event, ignoring rubocop directives' do expect(documenter.as_json[:events]).to match_array( [ { @@ -277,7 +316,7 @@ def some_event(success:, **extra) { event_name: 'some_event', previous_event_names: [], - description: '', + description: nil, attributes: [ { name: 'success', types: ['Boolean'], description: nil }, ],