Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track app #523

Merged
merged 6 commits into from
Jun 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# ignore tracking usage data config - we want a different one for each user
usage-data-config.json
.env
.sass-cache
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ New features:
- [#502 Add Cookies and Privacy policy text](https://github.com/alphagov/govuk_prototype_kit/pull/502)
- [#521 Do not track users who have enabled 'DoNotTrack'](https://github.com/alphagov/govuk_prototype_kit/pull/521)
- [#522 Add inline-code block styles](https://github.com/alphagov/govuk_prototype_kit/pull/522)
- [#523 Track app usage](https://github.com/alphagov/govuk_prototype_kit/pull/523)

Bug fixes:

Expand Down
29 changes: 29 additions & 0 deletions docs/documentation/usage-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Collecting usage data

You can choose to have the Prototype Kit send anonymous usage data for analysis.
This helps the team working on the Kit understand how it's being used, in order
to improve it - please don't turn off usage data unless you have to.

## How it works

When you first run the Prototype Kit, it will ask you for permission to send
usage data to Google Analytics. It will store your answer in `usage-data-config.json` and it won't ask
you again.

If you say yes, it will store an anonymous, unique ID number in `usage-data-config.json`.

## Data we collect

The kit will only send data when you run it on your computer. It does not send data when you run it on Heroku.

Whenever you start the Prototype Kit, it will send:

- your anonymous ID number
- the Prototype Kit version number
- your operating system (for example 'Windows 10')
- your Node.js version

## Change usage data settings

You can start or stop sending usage data at any time. Delete `usage-data-config.json`
and restart the Prototype Kit. It will ask you again whether you'd like to send data.
3 changes: 3 additions & 0 deletions docs/views/tutorials-and-examples.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ <h3 class="govuk-heading-s">Getting started</h3>
<div class="govuk-grid-column-one-third">
<h3 class="govuk-heading-s">Basic usage</h3>
<ul class="govuk-list govuk-list--bullet">
<li>
<a href="/docs/usage-data">Sending kit usage data</a>
</li>
<li>
<a href="/docs/making-pages">Making pages</a>
</li>
Expand Down
9 changes: 9 additions & 0 deletions lib/usage-data-prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

Help us improve the GOV.UK Prototype Kit
────────────────────────────────────────

With your permission, the kit can send useful anonymous usage data
for analysis to help the team improve the service. Read more here:
https://govuk-prototype-kit.herokuapp.com/docs/usage-data

Do you give permission for the kit to send anonymous usage data? (y/n)
95 changes: 95 additions & 0 deletions lib/usage_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Core dependencies
const path = require('path')
const fs = require('fs')
const os = require('os')

// NPM dependencies
const prompt = require('prompt')
const universalAnalytics = require('universal-analytics')
const uuidv4 = require('uuid/v4')

// Local dependencies
const packageJson = require('../package.json')

exports.getUsageDataConfig = function () {
// Try to read config file to see if usage data is opted in
let usageDataConfig = {}
try {
usageDataConfig = require(path.join(__dirname, '../usage-data-config.json'))
} catch (e) {
// do nothing - we will make a config
}
return usageDataConfig
}

exports.setUsageDataConfig = function (usageDataConfig) {
const usageDataConfigJSON = JSON.stringify(usageDataConfig, null, ' ')
try {
fs.writeFileSync(path.join(__dirname, '../usage-data-config.json'), usageDataConfigJSON)
return true
} catch (error) {
console.error(error)
}
return false
}

// Ask for permission to track data
// returns a Promise with the user's answer
exports.askForUsageDataPermission = function () {
return new Promise(function (resolve, reject) {
// Set up prompt settings
prompt.colors = false
prompt.start()
prompt.message = ''
prompt.delimiter = ''

const description = fs.readFileSync(path.join(__dirname, 'usage-data-prompt.txt'), 'utf8')

prompt.get([{
name: 'answer',
description: description,
required: true,
type: 'string',
pattern: /y(es)?|no?/i,
message: 'Please enter y or n'
}], function (err, result) {
if (err) {
reject(err)
}
if (result.answer.match(/y(es)?/i)) {
resolve('yes')
} else {
resolve('no')
}
})
})
}

exports.startTracking = function (usageDataConfig) {
// Get client ID for tracking
// https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#cid

if (usageDataConfig.clientId === undefined) {
usageDataConfig.clientId = uuidv4()
exports.setUsageDataConfig(usageDataConfig)
}

// Track kit start event, with kit version, operating system and Node.js version
const trackingId = 'UA-26179049-21'
const trackingUser = universalAnalytics(trackingId, usageDataConfig.clientId)

const kitVersion = packageJson.version
const operatingSystem = os.platform() + ' ' + os.release()
const nodeVersion = process.versions.node

// Anonymise the IP
trackingUser.set('anonymizeIp', 1)

// Set custom dimensions
trackingUser.set('cd1', operatingSystem)
trackingUser.set('cd2', kitVersion)
trackingUser.set('cd3', nodeVersion)

// Trigger start event
trackingUser.event('State', 'Start').send()
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
"run-sequence": "^1.2.2",
"standard": "^10.0.2",
"supertest": "^3.0.0",
"sync-request": "^4.0.3"
"sync-request": "^4.0.3",
"universal-analytics": "^0.4.16",
"uuid": "^3.2.1"
},
"greenkeeper": {
"ignore": [
Expand Down
76 changes: 56 additions & 20 deletions start.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,53 @@
const path = require('path')
const fs = require('fs')

// Warn if node_modules folder doesn't exist
const nodeModulesExists = fs.existsSync(path.join(__dirname, '/node_modules'))
if (!nodeModulesExists) {
console.error('ERROR: Node module folder missing. Try running `npm install`')
process.exit(0)
checkFiles()

// Local dependencies
const usageData = require('./lib/usage_data')

// Get usageDataConfig from file, if exists
const usageDataConfig = usageData.getUsageDataConfig()

if (usageDataConfig.collectUsageData === undefined) {
// No recorded answer, so ask for permission
let promptPromise = usageData.askForUsageDataPermission()
promptPromise.then(function (answer) {
if (answer === 'yes') {
usageDataConfig.collectUsageData = true
usageData.setUsageDataConfig(usageDataConfig)
usageData.startTracking(usageDataConfig)
} else if (answer === 'no') {
usageDataConfig.collectUsageData = false
usageData.setUsageDataConfig(usageDataConfig)
} else {
console.error(answer)
}
runGulp()
})
} else if (usageDataConfig.collectUsageData === true) {
// Opted in
usageData.startTracking(usageDataConfig)
runGulp()
} else {
// Opted out
runGulp()
}

// Create template .env file if it doesn't exist
const envExists = fs.existsSync(path.join(__dirname, '/.env'))
if (!envExists) {
console.log('Creating template .env file')
fs.createReadStream(path.join(__dirname, '/lib/template.env'))
.pipe(fs.createWriteStream(path.join(__dirname, '/.env')))
// Warn if node_modules folder doesn't exist
function checkFiles () {
const nodeModulesExists = fs.existsSync(path.join(__dirname, '/node_modules'))
if (!nodeModulesExists) {
console.error('ERROR: Node module folder missing. Try running `npm install`')
process.exit(0)
}

// Create template .env file if it doesn't exist
const envExists = fs.existsSync(path.join(__dirname, '/.env'))
if (!envExists) {
fs.createReadStream(path.join(__dirname, '/lib/template.env'))
.pipe(fs.createWriteStream(path.join(__dirname, '/.env')))
}
}

// Create template session data defaults file if it doesn't exist
Expand All @@ -33,14 +67,16 @@ if (!sessionDataDefaultsFileExists) {
}

// Run gulp
const spawn = require('cross-spawn')
function runGulp () {
const spawn = require('cross-spawn')

process.env['FORCE_COLOR'] = 1
var gulp = spawn('gulp')
gulp.stdout.pipe(process.stdout)
gulp.stderr.pipe(process.stderr)
process.stdin.pipe(gulp.stdin)
process.env['FORCE_COLOR'] = 1
var gulp = spawn('gulp')
gulp.stdout.pipe(process.stdout)
gulp.stderr.pipe(process.stderr)
process.stdin.pipe(gulp.stdin)

gulp.on('exit', function (code) {
console.log('gulp exited with code ' + code.toString())
})
gulp.on('exit', function (code) {
console.log('gulp exited with code ' + code.toString())
})
}