Skip to content

Commit

Permalink
Application Insights front-end telemetry (#1085)
Browse files Browse the repository at this point in the history
* Variable name change to be more explicit

* Fixed broken src tag

* Define the users identifier

* Set app config value for toggling browser analytics

* Moved iframe into body tag

* Added App Insights Browser JS

* Use identity name as email claim is not set

* Corrected EoL feed

* Added App Insights to CSP

* Updated GA script to match shared Layout.cshtml

* Remove unused injection

* Add App Insights SDK to layout variant

* Updated Cookie Policy statement

* Corrected line ending

* Expire App Insights cookies on rejection choice

* Build pipeline for minifying App Insights JS
  • Loading branch information
DrizzlyOwl authored Jun 17, 2024
1 parent b59420a commit 3ad6163
Show file tree
Hide file tree
Showing 10 changed files with 624 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
There is a separate <a href="@Model.TransfersCookiesUrl">cookies page for the transfers part of this service.</a>
</p>

<h2 class="govuk-heading-m">Analytics cookies (optional)</h2>

<h3 class="govuk-heading-s">Google Analytics</h3>

<p class="govuk-body">
With your permission, we use Google Analytics to collect data about how you use the service. This information helps us improve our service.
These cookies do not collect your name or address, they do collect a unique user identifier
Expand All @@ -50,19 +54,32 @@
<li>what you click on while you’re visiting the site</li>
</ul>
<p class="govuk-body">Google is not allowed to share our analytics data with anyone.</p>

<h3 class="govuk-heading-s">Azure Application Insights</h3>

<p class="govuk-body">We use Azure Application Insights software to collect information about how you use this website. We do this to help make sure the site is meeting the needs of its users and to help us make improvements.</p>
<p class="govuk-body">Azure Application Insights stores information about:</p>
<ul class="govuk-list govuk-list--bullet">
<li>the pages you visit on this website</li>
<li>how long you spend on each page</li>
<li>how you got to the site</li>
<li>what you click on while you're visiting the site</li>
</ul>
<p class="govuk-body">We don't allow Microsoft to use or share our analytics data.</p>

<form method="post">
<div class="govuk-form-group">
<fieldset class="govuk-fieldset">
<legend class="govuk-fieldset__legend govuk-fieldset__legend--m">
<h3 class="govuk-fieldset__heading">
Do you accept Google Analytics cookies?
Do you accept analytical cookies?
</h3>
</legend>
<div class="govuk-radios">
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="cookie-consent-accept" name="consent" type="radio" value="true" checked="@Model.Consent">
<label class="govuk-label govuk-radios__label" for="cookie-consent-accept">
Yes, opt in to Google Analytics cookies
Yes, opt in to analytical cookies
</label>
</div>
<div class="govuk-radios__item">
Expand All @@ -76,6 +93,7 @@
</div>
<input type="submit" name="commit" value="Save changes" class="govuk-button" data-qa="submit" data-module="govuk-button" data-disable-with="Save changes">
</form>

<h2 class="govuk-heading-l">How we use cookies</h2>
<h3 class="govuk-heading-m">Essential cookies</h3>
<p class="govuk-body">
Expand Down Expand Up @@ -119,7 +137,10 @@
</tr>
</tbody>
</table>
<h3 class="govuk-heading-m">Measuring website usage (Google Analytics)</h3>

<h2 class="govuk-heading-m">Analytical cookies</h2>

<h3 class="govuk-heading-s">Google Analytics</h3>
<p class="govuk-body">Google Analytics sets the following cookies:</p>
<table class="govuk-table" aria-label="Google analytics cookies">
<thead class="govuk-table__header">
Expand Down Expand Up @@ -152,5 +173,34 @@
</tr>
</tbody>
</table>

<h3 class="govuk-heading-s">Application Insights</h3>
<p class="govuk-body">Azure Application Insights sets the following cookies:</p>
<table class="govuk-table" aria-label="App Insights cookies">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th scope="col" class="govuk-table__header">Name</th>
<th scope="col" class="govuk-table__header">Purpose</th>
<th scope="col" class="govuk-table__header">Expires</th>
</tr>
</thead>
<tbody class="govuk-table__body">
<tr class="govuk-table__row">
<td class="govuk-table__cell">ai_session</td>
<td class="govuk-table__cell">This helps us track activity happening over a single browser session</td>
<td class="govuk-table__cell">1 hour</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell">ai_user</td>
<td class="govuk-table__cell">This helps us to identify the number of distinct users accessing the site over time by tracking if you've visited before</td>
<td class="govuk-table__cell">1 year</td>
</tr>
<tr class="govuk-table__row">
<td class="govuk-table__cell">ai_authuser</td>
<td class="govuk-table__cell">This helps us to identify authenticated users and how they interact with the site</td>
<td class="govuk-table__cell">When you close your browser</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,105 +1,112 @@
using Dfe.PrepareConversions.Configuration;
using Dfe.PrepareConversions.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;

namespace Dfe.PrepareConversions.Pages.Public;

public class CookiePreferences : PageModel
{
private const string CONSENT_COOKIE_NAME = ".ManageAnAcademyConversion.Consent";
private readonly ILogger<CookiePreferences> _logger;
private readonly IOptions<ServiceLinkOptions> _options;

public CookiePreferences(ILogger<CookiePreferences> logger, IOptions<ServiceLinkOptions> options)
{
_logger = logger;
_options = options;
}

public bool? Consent { get; set; }
public bool PreferencesSet { get; set; }
public string ReturnPath { get; set; }

public string TransfersCookiesUrl { get; set; }

public ActionResult OnGet(bool? consent, string returnUrl)
{
ReturnPath = returnUrl;
TransfersCookiesUrl = $"{_options.Value.TransfersUrl}/cookie-preferences?returnUrl=%2Fhome";

if (Request.Cookies.ContainsKey(CONSENT_COOKIE_NAME))
{
Consent = bool.Parse(Request.Cookies[CONSENT_COOKIE_NAME] ?? string.Empty);
}

if (consent.HasValue)
{
PreferencesSet = true;

ApplyCookieConsent(consent);

if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}

return RedirectToPage(Links.Public.CookiePreferences);
}

return Page();
}

public IActionResult OnPost(bool? consent, string returnUrl)
{
ReturnPath = returnUrl;

if (Request.Cookies.ContainsKey(CONSENT_COOKIE_NAME))
{
Consent = bool.Parse(Request.Cookies[CONSENT_COOKIE_NAME] ?? string.Empty);
}

if (consent.HasValue)
{
Consent = consent;
PreferencesSet = true;

CookieOptions cookieOptions = new() { Expires = DateTime.Today.AddMonths(6), Secure = true, HttpOnly = true };
Response.Cookies.Append(CONSENT_COOKIE_NAME, consent.Value.ToString(), cookieOptions);

if (consent.Value is false)
{
ApplyCookieConsent(false);
}

return Page();
}

return Page();
}

private void ApplyCookieConsent(bool? consent)
{
if (consent.HasValue)
{
CookieOptions cookieOptions = new() { Expires = DateTime.Today.AddMonths(6), Secure = true, HttpOnly = true };
Response.Cookies.Append(CONSENT_COOKIE_NAME, consent.Value.ToString(), cookieOptions);
}

if (consent is false)
{
foreach (string cookie in Request.Cookies.Keys)
{
if (cookie.StartsWith("_ga") || cookie.Equals("_gid"))
{
_logger.LogInformation("Expiring Google analytics cookie: {cookie}", cookie);
Response.Cookies.Append(cookie, string.Empty, new CookieOptions { Expires = DateTime.Now.AddDays(-1), Secure = true, SameSite = SameSiteMode.Lax, HttpOnly = true });
}
}
}
}
}
using Dfe.PrepareConversions.Configuration;
using Dfe.PrepareConversions.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;

