govuk_frontend_toolkit 7.2.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
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