Skip to content

Commit

Permalink
Document analytics events via YARD (LG-5590) (#6014)
Browse files Browse the repository at this point in the history
- Adds AnalyticsEvents module where we can put explicitly typed events
- Add script that uses YARD to parse these and output JSON
  that we could use to power more accessible documentation

* Use Makefile to generate the JSON
* Update analytics lint command and fix analytics lint errors
* Switch to dasherized /api/analytics-events endpoint, add to CORS allowlist
* gitignore
* eslint ignore doc dir
* build analytics_events JSON building to build-post-config
* Serve the analytics events data from Rails so that the CORS middleware wraps it

* Migrate ACCOUNT_RESET
* Migrate ACCOUNT_DELETE_SUBMITTED
* Migrate ACCOUNT_DELETE_VISITED
* Migrate ACCOUNT_DELETION event

changelog: Internal, Documentation, Add documentation for analytics events in events.log
  • Loading branch information
zachmargolis authored Mar 4, 2022
1 parent 321eaeb commit 51ff7b0
Show file tree
Hide file tree
Showing 27 changed files with 533 additions and 55 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
public
vendor
coverage
doc
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Vagrantfile
/kitchen/cookbooks
/log/*
/postgres-data
/public/api
/public/system
/public/user_flows
/pwned_passwords/*
Expand All @@ -69,6 +70,7 @@ Vagrantfile
/.tmp/
/.vscode/
/vendor/bundle
.yardoc

package-lock.json

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ gem 'view_component', '~> 2.49.1'
gem 'webauthn', '~> 2.1'
gem 'xmldsig', '~> 0.6'
gem 'xmlenc', '~> 0.7', '>= 0.7.1'
gem 'yard'

# This version of the zxcvbn gem matches the data and behavior of the zxcvbn NPM package.
# It should not be updated without verifying that the behavior still matches JS version 4.4.2.
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ DEPENDENCIES
webmock
xmldsig (~> 0.6)
xmlenc (~> 0.7, >= 0.7.1)
yard
zonebie
zxcvbn (= 0.1.7)

Expand Down
40 changes: 39 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,31 @@ PORT ?= 3000
GZIP_COMMAND ?= gzip
ARTIFACT_DESTINATION_FILE ?= ./tmp/idp.tar.gz

.PHONY: brakeman check check_asset_strings docker_setup fast_setup fast_test help lint lint_country_dialing_codes lint_erb lint_optimized_assets lint_yaml lintfix normalize_yaml optimize_assets optimize_svg run run setup test update_pinpoint_supported_countries build_artifact
.PHONY: \
analytics_events \
brakeman \
build_artifact \
check \
check_asset_strings \
docker_setup \
fast_setup \
fast_test \
help \
lint \
lint_analytics_events \
lint_country_dialing_codes \
lint_erb \
lint_optimized_assets \
lint_yaml \
lintfix \
normalize_yaml \
optimize_assets \
optimize_svg \
run \
run \
setup \
test \
update_pinpoint_supported_countries

help: ## Show this help
@echo "--- Help ---"
Expand All @@ -35,6 +59,8 @@ lint: ## Runs all lint tests
make lint_erb
@echo "--- rubocop ---"
bundle exec rubocop --parallel
@echo "--- analytics_events ---"
make lint_analytics_events
@echo "--- brakeman ---"
bundle exec brakeman
@echo "--- zeitwerk check ---"
Expand Down Expand Up @@ -158,3 +184,15 @@ build_artifact $(ARTIFACT_DESTINATION_FILE): ## Builds zipped tar file artifact
--exclude='./vendor/ruby' \
--exclude='./config/application.yml' \
-cf - "." | "$(GZIP_COMMAND)" > $(ARTIFACT_DESTINATION_FILE)

analytics_events: public/api/_analytics-events.json ## Generates a JSON file that documents analytics events for events.log

lint_analytics_events: .yardoc # Checks that all methods on AnalyticsEvents are documented
bundle exec ruby lib/analytics_events_documenter.rb --check $<

public/api/_analytics-events.json: .yardoc .yardoc/objects/root.dat
mkdir -p public/api
bundle exec ruby lib/analytics_events_documenter.rb --json $< > $@

.yardoc .yardoc/objects/root.dat: app/services/analytics_events.rb
bundle exec yard doc --type-tag identity.idp.event_name:"Event Name" --db $@ -- $<
2 changes: 1 addition & 1 deletion app/controllers/account_reset/cancel_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def create
private

def track_event(result)
analytics.track_event(Analytics::ACCOUNT_RESET, result.to_h)
analytics.account_reset(**result.to_h)
end

def handle_valid_token
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/account_reset/delete_account_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def show
render :show and return unless token

result = AccountReset::ValidateGrantedToken.new(token).call
analytics.track_event(Analytics::ACCOUNT_RESET, result.to_h)
analytics.account_reset(**result.to_h)

if result.success?
handle_valid_token
Expand All @@ -16,7 +16,7 @@ def show
def delete
granted_token = session.delete(:granted_token)
result = AccountReset::DeleteAccount.new(granted_token).call
analytics.track_event(Analytics::ACCOUNT_RESET, result.to_h.except(:email))
analytics.account_reset(**result.to_h.except(:email))

if result.success?
handle_successful_deletion(result)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/account_reset/request_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def create

def create_account_reset_request
response = AccountReset::CreateRequest.new(current_user).call
analytics.track_event(Analytics::ACCOUNT_RESET, response.to_h.merge(analytics_attributes))
analytics.account_reset(**response.to_h.merge(analytics_attributes))
end

def confirm_two_factor_enabled
Expand Down
18 changes: 18 additions & 0 deletions app/controllers/analytics_events_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Serve a static file from Rails so that the CORS middleware can add the correct headers
class AnalyticsEventsController < ApplicationController
prepend_before_action :skip_session_load
prepend_before_action :skip_session_expiration
skip_before_action :disable_caching

JSON_FILE = Rails.root.join('public', 'api', '_analytics-events.json')

def index
if File.exist?(JSON_FILE)
expires_in 15.minutes, public: true

send_file JSON_FILE, type: 'application/json', disposition: 'inline'
else
render_not_found
end
end
end
6 changes: 3 additions & 3 deletions app/controllers/users/delete_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ class DeleteController < ApplicationController
before_action :confirm_current_password, only: [:delete]

def show
analytics.track_event(Analytics::ACCOUNT_DELETE_VISITED)
analytics.account_delete_visited
end

def delete
send_push_notifications
delete_user
sign_out
flash[:success] = t('devise.registrations.destroyed')
analytics.track_event(Analytics::ACCOUNT_DELETE_SUBMITTED, success: true)
analytics.account_delete_submitted(success: true)
redirect_to root_url
end

Expand All @@ -29,7 +29,7 @@ def confirm_current_password
return if valid_password?

flash[:error] = t('idv.errors.incorrect_password')
analytics.track_event(Analytics::ACCOUNT_DELETE_SUBMITTED, success: false)
analytics.account_delete_submitted(success: false)
render :show
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def destroy

def track_account_deletion_event
properties = ParseControllerFromReferer.new(request.referer).call
analytics.track_event(Analytics::ACCOUNT_DELETION, properties)
analytics.account_deletion(**properties)
end

def destroy_user
Expand Down
3 changes: 1 addition & 2 deletions app/services/account_reset/grant_requests_and_send_emails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ def perform(now)
notifications_sent += 1 if grant_request_and_send_email(arr)
end

analytics.track_event(
Analytics::ACCOUNT_RESET,
analytics.account_reset(
event: :notifications,
count: notifications_sent,
)
Expand Down
6 changes: 2 additions & 4 deletions app/services/analytics.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class Analytics
include AnalyticsEvents

def initialize(user:, request:, sp:, session:, ahoy: nil)
@user = user
@request = request
Expand Down Expand Up @@ -120,10 +122,6 @@ def browser_attributes
end

# rubocop:disable Layout/LineLength
ACCOUNT_RESET = 'Account Reset'
ACCOUNT_DELETE_SUBMITTED = 'Account Delete submitted'
ACCOUNT_DELETE_VISITED = 'Account Delete visited'
ACCOUNT_DELETION = 'Account Deletion Requested'
ACCOUNT_RESET_VISIT = 'Account deletion and reset visited'
ACCOUNT_VISIT = 'Account Page Visited'
ADD_EMAIL = 'Add Email: Email Submitted'
Expand Down
78 changes: 78 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module AnalyticsEvents
# @identity.idp.event_name Account Reset
# @param [Boolean] success
# @param ["cancel", "delete", "cancel token validation", "granted token validation",
# "notifications"] event
# @param [String] message_id from AWS Pinpoint API
# @param [String] request_id from AWS Pinpoint API
# @param [Boolean] sms_phone
# @param [Boolean] totp does the user have an authentication app as a 2FA option?
# @param [Boolean] piv_cac does the user have PIV/CAC as a 2FA option?
# @param [Integer] count number of notifications sent
# @param [Hash] errors
# @param [Hash] error_details
# @param [String] user_id
# @param [Integer] account_age_in_days
# @param [Hash] mfa_method_counts
# @param [Integer] email_addresses number of email addresses the user has
# Tracks events related to a user requesting to delete their account during the sign in process
# (because they have no other means to sign in).
def account_reset(
success: nil,
event: nil,
message_id: nil,
piv_cac: nil,
request_id: nil,
sms_phone: nil,
totp: nil,
count: nil,
errors: nil,
user_id: nil,
account_age_in_days: nil,
mfa_method_counts: nil,
pii_like_keypaths: nil,
error_details: nil,
email_addresses: nil
)
track_event(
'Account Reset',
{
success: success,
event: event,
message_id: message_id,
piv_cac: piv_cac,
request_id: request_id,
sms_phone: sms_phone,
totp: totp,
count: count,
errors: errors,
user_id: user_id,
account_age_in_days: account_age_in_days,
mfa_method_counts: mfa_method_counts,
pii_like_keypaths: pii_like_keypaths,
error_details: error_details,
email_addresses: email_addresses,
}.compact,
)
end

# @identity.idp.event_name Account Delete submitted
# @param [Boolean] success
# When a user submits a form to delete their account
def account_delete_submitted(success:)
track_event('Account Delete submitted', success: success)
end

# @identity.idp.event_name Account Delete visited
# When a user visits the page to delete their account
def account_delete_visited
track_event('Account Delete visited')
end

# @identity.idp.event_name Account Deletion Requested
# @param [String] request_came_from the controller/action the request came from
# When a user deletes their account
def account_deletion(request_came_from:)
track_event('Account Deletion Requested', request_came_from: request_came_from)
end
end
1 change: 1 addition & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class Application < Rails::Application
end

origins allowed_origins
resource '/api/analytics-events', headers: :any, methods: [:get]
resource '/api/country-support', headers: :any, methods: [:get]
end
end
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Rails.application.routes.draw do
# Non i18n routes. Alphabetically sorted.
get '/api/analytics-events' => 'analytics_events#index'
get '/api/country-support' => 'country_support#index'
get '/api/health' => 'health/health#index'
get '/api/health/database' => 'health/database#index'
Expand Down
2 changes: 2 additions & 0 deletions deploy/build-post-config
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ bundle exec rake assets:precompile

bundle exec bin/copy_robots_file

make analytics_events

set +x

echo "deploy/build-post-config finished"
Loading

0 comments on commit 51ff7b0

Please sign in to comment.