Skip to content

Commit 8255ebd

Browse files
committed
Merge branch 'master' into next
2 parents 7d8e44b + 8bb3b70 commit 8255ebd

File tree

34 files changed

+347
-183
lines changed

34 files changed

+347
-183
lines changed

README.md

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

2-
<h2 align="center"> Countly Analytics </h2>
2+
<h1 align="center"> Countly Analytics </h1>
33

4-
<p align="center">
4+
<p align="right">
55

66
[![Build Status](https://api.travis-ci.org/Countly/countly-server.png?branch=master)](https://travis-ci.org/Countly/countly-server) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/98c2726f2d734697a5f1ac0d453f0a06)](https://app.codacy.com/app/ar2rsawseen/countly-server?utm_source=github.com&utm_medium=referral&utm_content=Countly/countly-server&utm_campaign=Badge_Grade_Dashboard) [![Install Countly on DigitalOcean](https://do.count.ly/button.svg?v2)](http://do.count.ly)
77

@@ -46,7 +46,7 @@
4646

4747

4848
## What is Countly?
49-
[Countly](http://count.ly) is an innovative, real-time, open source [mobile](https://count.ly/mobile-analytics) & [web analytics](http://count.ly/web-analytics), [rich push notifications](https://count.ly/plugins/rich-push-notifications) and [crash reporting](https://count.ly/plugins/crash-analytics) platform powering more than 2500 web sites, 14000 mobile applications and several tens of thousands of desktop applications as of 2017 Q3. It collects data from mobile, desktop, web apps including Apple Watch, TvOS and other internet-connected devices, and visualizes this information to analyze application usage and end-user behavior.
49+
[Countly](http://count.ly) is an innovative, real-time, open source [mobile](https://count.ly/mobile-analytics) & [web analytics](http://count.ly/web-analytics), [rich push notifications](https://count.ly/plugins/rich-push-notifications) and [crash reporting](https://count.ly/plugins/crash-analytics) platform powering more than 2500 web sites, 16000 mobile applications and several desktop applications as of 2019. It collects data from mobile, desktop, web apps including Apple Watch, TvOS and other internet-connected devices, and visualizes this information to analyze application usage and end-user behavior.
5050

5151
With the help of [Javascript SDK](http://github.com/countly/countly-sdk-web), Countly is a web analytics platform with features on par with mobile SDKs. For more information about web analytics capabilities, [see this link](http://count.ly/web-analytics).
5252

@@ -64,7 +64,7 @@ This repository includes server-side part of Countly, with following features:
6464
* Complete dashboard user interface
6565
* User, application and permission management
6666
* Read / write APIs
67-
* Plugin based system
67+
* Plugin based architecture
6868
* Analytics features for [mobile](https://count.ly/mobile-analytics), [web](https://count.ly/web-analytics) and [desktop](https://count.ly/desktop-analytics)
6969
* [Crash reporting](https://count.ly/plugins/crash-analytics) for iOS & Android and error reporting for Javascript
7070
* [Rich and interactive push notifications](https://count.ly/plugins/rich-push-notifications) for iOS & Android
@@ -79,7 +79,7 @@ This repository includes server-side part of Countly, with following features:
7979
## Built with
8080

8181
* **MongoDB** One of the most popular NoSQL databases
82-
* **Node.js** An open-source, cross-platform JavaScript runtime environment for developing a diverse variety of tools and applications.
82+
* **Node.js** An open-source, cross-platform JavaScript runtime environment
8383
* **Express.js** Popular Node.js web application framework
8484
* **Linux** What we love using ;-)
8585

@@ -91,22 +91,24 @@ Security is very important to us. If you discover any issue regarding security,
9191

9292
## What makes Countly unique?
9393

94-
Countly is a privacy-focused and 360-degree analytics approach with several, unique values:
94+
Countly is a privacy-focused and 360-degree analytics platform with several, unique values:
9595

9696
* Real-time mobile analytics, web analytics and push notifications.
9797
* Your data, your rules - since you can install Countly on your own server, or let us do a private cloud deployment for you.
9898
* Configurable and extensible via open source [plugins](https://count.ly/plugins).
99-
* Modern and easy to use web dashboard with a focus on user experience, helping to get complex insights easily.
100-
* Tracking more than 2500 web sites and 14000 mobile applications.
99+
* Modern and easy to use web based dashboard with a focus on user experience, which makes getting complex insights a breeze.
100+
* Tracking more than 2500 web sites and 16000 mobile applications.
101101
* Collecting more than 60 billion datapoints worlwide.
102102

103103
## Differences between Community Edition & Enterprise Edition
104104

105-
* **Audience:** Community Edition is for product managers, developers and analytics enthusiasts, whereas banks, operators, healthcare companies and businesses that run the world's leading websites choose Enterprise Edition.
106-
* **Deployment:** Community Edition deployments are for environments where the information stored is not critical since it does not include professional assistance. You must have a qualified technician. Enterprise Edition is for corporate environments that require availability and reliability where intellectual capital is critical.
107-
* **Technology:** Community Edition has a number of limitations, including no corporate support for sharding, replica sets or installation.
108-
* **Guarantee:** Community Edition is on the bleeding edge regarding version upgrades and with no bugfix guarantee. Enterprise Edition has bugfix guarantee, immediate resolution, verified builds, on-site and automatic version upgrades.
109-
* **Features:** Enterprise Edition has more features compared to Countly, with a focus on end-to-end analytics and marketing platform. Additional features include but not limited to automated push notifications, drilling on the raw data, user profiles, in-app purchase analytics, retention & engagement, user flows, cohorts and custom dashboards.
105+
* **Audience:** Community Edition is suitable for individual developers and small development houses whereas Enterprise Edition is a better fit for companies that require more advanced analytics and marketing capabilities together with ongoing support.
106+
* **Features:** Enterprise Edition has additional features compared to Community Edition including automated push notifications, advanced segmentation, user profiles, in-app purchase analytics, retention, user flows, behavioral cohorts and custom dashboards.
107+
* **Data granularity:** Community Edition stores data (only) in an aggregated format, which reduces the required storage and makes reporting incredibly fast. Enterprise Edition takes advantage of the same format but also stores individual occurrences of data points which enables more advanced capabilities such as segmentation, funnels, user profiles and behavioral cohorts to name a few.
108+
* **Working with raw data:** Granular data, exclusive to Enterprise Edition, enables customers to take advantage of external BI tools or work directly with MongoDB to process and report data as they need.
109+
* **Deployment:** Running and maintaining a Community Edition instance requires technical know-how of several technologies including Linux, Nginx, Node.js and MongoDB. Whereas an Enterprise Edition hosted or on-premise subscription includes hands on support.
110+
* **High availability and scalability:** Countly engineers provide active support to Enterprise Edition customers for deployment planning and realization of this plan including replica set, sharding and a load balanced deployment setup on-premises or on popular cloud environments such as Google Cloud, AWS and Azure.
111+
* **Service-level agreement:** Enterprise Edition subscriptions include an SLA with response and issue resolution guarantees. Community Edition users can take advantage of the community forum or GitHub to post issues.
110112

111113
## Installing and upgrading Countly server
112114

@@ -151,7 +153,9 @@ Also, you are encouraged to read an extended contribution section on [how to con
151153
[![4](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/images/4)](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/links/4)
152154
[![5](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/images/5)](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/links/5)
153155
[![6](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/images/6)](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/links/6)
154-
[![7](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/images/7)](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/links/7)
156+
[![7](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/images/7)](https://sourcerer.io/fame/ar2rsawseen/Countly/countly-server/links/7)
157+
158+
[![Greenkeeper badge](https://badges.greenkeeper.io/Countly/countly-server.svg)](https://greenkeeper.io/)
155159

156160
## Badges
157161

api/parts/data/usage.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ function locFromGeoip(loc, ip_address) {
8181
if (data) {
8282
loc.country = loc.country || (data && data.country);
8383
loc.city = loc.city || (data && data.city);
84+
loc.region = loc.region || (data && data.region);
8485
loc.lat = loc.lat === undefined ? (data && data.ll && data.ll[0]) : loc.lat;
8586
loc.lon = loc.lon === undefined ? (data && data.ll && data.ll[1]) : loc.lon;
8687
resolve(loc);
@@ -112,10 +113,17 @@ function updateLoc(params, optout, loc) {
112113

113114
loc.gps = loc.gps || false;
114115
if (optout) {
115-
loc.country = loc.city = 'Unknown';
116+
loc.country = loc.region = loc.city = 'Unknown';
116117
update.$unset = {loc: 1};
117118
}
118119
else {
120+
if (loc.country && loc.region && loc.region.length) {
121+
loc.region = loc.country + "-" + loc.region;
122+
}
123+
else {
124+
loc.region = 'Unknown';
125+
}
126+
119127
if (!loc.country) {
120128
loc.country = loc.city = 'Unknown';
121129
}
@@ -125,6 +133,7 @@ function updateLoc(params, optout, loc) {
125133
}
126134

127135
params.user.country = loc.country;
136+
params.user.region = loc.region;
128137
params.user.city = loc.city;
129138

130139
if (!params.app_user || params.app_user[common.dbUserMap.country_code] !== loc.country) {
@@ -136,6 +145,12 @@ function updateLoc(params, optout, loc) {
136145
}
137146
update.$set[common.dbUserMap.city] = loc.city;
138147
}
148+
if (!params.app_user || params.app_user[common.dbUserMap.region] !== loc.region) {
149+
if (!update.$set) {
150+
update.$set = {};
151+
}
152+
update.$set[common.dbUserMap.region] = loc.region;
153+
}
139154
if (!params.app_user || (loc.tz !== undefined && params.app_user.tz !== loc.tz)) {
140155
if (!update.$set) {
141156
update.$set = {};
@@ -382,11 +397,12 @@ usage.endUserSession = function(params, done) {
382397
**/
383398
usage.processSessionDuration = function(params, callback) {
384399
var updateUsers = {},
385-
session_duration = parseInt(params.qstring.session_duration);
400+
session_duration = parseInt(params.qstring.session_duration),
401+
session_duration_limit = parseInt(plugins.getConfig("api", params.app && params.app.plugins, true).session_duration_limit);
386402

387403
if (session_duration) {
388-
if (plugins.getConfig("api", params.app && params.app.plugins, true).session_duration_limit && session_duration > plugins.getConfig("api", params.app && params.app.plugins, true).session_duration_limit) {
389-
session_duration = plugins.getConfig("api", params.app && params.app.plugins, true).session_duration_limit;
404+
if (session_duration_limit && session_duration > session_duration_limit) {
405+
session_duration = session_duration_limit;
390406
}
391407

392408
if (session_duration < 0) {

api/utils/common.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ common.dbUserMap = {
185185
'device': 'd',
186186
'carrier': 'c',
187187
'city': 'cty',
188+
'region': 'rgn',
188189
'country_code': 'cc',
189190
'platform': 'p',
190191
'platform_version': 'pv',
@@ -984,7 +985,7 @@ common.returnRaw = function(params, returnCode, body, heads) {
984985
params.res = {};
985986
}
986987
params.res.finished = true;
987-
params.APICallback(returnCode === 200, body, heads, returnCode, params);
988+
params.APICallback(returnCode !== 200, body, heads, returnCode, params);
988989
}
989990
return;
990991
}
@@ -1025,7 +1026,7 @@ common.returnMessage = function(params, returnCode, message, heads) {
10251026
params.res = {};
10261027
}
10271028
params.res.finished = true;
1028-
params.APICallback(returnCode === 200, JSON.stringify({result: message}), heads, returnCode, params);
1029+
params.APICallback(returnCode !== 200, JSON.stringify({result: message}), heads, returnCode, params);
10291030
}
10301031
return;
10311032
}
@@ -1083,7 +1084,7 @@ common.returnOutput = function(params, output, noescape, heads) {
10831084
params.res = {};
10841085
}
10851086
params.res.finished = true;
1086-
params.APICallback(true, output, heads, 200, params);
1087+
params.APICallback(false, output, heads, 200, params);
10871088
}
10881089
return;
10891090
}

api/utils/requestProcessor.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ const processRequest = (params) => {
373373
common.returnMessage(params, 400, 'No users matching criteria');
374374
return false;
375375
}
376-
if (count > 1) {
376+
if (count > 1 && !params.qstring.force) {
377377
common.returnMessage(params, 400, 'This query would delete more than one user');
378378
return false;
379379
}
@@ -616,8 +616,7 @@ const processRequest = (params) => {
616616

617617
break;
618618
}
619-
case '/i/events':
620-
{
619+
case '/i/events': {
621620
switch (paths[3]) {
622621
case 'edit_map':
623622
{
@@ -1111,7 +1110,7 @@ const processRequest = (params) => {
11111110
.digest('hex');
11121111
}
11131112

1114-
if (params.qstring.events) {
1113+
if (params.qstring.events && typeof params.qstring.events === "string") {
11151114
try {
11161115
params.qstring.events = JSON.parse(params.qstring.events);
11171116
}
@@ -1983,6 +1982,8 @@ const validateAppForWriteAPI = (params, done, try_times) => {
19831982
if (params.qstring.device_id === "00000000-0000-0000-0000-000000000000") {
19841983
common.returnMessage(params, 400, 'Ignoring device_id');
19851984
common.log("request").i('Request ignored: Ignoring zero IDFA device_id', params.req.url, params.req.body);
1985+
params.cancelRequest = "Ignoring zero IDFA device_id";
1986+
plugins.dispatch("/sdk/cancel", {params: params});
19861987
return done ? done() : false;
19871988
}
19881989
common.db.collection('apps').findOne({'key': params.qstring.app_key}, (err, app) => {
@@ -2010,6 +2011,8 @@ const validateAppForWriteAPI = (params, done, try_times) => {
20102011
}
20112012
if (payloads.indexOf((params.qstring.checksum + "").toUpperCase()) === -1) {
20122013
console.log("Checksum did not match", params.href, params.req.body, payloads);
2014+
params.cancelRequest = 'Request does not match checksum sha1';
2015+
plugins.dispatch("/sdk/cancel", {params: params});
20132016
if (plugins.getConfig("api", params.app && params.app.plugins, true).safe) {
20142017
common.returnMessage(params, 400, 'Request does not match checksum');
20152018
}
@@ -2023,6 +2026,8 @@ const validateAppForWriteAPI = (params, done, try_times) => {
20232026
}
20242027
if (payloads.indexOf((params.qstring.checksum256 + "").toUpperCase()) === -1) {
20252028
console.log("Checksum did not match", params.href, params.req.body, payloads);
2029+
params.cancelRequest = 'Request does not match checksum sha256';
2030+
plugins.dispatch("/sdk/cancel", {params: params});
20262031
if (plugins.getConfig("api", params.app && params.app.plugins, true).safe) {
20272032
common.returnMessage(params, 400, 'Request does not match checksum');
20282033
}
@@ -2031,6 +2036,8 @@ const validateAppForWriteAPI = (params, done, try_times) => {
20312036
}
20322037
else {
20332038
console.log("Request does not have checksum", params.href, params.req.body);
2039+
params.cancelRequest = "Request does not have checksum";
2040+
plugins.dispatch("/sdk/cancel", {params: params});
20342041
if (plugins.getConfig("api", params.app && params.app.plugins, true).safe) {
20352042
common.returnMessage(params, 400, 'Request does not have checksum');
20362043
}

bin/scripts/device_list/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@
2323
},
2424
"homepage": "https://count.ly/",
2525
"dependencies": {
26-
"csvtojson": "^1.1.9"
26+
"csvtojson": "^2.0.8"
2727
}
2828
}

frontend/express/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ app.get(countlyConfig.path + "*/screenshots/*", function(req, res) {
487487
var oneYear = 31557600000;
488488
app.use(countlyConfig.path, express.static(__dirname + '/public', { maxAge: oneYear }));
489489
app.use(session({
490-
secret: 'countlyss',
490+
secret: countlyConfig.web.session_secret || 'countlyss',
491491
cookie: { httpOnly: true, maxAge: 1000 * 60 * 60 * 24 * 365, secure: countlyConfig.web.secure_cookies || false },
492492
store: new SkinStore(countlyDb),
493493
saveUninitialized: false,

frontend/express/config.sample.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ var countlyConfig = {
7070
* @property {boolean} use_intercom - true, to use intercom in dashboard for communication with Countly
7171
* @property {boolean} secure_cookies - true, to use secure cookies, enable only if you have https enabled
7272
* @property {string} track - allow Countly to collect stats about amount of apps and datapoints as well as feature usage.
73+
* @property {string} session_secret - secret used to sign the session ID cookie.
7374
* Possible values are:
7475
* "all" - track all,
7576
* "GA" - track only Global admins,
@@ -81,7 +82,8 @@ var countlyConfig = {
8182
host: "localhost",
8283
use_intercom: true,
8384
secure_cookies: false,
84-
track: "all"
85+
track: "all",
86+
session_secret: "countlyss"
8587
},
8688
/**
8789
* Legacy value, not supported

frontend/express/public/javascripts/countly/countly.common.js

+40
Original file line numberDiff line numberDiff line change
@@ -3113,6 +3113,7 @@
31133113
periodMin = 1;
31143114
dateString = "D MMM";
31153115
numberOfDays = moment(currentTimeStamp || undefined).format("D");
3116+
daysInPeriod = numberOfDays;
31163117
break;
31173118
case "yesterday":
31183119
var yesterday = moment(currentTimeStamp || undefined).subtract(1, 'days'),
@@ -3785,6 +3786,45 @@
37853786
return tmpAvgVal.toFixed(2);
37863787
};
37873788

3789+
/**
3790+
* Get timestamp range in format as [startTime, endTime] with period and base time
3791+
* @param {object} period - period has two format: array or string
3792+
* @param {number} baseTimeStamp - base timestamp to calc the period range
3793+
* @returns {array} period range
3794+
*/
3795+
countlyCommon.getPeriodRange = function(period, baseTimeStamp) {
3796+
var periodRange;
3797+
if (Object.prototype.toString.call(period) === '[object Array]' && period.length === 2) { //range
3798+
periodRange = period;
3799+
return periodRange;
3800+
}
3801+
var endTimeStamp = baseTimeStamp;
3802+
var start;
3803+
switch (period) {
3804+
case 'hour':
3805+
start = moment(baseTimeStamp).hour(0).minute(0).second(0);
3806+
break;
3807+
case 'yesterday':
3808+
start = moment(baseTimeStamp).subtract(1, 'day').hour(0).minute(0).second(0);
3809+
endTimeStamp = moment(baseTimeStamp).subtract(1, 'day').hour(23).minute(59).second(59).toDate().getTime();
3810+
break;
3811+
case 'day':
3812+
start = moment(baseTimeStamp).date(1).hour(0).minute(0).second(0);
3813+
break;
3814+
case 'month':
3815+
start = moment(baseTimeStamp).month(0).date(1).hour(0).minute(0).second(0);
3816+
break;
3817+
default:
3818+
if (/([0-9]+)days/.test(period)) {
3819+
var match = /([0-9]+)days/.exec(period);
3820+
if (match[1] && (parseInt(match[1]) > 1)) {
3821+
start = moment(baseTimeStamp).subtract(parseInt(match[1]) - 1, 'day').hour(0).minute(0);
3822+
}
3823+
}
3824+
}
3825+
periodRange = [start.toDate().getTime(), endTimeStamp];
3826+
return periodRange;
3827+
};
37883828
};
37893829

37903830
window.CommonConstructor = CommonConstructor;

0 commit comments

Comments
 (0)