UNPKG

gpii-universal

Version:

Cross platform, core components of the GPII personalization infrastructure.

335 lines (315 loc) 12.2 kB
/* * 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"] }, { func: "{that}.startSubSession" }], "onInactive.metrics": [{ func: "{that}.logMetric", args: ["inactive-begin", "{arguments}.0"] }, { 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: { "{lifecycleManager}.events.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, newValue: value.toString() }); }); } }; /** * 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; };