Repository: openfun/richie

Issue:
Send web analytics event when user enrolls on a course run
## Feature Request

**Is your feature request related to a problem or unsupported use case? Please describe.**
To better monitor witch marketing campaigns have better performance, we need to generate events when an user enrolls to a course. 

**Describe the solution you'd like**
I would like to the add support to the web analytics module, the some react events, at least when the user enrolls to a course.

**Describe alternatives you've considered**
I don't any.

**Discovery, Documentation, Adoption, Migration Strategy**
It is a new feature and it's compatible with current implementation. 
Add information on web analytics docs on google analytics sub section that richie would send events, and list them.

**Do you want to work on it through a Pull Request?**
Yes. Probably create an util to send web analytics events.

How to configure Google Analytics to receive the Google Tag Manager custom events:
https://www.analyticsmania.com/post/google-tag-manager-custom-event-trigger/


Discussion:

Resolving patch:
diff --git a/src/richie/apps/core/context_processors.py b/src/richie/apps/core/context_processors.py
--- a/src/richie/apps/core/context_processors.py
+++ b/src/richie/apps/core/context_processors.py
@@ -47,6 +47,7 @@ def site_metas(request: HttpRequest):
                 "environment": getattr(settings, "ENVIRONMENT", ""),
                 "release": getattr(settings, "RELEASE", ""),
                 "sentry_dsn": getattr(settings, "SENTRY_DSN", ""),
+                **WebAnalyticsContextProcessor().frontend_context_processor(request),
             }
         },
         **WebAnalyticsContextProcessor().context_processor(request),
@@ -130,6 +131,18 @@ class WebAnalyticsContextProcessor:
     frontend.
     """
 
+    # pylint: disable=no-self-use
+    def frontend_context_processor(self, request: HttpRequest) -> dict:
+        """
+        Additional web analytics information for the frontend react
+        """
+        context = {}
+        if getattr(settings, "WEB_ANALYTICS_ID"):
+            context["web_analytics_provider"] = getattr(
+                settings, "WEB_ANALYTICS_PROVIDER", "google_analytics"
+            )
+        return context
+
     def context_processor(self, request: HttpRequest) -> dict:
         """
         Real implementation of the context processor for the Web Analytics core app sub-module


Test patch:
diff --git a/src/frontend/js/utils/test/factories.ts b/src/frontend/js/utils/test/factories.ts
--- a/src/frontend/js/utils/test/factories.ts
+++ b/src/frontend/js/utils/test/factories.ts
@@ -65,6 +65,7 @@ export const ContextFactory = (context: Partial<CommonDataProps['context']> = {}
     ],
     release: faker.system.semver(),
     sentry_dsn: null,
+    web_analytics_provider: null,
     ...context,
   });
 
diff --git a/tests/apps/core/test_pages.py b/tests/apps/core/test_pages.py
--- a/tests/apps/core/test_pages.py
+++ b/tests/apps/core/test_pages.py
@@ -84,6 +84,7 @@ def test_pages_i18n_hreflang(self):
             }
         ]
     )
