Repository: cal-itp/benefits

Issue:
Send eligibility type with the started/finished eligibility events
With the `started eligibility` and `returned eligibility` events, let's also send the `eligibility` that was checked

_Originally posted by @thekaveman in https://github.com/cal-itp/benefits/issues/342#issuecomment-1219762888_


Discussion:

Resolving patch:
diff --git a/benefits/eligibility/analytics.py b/benefits/eligibility/analytics.py
--- a/benefits/eligibility/analytics.py
+++ b/benefits/eligibility/analytics.py
@@ -4,37 +4,45 @@
 from benefits.core import analytics as core
 
 
-class StartedEligibilityEvent(core.Event):
+class EligibilityEvent(core.Event):
+    """Base analytics event for eligibility verification."""
+
+    def __init__(self, request, event_type, eligibility_types):
+        super().__init__(request, event_type)
+        self.update_event_properties(eligibility_types=eligibility_types)
+
+
+class StartedEligibilityEvent(EligibilityEvent):
     """Analytics event representing the beginning of an eligibility verification check."""
 
-    def __init__(self, request):
-        super().__init__(request, "started eligibility")
+    def __init__(self, request, eligibility_types):
+        super().__init__(request, "started eligibility", eligibility_types)
 
 
-class ReturnedEligibilityEvent(core.Event):
+class ReturnedEligibilityEvent(EligibilityEvent):
     """Analytics event representing the end of an eligibility verification check."""
 
-    def __init__(self, request, status, error=None):
-        super().__init__(request, "returned eligibility")
+    def __init__(self, request, eligibility_types, status, error=None):
+        super().__init__(request, "returned eligibility", eligibility_types)
         if str(status).lower() in ("error", "fail", "success"):
             self.update_event_properties(status=status, error=error)
 
 
-def started_eligibility(request):
+def started_eligibility(request, eligibility_types):
     """Send the "started eligibility" analytics event."""
-    core.send_event(StartedEligibilityEvent(request))
+    core.send_event(StartedEligibilityEvent(request, eligibility_types))
 
 
-def returned_error(request, error):
+def returned_error(request, eligibility_types, error):
     """Send the "returned eligibility" analytics event with an error status."""
-    core.send_event(ReturnedEligibilityEvent(request, status="error", error=error))
+    core.send_event(ReturnedEligibilityEvent(request, eligibility_types, status="error", error=error))
 
 
-def returned_fail(request):
+def returned_fail(request, eligibility_types):
     """Send the "returned eligibility" analytics event with a fail status."""
-    core.send_event(ReturnedEligibilityEvent(request, status="fail"))
+    core.send_event(ReturnedEligibilityEvent(request, eligibility_types, status="fail"))
 
 
-def returned_success(request):
+def returned_success(request, eligibility_types):
     """Send the "returned eligibility" analytics event with a success status."""
-    core.send_event(ReturnedEligibilityEvent(request, status="success"))
+    core.send_event(ReturnedEligibilityEvent(request, eligibility_types, status="success"))
diff --git a/benefits/eligibility/verify.py b/benefits/eligibility/verify.py
--- a/benefits/eligibility/verify.py
+++ b/benefits/eligibility/verify.py
@@ -3,6 +3,11 @@
 from eligibility_api.client import Client
 
 
+def typenames_to_verify(agency, verifier):
+    """Get the names of the eligibility types to check for the agency/verifier pair."""
+    return [t.name for t in agency.types_to_verify(verifier)]
+
+
 def eligibility_from_api(verifier, form, agency):
     sub, name = form.cleaned_data.get("sub"), form.cleaned_data.get("name")
 
@@ -18,10 +23,7 @@ def eligibility_from_api(verifier, form, agency):
         server_public_key=verifier.public_key_data,
     )
 
-    # get the eligibility type names to check
-    types = list(map(lambda t: t.name, agency.types_to_verify(verifier)))
-
-    response = client.verify(sub, name, types)
+    response = client.verify(sub, name, typenames_to_verify(agency, verifier))
 
     if response.error and any(response.error):
         form.add_api_errors(response.error)
@@ -34,6 +36,6 @@ def eligibility_from_api(verifier, form, agency):
 
 def eligibility_from_oauth(verifier, oauth_claim, agency):
     if verifier.uses_auth_verification and verifier.auth_provider.claim == oauth_claim:
-        return list(map(lambda t: t.name, agency.types_to_verify(verifier)))
+        return typenames_to_verify(agency, verifier)
     else:
         return []
diff --git a/benefits/eligibility/views.py b/benefits/eligibility/views.py
--- a/benefits/eligibility/views.py
+++ b/benefits/eligibility/views.py
@@ -146,66 +146,67 @@ def confirm(request):
         eligibility = session.eligibility(request)
         return verified(request, [eligibility.name])
 
+    agency = session.agency(request)
     verifier = session.verifier(request)
+    types_to_verify = verify.typenames_to_verify(agency, verifier)
 
     # GET for OAuth verification
     if request.method == "GET" and verifier.uses_auth_verification:
-        analytics.started_eligibility(request)
+        analytics.started_eligibility(request, types_to_verify)
 
