Skip to content

Commit 656a42a

Browse files
authored
Manage collection of usage data (#207)
* Add support for usage measurement where enabled When a user of the prototype kit creates a new prototype, they are asked if they would like to allow usage data to be shared with the authors. This change uses that permission to measure usage unless the user has explicitly disabled the plugin from collecting metrics, or changes the prototype kit permission to false. * Add info when running prototype about stats This commit adds an explanation to the console when running the prototype kit, about the data importer's anonymous usage data. Currently it is set to the following when the user has agreed to allow usage data, and has not explicitly disabled it. ``` [WebServer] Watching dependency @register-dynamics/importer as it's a symbolic link. [WebServer] [WebServer] You are currently sending anonymous usage data to the prototype kit team. [WebServer] In addition to this, anonymous usage data is also being sent to the authors [WebServer] of the Data Upload Design Kit. [WebServer] Should you wish to disable this you can do so by adding [WebServer] [WebServer] disableImporterUsageData: true [WebServer] [WebServer] to the usage-data-config.json file in the prototype's directory. ``` We initially discussed doing this during the postinstall step, but this results in it being output too early (before the user has accepted on the first npm run dev) and obscured by more following content. * Refactor to use separate setting for usage data permissions * Add config option to allow RD to collect usage data * Fix dodgy acronym * Clarify use of anonymous ID * Add hint to contact us on github * Fix incorrect use of new var name
1 parent 11d8e8f commit 656a42a

4 files changed

Lines changed: 187 additions & 3 deletions

File tree

lib/importer/src/index.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const session_lib = require("./session.js");
66
const sheets_lib = require("./sheets.js");
77
const tpl_functions = require("./functions.js")
88
const tpl_filters = require("./filters.js")
9+
const usage = require("./usage.js")
910

1011
const IMPORTER_SESSION_KEY = "importer.session";
1112
const IMPORTER_ERROR_KEY = "importer.error";
@@ -36,6 +37,11 @@ exports.Initialise = (config, router, prototypeKit) => {
3637

3738
const plugin_config = new conf.PluginConfig(config);
3839

40+
const usageRecorder = new usage.UsageCollection(plugin_config)
41+
42+
// Record initialisation of the plugin
43+
usageRecorder.recordEvent("Plugin initialised")
44+
3945
//--------------------------------------------------------------------
4046
// To be able to add a local views folder, we need to update the
4147
// nunjucks environment's default loader to include the local folder.
@@ -338,10 +344,16 @@ exports.Initialise = (config, router, prototypeKit) => {
338344
router.get(
339345
IMPORTER_ROUTE_MAP.get("importerConfiguration"),
340346
(request, response) => {
347+
348+
// Record initialisation of the plugin
349+
usageRecorder.recordEvent("Plugin configured")
350+
351+
341352
const pc = {
342353
config: {
343354
fields: plugin_config.fields
344-
}
355+
},
356+
collectUsageData: usageRecorder.enabled,
345357
}
346358

347359
// Copy flash message to template object and then remove from memory
@@ -369,6 +381,19 @@ exports.Initialise = (config, router, prototypeKit) => {
369381
(request, response) => {
370382
request.session.data['internal-message'] = "Configuration changes have been saved, and the application will restart shortly"
371383

384+
// Check the collectData value supplied, it'll either be the value or an array
385+
// containing the value. The latter case appears to be extra junk submitted by
386+
// the design system.
387+
if (request.body.collectData == 'true' ||
388+
(Array.isArray(request.body.collectData) &&
389+
request.body.collectData.includes('true'))
390+
) {
391+
usageRecorder.setPermission(true)
392+
} else {
393+
usageRecorder.setPermission(false)
394+
}
395+
396+
372397
setTimeout(function () {
373398
plugin_config.persistConfig()
374399
}, 2500)

lib/importer/src/usage.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//--------------------------------------------------------------------
2+
// This class handles the recording of usage data where the user
3+
// has enabled data collection for the prototype and not explicitly
4+
// disabled it for the plugin.
5+
//
6+
// Once constructed the class can be used to `recordEvent` but
7+
// will check it is enabled before doing anything to ensure the
8+
// user's preferences are respected and correctly applied.
9+
//--------------------------------------------------------------------
10+
11+
12+
const fs = require('node:fs');
13+
const path = require('node:path');
14+
15+
16+
exports.UsageCollection = class {
17+
constructor() {
18+
const settings = prototypeKitUsageCollectionSettings()
19+
this.clientId = settings.clientId
20+
this.enabled = settings.ddukCollectUsageData
21+
}
22+
23+
setPermission(perm) {
24+
updateDUDKPermission(perm)
25+
this.enabled = perm
26+
}
27+
28+
recordEvent(eventName) {
29+
if (!this.enabled) return false;
30+
31+
//TODO: Actually record the usage against eventName
32+
console.log("EVENT: " + eventName)
33+
34+
return eventName != ""
35+
}
36+
}
37+
38+
const prototypeKitUsageCollectionSettings = () => {
39+
const projectDir = path.resolve(
40+
process.env.KIT_PROJECT_DIR || process.cwd(),
41+
);
42+
const usage_config = path.join(projectDir, "usage-data-config.json")
43+
const data = fs.readFileSync(usage_config);
44+
return JSON.parse(data)
45+
}
46+
47+
const updateDUDKPermission = (dudkPermission) => {
48+
const projectDir = path.resolve(
49+
process.env.KIT_PROJECT_DIR || process.cwd(),
50+
);
51+
const usage_config = path.join(projectDir, "usage-data-config.json")
52+
const data = fs.readFileSync(usage_config);
53+
54+
const obj = JSON.parse(data)
55+
obj.ddukCollectUsageData = dudkPermission
56+
57+
fs.writeFileSync(usage_config, JSON.stringify(obj))
58+
}

lib/importer/src/usage.test.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const mock_files = require('mock-fs');
2+
var usage = require('./usage');
3+
4+
5+
describe("Usage recording tests", () => {
6+
beforeEach(() => {
7+
process.env.KIT_PROJECT_DIR = "./empty"
8+
});
9+
10+
afterEach(() => {
11+
mock_files.restore();
12+
});
13+
14+
test('not present', () => {
15+
mock_files({
16+
'./empty/usage-data-config.json': JSON.stringify({ collectUsageData: false })
17+
})
18+
19+
const u = new usage.UsageCollection()
20+
expect(u).not.toBeNull()
21+
expect(u.recordEvent("test")).toBe(false)
22+
});
23+
24+
25+
test('not enabled', () => {
26+
mock_files({
27+
'./empty/usage-data-config.json': JSON.stringify({ collectUsageData: false, ddukCollectUsageData: false })
28+
})
29+
30+
const u = new usage.UsageCollection()
31+
expect(u).not.toBeNull()
32+
expect(u.recordEvent("test")).toBe(false)
33+
});
34+
35+
test('enabled with prototype kit enabled', () => {
36+
mock_files({
37+
'./empty/usage-data-config.json': JSON.stringify({ collectUsageData: true, ddukCollectUsageData: true, clientId: "1" })
38+
})
39+
40+
const u = new usage.UsageCollection()
41+
expect(u).not.toBeNull()
42+
43+
expect(u.recordEvent("test")).toBe(true)
44+
expect(u.clientId).toBe("1")
45+
});
46+
47+
test('enabled with prototype kit disabled', () => {
48+
mock_files({
49+
'./empty/usage-data-config.json': JSON.stringify({ collectUsageData: false, ddukCollectUsageData: true, clientId: "1" })
50+
})
51+
52+
const u = new usage.UsageCollection()
53+
expect(u).not.toBeNull()
54+
55+
expect(u.recordEvent("test")).toBe(true)
56+
expect(u.clientId).toBe("1")
57+
});
58+
59+
});

lib/importer/views/plugin_config.html

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,50 @@ <h2 class="govuk-heading-l">Column settings</h2>
9393
</div>
9494

9595
</div>
96-
</div>
96+
<form action="config" method="post">
97+
98+
<div class="govuk-grid-column-full govuk-!-padding-top-6" >
99+
<h2 class="govuk-heading-l"></h2>
100+
101+
<div class="govuk-form-group">
102+
<fieldset class="govuk-fieldset">
103+
<legend class="govuk-fieldset__legend govuk-fieldset__legend--l">
104+
<h1 class="govuk-fieldset__heading">
105+
Report usage data
106+
</h1>
107+
</legend>
108+
109+
<p class="govuk-body">
110+
Allow Register Dynamics to collect usage data on your use of the Data Upload Design Kit.
111+
When enabled, the Data Upload Design Kit will only report the following events with an
112+
anonymous identifier specific to this prototype:
113+
<ul class="govuk-list govuk-list--bullet">
114+
<li>The Plugin was initialised</li>
115+
<li>The Plugin was configured</li>
116+
</ul>
117+
</p>
118+
119+
<p class="govuk-body">
120+
<div class="govuk-checkboxes" data-module="govuk-checkboxes">
121+
<div class="govuk-checkboxes__item">
122+
<input class="govuk-checkboxes__input" id="collectData" name="collectData" type="checkbox" value="true" {% if collectUsageData == true %}checked{% endif %}>
123+
<label class="govuk-label govuk-checkboxes__label" for="collectData">
124+
Allow collection of usage data
125+
</label>
126+
</div>
127+
</div>
128+
</p>
129+
130+
131+
<p class="govuk-body">
132+
If you don't want usage data to be collected, please consider
133+
<a href="https://github.com/register-dynamics/data-import/discussions">dropping a note to us on Github</a>
134+
to let us know how you're using the Kit. Thanks!
135+
</p>
136+
</fieldset>
137+
</div>
138+
</div>
139+
97140

98141
<div class="govuk-grid-column-full govuk-!-padding-top-6" >
99142
<h2 class="govuk-heading-l">Save changes</h2>
@@ -105,7 +148,6 @@ <h2 class="govuk-heading-l">Save changes</h2>
105148
use the new settings.
106149
</p>
107150

108-
<form action="config" method="post">
109151
<div class="govuk-form-group {% if error %}govuk-form-group--error{% endif %}">
110152
{% if error %}
111153
<p id="path_err" class="govuk-error-message">

0 commit comments

Comments
 (0)