namespace Dfe.PrepareConversions.Pages.Public;

public class CookiePreferences : PageModel
{
private const string CONSENT_COOKIE_NAME = ".ManageAnAcademyConversion.Consent";
private readonly ILogger<CookiePreferences> _logger;
private readonly IOptions<ServiceLinkOptions> _options;

public CookiePreferences(ILogger<CookiePreferences> logger, IOptions<ServiceLinkOptions> options)
{
_logger = logger;
_options = options;
}

public bool? Consent { get; set; }
public bool PreferencesSet { get; set; }
public string ReturnPath { get; set; }

public string TransfersCookiesUrl { get; set; }

public ActionResult OnGet(bool? consent, string returnUrl)
{
ReturnPath = returnUrl;
TransfersCookiesUrl = $"{_options.Value.TransfersUrl}/cookie-preferences?returnUrl=%2Fhome";

if (Request.Cookies.ContainsKey(CONSENT_COOKIE_NAME))
{
Consent = bool.Parse(Request.Cookies[CONSENT_COOKIE_NAME] ?? string.Empty);
}

if (consent.HasValue)
{
PreferencesSet = true;

ApplyCookieConsent(consent);

if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}

return RedirectToPage(Links.Public.CookiePreferences);
}

return Page();
}

public IActionResult OnPost(bool? consent, string returnUrl)
{
ReturnPath = returnUrl;

if (Request.Cookies.ContainsKey(CONSENT_COOKIE_NAME))
{
Consent = bool.Parse(Request.Cookies[CONSENT_COOKIE_NAME] ?? string.Empty);
}

if (consent.HasValue)
{
Consent = consent;
PreferencesSet = true;

CookieOptions cookieOptions = new() { Expires = DateTime.Today.AddMonths(6), Secure = true, HttpOnly = true };
Response.Cookies.Append(CONSENT_COOKIE_NAME, consent.Value.ToString(), cookieOptions);

if (consent.Value is false)
{
ApplyCookieConsent(false);
}

return Page();
}

return Page();
}