-        verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), session.agency(request))
+        verified_types = verify.eligibility_from_oauth(verifier, session.oauth_claim(request), agency)
         if verified_types:
             return verified(request, verified_types)
         else:
             return unverified(request)
 
     # GET/POST for Eligibility API verification
-    else:
-        page = viewmodels.Page(
-            title=_(verifier.form_title),
-            content_title=_(verifier.form_content_title),
-            paragraphs=[_(verifier.form_blurb)],
-            form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier),
-            classes="text-lg-center",
-        )
-
-        # POST form submission, process form data
-        if request.method == "POST":
-            analytics.started_eligibility(request)
-
-            form = forms.EligibilityVerificationForm(data=request.POST, verifier=verifier)
-            # form was not valid, allow for correction/resubmission
-            if not form.is_valid():
-                if recaptcha.has_error(form):
-                    messages.error(request, "Recaptcha failed. Please try again.")
+    page = viewmodels.Page(
+        title=_(verifier.form_title),
+        content_title=_(verifier.form_content_title),
+        paragraphs=[_(verifier.form_blurb)],
+        form=forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier),
+        classes="text-lg-center",
+    )
 
-                page.forms = [form]
-                return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict())
+    # GET from an unverified user, present the form
+    if request.method == "GET":
+        return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict())
+    # POST form submission, process form data, make Eligibility Verification API call
+    elif request.method == "POST":
+        analytics.started_eligibility(request, types_to_verify)
 
-            # form is valid, make Eligibility Verification request to get the verified types
-            verified_types = verify.eligibility_from_api(verifier, form, session.agency(request))
+        form = forms.EligibilityVerificationForm(data=request.POST, verifier=verifier)
+        # form was not valid, allow for correction/resubmission
+        if not form.is_valid():
+            if recaptcha.has_error(form):
+                messages.error(request, "Recaptcha failed. Please try again.")
 
-            # form was not valid, allow for correction/resubmission
-            if verified_types is None:
-                analytics.returned_error(request, form.errors)
-                page.forms = [form]
-                return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict())
-            # no types were verified
-            elif len(verified_types) == 0:
-                return unverified(request)
-            # type(s) were verified
-            else:
-                return verified(request, verified_types)
-        # GET from an unverified user, see if verifier can get verified types and if not, present the form
-        else:
+            page.forms = [form]
             return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict())
 
+        # form is valid, make Eligibility Verification request to get the verified types
+        verified_types = verify.eligibility_from_api(verifier, form, agency)
+
+        # form was not valid, allow for correction/resubmission
+        if verified_types is None:
+            analytics.returned_error(request, types_to_verify, form.errors)
+            page.forms = [form]
+            return TemplateResponse(request, TEMPLATE_CONFIRM, page.context_dict())
+        # no types were verified
+        elif len(verified_types) == 0:
+            return unverified(request)
+        # type(s) were verified
+        else:
+            return verified(request, verified_types)
+
 
 @decorator_from_middleware(AgencySessionRequired)
 @decorator_from_middleware(LoginRequired)
 def verified(request, verified_types):
     """View handler for the verified eligibility page."""
 
-    analytics.returned_success(request)
+    analytics.returned_success(request, verified_types)
 
     session.update(request, eligibility_types=verified_types)
 
@@ -217,15 +218,16 @@ def verified(request, verified_types):
 def unverified(request):
     """View handler for the unverified eligibility page."""
 
-    analytics.returned_fail(request)
+    agency = session.agency(request)
+    verifier = session.verifier(request)
+    types_to_verify = verify.typenames_to_verify(agency, verifier)
+
+    analytics.returned_fail(request, types_to_verify)
 
     # tel: link to agency phone number
-    agency = session.agency(request)
     buttons = viewmodels.Button.agency_contact_links(agency)
     buttons.append(viewmodels.Button.home(request))
 
-    verifier = session.verifier(request)
-
     page = viewmodels.Page(
         noimage=True,
         title=_(verifier.unverified_title),


Test patch:
diff --git a/tests/pytest/eligibility/test_verify.py b/tests/pytest/eligibility/test_verify.py
--- a/tests/pytest/eligibility/test_verify.py
+++ b/tests/pytest/eligibility/test_verify.py
@@ -1,7 +1,7 @@
 import pytest
 
 from benefits.eligibility.forms import EligibilityVerificationForm
-from benefits.eligibility.verify import eligibility_from_api, eligibility_from_oauth
+from benefits.eligibility.verify import typenames_to_verify, eligibility_from_api, eligibility_from_oauth
 
 
 @pytest.fixture
@@ -14,6 +14,15 @@ def mock_api_client_verify(mocker):
     return mocker.patch("benefits.eligibility.verify.Client.verify")
 
 
+@pytest.mark.django_db
+def test_typenames_to_verify(model_TransitAgency, model_EligibilityVerifier):
+    expected = [t.name for t in model_TransitAgency.types_to_verify(model_EligibilityVerifier)]
+
+    result = typenames_to_verify(model_TransitAgency, model_EligibilityVerifier)
+
+    assert result == expected
+
+
 @pytest.mark.django_db
 def test_eligibility_from_api_error(mocker, model_TransitAgency, model_EligibilityVerifier, mock_api_client_verify, form):
     api_errors = {"name": "Name error"}