govuk_frontend_toolkit 7.2.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 538b9de6aaf9bd4fa8ec41041f55e19fc38bc25716f2684efde95b3eb5a0e3b9
4
- data.tar.gz: 48c37a0f38db00cef1d3472610d5890bc0f7c70938d8b84f9adb01e6ee633ee0
3
+ metadata.gz: 0334902cdeefff962323abe904470c7558848f31bda9660316d85abf05e26d34
4
+ data.tar.gz: f69772e14fcbbbd38c6a5fecdc627618c990d481f6a818b8bba157d231b69a73
5
5
  SHA512:
6
- metadata.gz: a4219bbd88e18c1bd0815f4bb89c38e01723ca3887c6ea1016673f69c3eded8818254053992659f0a0d5ad86825aedbeee190acb901c2a487b5cf9a78608a99a
7
- data.tar.gz: 0d6bbce22e7e53064c9d01762f725958827bd68a2fec32c223aefb3d80d32c0fe596b4991e658be4f1861cb1a60e6351dedae60d3b075456719a5de622545d6e
6
+ metadata.gz: 57596cc95ffa5ec4c46b0e020cefde1007f2c2d074707e53b6ed3a221e5807e13c040f62289fc89fc561d5e56f758b6279e56f685f839a46149eb2eae1f12425
7
+ data.tar.gz: 608f7fa34a8f4723b883b564d401f52a3c9afca816303c574f7f0ca4574c458d0d9b32e243fa6828e5840ab403069275ed405d6a564efde0532fda50b2adf919
@@ -1,3 +1,8 @@
1
+ # 7.3.0
2
+
3
+ - Strip PII from all arguments passed to GA. Emails are stripped by default, postcodes can also be stripped if configured to do so: ([PR #435](https://github.com/alphagov/govuk_frontend_toolkit/pull/435)).
4
+ - Update sass and govuk-lint dependencies: ([PR #444](https://github.com/alphagov/govuk_frontend_toolkit/pull/444)).
5
+
1
6
  # 7.2.0
2
7
 
3
8
  - Add custom dimension on TrackEvent to duplicate the url information that we normally send on a the `event action`. This will be used to join up with a scheduled custom upload called "External Link Status". We can only join uploads on custom dimensions, not on `event actions`, where we normally add the url info. ([PR #436](alphagov/govuk_frontend_toolkit#436) and [PR #439](alphagov/govuk_frontend_toolkit#439))
data/app/assets/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'sass', '3.4.15'
4
- gem 'govuk-lint', '0.7.0'
3
+ gem 'sass', '3.5.5'
4
+ gem 'govuk-lint', '3.6.0'
@@ -1 +1 @@
1
- 7.2.0
1
+ 7.3.0
@@ -7,6 +7,7 @@ The toolkit provides an abstraction around analytics to make tracking pageviews,
7
7
  * a generic Analytics wrapper that allows multiple trackers to be configured
8
8
  * sensible defaults such as anonymising IPs
9
9
  * data coercion into the format required by Google Analytics (eg a custom dimension’s value must be a string)
10
+ * stripping of PII from data sent to the tracker (strips email by default, can be configured to also strip UK postcodes)
10
11
 
11
12
  ## Create an analytics tracker
12
13
 
@@ -216,3 +217,35 @@ plugin uses Google Analytics’ `transport: beacon` method so that events are tr
216
217
  Category | Action | Label
217
218
  ---------|--------|-------
218
219
  Mailto Link Clicked | mailto:name@email.com | Link text
220
+
221
+ ### Stripping Personally Identifiable Information (PII)
222
+
223
+ The tracker will strip any PII it detects from all arguments sent to the
224
+ tracker. If a PII is detected in the arguments it is replaced with a
225
+ placeholder value of `[<type of PII removed>]`; for example: `[email]` if an
226
+ email address was removed, or `[postcode]` if a postcode was removed.
227
+
228
+ We have to parse all arguments which means that if you don't pass a path to
229
+ `trackPageView` to track the current page we have to extract the current page
230
+ and parse it, turning all `trackPageView` calls into ones with a path argument.
231
+ We use `window.location.href.split('#')[0]` as the default path when one is
232
+ not provided. The original behaviour would have been to ignore the anchor
233
+ part of the URL anyway so this doesn't change the behaviour other than to make
234
+ the path explicit.
235
+
236
+ By default we strip email addresses, but it can also be configured to strip
237
+ postcodes too. Postcodes are off by default because they're more likely to
238
+ cause false positives. If you know you are likely to include postcodes in
239
+ the data you send to the tracker you can configure to strip postcodes at
240
+ initialize time as follows:
241
+
242
+ ```js
243
+ GOVUK.analytics = new GOVUK.Analytics({
244
+ universalId: 'UA-XXXXXXXX-X',
245
+ cookieDomain: cookieDomain,
246
+ stripPostcodePII: true
247
+ });
248
+ ````
249
+
250
+ Any value other than the JS literal `true` for `stripPostcodePII` will leave
251
+ the analytics module configured not to strip postcodes.
@@ -286,7 +286,7 @@ Links styled to look like buttons lack button behaviour. This script will allow
286
286
 
287
287
  ### Usage
288
288
 
289
- By default, this behaviour will only be applied to links with a role of button.
289
+ This behaviour will be applied to elements with a role of button.
290
290
 
291
291
  ```html
292
292
  <a class="button" role="button">A button</a>
@@ -296,23 +296,6 @@ By default, this behaviour will only be applied to links with a role of button.
296
296
  GOVUK.shimLinksWithButtonRole.init()
297
297
  ```
298
298
 
299
- If you need to override the elements this is applied to then you can do that by passing in a custom selector to the initialiser:
300
-
301
- ```javascript
302
- GOVUK.shimLinksWithButtonRole.init({
303
- selector: '.my-class'
304
- })
305
- ```
306
-
307
- It’s also possible to define more or different keycodes to activate against:
308
-
309
- ```javascript
310
- // activate when the user presses space or ‘r’
311
- GOVUK.shimLinksWithButtonRole.init({
312
- keycodes: [32, 114]
313
- });
314
- ```
315
-
316
299
  ## Show/Hide content
317
300
 
318
301
  Script to support show/hide content, toggled by radio buttons and checkboxes. This allows for progressive disclosure of question and answer forms based on selected values:
@@ -2,11 +2,20 @@
2
2
  'use strict'
3
3
 
4
4
  var GOVUK = global.GOVUK || {}
5
+ var EMAIL_PATTERN = /[^\s=/?&]+(?:@|%40)[^\s=/?&]+/g
6
+ var POSTCODE_PATTERN = /[A-PR-UWYZ][A-HJ-Z]?[0-9][0-9A-HJKMNPR-Y]?(?:[\s+]|%20)*[0-9][ABD-HJLNPQ-Z]{2}/gi
5
7
 
6
8
  // For usage and initialisation see:
7
9
  // https://github.com/alphagov/govuk_frontend_toolkit/blob/master/docs/analytics.md#create-an-analytics-tracker
8
10
 
9
11
  var Analytics = function (config) {
12
+ this.stripPostcodePII = false
13
+ if (typeof config.stripPostcodePII !== 'undefined') {
14
+ this.stripPostcodePII = (config.stripPostcodePII === true)
15
+ // remove the option so we don't pass it to other trackers - it's not
16
+ // their concern
17
+ delete config.stripPostcodePII
18
+ }
10
19
  this.trackers = []
11
20
  if (typeof config.universalId !== 'undefined') {
12
21
  var universalId = config.universalId
@@ -20,6 +29,45 @@
20
29
  }
21
30
  }
22
31
 
32
+ Analytics.prototype.stripPII = function (value) {
33
+ if (typeof value === 'string') {
34
+ return this.stripPIIFromString(value)
35
+ } else if (Object.prototype.toString.call(value) === '[object Array]') {
36
+ return this.stripPIIFromArray(value)
37
+ } else if (typeof value === 'object') {
38
+ return this.stripPIIFromObject(value)
39
+ } else {
40
+ return value
41
+ }
42
+ }
43
+
44
+ Analytics.prototype.stripPIIFromString = function (string) {
45
+ var emailStripped = string.replace(EMAIL_PATTERN, '[email]')
46
+ if (this.stripPostcodePII === true) {
47
+ return emailStripped.replace(POSTCODE_PATTERN, '[postcode]')
48
+ } else {
49
+ return emailStripped
50
+ }
51
+ }
52
+
53
+ Analytics.prototype.stripPIIFromObject = function (object) {
54
+ for (var property in object) {
55
+ var value = object[property]
56
+
57
+ object[property] = this.stripPII(value)
58
+ }
59
+ return object
60
+ }
61
+
62
+ Analytics.prototype.stripPIIFromArray = function (array) {
63
+ for (var i = 0, l = array.length; i < l; i++) {
64
+ var elem = array[i]
65
+
66
+ array[i] = this.stripPII(elem)
67
+ }
68
+ return array
69
+ }
70
+
23
71
  Analytics.prototype.sendToTrackers = function (method, args) {
24
72
  for (var i = 0, l = this.trackers.length; i < l; i++) {
25
73
  var tracker = this.trackers[i]
@@ -36,8 +84,19 @@
36
84
  GOVUK.GOVUKTracker.load()
37
85
  }
38
86
 
87
+ Analytics.prototype.defaultPathForTrackPageview = function () {
88
+ // Ignore anchor, but keep query string as per default behaviour of
89
+ // GA (see: https://developers.google.com/analytics/devguides/collection/analyticsjs/pages#overview)
90
+ // we ignore the possibility of there being campaign variables in the
91
+ // anchor because we wouldn't know how to detect and parse them if they
92
+ // were present
93
+ return this.stripPIIFromString(window.location.href.split('#')[0])
94
+ }
95
+
39
96
  Analytics.prototype.trackPageview = function (path, title, options) {
40
- this.sendToTrackers('trackPageview', arguments)
97
+ arguments[0] = arguments[0] || this.defaultPathForTrackPageview()
98
+ if (arguments.length === 0) { arguments.length = 1 }
99
+ this.sendToTrackers('trackPageview', this.stripPII(arguments))
41
100
  }
42
101
 
43
102
  /*
@@ -47,11 +106,11 @@
47
106
  options.nonInteraction – Prevent event from impacting bounce rate
48
107
  */
49
108
  Analytics.prototype.trackEvent = function (category, action, options) {
50
- this.sendToTrackers('trackEvent', arguments)
109
+ this.sendToTrackers('trackEvent', this.stripPII(arguments))
51
110
  }
52
111
 
53
112
  Analytics.prototype.trackShare = function (network, options) {
54
- this.sendToTrackers('trackSocial', [network, 'share', global.location.pathname, options])
113
+ this.sendToTrackers('trackSocial', this.stripPII([network, 'share', global.location.pathname, options]))
55
114
  }
56
115
 
57
116
  /*
@@ -59,7 +118,7 @@
59
118
  Universal Analytics profile
60
119
  */
61
120
  Analytics.prototype.setDimension = function (index, value) {
62
- this.sendToTrackers('setDimension', arguments)
121
+ this.sendToTrackers('setDimension', this.stripPII(arguments))
63
122
  }
64
123
 
65
124
  /*
@@ -32,6 +32,71 @@ describe('GOVUK.Analytics', function () {
32
32
  expect(universalSetupArguments[1]).toEqual(['set', 'anonymizeIp', true])
33
33
  expect(universalSetupArguments[2]).toEqual(['set', 'displayFeaturesTask', null])
34
34
  })
35
+
36
+ it('is configured not to strip postcode data from GA calls', function () {
37
+ expect(analytics.stripPostcodePII).toEqual(false)
38
+ })
39
+
40
+ it('can be told to strip postcode data from GA calls', function () {
41
+ analytics = new GOVUK.Analytics({
42
+ universalId: 'universal-id',
43
+ cookieDomain: '.www.gov.uk',
44
+ siteSpeedSampleRate: 100,
45
+ stripPostcodePII: true
46
+ })
47
+
48
+ expect(analytics.stripPostcodePII).toEqual(true)
49
+ })
50
+ })
51
+
52
+ describe('tracking pageviews', function () {
53
+ beforeEach(function () {
54
+ spyOn(analytics, 'defaultPathForTrackPageview').and.returnValue('https://govuk-frontend-toolkit.example.com/a/page?with=a&query=string')
55
+ })
56
+
57
+ it('injects a default path if no args are supplied', function () {
58
+ analytics.trackPageview()
59
+ console.log(window.ga.calls.mostRecent().args)
60
+ expect(window.ga.calls.mostRecent().args[2].page).toEqual('https://govuk-frontend-toolkit.example.com/a/page?with=a&query=string')
61
+ })
62
+
63
+ it('injects a default path if args are supplied, but the path arg is blank', function () {
64
+ analytics.trackPageview(null)
65
+ console.log(window.ga.calls.mostRecent().args)
66
+ expect(window.ga.calls.mostRecent().args[2].page).toEqual('https://govuk-frontend-toolkit.example.com/a/page?with=a&query=string')
67
+
68
+ analytics.trackPageview(undefined)
69
+ console.log(window.ga.calls.mostRecent().args)
70
+ expect(window.ga.calls.mostRecent().args[2].page).toEqual('https://govuk-frontend-toolkit.example.com/a/page?with=a&query=string')
71
+ })
72
+
73
+ it('uses the supplied path', function () {
74
+ analytics.trackPageview('/foo')
75
+ console.log(window.ga.calls.mostRecent().args)
76
+ expect(window.ga.calls.mostRecent().args[2].page).toEqual('/foo')
77
+ })
78
+
79
+ it('does not inject a default title if no args are supplied', function () {
80
+ analytics.trackPageview()
81
+ console.log(window.ga.calls.mostRecent().args)
82
+ expect(window.ga.calls.mostRecent().args[2].title).toEqual(undefined)
83
+ })
84
+
85
+ it('does not inject a default title if args are supplied, but the title arg is blank', function () {
86
+ analytics.trackPageview('/foo', null)
87
+ console.log(window.ga.calls.mostRecent().args)
88
+ expect(window.ga.calls.mostRecent().args[2].title).toEqual(undefined)
89
+
90
+ analytics.trackPageview('/foo', undefined)
91
+ console.log(window.ga.calls.mostRecent().args)
92
+ expect(window.ga.calls.mostRecent().args[2].title).toEqual(undefined)
93
+ })
94
+
95
+ it('uses the supplied title', function () {
96
+ analytics.trackPageview('/foo', 'A page')
97
+ console.log(window.ga.calls.mostRecent().args)
98
+ expect(window.ga.calls.mostRecent().args[2].title).toEqual('A page')
99
+ })
35
100
  })
36
101
 
37
102
  describe('when tracking pageviews, events and custom dimensions', function () {
@@ -45,6 +110,46 @@ describe('GOVUK.Analytics', function () {
45
110
  analytics.setDimension(1, 'value', 'name')
46
111
  expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', 'value'])
47
112
  })
113
+
114
+ it('strips email addresses embedded in arguments', function () {
115
+ analytics.trackPageview('/path/to/an/embedded.email@example.com/address/?with=an&email=in.it@example.com', 'an.email@example.com', { label: 'another.email@example.com', value: ['data', 'data', 'someone has added their personal.email@example.com address'] })
116
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', { page: '/path/to/an/[email]/address/?with=an&email=[email]', title: '[email]', label: '[email]', value: ['data', 'data', 'someone has added their [email] address'] }])
117
+
118
+ analytics.trackEvent('an_email@example.com_address-category', 'an.email@example.com-action', { label: 'another.email@example.com', value: ['data', 'data', 'someone has added their personal.email@example.com address'] })
119
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', { hitType: 'event', eventCategory: '[email]', eventAction: '[email]', eventLabel: '[email]' }]) // trackEvent ignores options other than label or integer values for value
120
+
121
+ analytics.setDimension(1, 'an_email@example.com_address-value', { label: 'another.email@example.com', value: ['data', 'data', 'someone has added their personal.email@example.com address'] })
122
+ expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', '[email]']) // set dimension ignores extra options
123
+ })
124
+
125
+ it('leaves postcodes embedded in arguments by default', function () {
126
+ analytics.trackPageview('/path/to/an/embedded/SW1+1AA/postcode/?with=an&postcode=SP4%207DE', 'TD15 2SE', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
127
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', { page: '/path/to/an/embedded/SW1+1AA/postcode/?with=an&postcode=SP4%207DE', title: 'TD15 2SE', label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] }])
128
+
129
+ analytics.trackEvent('SW1+1AA-category', 'SP4%207DE-action', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
130
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', { hitType: 'event', eventCategory: 'SW1+1AA-category', eventAction: 'SP4%207DE-action', eventLabel: 'RG209NJ' }]) // trackEvent ignores options other than label or integer values for value
131
+
132
+ analytics.setDimension(1, 'SW1+1AA-value', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
133
+ expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', 'SW1+1AA-value']) // set dimension ignores extra options
134
+ })
135
+
136
+ it('strips postcodes embedded in arguments if configured to do so', function () {
137
+ analytics = new GOVUK.Analytics({
138
+ universalId: 'universal-id',
139
+ cookieDomain: '.www.gov.uk',
140
+ siteSpeedSampleRate: 100,
141
+ stripPostcodePII: true
142
+ })
143
+
144
+ analytics.trackPageview('/path/to/an/embedded/SW1+1AA/postcode/?with=an&postcode=SP4%207DE', 'TD15 2SE', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
145
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', 'pageview', { page: '/path/to/an/embedded/[postcode]/postcode/?with=an&postcode=[postcode]', title: '[postcode]', label: '[postcode]', value: ['data', 'data', 'someone has added their personal[postcode] postcode'] }])
146
+
147
+ analytics.trackEvent('SW1+1AA-category', 'SP4%207DE-action', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
148
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', { hitType: 'event', eventCategory: '[postcode]-category', eventAction: '[postcode]-action', eventLabel: '[postcode]' }]) // trackEvent ignores options other than label or integer values for value
149
+
150
+ analytics.setDimension(1, 'SW1+1AA-value', { label: 'RG209NJ', value: ['data', 'data', 'someone has added their personalIV63 6TU postcode'] })
151
+ expect(window.ga.calls.mostRecent().args).toEqual(['set', 'dimension1', '[postcode]-value']) // set dimension ignores extra options
152
+ })
48
153
  })
49
154
 
50
155
  describe('when tracking social media shares', function () {
@@ -58,6 +163,67 @@ describe('GOVUK.Analytics', function () {
58
163
  socialTarget: jasmine.any(String)
59
164
  }])
60
165
  })
166
+
167
+ it('strips email addresses embedded in arguments', function () {
168
+ analytics.trackShare('email', {
169
+ to: 'myfriend@example.com',
170
+ label: 'another.email@example.com',
171
+ value: ['data', 'data', 'someone has added their personal.email@example.com address']
172
+ })
173
+
174
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', {
175
+ hitType: 'social',
176
+ socialNetwork: 'email',
177
+ socialAction: 'share',
178
+ socialTarget: jasmine.any(String),
179
+ to: '[email]',
180
+ label: '[email]',
181
+ value: ['data', 'data', 'someone has added their [email] address']
182
+ }])
183
+ })
184
+
185
+ it('leaves postcodes embedded in arguments by default', function () {
186
+ analytics.trackShare('email', {
187
+ to: 'IV63 6TU',
188
+ label: 'SP4%207DE',
189
+ value: ['data', 'data', 'someone has added their personalTD15 2SE postcode']
190
+ })
191
+
192
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', {
193
+ hitType: 'social',
194
+ socialNetwork: 'email',
195
+ socialAction: 'share',
196
+ socialTarget: jasmine.any(String),
197
+ to: 'IV63 6TU',
198
+ label: 'SP4%207DE',
199
+ value: ['data', 'data', 'someone has added their personalTD15 2SE postcode']
200
+ }])
201
+ })
202
+
203
+ it('strips postcodes embedded in arguments if configured to do so', function () {
204
+ analytics = new GOVUK.Analytics({
205
+ universalId: 'universal-id',
206
+ cookieDomain: '.www.gov.uk',
207
+ siteSpeedSampleRate: 100,
208
+ stripPostcodePII: true
209
+ })
210
+
211
+ analytics.trackShare('email', {
212
+ to: 'IV63 6TU',
213
+ label: 'SP4%207DE',
214
+ value: ['data', 'data', 'someone has added their personalTD15 2SE postcode']
215
+ })
216
+
217
+ expect(window.ga.calls.mostRecent().args).toEqual(['send', {
218
+ hitType: 'social',
219
+ socialNetwork: 'email',
220
+ socialAction: 'share',
221
+ socialTarget: jasmine.any(String),
222
+ to: '[postcode]',
223
+ label: '[postcode]',
224
+ value: ['data', 'data', 'someone has added their personal[postcode] postcode']
225
+ }])
226
+ })
61
227
  })
62
228
 
63
229
  describe('when adding a linked domain', function () {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_frontend_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.0
4
+ version: 7.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Government Digital Service
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-13 00:00:00.000000000 Z
11
+ date: 2018-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -305,7 +305,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
305
305
  version: '0'
306
306
  requirements: []
307
307
  rubyforge_project:
308
- rubygems_version: 2.7.2
308
+ rubygems_version: 2.7.5
309
309
  signing_key:
310
310
  specification_version: 4
311
311
  summary: Tools for building frontend applications