Skip to content

Commit

Permalink
refactor(analytics): pass the flow in call chain
Browse files Browse the repository at this point in the history
hide the usage of flow.system_name from callers, this is an
implementation detail of the analytics code
  • Loading branch information
thekaveman committed Sep 21, 2024
1 parent 4cb6401 commit b7d9993
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 40 deletions.
17 changes: 13 additions & 4 deletions benefits/core/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import requests

from benefits import VERSION
from . import session
from . import models, session


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,12 +46,10 @@ def __init__(self, request, event_type, **kwargs):
agency_name = agency.long_name if agency else None
flow = session.flow(request)
verifier_name = flow.eligibility_verifier if flow else None
enrollment_flows = [flow.system_name] if flow else None

self.update_event_properties(
path=request.path,
transit_agency=agency_name,
enrollment_flows=enrollment_flows,
eligibility_verifier=verifier_name,
)

Expand All @@ -66,10 +64,12 @@ def __init__(self, request, event_type, **kwargs):
referring_domain=refdom,
user_agent=uagent,
transit_agency=agency_name,
enrollment_flows=enrollment_flows,
eligibility_verifier=verifier_name,
)

if flow:
self.update_enrollment_flows(flow)

# event is initialized, consume next counter
self.event_id = next(Event._counter)

Expand All @@ -84,6 +84,15 @@ def update_user_properties(self, **kwargs):
"""Merge kwargs into the self.user_properties dict."""
self.user_properties.update(kwargs)

def update_enrollment_flows(self, flow: models.EnrollmentFlow):
enrollment_flows = [flow.system_name]
self.update_event_properties(
enrollment_flows=enrollment_flows,
)
self.update_user_properties(
enrollment_flows=enrollment_flows,
)


