Implementing Better Visitor Analytics

BADCamp 2018

Geoff Appleby
https://gapple.github.io/presentation-better-analytics-badcamp/
  • Google Analytics capabilities
  • The Googalytics module
  • Programming custom behaviour
  • Analytics Reports

						<script>
						window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
						ga('create', 'UA-XXXXX-Y', 'auto');
						ga('send', 'pageview');
						</script>
						<script async src='https://www.google-analytics.com/analytics.js'></script>
					

Category

Subcategory

Product

Whitepaper

Case Study

Brochure

Show

Page

News

Media

  • Season
  • Episode

Dimensions & Metrics

Dimension Scopes

  • Hit
  • Session
  • User

Events

User Timings

Plugins

Usage Limits

  • 10 million hits per month
  • 200,000 hits per user per day
  • 500 hits per session

Why?

  • OOP
  • Content Security Policy
  • Configurable
  • Programmable
Googalytics module configuration page
Googalytics module dimensions and metrics configuration page
  1. Create
  2. Require
  3. Set
  4. Send
JS

								ga('<command>' [, args...])
							
PHP

								new <Command>([args...])
							

Create

trackingId:{string}
cookieDomain:{string}optional
trackerName:{string}optional
fieldsObject:{object}optional
JS

								ga('create', 'UA-107047227-2', 'auto');
							
PHP

								new Create('UA-107047227-2');
							

Require

plugin:{string}
fieldsObject:{object}optional
JS

								ga('require', 'linker');
							
PHP

								new RequirePlugin('linker');
							

Dimensions

Set

key:{string}
value:{string}
fieldsObject:{object}optional
JS

								ga('set', 'dimension1', 'article');
							
PHP

								new Set('dimension1', 'article');
							

Metrics

Set

key:{number}
value:{number}
fieldsObject:{object}optional
JS

								ga('set', 'metric1', 2);
							
PHP

								new Set('metric1', 2);
							

Pageview

Send

title:{string}optional
location:{string}optional
page:{string}optional
fieldsObject:{object}optional
JS

								ga('send', 'pageview');
							
PHP

								new Pageview();
							

Events

Send

category:{string}
action:{string}
label:{string}optional
value:{number}optional
fieldsObject:{object}optional
JS

								ga('send', 'event', 'video', 'play', 'cats.mp4');
							
PHP

								new Event('video', 'play', 'cats.mp4');
							

User Timings

Send

category:{string}
var:{string}
value:{number}
label:{string}optional
fieldsObject:{object}optional
JS

								ga('send', 'timing', 'resource', 'get', 100);
							
PHP

								new Timing('resource', 'get', 100);
							

Social

Send

network:{string}
action:{string}
target:{string}
fieldsObject:{object}optional
JS

								ga('send', 'social', 'twitter', 'tweet', 'articles/give-it-a-go-and-grow-your-own-herbs');
							
PHP

								new Send('social', [
									'socialNetwork' => 'twitter',
									'socialAction' => 'tweet',
									'socialTarget' => 'articles/give-it-a-go-and-grow-your-own-herbs'
								]);
							

						services:
							umami_ga.analytics_subscriber:
								class: Drupal\umami_ga\EventSubscriber\AnalyticsSubscriber
								arguments: ['@current_route_match']
								tags:
								  - { name: event_subscriber }
					
umami_ga.services.yml

						namespace Drupal\umami_ga\EventSubscriber;

						class AnalyticsSubscriber implements EventSubscriberInterface {

							public static function getSubscribedEvents() {
								return [
									AnalyticsEvents::COLLECT => [
										['setNodeDimensions'],
										['setTaxonomyDimensions'],
									],
								];
							}

							public function __construct(RouteMatchInterface $routeMatch) {
								$this->routeMatch = $routeMatch;
							}

						}
					
src/EventSubscriber/AnalyticsSubscriber.php

						public function setNodeDimensions(CollectEvent $event) {
							$node = $this->routeMatch->getParameter('node')

							if (empty($node)) { return; }

							if (!empty($node->field_recipe_category[0]->entity)) {
								$category = $node->field_recipe_category[0]->entity->name->value;
								$event->addCommand(new Set('dimension1', $category));
							}

							if (!empty($node->field_difficulty)) {
								$difficulty = $node->field_difficulty->value;
								$event->addCommand(new Set('dimension2', $difficulty));
							}

							if ($node->bundle() == 'article') {
								$username = $node->getRevisionUser()->getAccountName();
								$event->addCommand(new Set('dimension3', $username));
							}
						}
					
src/EventSubscriber/AnalyticsSubscriber.php

						analytics:
							version: 1.x
							js:
								js/analytics.js: { }
							dependencies:
								- core/jquery
								- ga/analytics
					
umami_ga.libraries.yml

						(function ($) {

							$('body').on('play', 'video', function (e) {
								ga('send', 'event', {
									eventCategory: 'video',
									eventAction: 'play',
									eventLabel: $(this).data('video-id'),
								});
							});

						})(jQuery);
					

						function updateSomethingWithApiData() {
							var dataFetchStart = new Date();

							$.get('/resource')
								.done(function (data) {
									var dataFetchEnd = new Date();
									var dataFetchDuration = dataFetchEnd.getTime() - dataFetchStart.getTime();

									// Update some widget with the data.

									var widgetRenderEnd = new Date();
									var widgetRenderDuration = widgetRenderEnd.getTime() - dataFetchEnd.getTime();

									ga('send', 'timing', 'resource', 'get', dataFetchDuration);
									ga('send', 'timing', 'widget', 'update', widgetRenderDuration);
								});
						};
					

						(function ($) {
							jQuery('body').on('click', 'a', function (event) {
								var href = $(this).attr('href');
								if (
									!href.match('^(\d+):\/\/')
									||
									href.match('^(https?):\/\/' + window.location.host)
								) {
									return;
								}

								ga('send', 'event', {
									eventCategory: 'Outbound Link',
									eventAction: 'click',
									eventLabel: href,
									transport: 'beacon'
								});
							});
						})();
					

						(function ($) {
							$('body').on('click', 'a', function (event) {
								var href = $(this).attr('href');
								if (!href.match('^(\d+):\/\/') || href.match('^(https?):\/\/' + window.location.host)) {
									return;
								}

								event.preventDefault();

								ga('send', 'event', {
									eventCategory: 'Outbound Link',
									eventAction: 'click',
									eventLabel: href,
									hitCallback: function() {
										document.location = href;
									}
								});
							});
						})(jQuery);