@aicore/core-analytics-client-lib 1.0.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -34
- package/dist/analytics.min.js +3 -2
- package/dist/analytics.min.js.map +1 -1
- package/package.json +18 -15
- package/src/analytics.js +302 -187
- package/src/client-schema.md +48 -35
package/README.md
CHANGED
|
@@ -19,67 +19,94 @@ events for [Core-Analytics-Server](https://github.com/aicore/Core-Analytics-Serv
|
|
|
19
19
|
|
|
20
20
|
# Usage
|
|
21
21
|
|
|
22
|
-
##
|
|
23
|
-
Embed the script in your HTML file
|
|
22
|
+
## Load the Library
|
|
23
|
+
Embed the script in your HTML file and replace `your_analytics_account_ID` and `appName`
|
|
24
|
+
in the `initAnalyticsSession` call below:
|
|
24
25
|
```html
|
|
25
|
-
|
|
26
|
-
<script
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
<!-- Global window.analytics object - core.ai analytics services -->
|
|
27
|
+
<script async src="https://unpkg.com/@aicore/core-analytics-client-lib/src/analytics.js"
|
|
28
|
+
onload="analyticsLibLoaded()"></script>
|
|
29
|
+
<script>
|
|
30
|
+
if(!window.analytics){ window.analytics = {
|
|
31
|
+
_initData : [], loadStartTime: new Date().getTime(),
|
|
32
|
+
event: function (){window.analytics._initData.push(arguments);}
|
|
33
|
+
};}
|
|
34
|
+
function analyticsLibLoaded() {
|
|
35
|
+
initAnalyticsSession('your_analytics_account_ID', 'appName');
|
|
36
|
+
analytics.event("core-analytics", "client-lib", "loadTime", 1, (new Date().getTime())-analytics.loadStartTime);
|
|
37
|
+
}
|
|
31
38
|
</script>
|
|
32
|
-
</html>
|
|
33
39
|
```
|
|
40
|
+
This will create a global `analytics` variable which can be used to access the analytics APIs.
|
|
34
41
|
|
|
35
|
-
initSession(): Initialize the analytics session. It takes the following parameters:
|
|
36
|
-
|
|
37
|
-
* `accountID`: Your analytics account id as configured in the server or core.ai analytics
|
|
38
|
-
* `appName`: The app name to log the events against. Eg: "phoenixCode"
|
|
39
|
-
* `postIntervalSeconds` (_Optional_): This defines the interval between sending analytics events to the server. Default is 10 minutes
|
|
40
|
-
* `granularitySec` (_Optional_): The smallest time period under which the events can be distinguished. Multiple
|
|
41
|
-
events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means
|
|
42
|
-
that any events that happen within 3 seconds cannot be distinguished in ordering.
|
|
43
|
-
* `postBaseURLInit` Optional: Provide your own analytics server address if you self-hosted the server
|
|
44
|
-
|
|
45
|
-
```javascript
|
|
46
|
-
// Example for custom initSession where the analytics aggregated data
|
|
47
|
-
// is posted to custom server https://localhost:3000 every 600 secs
|
|
48
|
-
// with a granularity(resolution) of 5 seconds.
|
|
49
|
-
|
|
50
|
-
initSession("accountID", "appName", 600, 5, "https://localhost:3000");
|
|
51
|
-
```
|
|
52
42
|
|
|
53
43
|
## Raising analytics events
|
|
54
|
-
|
|
55
|
-
The
|
|
44
|
+
We can now start logging analytics events by calling `analytics.event` API.
|
|
45
|
+
The events will be aggregated and send to the analytics server periodically.
|
|
56
46
|
|
|
57
47
|
```javascript
|
|
58
48
|
// analyticsEvent(eventType, eventCategory, subCategory, eventCount, eventValue);
|
|
59
49
|
|
|
60
50
|
// Eg: event without counts and values
|
|
61
|
-
|
|
51
|
+
analytics.event("platform", "os", "linux");
|
|
62
52
|
|
|
63
53
|
// Eg: event with count, here it logs that html file is opened 100 times
|
|
64
|
-
|
|
54
|
+
analytics.event("file", "opened", "html", 100);
|
|
65
55
|
|
|
66
56
|
// Eg: event with count and value, here it logs that the startup time is 250 milliseconds.
|
|
67
57
|
// Note that the value is unitless from analytics perspective. unit is deduced from subCategory name
|
|
68
|
-
|
|
58
|
+
analytics.event("platform", "performance", "startupTimeMs", 1, 250);
|
|
69
59
|
|
|
70
60
|
// Eg: event with fractional value.
|
|
71
|
-
|
|
61
|
+
analytics.event("platform", "CPU", "utilization", 1, .45);
|
|
72
62
|
// Eg. Here we register that the system has 8 cores with each core having 2300MHz frequency.
|
|
73
|
-
|
|
63
|
+
analytics.event("platform", "CPU", "coreCountsAndFrequencyMhz", 8, 2300);
|
|
74
64
|
```
|
|
75
65
|
### API parameters
|
|
76
66
|
* `eventType` - A string, required
|
|
77
67
|
* `eventCategory` - A string, required
|
|
78
68
|
* `subCategory` - A string, required
|
|
79
69
|
* `eventCount` (_Optional_) : A non-negative number indicating the number of times the event (or an event with a
|
|
80
|
-
particular value if a value is specified) happened. defaults to 1.
|
|
70
|
+
particular value if a value is specified) happened. defaults to 1.
|
|
81
71
|
* `eventValue` (_Optional_) : A number value associated with the event. defaults to 0
|
|
82
72
|
|
|
73
|
+
|
|
74
|
+
## Advanced Usages
|
|
75
|
+
If you want to modify how analytics library collects and sends information, it is recommended to do so
|
|
76
|
+
with analytics server [accountConfig](https://github.com/aicore/Core-Analytics-Server#accountconfig-configuration).
|
|
77
|
+
|
|
78
|
+
Alternatively for one off development time uses, the behavior of the library can be configured
|
|
79
|
+
during the `initAnalyticsSession` call. `initAnalyticsSession()` takes the following parameters:
|
|
80
|
+
|
|
81
|
+
* `accountID`: Your analytics account id as configured in the server or core.ai analytics
|
|
82
|
+
* `appName`: The app name to log the events against. Eg: "phoenixCode"
|
|
83
|
+
* `postIntervalSeconds` (_Optional_): This defines the interval between sending analytics events to the server. Default is 10 minutes
|
|
84
|
+
* `granularitySec` (_Optional_): The smallest time period under which the events can be distinguished. Multiple
|
|
85
|
+
events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means
|
|
86
|
+
that any events that happen within 3 seconds cannot be distinguished in ordering.
|
|
87
|
+
* `analyticsURL` (_Optional_): Provide your own analytics server address if you self-hosted the server
|
|
88
|
+
* `debug` (_Optional_): set to true if you want to see detailed debug logs.
|
|
89
|
+
|
|
90
|
+
### usageExample
|
|
91
|
+
```javascript
|
|
92
|
+
// Init with default values and server controlled config. use the following `analyticsLibLoaded` function
|
|
93
|
+
function analyticsLibLoaded() {
|
|
94
|
+
initAnalyticsSession('your_analytics_account_ID', 'appName');
|
|
95
|
+
analytics.event("core-analytics", "client-lib", "loadTime", 1, (new Date().getTime())-analytics.loadStartTime);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//Replace initAnalyticsSession in analyticsLibLoaded function for the below use cases.
|
|
99
|
+
|
|
100
|
+
// Example for custom initSession where the analytics aggregated data
|
|
101
|
+
// is posted to custom server https://localhost:3000 every 600 secs
|
|
102
|
+
// with a granularity(resolution) of 5 seconds.
|
|
103
|
+
initAnalyticsSession("accountID", "appName", "https://localhost:3000", 600, 5);
|
|
104
|
+
|
|
105
|
+
// To initSession in debug mode set debug arg in init to true. In debug mode, details logs
|
|
106
|
+
// about analytics library events will be emitted.
|
|
107
|
+
initAnalyticsSession("accountID", "appName", "https://localhost:3000", 600, 5, true);
|
|
108
|
+
```
|
|
109
|
+
|
|
83
110
|
# Contribute to core-analytics-client-lib
|
|
84
111
|
|
|
85
112
|
## Building
|
package/dist/analytics.min.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
let
|
|
2
|
-
typically means that you may be sending too many value events? .`),window.fetch(
|
|
1
|
+
function initAnalyticsSession(e,t,n,a,o,i){let r,s,l,u,c,f,v,y,d={};const m=30,p="aicore.analytics.userID",g=1e4;let h=null;var w,C="undefined"==typeof window;let b="https://analytics.core.ai",I,S,D=0,E=!1,T=!1;function A(...e){T&&console.log(...e)}if(C)throw new Error("Node environment is not currently supported");function N(){return{schemaVersion:1,accountID:r,appName:s,uuid:l,sessionID:u,unixTimestampUTC:+new Date,numEventsTotal:0,events:{}}}function $(){if(!h)throw new Error("Please call initSession before using any analytics event")}function k(e){e.backoffCount=(e.backoffCount||0)+1,A(`Failed to call core analytics server. Will retry in ${m*e.backoffCount}s: `),setTimeout(()=>{U(e)},1e3*m*e.backoffCount)}function U(t){var e;E||(t||(t=h,D=0,R(),h=N()),0!==t.numEventsTotal&&((e=JSON.stringify(t)).length>g&&console.warn(`Analytics event generated is very large at greater than ${e.length}B. This
|
|
2
|
+
typically means that you may be sending too many value events? .`),A("Sending Analytics data of length: ",e.length,"B"),window.fetch(y,{method:"POST",headers:{"Content-Type":"application/json"},body:e}).then(e=>{200!==e.status&&(400!==e.status?k(t):console.error("Analytics client: Bad Request, this is most likely a problem with the library, update to latest version."))}).catch(e=>{e=[e],T&&console.error(...e),k(t)})))}function R(e){I&&(clearInterval(I),I=null),e||(I=setInterval(()=>{D+=f},1e3*f))}function _(e){R(e),S&&(clearInterval(S),S=null),e||(S=setInterval(U,1e3*c))}function B(n,a,o,i=1,r=0){if(!E){var s=n,l=a,u=o,c=i,f=r;if($(),!s||!l||!u)throw new Error("missing eventType or category or subCategory");if("number"!=typeof c||c<0)throw new Error("invalid count, count should be a positive number");if("number"!=typeof f)throw new Error("invalid value, value should be a number");{s=n;l=a;u=o;let e=h.events;e[s]=e[s]||{},e[s][l]=e[s][l]||{},e[s][l][u]=e[s][l][u]||{time:[],valueCount:[]}}let t=h.events;c=t[n][a][o].time;if((0<c.length?c[c.length-1]:null)===D){f=t[n][a][o].valueCount.length-1;{var s=f,l=n,u=a,c=o,f=i,v=r;let t=h.events;var e="number"==typeof t[l][u][c].valueCount[s];if(e&&0===v)t[l][u][c].valueCount[s]+=f;else if(e&&0!==v){let e={};e[v]=f,e[0]=t[l][u][c].valueCount[s],t[l][u][c].valueCount[s]=e}else if(!e){let e=t[l][u][c].valueCount[s];e[v]=(e[v]||0)+f}h.numEventsTotal+=1}}else{if(t[n][a][o].time.push(D),0===r)t[n][a][o].valueCount.push(i);else{let e={};e[r]=i,t[n][a][o].valueCount.push(e)}h.numEventsTotal+=1}}}if(!e||!t)throw new Error("accountID and appName must exist for init");v=n?n.replace(/\/$/,""):b,r=e,s=t,T=i||!1,c=a||600,f=o||3,y=v+"/ingest",l=function(){let e=localStorage.getItem(p);return e||(e=crypto.randomUUID(),localStorage.setItem(p,e)),e}(),u=function(){let e=sessionStorage.getItem(p);return e||(e=Math.random().toString(36).substr(2,10),sessionStorage.setItem(p,e)),e}(),h=N(),_(),async function(e,t){(d=await new Promise((n,a)=>{var e=v+(`/getAppConfig?accountID=${r}&appName=`+s);window.fetch(e).then(async e=>{switch(e.status){case 200:var t=await e.json();return void n(t);case 400:a("Bad Request, check library version compatible?",e);break;default:a("analytics client: Could not update from remote config. Continuing with defaults.",e)}}).catch(e=>{a("analytics client: Could not update from remote config. Continuing with defaults.",e)})}))!=={}&&(c=e||d.postIntervalSecondsInit||600,f=t||d.granularitySecInit||3,v=d.analyticsURLInit||v||b,_(E=!0===d.disabled),A(`Init analytics Config from remote. disabled: ${E}
|
|
3
|
+
postIntervalSeconds:${c}, granularitySec: ${f} ,URL: `+v),E&&console.warn(`Core Analytics is disabled from the server for app: ${r}:`+s))}(a,o);for(w of analytics._initData)B(...w);analytics._initData=[],analytics._getCurrentAnalyticsEvent=function(){return $(),JSON.parse(JSON.stringify(h))},analytics._getAppConfig=function(){return{accountID:r,appName:s,disabled:E,uuid:l,sessionID:u,postIntervalSeconds:c,granularitySec:f,analyticsURL:v,serverConfig:d}},analytics.event=B}window.analytics||(window.analytics={_initData:[]});
|
|
3
4
|
//# sourceMappingURL=analytics.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sourceRoot":"src/analytics.js","sources":["src/analytics.js"],"names":["let","accountID","appName","userID","sessionID","postIntervalSeconds","granularitySec","
|
|
1
|
+
{"version":3,"sourceRoot":"src/analytics.js","sources":["src/analytics.js"],"names":["initAnalyticsSession","accountIDInit","appNameInit","analyticsURLInit","postIntervalSecondsInit","granularitySecInit","debug","let","accountID","appName","userID","sessionID","postIntervalSeconds","granularitySec","analyticsURL","postURL","serverConfig","DEFAULT_RETRY_TIME_IN_SECONDS","USERID_LOCAL_STORAGE_KEY","POST_LARGE_DATA_THRESHOLD_BYTES","currentAnalyticsEvent","eventData","IS_NODE_ENV","window","DEFAULT_BASE_URL","granularityTimer","postTimer","currentQuantisedTime","disabled","debugMode","debugLog","args","console","log","Error","_createAnalyticsEvent","schemaVersion","uuid","unixTimestampUTC","Date","numEventsTotal","events","_validateCurrentState","_retryPost","eventToSend","backoffCount","setTimeout","_postCurrentAnalyticsEvent","textToSend","_resetGranularityTimer","JSON","stringify","length","warn","fetch","method","headers","Content-Type","body","then","res","status","error","catch","debugError","disable","clearInterval","setInterval","_setupTimers","event","eventType","eventCategory","subCategory","eventCount","eventValue","_validateEvent","category","count","value","_ensureAnalyticsEventExists","time","valueCount","timeArray","modificationIndex","_updateExistingAnalyticsEvent","index","newValue","storedValueIsCount","newValueCount","storedValueObject","push","replace","localUserID","localStorage","getItem","crypto","randomUUID","setItem","_getOrCreateUserID","localSessionID","sessionStorage","Math","random","toString","substr","_getOrCreateSessionID","async","postIntervalSecondsInitial","granularitySecInitial","Promise","resolve","reject","configURL","serverResponse","json","err","_initFromRemoteConfig","analytics","_initData","_getCurrentAnalyticsEvent","parse","_getAppConfig"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicore/core-analytics-client-lib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Analytics client library for https://github.com/aicore/Core-Analytics-Server",
|
|
5
5
|
"main": "dist/analytics.min.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,16 +19,18 @@
|
|
|
19
19
|
"lint": "eslint --quiet src test",
|
|
20
20
|
"lint:fix": "eslint --quiet --fix src test",
|
|
21
21
|
"prepare": "husky install",
|
|
22
|
-
"test": "echo please open test/
|
|
23
|
-
"test:unit": "
|
|
22
|
+
"test": "echo please run - npm run serve - and then open test/unit-test.html in browser to run tests",
|
|
23
|
+
"test:unit": "npm run test",
|
|
24
24
|
"test:integ": "echo integration tests are disabled in this repo",
|
|
25
|
-
"cover": "echo
|
|
26
|
-
"cover:unit": "echo
|
|
27
|
-
"cover:integ": "echo
|
|
28
|
-
"build": "",
|
|
29
|
-
"patchVersion": "npm --no-git-tag-version version patch",
|
|
25
|
+
"cover": "echo no coverage for now",
|
|
26
|
+
"cover:unit": "echo no coverage for now",
|
|
27
|
+
"cover:integ": "echo no coverage for now",
|
|
28
|
+
"build": "npm run minify",
|
|
30
29
|
"minify": "echo creating minified package dist/analytics.min.js && mkdir -p dist && uglifyjs src/analytics.js --compress --mangle -o dist/analytics.min.js -c -m --source-map \"root='src/analytics.js',url='analytics.min.js.map'\"",
|
|
31
|
-
"
|
|
30
|
+
"bumpPatchVersion": "npm --no-git-tag-version version patch",
|
|
31
|
+
"bumpPatchVersionWithGitTag": "npm version patch",
|
|
32
|
+
"release": "npm run minify && npm run bumpPatchVersionWithGitTag",
|
|
33
|
+
"serve": "http-server . -p 8000 -c-1"
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
36
|
"src",
|
|
@@ -45,13 +47,14 @@
|
|
|
45
47
|
},
|
|
46
48
|
"homepage": "https://github.com/aicore/core-analytics-client-lib#readme",
|
|
47
49
|
"devDependencies": {
|
|
48
|
-
"@commitlint/cli": "16.
|
|
49
|
-
"@commitlint/config-conventional": "16.
|
|
50
|
-
"c8": "7.11.
|
|
50
|
+
"@commitlint/cli": "16.3.0",
|
|
51
|
+
"@commitlint/config-conventional": "16.2.4",
|
|
52
|
+
"c8": "7.11.2",
|
|
51
53
|
"chai": "4.3.6",
|
|
52
|
-
"eslint": "8.
|
|
54
|
+
"eslint": "8.15.0",
|
|
53
55
|
"husky": "7.0.4",
|
|
54
|
-
"mocha": "9.2.
|
|
55
|
-
"uglify-js": "3.15.
|
|
56
|
+
"mocha": "9.2.2",
|
|
57
|
+
"uglify-js": "3.15.5",
|
|
58
|
+
"http-server": "14.1.0"
|
|
56
59
|
}
|
|
57
60
|
}
|
package/src/analytics.js
CHANGED
|
@@ -1,230 +1,345 @@
|
|
|
1
1
|
// GNU AGPL-3.0 License Copyright (c) 2021 - present core.ai . All rights reserved.
|
|
2
2
|
|
|
3
3
|
// jshint ignore: start
|
|
4
|
-
/*global localStorage, sessionStorage, crypto*/
|
|
5
|
-
|
|
6
|
-
let accountID, appName, userID, sessionID, postIntervalSeconds, granularitySec, postURL;
|
|
7
|
-
const DEFAULT_GRANULARITY_IN_SECONDS = 3;
|
|
8
|
-
const DEFAULT_RETRY_TIME_IN_SECONDS = 30;
|
|
9
|
-
const DEFAULT_POST_INTERVAL_SECONDS = 600; // 10 minutes
|
|
10
|
-
const USERID_LOCAL_STORAGE_KEY = 'aicore.analytics.userID';
|
|
11
|
-
const POST_LARGE_DATA_THRESHOLD_BYTES = 10000;
|
|
12
|
-
let currentAnalyticsEvent = null;
|
|
13
|
-
const IS_NODE_ENV = (typeof window === 'undefined');
|
|
14
|
-
let DEFAULT_BASE_URL = "https://analytics.core.ai";
|
|
15
|
-
|
|
16
|
-
let granularityTimer;
|
|
17
|
-
let postTimer;
|
|
18
|
-
let currentQuantisedTime = 0;
|
|
19
|
-
|
|
20
|
-
if(IS_NODE_ENV){
|
|
21
|
-
throw new Error("Node environment is not currently supported");
|
|
22
|
-
}
|
|
4
|
+
/*global localStorage, sessionStorage, crypto, analytics*/
|
|
5
|
+
|
|
23
6
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
accountID: accountID,
|
|
28
|
-
appName: appName,
|
|
29
|
-
uuid: userID,
|
|
30
|
-
sessionID: sessionID,
|
|
31
|
-
granularitySec: granularitySec,
|
|
32
|
-
unixTimestampUTC: +new Date(),
|
|
33
|
-
numEventsTotal: 0,
|
|
34
|
-
events: {}
|
|
7
|
+
if(!window.analytics){
|
|
8
|
+
window.analytics = {
|
|
9
|
+
_initData: []
|
|
35
10
|
};
|
|
36
11
|
}
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the analytics session
|
|
15
|
+
* @param accountIDInit Your analytics account id as configured in the server or core.ai analytics
|
|
16
|
+
* @param appNameInit The app name to log the events against.
|
|
17
|
+
* @param analyticsURLInit Optional: Provide your own analytics server address if you self-hosted the server
|
|
18
|
+
* @param postIntervalSecondsInit Optional: This defines the interval between sending analytics events to the server.
|
|
19
|
+
* Default is 10 minutes
|
|
20
|
+
* @param granularitySecInit Optional: The smallest time period under which the events can be distinguished. Multiple
|
|
21
|
+
* events happening during this time period is aggregated to a count. The default granularity is 3 Seconds, which means
|
|
22
|
+
* that any events that happen within 3 seconds cannot be distinguished in ordering.
|
|
23
|
+
* @param debug set to true if you want to see detailed debug logs.
|
|
24
|
+
*/
|
|
25
|
+
function initAnalyticsSession(accountIDInit, appNameInit, analyticsURLInit,
|
|
26
|
+
postIntervalSecondsInit, granularitySecInit, debug) {
|
|
27
|
+
let accountID, appName, userID, sessionID, postIntervalSeconds,
|
|
28
|
+
granularitySec, analyticsURL, postURL, serverConfig={};
|
|
29
|
+
const DEFAULT_GRANULARITY_IN_SECONDS = 3;
|
|
30
|
+
const DEFAULT_RETRY_TIME_IN_SECONDS = 30;
|
|
31
|
+
const DEFAULT_POST_INTERVAL_SECONDS = 600; // 10 minutes
|
|
32
|
+
const USERID_LOCAL_STORAGE_KEY = 'aicore.analytics.userID';
|
|
33
|
+
const POST_LARGE_DATA_THRESHOLD_BYTES = 10000;
|
|
34
|
+
let currentAnalyticsEvent = null;
|
|
35
|
+
const IS_NODE_ENV = (typeof window === 'undefined');
|
|
36
|
+
let DEFAULT_BASE_URL = "https://analytics.core.ai";
|
|
37
|
+
|
|
38
|
+
let granularityTimer;
|
|
39
|
+
let postTimer;
|
|
40
|
+
let currentQuantisedTime = 0;
|
|
41
|
+
let disabled = false;
|
|
42
|
+
let debugMode = false;
|
|
43
|
+
|
|
44
|
+
function debugLog(...args) {
|
|
45
|
+
if(!debugMode){
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(...args);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function debugError(...args) {
|
|
52
|
+
if(!debugMode){
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.error(...args);
|
|
41
56
|
}
|
|
42
|
-
}
|
|
43
57
|
|
|
44
|
-
function getCurrentAnalyticsEvent() {
|
|
45
|
-
_validateCurrentState();
|
|
46
|
-
// return a clone
|
|
47
|
-
return JSON.parse(JSON.stringify(currentAnalyticsEvent));
|
|
48
|
-
}
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if(!localUserID){
|
|
53
|
-
localUserID = crypto.randomUUID();
|
|
54
|
-
localStorage.setItem(USERID_LOCAL_STORAGE_KEY, localUserID);
|
|
59
|
+
if(IS_NODE_ENV){
|
|
60
|
+
throw new Error("Node environment is not currently supported");
|
|
55
61
|
}
|
|
56
|
-
return localUserID;
|
|
57
|
-
}
|
|
58
62
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
function _createAnalyticsEvent() {
|
|
64
|
+
return {
|
|
65
|
+
schemaVersion: 1,
|
|
66
|
+
accountID: accountID,
|
|
67
|
+
appName: appName,
|
|
68
|
+
uuid: userID,
|
|
69
|
+
sessionID: sessionID,
|
|
70
|
+
unixTimestampUTC: +new Date(),
|
|
71
|
+
numEventsTotal: 0,
|
|
72
|
+
events: {}
|
|
73
|
+
};
|
|
64
74
|
}
|
|
65
|
-
return localSessionID;
|
|
66
|
-
}
|
|
67
75
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
76
|
+
function _validateCurrentState() {
|
|
77
|
+
if(!currentAnalyticsEvent){
|
|
78
|
+
throw new Error("Please call initSession before using any analytics event");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
72
81
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
_postCurrentAnalyticsEvent(eventToSend);
|
|
79
|
-
}, DEFAULT_RETRY_TIME_IN_SECONDS * 1000 * eventToSend.backoffCount);
|
|
80
|
-
}
|
|
82
|
+
function _getCurrentAnalyticsEvent() {
|
|
83
|
+
_validateCurrentState();
|
|
84
|
+
// return a clone
|
|
85
|
+
return JSON.parse(JSON.stringify(currentAnalyticsEvent));
|
|
86
|
+
}
|
|
81
87
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
function _getOrCreateUserID() {
|
|
89
|
+
let localUserID = localStorage.getItem(USERID_LOCAL_STORAGE_KEY);
|
|
90
|
+
if(!localUserID){
|
|
91
|
+
localUserID = crypto.randomUUID();
|
|
92
|
+
localStorage.setItem(USERID_LOCAL_STORAGE_KEY, localUserID);
|
|
93
|
+
}
|
|
94
|
+
return localUserID;
|
|
86
95
|
}
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
|
|
97
|
+
function _getOrCreateSessionID() {
|
|
98
|
+
let localSessionID = sessionStorage.getItem(USERID_LOCAL_STORAGE_KEY);
|
|
99
|
+
if(!localSessionID){
|
|
100
|
+
localSessionID = Math.random().toString(36).substr(2, 10);
|
|
101
|
+
sessionStorage.setItem(USERID_LOCAL_STORAGE_KEY, localSessionID);
|
|
102
|
+
}
|
|
103
|
+
return localSessionID;
|
|
89
104
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
|
|
106
|
+
function _setupIDs() {
|
|
107
|
+
userID = _getOrCreateUserID();
|
|
108
|
+
sessionID = _getOrCreateSessionID();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _retryPost(eventToSend) {
|
|
112
|
+
eventToSend.backoffCount = (eventToSend.backoffCount || 0) + 1;
|
|
113
|
+
debugLog(`Failed to call core analytics server. Will retry in ${
|
|
114
|
+
DEFAULT_RETRY_TIME_IN_SECONDS * eventToSend.backoffCount}s: `);
|
|
115
|
+
setTimeout(()=>{
|
|
116
|
+
_postCurrentAnalyticsEvent(eventToSend);
|
|
117
|
+
}, DEFAULT_RETRY_TIME_IN_SECONDS * 1000 * eventToSend.backoffCount);
|
|
94
118
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
body: textToSend
|
|
99
|
-
}).then(res=>{
|
|
100
|
-
if(res.status === 200){
|
|
119
|
+
|
|
120
|
+
function _postCurrentAnalyticsEvent(eventToSend) {
|
|
121
|
+
if(disabled){
|
|
101
122
|
return;
|
|
102
123
|
}
|
|
103
|
-
if(
|
|
124
|
+
if(!eventToSend){
|
|
125
|
+
eventToSend = currentAnalyticsEvent;
|
|
126
|
+
currentQuantisedTime = 0;
|
|
127
|
+
_resetGranularityTimer();
|
|
128
|
+
currentAnalyticsEvent = _createAnalyticsEvent();
|
|
129
|
+
}
|
|
130
|
+
if(eventToSend.numEventsTotal === 0 ){
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
let textToSend = JSON.stringify(eventToSend);
|
|
134
|
+
if(textToSend.length > POST_LARGE_DATA_THRESHOLD_BYTES){
|
|
135
|
+
console.warn(`Analytics event generated is very large at greater than ${textToSend.length}B. This
|
|
136
|
+
typically means that you may be sending too many value events? .`);
|
|
137
|
+
}
|
|
138
|
+
debugLog("Sending Analytics data of length: ", textToSend.length, "B");
|
|
139
|
+
window.fetch(postURL, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {'Content-Type': 'application/json'},
|
|
142
|
+
body: textToSend
|
|
143
|
+
}).then(res=>{
|
|
144
|
+
if(res.status === 200){
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if(res.status !== 400){ // we don't retry bad requests
|
|
148
|
+
_retryPost(eventToSend);
|
|
149
|
+
} else {
|
|
150
|
+
console.error("Analytics client: " +
|
|
151
|
+
"Bad Request, this is most likely a problem with the library, update to latest version.");
|
|
152
|
+
}
|
|
153
|
+
}).catch(res => {
|
|
154
|
+
debugError(res);
|
|
104
155
|
_retryPost(eventToSend);
|
|
105
|
-
}
|
|
106
|
-
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function _resetGranularityTimer(disable) {
|
|
160
|
+
if(granularityTimer){
|
|
161
|
+
clearInterval(granularityTimer);
|
|
162
|
+
granularityTimer = null;
|
|
107
163
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
if(disable){
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
granularityTimer = setInterval(()=>{
|
|
168
|
+
currentQuantisedTime = currentQuantisedTime + granularitySec;
|
|
169
|
+
}, granularitySec*1000);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function _setupTimers(disable) {
|
|
173
|
+
_resetGranularityTimer(disable);
|
|
174
|
+
if(postTimer){
|
|
175
|
+
clearInterval(postTimer);
|
|
176
|
+
postTimer = null;
|
|
177
|
+
}
|
|
178
|
+
if(disable){
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
postTimer = setInterval(_postCurrentAnalyticsEvent, postIntervalSeconds*1000);
|
|
182
|
+
}
|
|
113
183
|
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
184
|
+
async function _getServerConfig() {
|
|
185
|
+
return new Promise((resolve, reject)=>{
|
|
186
|
+
let configURL = analyticsURL + `/getAppConfig?accountID=${accountID}&appName=${appName}`;
|
|
187
|
+
window.fetch(configURL).then(async res=>{
|
|
188
|
+
switch (res.status) {
|
|
189
|
+
case 200:
|
|
190
|
+
let serverResponse = await res.json();
|
|
191
|
+
resolve(serverResponse);
|
|
192
|
+
return;
|
|
193
|
+
case 400:
|
|
194
|
+
reject("Bad Request, check library version compatible?", res);
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
reject("analytics client: Could not update from remote config. Continuing with defaults.", res);
|
|
198
|
+
}
|
|
199
|
+
}).catch(err => {
|
|
200
|
+
reject("analytics client: Could not update from remote config. Continuing with defaults.", err);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
118
203
|
}
|
|
119
|
-
granularityTimer = setInterval(()=>{
|
|
120
|
-
currentQuantisedTime = currentQuantisedTime + granularitySec;
|
|
121
|
-
}, granularitySec*1000);
|
|
122
204
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Returns the analytics config for the app
|
|
207
|
+
* @returns {Object}
|
|
208
|
+
*/
|
|
209
|
+
function _getAppConfig() {
|
|
210
|
+
return {
|
|
211
|
+
accountID, appName, disabled,
|
|
212
|
+
uuid: userID, sessionID,
|
|
213
|
+
postIntervalSeconds, granularitySec, analyticsURL, serverConfig
|
|
214
|
+
};
|
|
126
215
|
}
|
|
127
|
-
postTimer = setInterval(_postCurrentAnalyticsEvent, postIntervalSeconds*1000);
|
|
128
|
-
}
|
|
129
216
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
217
|
+
async function _initFromRemoteConfig(postIntervalSecondsInitial, granularitySecInitial) {
|
|
218
|
+
serverConfig = await _getServerConfig();
|
|
219
|
+
if(serverConfig !== {}){
|
|
220
|
+
// User init overrides takes precedence over server overrides
|
|
221
|
+
postIntervalSeconds = postIntervalSecondsInitial ||
|
|
222
|
+
serverConfig["postIntervalSecondsInit"] || DEFAULT_POST_INTERVAL_SECONDS;
|
|
223
|
+
granularitySec = granularitySecInitial || serverConfig["granularitySecInit"] || DEFAULT_GRANULARITY_IN_SECONDS;
|
|
224
|
+
// For URLs, the server suggested URL takes precedence over user init values
|
|
225
|
+
analyticsURL = serverConfig["analyticsURLInit"] || analyticsURL || DEFAULT_BASE_URL;
|
|
226
|
+
disabled = serverConfig["disabled"] === true;
|
|
227
|
+
_setupTimers(disabled);
|
|
228
|
+
debugLog(`Init analytics Config from remote. disabled: ${disabled}
|
|
229
|
+
postIntervalSeconds:${postIntervalSeconds}, granularitySec: ${granularitySec} ,URL: ${analyticsURL}`);
|
|
230
|
+
if(disabled){
|
|
231
|
+
console.warn(`Core Analytics is disabled from the server for app: ${accountID}:${appName}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function _stripTrailingSlash(url) {
|
|
237
|
+
return url.replace(/\/$/, "");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function _ensureAnalyticsEventExists(eventType, category, subCategory) {
|
|
241
|
+
let events = currentAnalyticsEvent.events;
|
|
242
|
+
events[eventType] = events[eventType] || {};
|
|
243
|
+
events[eventType][category] = events[eventType][category] || {};
|
|
244
|
+
events[eventType][category][subCategory] = events[eventType][category][subCategory] || {
|
|
245
|
+
time: [], // quantised time
|
|
246
|
+
valueCount: [] // value and count array, If a single value, then it is count, else object {"val1":count1, ...}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function _validateEvent(eventType, category, subCategory, count, value) {
|
|
251
|
+
_validateCurrentState();
|
|
252
|
+
if(!eventType || !category || !subCategory){
|
|
253
|
+
throw new Error("missing eventType or category or subCategory");
|
|
254
|
+
}
|
|
255
|
+
if(typeof(count)!== 'number' || count <0){
|
|
256
|
+
throw new Error("invalid count, count should be a positive number");
|
|
257
|
+
}
|
|
258
|
+
if(typeof(value)!== 'number'){
|
|
259
|
+
throw new Error("invalid value, value should be a number");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function _updateExistingAnalyticsEvent(index, eventType, category, subCategory, count, newValue) {
|
|
264
|
+
let events = currentAnalyticsEvent.events;
|
|
265
|
+
const storedValueIsCount = typeof(events[eventType][category][subCategory]["valueCount"][index]) === 'number';
|
|
266
|
+
if(storedValueIsCount && newValue === 0){
|
|
267
|
+
events[eventType][category][subCategory]["valueCount"][index] += count;
|
|
268
|
+
} else if(storedValueIsCount && newValue !== 0){
|
|
269
|
+
let newValueCount = {};
|
|
270
|
+
newValueCount[newValue] = count;
|
|
271
|
+
newValueCount[0] = events[eventType][category][subCategory]["valueCount"][index];
|
|
272
|
+
events[eventType][category][subCategory]["valueCount"][index] = newValueCount;
|
|
273
|
+
} else if(!storedValueIsCount){
|
|
274
|
+
let storedValueObject = events[eventType][category][subCategory]["valueCount"][index];
|
|
275
|
+
storedValueObject[newValue] = (storedValueObject[newValue] || 0) + count;
|
|
276
|
+
}
|
|
277
|
+
currentAnalyticsEvent.numEventsTotal += 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Register an analytics event. The events will be aggregated and send to the analytics server periodically.
|
|
282
|
+
* @param eventType - String, required
|
|
283
|
+
* @param eventCategory - String, required
|
|
284
|
+
* @param subCategory - String, required
|
|
285
|
+
* @param eventCount (Optional) : A non-negative number indicating the number of times the event (or an event with a
|
|
286
|
+
* particular value if a value is specified) happened. defaults to 1.
|
|
287
|
+
* @param eventValue (Optional) : A number value associated with the event. defaults to 0
|
|
288
|
+
*/
|
|
289
|
+
function event(eventType, eventCategory, subCategory, eventCount=1, eventValue=0) {
|
|
290
|
+
if(disabled){
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
_validateEvent(eventType, eventCategory, subCategory, eventCount, eventValue);
|
|
294
|
+
_ensureAnalyticsEventExists(eventType, eventCategory, subCategory);
|
|
295
|
+
let events = currentAnalyticsEvent.events;
|
|
296
|
+
let timeArray = events[eventType][eventCategory][subCategory]["time"];
|
|
297
|
+
let lastTime = timeArray.length>0? timeArray[timeArray.length-1] : null;
|
|
298
|
+
if(lastTime !== currentQuantisedTime){
|
|
299
|
+
events[eventType][eventCategory][subCategory]["time"].push(currentQuantisedTime);
|
|
300
|
+
if(eventValue===0){
|
|
301
|
+
events[eventType][eventCategory][subCategory]["valueCount"].push(eventCount);
|
|
302
|
+
} else {
|
|
303
|
+
let valueCount = {};
|
|
304
|
+
valueCount[eventValue] = eventCount;
|
|
305
|
+
events[eventType][eventCategory][subCategory]["valueCount"].push(valueCount);
|
|
306
|
+
}
|
|
307
|
+
currentAnalyticsEvent.numEventsTotal += 1;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
let modificationIndex = events[eventType][eventCategory][subCategory]["valueCount"].length -1;
|
|
311
|
+
_updateExistingAnalyticsEvent(modificationIndex, eventType, eventCategory, subCategory, eventCount, eventValue);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Init Analytics
|
|
142
315
|
if(!accountIDInit || !appNameInit){
|
|
143
316
|
throw new Error("accountID and appName must exist for init");
|
|
144
317
|
}
|
|
318
|
+
analyticsURL = analyticsURLInit? _stripTrailingSlash(analyticsURLInit) : DEFAULT_BASE_URL;
|
|
145
319
|
accountID = accountIDInit;
|
|
146
320
|
appName = appNameInit;
|
|
321
|
+
debugMode = debug || false;
|
|
147
322
|
postIntervalSeconds = postIntervalSecondsInit || DEFAULT_POST_INTERVAL_SECONDS;
|
|
148
323
|
granularitySec = granularitySecInit || DEFAULT_GRANULARITY_IN_SECONDS;
|
|
149
|
-
postURL =
|
|
324
|
+
postURL = analyticsURL + "/ingest";
|
|
150
325
|
_setupIDs();
|
|
151
326
|
currentAnalyticsEvent = _createAnalyticsEvent();
|
|
152
327
|
_setupTimers();
|
|
153
|
-
|
|
328
|
+
_initFromRemoteConfig(postIntervalSecondsInit, granularitySecInit);
|
|
154
329
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
valueCount: [] // value and count array, If a single value, then it is count, else object {"val1":count1, ...}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function _validateEvent(eventType, category, subCategory, count, value) {
|
|
166
|
-
_validateCurrentState();
|
|
167
|
-
if(!eventType || !category || !subCategory){
|
|
168
|
-
throw new Error("missing eventType or category or subCategory");
|
|
169
|
-
}
|
|
170
|
-
if(typeof(count)!== 'number' || count <0){
|
|
171
|
-
throw new Error("invalid count");
|
|
330
|
+
// As analytics lib load is async, our loading scripts will push event data into initData array till the lib load
|
|
331
|
+
// is complete. Now as load is done, we need to push these pending analytics events. Assuming that the async load of
|
|
332
|
+
// the lib should mostly happen under a second, we should not lose any accuracy of event times within the initial
|
|
333
|
+
// 3-second granularity. For more precision use cases, load the lib sync.
|
|
334
|
+
for(let eventData of analytics._initData){
|
|
335
|
+
event(...eventData);
|
|
172
336
|
}
|
|
173
|
-
|
|
174
|
-
throw new Error("invalid value");
|
|
175
|
-
}
|
|
176
|
-
}
|
|
337
|
+
analytics._initData = [];
|
|
177
338
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if(storedValueIsCount && newValue === 0){
|
|
182
|
-
events[eventType][category][subCategory]["valueCount"][index] += count;
|
|
183
|
-
} else if(storedValueIsCount && newValue !== 0){
|
|
184
|
-
let newValueCount = {};
|
|
185
|
-
newValueCount[newValue] = count;
|
|
186
|
-
newValueCount[0] = events[eventType][category][subCategory]["valueCount"][index];
|
|
187
|
-
events[eventType][category][subCategory]["valueCount"][index] = newValueCount;
|
|
188
|
-
} else if(!storedValueIsCount){
|
|
189
|
-
let storedValueObject = events[eventType][category][subCategory]["valueCount"][index];
|
|
190
|
-
storedValueObject[newValue] = (storedValueObject[newValue] || 0) + count;
|
|
191
|
-
}
|
|
192
|
-
currentAnalyticsEvent.numEventsTotal += 1;
|
|
193
|
-
}
|
|
339
|
+
// Private API for tests
|
|
340
|
+
analytics._getCurrentAnalyticsEvent = _getCurrentAnalyticsEvent;
|
|
341
|
+
analytics._getAppConfig = _getAppConfig;
|
|
194
342
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
* @param eventType - String, required
|
|
198
|
-
* @param eventCategory - String, required
|
|
199
|
-
* @param subCategory - String, required
|
|
200
|
-
* @param eventCount (Optional) : A non-negative number indicating the number of times the event (or an event with a
|
|
201
|
-
* particular value if a value is specified) happened. defaults to 1.
|
|
202
|
-
* @param eventValue (Optional) : A number value associated with the event. defaults to 0
|
|
203
|
-
*/
|
|
204
|
-
function analyticsEvent(eventType, eventCategory, subCategory, eventCount=1, eventValue=0) {
|
|
205
|
-
_validateEvent(eventType, eventCategory, subCategory, eventCount, eventValue);
|
|
206
|
-
_ensureAnalyticsEventExists(eventType, eventCategory, subCategory);
|
|
207
|
-
let events = currentAnalyticsEvent.events;
|
|
208
|
-
let timeArray = events[eventType][eventCategory][subCategory]["time"];
|
|
209
|
-
let lastTime = timeArray.length>0? timeArray[timeArray.length-1] : null;
|
|
210
|
-
if(lastTime !== currentQuantisedTime){
|
|
211
|
-
events[eventType][eventCategory][subCategory]["time"].push(currentQuantisedTime);
|
|
212
|
-
if(eventValue===0){
|
|
213
|
-
events[eventType][eventCategory][subCategory]["valueCount"].push(eventCount);
|
|
214
|
-
} else {
|
|
215
|
-
let valueCount = {};
|
|
216
|
-
valueCount[eventValue] = eventCount;
|
|
217
|
-
events[eventType][eventCategory][subCategory]["valueCount"].push(valueCount);
|
|
218
|
-
}
|
|
219
|
-
currentAnalyticsEvent.numEventsTotal += 1;
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
let modificationIndex = events[eventType][eventCategory][subCategory]["valueCount"].length -1;
|
|
223
|
-
_updateExistingAnalyticsEvent(modificationIndex, eventType, eventCategory, subCategory, eventCount, eventValue);
|
|
343
|
+
// Public API
|
|
344
|
+
analytics.event = event;
|
|
224
345
|
}
|
|
225
|
-
|
|
226
|
-
export {
|
|
227
|
-
initSession,
|
|
228
|
-
getCurrentAnalyticsEvent,
|
|
229
|
-
analyticsEvent
|
|
230
|
-
};
|
package/src/client-schema.md
CHANGED
|
@@ -8,16 +8,20 @@ schema:
|
|
|
8
8
|
"appName" : "app1",
|
|
9
9
|
"uuid": "u1",
|
|
10
10
|
"sessionID": "s1",
|
|
11
|
-
"granularitySec" : 3,
|
|
12
11
|
"unixTimestampUTC" : 1643043376,
|
|
13
|
-
"numEventsTotal":
|
|
12
|
+
"numEventsTotal": 8,
|
|
14
13
|
"events":{
|
|
15
14
|
"eventType":{
|
|
16
15
|
"category":{
|
|
17
16
|
"subCategory": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
17
|
+
"time": [0,2],
|
|
18
|
+
"valueCount": [
|
|
19
|
+
1,
|
|
20
|
+
[{
|
|
21
|
+
"1": 5,
|
|
22
|
+
"3": 2
|
|
23
|
+
}]
|
|
24
|
+
]
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -30,49 +34,58 @@ schema:
|
|
|
30
34
|
1. `appName`: The application name for which the event is being raised.
|
|
31
35
|
2. `uuid`: The unique id for the user. or the string `default` if there is no UUID/anonymous analytics is used from client.
|
|
32
36
|
3. `sessionID`: The unique session-id autogenerated by the client lib for each session. Will be reset for every session.
|
|
33
|
-
4. `granularitySec`: The minimum granularity for event recording in seconds. Repeating events within the same granularity window will be aggregated and only the count will be increased.
|
|
34
37
|
5. `unixTimestampUTC`: The start Unix time from which the client time is being recorded.
|
|
35
38
|
5. `numEventsTotal`: The sum total of all events raised from the client. This aggregates all count values `c`
|
|
36
|
-
6. `events` : A map of events that took place from `
|
|
37
|
-
* Within a subcategory, `
|
|
38
|
-
* `
|
|
39
|
-
|
|
39
|
+
6. `events` : A map of events that took place from `unixTimestampUTC`. The keys are nested in order `eventType` > `category` > `subCategory`
|
|
40
|
+
* Within a subcategory, `time` array specifies the `time drift` in seconds from the `unixTimestampUTC` for which the current event is raised.
|
|
41
|
+
* `valueCount` array specifies the `values` and/or related counts. If the array
|
|
42
|
+
item is a number, then it signifies count. If it is an object, then
|
|
43
|
+
it indicates values and the corresponding counts.
|
|
40
44
|
|
|
41
45
|
## sample analytics event
|
|
42
46
|
Schema
|
|
43
47
|
```json
|
|
44
48
|
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
"schemaVersion": 1,
|
|
50
|
+
"appName": "brackets",
|
|
51
|
+
"uuid": "u1",
|
|
52
|
+
"sessionID": "s1",
|
|
53
|
+
"unixTimestampUTC": 1643043376,
|
|
54
|
+
"numEventsTotal": 5,
|
|
55
|
+
"events": {
|
|
56
|
+
"platform": {
|
|
57
|
+
"os": {
|
|
58
|
+
"win": {
|
|
59
|
+
"time": [0],
|
|
60
|
+
"valueCount": [1]
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"performance": {
|
|
64
|
+
"fileOpenTimeMs": {
|
|
65
|
+
"time": [0, 2, 5],
|
|
66
|
+
"valueCount": [
|
|
67
|
+
[{
|
|
68
|
+
"121": 1
|
|
69
|
+
}],
|
|
70
|
+
[{
|
|
71
|
+
"100": 1,
|
|
72
|
+
"300": 1
|
|
73
|
+
}],
|
|
74
|
+
[{
|
|
75
|
+
"100": 5
|
|
76
|
+
}]
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
65
80
|
}
|
|
66
|
-
}
|
|
67
81
|
}
|
|
68
|
-
}
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
```
|
|
72
85
|
The above event raises two event types :
|
|
73
86
|
1. (platform, os, win)
|
|
74
87
|
2. (platform, performance, fileOpenTimeMs)
|
|
75
|
-
* at time `1643043376 + 0`, a value of `
|
|
76
|
-
* at time `1643043376 + 3000`, two values `[
|
|
77
|
-
* at time `1643043376 + 6000`, a value of
|
|
88
|
+
* at time `1643043376 + 0`, a value of `121` is registered and count is 1.
|
|
89
|
+
* at time `1643043376 + 3000`, two values `[100,300]` are registered with a count each of 1.
|
|
90
|
+
* at time `1643043376 + 6000`, a value of `500` is registered with a count of 5.
|
|
78
91
|
|