class ViewedPageEvent(Event):
"""Analytics event representing a single page view."""
Expand Down
45 changes: 20 additions & 25 deletions benefits/eligibility/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,62 @@
The eligibility application: analytics implementation.
"""

from benefits.core import analytics as core
from benefits.core import analytics as core, models


class EligibilityEvent(core.Event):
"""Base analytics event for eligibility verification."""

def __init__(self, request, event_type, flow_name):
def __init__(self, request, event_type, flow: models.EnrollmentFlow):
super().__init__(request, event_type)
# pass a (converted from string to list) flow_name to preserve analytics reporting
enrollment_flows = [flow_name]
# overwrite core.Event enrollment_flows
self.update_event_properties(enrollment_flows=enrollment_flows)
self.update_user_properties(enrollment_flows=enrollment_flows)
# overwrite core.Event enrollment flow
self.update_enrollment_flows(flow)


class SelectedVerifierEvent(EligibilityEvent):
"""Analytics event representing the user selecting an enrollment flow."""

def __init__(self, request, enrollment_flows):
super().__init__(request, "selected enrollment flow", enrollment_flows)
def __init__(self, request, flow: models.EnrollmentFlow):
super().__init__(request, "selected enrollment flow", flow)


class StartedEligibilityEvent(EligibilityEvent):
"""Analytics event representing the beginning of an eligibility verification check."""

def __init__(self, request, enrollment_flows):
super().__init__(request, "started eligibility", enrollment_flows)
def __init__(self, request, flow: models.EnrollmentFlow):
super().__init__(request, "started eligibility", flow)


class ReturnedEligibilityEvent(EligibilityEvent):
"""Analytics event representing the end of an eligibility verification check."""

def __init__(self, request, enrollment_flows, status, error=None):
super().__init__(request, "returned eligibility", enrollment_flows)
def __init__(self, request, flow: models.EnrollmentFlow, status, error=None):
super().__init__(request, "returned eligibility", flow)
status = str(status).lower()
if status in ("error", "fail", "success"):
self.update_event_properties(status=status, error=error)
if status == "success":
self.update_user_properties(enrollment_flows=enrollment_flows)


def selected_verifier(request, enrollment_flows):
def selected_verifier(request, flow: models.EnrollmentFlow):
"""Send the "selected eligibility verifier" analytics event."""
core.send_event(SelectedVerifierEvent(request, enrollment_flows))
core.send_event(SelectedVerifierEvent(request, flow))


def started_eligibility(request, enrollment_flows):
def started_eligibility(request, flow: models.EnrollmentFlow):
"""Send the "started eligibility" analytics event."""
core.send_event(StartedEligibilityEvent(request, enrollment_flows))
core.send_event(StartedEligibilityEvent(request, flow))


def returned_error(request, enrollment_flows, error):
def returned_error(request, flow: models.EnrollmentFlow, error):
"""Send the "returned eligibility" analytics event with an error status."""
core.send_event(ReturnedEligibilityEvent(request, enrollment_flows, status="error", error=error))
core.send_event(ReturnedEligibilityEvent(request, flow, status="error", error=error))


def returned_fail(request, enrollment_flows):
def returned_fail(request, flow: models.EnrollmentFlow):
"""Send the "returned eligibility" analytics event with a fail status."""
core.send_event(ReturnedEligibilityEvent(request, enrollment_flows, status="fail"))
core.send_event(ReturnedEligibilityEvent(request, flow, status="fail"))


def returned_success(request, enrollment_flows):
def returned_success(request, flow: models.EnrollmentFlow):
"""Send the "returned eligibility" analytics event with a success status."""
core.send_event(ReturnedEligibilityEvent(request, enrollment_flows, status="success"))
core.send_event(ReturnedEligibilityEvent(request, flow, status="success"))
12 changes: 6 additions & 6 deletions benefits/eligibility/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def index(request):
flow = EnrollmentFlow.objects.get(id=flow_id)
session.update(request, flow=flow)

analytics.selected_verifier(request, flow.system_name)
analytics.selected_verifier(request, flow)

eligibility_start = reverse(routes.ELIGIBILITY_START)
response = redirect(eligibility_start)
Expand Down Expand Up @@ -83,7 +83,7 @@ def confirm(request):

# GET for OAuth verification
if request.method == "GET" and flow.uses_claims_verification:
analytics.started_eligibility(request, flow.system_name)
analytics.started_eligibility(request, flow)

is_verified = verify.eligibility_from_oauth(flow, session.oauth_claim(request), agency)

Expand All @@ -103,7 +103,7 @@ def confirm(request):
return TemplateResponse(request, TEMPLATE_CONFIRM, context)
# POST form submission, process form data, make Eligibility Verification API call
elif request.method == "POST":
analytics.started_eligibility(request, flow.system_name)
analytics.started_eligibility(request, flow)

form = flow.eligibility_form_instance(data=request.POST)
# form was not valid, allow for correction/resubmission
Expand All @@ -118,7 +118,7 @@ def confirm(request):

# form was not valid, allow for correction/resubmission
if is_verified is None:
analytics.returned_error(request, flow.system_name, form.errors)
analytics.returned_error(request, flow, form.errors)
context["form"] = form
return TemplateResponse(request, TEMPLATE_CONFIRM, context)
# no type was verified
Expand All @@ -135,7 +135,7 @@ def verified(request):
"""View handler for the verified eligibility page."""

flow = session.flow(request)
analytics.returned_success(request, flow.system_name)
analytics.returned_success(request, flow)

session.update(request, eligible=True)

Expand All @@ -149,6 +149,6 @@ def unverified(request):

flow = session.flow(request)

analytics.returned_fail(request, flow.system_name)
analytics.returned_fail(request, flow)

return TemplateResponse(request, flow.eligibility_unverified_template)
29 changes: 27 additions & 2 deletions tests/pytest/core/test_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def test_Event_sets_default_event_properties(app_request, mocker):

assert "path" in update_spy.call_args.kwargs
assert "transit_agency" in update_spy.call_args.kwargs
assert "enrollment_flows" in update_spy.call_args.kwargs
assert "eligibility_verifier" in update_spy.call_args.kwargs


Expand All @@ -83,10 +82,36 @@ def test_Event_sets_default_user_properties(app_request, mocker):
assert "referring_domain" in update_spy.call_args.kwargs
assert "user_agent" in update_spy.call_args.kwargs
assert "transit_agency" in update_spy.call_args.kwargs
assert "enrollment_flows" in update_spy.call_args.kwargs
assert "eligibility_verifier" in update_spy.call_args.kwargs


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_flow")
def test_Event_calls_update_enrollment_flows(app_request, mocker, model_EnrollmentFlow):
update_spy = mocker.spy(benefits.core.analytics.Event, "update_enrollment_flows")

Event(app_request, "event_type")

assert model_EnrollmentFlow in update_spy.call_args.args


@pytest.mark.django_db
def test_Event_update_enrollment_flows(app_request, mocker, model_EnrollmentFlow):
event = Event(app_request, "event_type")

assert "enrollment_flows" not in event.event_properties
assert "enrollment_flows" not in event.user_properties

event_spy = mocker.spy(benefits.core.analytics.Event, "update_event_properties")
user_spy = mocker.spy(benefits.core.analytics.Event, "update_user_properties")
event.update_enrollment_flows(model_EnrollmentFlow)

event_spy.assert_called_once()
user_spy.assert_called_once()
assert event.event_properties["enrollment_flows"] == [model_EnrollmentFlow.system_name]
assert event.user_properties["enrollment_flows"] == [model_EnrollmentFlow.system_name]


@pytest.mark.django_db
def test_Event_update_event_properties(app_request):
key, value = "key", "value"
Expand Down
8 changes: 5 additions & 3 deletions tests/pytest/eligibility/test_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
@pytest.mark.django_db
def test_EligibilityEvent_overwrites_enrollment_flows(app_request, mocker, model_EnrollmentFlow):
key, type1, type2 = "enrollment_flows", "type1", "type2"
model_EnrollmentFlow.system_name = type1
mocker.patch("benefits.core.analytics.session.flow", return_value=model_EnrollmentFlow)
mock_flow = mocker.Mock()
mock_flow.system_name = type1
mocker.patch("benefits.core.analytics.session.flow", return_value=mock_flow)

event = EligibilityEvent(app_request, "event_type", type2)
model_EnrollmentFlow.system_name = type2
event = EligibilityEvent(app_request, "event_type", model_EnrollmentFlow)

# event_properties should have been overwritten
assert key in event.event_properties
Expand Down

0 comments on commit b7d9993

Please sign in to comment.