-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathmetrics.js
More file actions
336 lines (317 loc) · 11.9 KB
/
metrics.js
File metadata and controls
336 lines (317 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
* Metrics logging.
* This acts upon events that are deemed interesting enough to be recorded and sent to a log server for analysis.
*
* Copyright 2017 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("infusion");
var gpii = fluid.registerNamespace("gpii");
fluid.defaults("gpii.metrics", {
gradeNames: ["fluid.modelComponent", "fluid.contextAware"],
contextAwareness: {
platform: {
checks: {
test: {
contextValue: "{gpii.contexts.test}",
gradeNames: "gpii.metrics.test"
},
windows: {
contextValue: "{gpii.contexts.windows}",
gradeNames: "gpii.windowsMetrics"
}
}
}
},
invokers: {
logMetric: {
func: "{eventLog}.logEvent",
args: ["metrics", "{arguments}.0", "{arguments}.1"] // event, data
},
startSubSession: {
funcName: "gpii.metrics.startSubSession",
args: ["{that}", "{eventLog}"]
},
stopSubSession: {
funcName: "gpii.metrics.stopSubSession",
args: ["{that}", "{eventLog}"]
}
},
members: {
sessionSolutions: {},
// Incrementing sub-session id.
subSessionIncrementer: 0
},
events: {
"onStartMetrics": null,
"onStopMetrics": null,
// The user has become active (called on the first input after onInactive)
"onActive": null, // args: duration-inactive
// The user has become inactive (no input was received after {that}.config.input.inactiveTime)
"onInactive": null // args: {sleep:true}
},
listeners: {
"onStartMetrics.log": {
funcName: "fluid.log",
args: "Metrics started"
},
"onActive.metrics": {
func: "{that}.logMetric",
args: ["inactive-stop"]
},
"onActive.subsession": {
func: "{that}.startSubSession"
},
"onInactive.metrics": {
func: "{that}.logMetric",
args: ["inactive-begin", "{arguments}.0"]
},
"onInactive.subsession": {
func: "{that}.stopSubSession"
}
},
durationEvents: {
"start": "stop",
"inactive-begin": "inactive-stop",
"SessionStart": "SessionStop",
"subsession-begin": "subsession-end"
},
siteConfig: {}
});
// Mixin grade for gpii.metrics to log lifecycle manager things
fluid.defaults("gpii.metrics.lifecycle", {
modelListeners: {
"{lifecycleManager}.model.logonChange": {
funcName: "gpii.metrics.logonStateChanged",
args: ["{that}", "{eventLog}", "{lifecycleManager}", "{change}.oldValue", "{change}.value"]
}
},
invokers: {
sessionStopped: {
funcName: "gpii.metrics.sessionStopped",
args: [ "{that}", "{eventLog}"]
}
},
listeners: {
"onCreate": {
namespace: "trackPrefsSetChange",
listener: "gpii.metrics.trackPrefsSetChange",
args: ["{that}", "{lifecycleManager}"]
},
"{lifecycleManager}.events.onSessionStart": [
{
namespace: "metrics.session",
funcName: "gpii.metrics.sessionStarted",
args: ["{that}", "{eventLog}", "{arguments}.1"]
}, {
func: "{that}.events.onStartMetrics"
}],
"{lifecycleManager}.events.onSessionStop": [{
namespace: "metrics.session",
func: "{that}.sessionStopped",
args: ["{that}", "{eventLog}", "{arguments}.1.id"]
}, {
"func": "{that}.events.onStopMetrics",
"priority": "before:eventLog"
}],
"{lifecycleManager}.events.onSessionSnapshotUpdate": {
namespace: "metrics",
funcName: "gpii.metrics.snapshotUpdate",
args: ["{that}", "{arguments}.2"]
},
"onDestroy.session": {
func: "{that}.sessionStopped"
}
}
});
fluid.defaults("gpii.metrics.standalone", {
listeners: {
"onCreate": "{that}.events.onStartMetrics.fire",
"onDestroy": "{that}.events.onStopMetrics.fire"
}
});
/**
* Attached as the lifecycleManager.onCreate event.
*
* Adds a model change listener to the user session's model to listen for changes to the preferences.
*
* @param {Component} that - The gpii.metrics instance.
* @param {Component} lifecycleManager - The lifecycleManager instance.
*/
gpii.metrics.trackPrefsSetChange = function (that, lifecycleManager) {
var userSession = lifecycleManager.getSession();
userSession.applier.modelChanged.addListener({
path: "preferences.contexts",
excludeSource: "SessionCleanup"
}, function (newValue, oldValue) {
var current = newValue && newValue[userSession.model.activePrefsSetName];
current = current && current.preferences;
var previous = oldValue && oldValue[userSession.model.activePrefsSetName];
previous = previous && previous.preferences;
gpii.metrics.preferenceChanged(that, current, previous);
});
};
/**
* Log the solutions as they're applied.
*
* @param {Component} that - The gpii.metrics instance.
* @param {Object} originalSettings - The original settings (only interested in the keys).
*/
gpii.metrics.snapshotUpdate = function (that, originalSettings) {
var ids = fluid.keys(originalSettings);
if (!that.sessionSolutions) {
that.sessionSolutions = {};
}
// Log the solution IDs that haven't been logged.
fluid.each(ids, function (id) {
if (!that.sessionSolutions[id]) {
that.logMetric("solution-applied", {
solutionID: id
});
that.sessionSolutions[id] = true;
}
});
};
/**
* Called when a preference has changed. (a change in the preferences field of the session's model.)
*
* @param {Comment} that - The gpii.metrics instance.
* @param {Object} current - The current preferences map for the active preferences set.
* @param {Object} previous - The previous preferences map for the active preferences set.
*/
gpii.metrics.preferenceChanged = function (that, current, previous) {
var diff = { changeMap: {}, changes: 0, unchanged: 0};
var same = fluid.model.diff(previous, current, diff);
if (!same) {
var changedPreferences;
if (fluid.isPlainObject(diff.changeMap)) {
changedPreferences = {};
fluid.each(diff.changeMap, function (value, key) {
if (value === "ADD") {
changedPreferences[key] = current[key];
}
});
} else if (diff.changeMap === "ADD") {
// Everything is new
changedPreferences = current;
}
fluid.each(changedPreferences, function (value, name) {
that.logMetric("preference", {
name: name,
setTo: fluid.isPrimitive(value) ? value.toString() : value
});
});
}
};
/**
* Updates the logged gpiiKey
* @param {Component} that - The gpii.metrics instance.
* @param {Component} eventLog - The gpii.eventLog instance.
* @param {String} gpiiKey - The gpiiKey used for this session.
*/
gpii.metrics.sessionStarted = function (that, eventLog) {
eventLog.eventData.sessionID = fluid.allocateGuid();
eventLog.logEvent("lifecycle", "SessionStart");
that.startSubSession();
};
/**
* Removes the logged solution IDs for the session, and the current gpii key.
* @param {Component} that - The gpii.metrics instance.
* @param {Component} eventLog - The gpii.eventLog instance.
* @param {String} sessionID - Session ID.
*/
gpii.metrics.sessionStopped = function (that, eventLog) {
if (eventLog.eventData.sessionID) {
eventLog.logEvent("lifecycle", "SessionStop");
delete eventLog.eventData.sessionID;
that.sessionSolutions = {};
that.startSubSession();
}
};
/**
* A model state listener for {lifecycleManager}.model.logonChange.
* When the login state changes from "login" to "logout", log the solutions that did not get applied for that session.
*
* @param {Component} that - The gpii.metrics instance.
* @param {Component} eventLog - The eventLog instance.
* @param {Component} lifecycleManager - The lifecycleManager instance.
* @param {Object} oldValue - The old value.
* @param {Object} newValue - The new value.
*/
gpii.metrics.logonStateChanged = function (that, eventLog, lifecycleManager, oldValue, newValue) {
if (newValue.type === "login") {
if (newValue.inProgress) {
// Start marking all events with the gpiiKey.
if (newValue.gpiiKey === "noUser") {
eventLog.eventData.gpiiKeyBefore = eventLog.eventData.gpiiKey;
delete eventLog.eventData.gpiiKey;
} else {
eventLog.eventData.gpiiKey = newValue.gpiiKey;
}
// So metrics knows the next set of events is due to someone keying in
eventLog.eventData.logon = "in";
} else {
// Login is complete, so all further events are unrelated to key-in. There's a 15 second grace period for
// events that may occur shortly after, however.
eventLog.eventData.logon = "in-after";
setTimeout(function () {
delete eventLog.eventData.gpiiKeyBefore;
delete eventLog.eventData.logon;
}, 15000);
}
} else if (newValue.type === "logout" && !newValue.inProgress) {
// Stop marking events with the gpiiKey when they've completely logged out
delete eventLog.eventData.gpiiKey;
if (newValue.inProgress) {
eventLog.eventData.gpiiKeyBefore = eventLog.eventData.gpiiKey;
eventLog.eventData.logon = "out";
} else {
eventLog.eventData.logon = "out-after";
}
setTimeout(function () {
delete eventLog.eventData.gpiiKeyBefore;
delete eventLog.eventData.logon;
}, 15000);
}
if (oldValue && oldValue.type === "login" && (newValue.type === "logout" || !newValue.inProgress)) {
var session = lifecycleManager.getSession(oldValue.gpiiKey);
// The reason for this null checker is that the lifecyclaManager's "session" may be null after
// having logged on with a bogus token which terminated the request in error before the
// "session" could be created.
if (session && session.model.currentSettings) {
// Log the solution IDs that hadn't been applied.
var expectedSolutions = fluid.keys(session.model.activeConfiguration.lifecycleInstructions);
fluid.each(expectedSolutions, function (id) {
if (!session.model.currentSettings[id]) {
that.logMetric("solution-failed", {
solutionID: id
});
}
});
}
}
};
gpii.metrics.startSubSession = function (that, eventLog) {
eventLog.eventData.subSessionID = eventLog.eventData.sessionID + "-" + that.subSessionIncrementer++;
that.logMetric("subsession-begin", { subSessionID: eventLog.eventData.subSessionID});
};
gpii.metrics.stopSubSession = function (that, eventLog) {
if (eventLog.eventData.subSessionID) {
that.logMetric("subsession-end", {
subSessionID: eventLog.eventData.subSessionID
});
}
delete eventLog.eventData.subSessionID;
};