+    @override_settings(WEB_ANALYTICS_ID="TRACKING_ID")
     def test_page_includes_frontend_context(self):
         """
         Create a page and make sure it includes the frontend context as included
@@ -116,3 +117,7 @@ def test_page_includes_frontend_context(self):
                 r"\u0022^https://lms\u005C\u005C.example\u005C\u005C.com/courses/(.*)/course/?$\u0022"  # noqa pylint: disable=line-too-long
             ),
         )
+        self.assertContains(
+            response,
+            r"\u0022web_analytics_provider\u0022: \u0022google_analytics\u0022",
+        )
diff --git a/tests/apps/core/test_web_analytics.py b/tests/apps/core/test_web_analytics.py
--- a/tests/apps/core/test_web_analytics.py
+++ b/tests/apps/core/test_web_analytics.py
@@ -43,13 +43,13 @@ def test_web_analytics_organization_page(self):
 
         self.assertContains(
             response,
-            "googletagmanager",
-            msg_prefix="Page should include the Google Analytics snippet code",
+            "google-analytics.com",
+            msg_prefix="Page should include the Google Analytics js code",
         )
         self.assertContains(
             response,
             "UA-XXXXXXXXX-X",
-            msg_prefix="Page should include the Google Analytics tracking code",
+            msg_prefix="Page should include the Google Analytics tracking id code",
         )
         self.assertRegex(
             response.content.decode("UTF-8"),
@@ -64,7 +64,7 @@ def test_web_analytics_organization_page(self):
         response_content = response.content.decode("UTF-8")
         self.assertGreater(
             response_content.index("<body"),
-            response_content.index("googletagmanager"),
+            response_content.index("google-analytics.com"),
             "Web tracking should be at the bottom of the page",
         )
 
@@ -119,7 +119,7 @@ def test_web_analytics_course_page(self):
 
         self.assertContains(
             response,
-            "googletagmanager",
+            "google-analytics.com",
             msg_prefix="Page should include the Google Analytics snippet code",
         )
         self.assertContains(
@@ -146,16 +146,15 @@ def test_web_analytics_course_page(self):
         self.assertContains(
             response,
             "dimension4': '" + course_run_link,
-            msg_prefix="Should include course resouce link on the 4th custom dimension",
+            msg_prefix="Should include course resource link on the 4th custom dimension",
         )
         self.assertRegex(
             response_content,
             "dimension5.*" + course_page_title.get("fr"),
             msg="Should include course page title on the 5th custom dimension",
         )
-
         self.assertGreater(
-            response_content.index("googletagmanager"),
+            response_content.index("google-analytics.com"),
             response_content.index("<body"),
             "Web tracking should be at the bottom of the page",
         )
@@ -221,12 +220,12 @@ def test_web_analytics_with_only_id(self):
 
         self.assertContains(
             response,
-            "googletagmanager",
+            "google-analytics.com",
             msg_prefix="Page should include the Google Analytics snippet code",
         )
         self.assertGreater(
             response_content.index("<body"),
-            response_content.index("googletagmanager"),
+            response_content.index("google-analytics.com"),
             "Web tracking should be at the head of the page",
         )
         self.assertContains(
@@ -283,3 +282,88 @@ def test_web_analytics_course_page_course_run_without_title(self):
             "dimension3': ''",
             msg_prefix="Should include an empty course run title on the 3rd custom dimension",
         )
+
+    @override_settings(
+        WEB_ANALYTICS_ID="UA-XXXXXXXXX-X", WEB_ANALYTICS_PROVIDER="google_tag_manager"
+    )
+    def test_web_analytics_google_tag_manager(self):
+        """
+        Test Google Tag Manager has Web Analytics provider for a course page with all the
+        dimensions.
+        """
+        org_page_code = "PUBLIC_ORG"
+        org_page_title = "public title"
+
+        # Create an Organization
+        organization = OrganizationFactory(
+            page_title=org_page_title, should_publish=True, code=org_page_code
+        )
+
+        course_page_code = "XPTO_CODE_FOR_COURSE"
+        course_page_title = {
+            "en": "A fancy title for a course",
+            "fr": "Un titre fantaisiste pour un cours",
+        }
+        course = CourseFactory(
+            page_title=course_page_title,
+            fill_organizations=[organization],
+            code=course_page_code,
+        )
+
+        course_run_link = (
+            "http://example.edx:8073/courses/course-v1:edX+DemoX+01/course/"
+        )
+        course_run_title = "Édition d'automne"
+        course_run_title_safe = "Édition d&#x27;automne"
+        CourseRunFactory(
+            direct_course=course,
+            resource_link=course_run_link,
+            sync_mode="sync_to_public",
+            title=course_run_title,
+            languages=["en", "fr"],
+        )
+
+        course_page = course.extended_object
+        # publish after course run creation so the course run is also published
+        course_page.publish("fr")
+        course_page.publish("en")
+
+        url = course_page.get_absolute_url(language="fr")
+        response = self.client.get(url)
+
+        self.assertContains(
+            response,
+            "googletagmanager.com",
+            msg_prefix="Page should include the Google Tag Manager js code",
+        )
+        self.assertContains(
+            response,
+            "UA-XXXXXXXXX-X",
+            msg_prefix="Page should include the Google Tag Manager tracking id code",
+        )
+        response_content = response.content.decode("UTF-8")
+        self.assertRegex(
+            response_content,
+            "dimension1.*" + org_page_code,
+            msg="Should include organization code on the 1st custom dimension",
+        )
+        self.assertRegex(
+            response_content,
+            "dimension2.*" + course_page_code,
+            msg="Should include course code on the 2nd custom dimension",
+        )
+        self.assertRegex(
+            response_content,
+            "dimension3.*" + course_run_title_safe,
+            msg="Should include course run title on the 3rd custom dimension",
+        )
+        self.assertContains(
+            response,
+            "dimension4': '" + course_run_link,
+            msg_prefix="Should include course resource link on the 4th custom dimension",
+        )
+        self.assertRegex(
+            response_content,
+            "dimension5.*" + course_page_title.get("fr"),
+            msg="Should include course page title on the 5th custom dimension",
+        )