private void ApplyCookieConsent(bool? consent)
{
if (consent.HasValue)
{
CookieOptions cookieOptions = new() { Expires = DateTime.Today.AddMonths(6), Secure = true, HttpOnly = true };
Response.Cookies.Append(CONSENT_COOKIE_NAME, consent.Value.ToString(), cookieOptions);
}

if (consent is false)
{
foreach (string cookie in Request.Cookies.Keys)
{
// Google Analytics
if (cookie.StartsWith("_ga") || cookie.Equals("_gid"))
{
_logger.LogInformation("Expiring Google analytics cookie: {cookie}", cookie);
Response.Cookies.Append(cookie, string.Empty, new CookieOptions { Expires = DateTime.Now.AddDays(-1), Secure = true, SameSite = SameSiteMode.Lax, HttpOnly = true });
}
// App Insights
if (cookie.StartsWith("ai_"))
{
_logger.LogInformation("Expiring App insights cookie: {cookie}", cookie);
Response.Cookies.Append(cookie, string.Empty, new CookieOptions { Expires = DateTime.Now.AddYears(-1), Secure = true, SameSite = SameSiteMode.Lax, HttpOnly = true });
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
var analyticsConsent = Context.Request.Cookies.ContainsKey(".ManageAnAcademyConversion.Consent")
&& bool.Parse(Context.Request.Cookies[".ManageAnAcademyConversion.Consent"] ?? string.Empty);

var showAnalytics = Configuration["GoogleAnalytics:Enable"] == "Yes" && analyticsConsent;
var enableGoogleAnalytics = Configuration["GoogleAnalytics:Enable"] == "Yes" && analyticsConsent;
var enableAppInsightsAnalytics = Configuration["ApplicationInsights:EnableBrowserAnalytics"] == "Yes" && analyticsConsent;

var titleDescription = Context.Request.Path == "/project-type"
? string.Empty
Expand All @@ -15,6 +16,8 @@
var isFormAMatPage = Context.Request.Path.Value.Contains("/form-a-mat/project-list") || Context.Request.Path.Value.Contains("/schools-in-this-mat/");

var notificationBannerMessage = Configuration["notificationBannerMessage"] ?? string.Empty;

var authenticatedUserId = User.Identity is not null && User.Identity.IsAuthenticated ? User.Identity.Name ?? "Unknown" : "Anonymous";
}

<!DOCTYPE html>
Expand All @@ -38,7 +41,7 @@
<link rel="stylesheet" href="~/dist/site.css" />
<link rel="stylesheet" href="~/dist/accessible-autocomplete.min.css" asp-add-nonce />
<link rel="stylesheet" href="~/dist/dfefrontend-1.0.1.min.css" asp-add-nonce />
@if (showAnalytics)
@if (enableGoogleAnalytics)
{
<!-- Google Tag Manager -->
<script asp-add-nonce>
Expand All @@ -49,24 +52,39 @@
}); var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-5H6G773');</script>
<!-- End Google Tag Manager -->
})(window, document, 'script', 'dataLayer', 'GTM-5H6G773');
</script>
<!-- End Google Tag Manager -->
}

@if (analyticsConsent)
@if (enableAppInsightsAnalytics)
{
<!-- Google Tag Manager (noscript) -->
<noscript asp-add-nonce>
<iframe title="GATagManager" src=https://www.googletagmanager.com/ns.html?id =GTM-5H6G773 height="0" width="0" style="display: none; visibility: hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<!-- Application insights -->
<script type="text/javascript" integrity="sha384-g/ZkzetdQypWdY0NBZT5r2L3BR9/hURD8OBcd1rEaBpgX6QC7EaTL+o+mzWrBcXW" crossorigin="anonymous" src="https://js.monitor.azure.com/scripts/b/ext/ai.clck.2.8.18.min.js"></script>
<script type="text/javascript" asp-add-nonce>
window.appInsights = {
connectionString: '@Configuration["ApplicationInsights:ConnectionString"]',
authenticatedUserId: '@authenticatedUserId'
}
</script>
<script type="text/javascript" src="~/dist/application-insights.min.js" asp-add-nonce></script>
<!-- End Application insights -->
}

<script type="text/javascript" src="~/dist/accessible-autocomplete.min.js" asp-add-nonce></script>

</head>
<body class="govuk-template__body">

@if (enableGoogleAnalytics)
{
<!-- Google Tag Manager (noscript) -->
<noscript asp-add-nonce>
<iframe title="GATagManager" src="https://www.googletagmanager.com/ns.html?id=GTM-5H6G773" height="0" width="0" style="display: none; visibility: hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
}

<script asp-add-nonce>
document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');
</script>
Expand Down
Loading

0 comments on commit 3ad6163

Please sign in to comment.