Skip to content

Commit eb57e89

Browse files
authored
Merge pull request #4834 from Cookiezaurs/master-user-merging
[SER-1077] Add option to pass bucket (daily, monthly) for /o/analytics/session and /o/analytics/events endpoints
2 parents 97cd7cf + 078b4d8 commit eb57e89

File tree

6 files changed

+86
-32
lines changed

6 files changed

+86
-32
lines changed

api/lib/countly.common.js

+65-22
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,11 @@ function fixTimestampToMilliseconds(ts) {
355355

356356
/**
357357
* Returns a period object used by all time related data calculation functions
358-
* @param {moment} prmPeriod period to be calculated
358+
* @param {moment} prmPeriod period to be calculated (optional)
359+
* @param {string} bucket - daily or monthly. If bucket is set, period will be modified to fit full months or days
359360
* @returns {timeObject} time object
360361
**/
361-
function getPeriodObject(prmPeriod) {
362+
function getPeriodObject(prmPeriod, bucket) {
362363
var startTimestamp, endTimestamp, periodObject, cycleDuration;
363364
periodObject = {
364365
start: 0,
@@ -424,7 +425,7 @@ function getPeriodObject(prmPeriod) {
424425
fromDate.tz(_appTimezone);
425426
toDate.tz(_appTimezone);
426427

427-
if (fromDate.valueOf() === toDate.valueOf()) {
428+
if (fromDate.valueOf() === toDate.valueOf()) { //single day
428429
cycleDuration = moment.duration(1, "day");
429430
Object.assign(periodObject, {
430431
dateString: "D MMM, HH:mm",
@@ -528,7 +529,7 @@ function getPeriodObject(prmPeriod) {
528529
else if (/([1-9][0-9]*)weeks/.test(period)) {
529530
const nWeeks = parseInt(/([1-9][0-9]*)weeks/.exec(period)[1]);
530531
startTimestamp = _currMoment.clone().startOf("week").subtract((nWeeks - 1), "weeks");
531-
cycleDuration = moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1;
532+
cycleDuration = moment.duration(moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1, "days");
532533
Object.assign(periodObject, {
533534
dateString: "D MMM",
534535
isSpecialPeriod: true
@@ -537,7 +538,7 @@ function getPeriodObject(prmPeriod) {
537538
else if (/([1-9][0-9]*)months/.test(period)) {
538539
const nMonths = parseInt(/([1-9][0-9]*)months/.exec(period)[1]);
539540
startTimestamp = _currMoment.clone().startOf("month").subtract((nMonths - 1), "months");
540-
cycleDuration = moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1;
541+
cycleDuration = moment.duration(moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1, "days");
541542
Object.assign(periodObject, {
542543
dateString: "D MMM",
543544
isSpecialPeriod: true
@@ -546,7 +547,7 @@ function getPeriodObject(prmPeriod) {
546547
else if (/([1-9][0-9]*)years/.test(period)) {
547548
const nYears = parseInt(/([1-9][0-9]*)years/.exec(period)[1]);
548549
startTimestamp = _currMoment.clone().startOf("year").subtract((nYears - 1), "years");
549-
cycleDuration = moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1;
550+
cycleDuration = moment.duration(moment.duration(_currMoment.clone().diff(startTimestamp)).asDays() + 1, "days");
550551
Object.assign(periodObject, {
551552
dateString: "D MMM",
552553
isSpecialPeriod: true
@@ -555,7 +556,6 @@ function getPeriodObject(prmPeriod) {
555556
//incorrect period, defaulting to 30 days
556557
else {
557558
let nDays = 30;
558-
559559
startTimestamp = _currMoment.clone().startOf("day").subtract(nDays - 1, "days");
560560
cycleDuration = moment.duration(nDays, "days");
561561
Object.assign(periodObject, {
@@ -564,6 +564,27 @@ function getPeriodObject(prmPeriod) {
564564
});
565565
}
566566

567+
if (bucket) {
568+
if (bucket === "monthly") {
569+
//we modify choosen period to extend to full months
570+
startTimestamp = startTimestamp.clone().startOf("month");
571+
endTimestamp = endTimestamp.clone().endOf("month");
572+
Object.assign(periodObject, {
573+
dateString: "MMM",
574+
isSpecialPeriod: true
575+
});
576+
577+
cycleDuration = moment.duration(moment.duration(Math.round(moment.duration(endTimestamp - startTimestamp).asMonths()), "months"), "months");
578+
}
579+
else if (bucket === "daily") {
580+
Object.assign(periodObject, {
581+
dateString: "D MMM",
582+
isSpecialPeriod: true
583+
});
584+
cycleDuration = moment.duration(moment.duration(Math.round(moment.duration(endTimestamp - startTimestamp).asDays()), "days"), "days");
585+
}
586+
}
587+
567588
Object.assign(periodObject, {
568589
start: startTimestamp.valueOf(),
569590
end: endTimestamp.valueOf(),
@@ -594,6 +615,12 @@ function getPeriodObject(prmPeriod) {
594615
var peY = date1[0];
595616
var peM = date1[1];
596617

618+
var dateFormat = "YYYY.M.D";
619+
if (bucket) {
620+
if (bucket === "monthly") {
621+
dateFormat = "YYYY.M";
622+
}
623+
}
597624

598625
for (var dayIt = startTimestamp.clone(); dayIt < endTimestamp; dayIt.add(1, "day")) {
599626

@@ -602,16 +629,25 @@ function getPeriodObject(prmPeriod) {
602629
dateVal = dateVal.split(".");
603630

604631
uniqueMap[dateVal[0]] = uniqueMap[dateVal[0]] || {};//each year
605-
if (dateVal[0] === sY || dateVal[0] === eY) {
632+
if (dateVal.length >= 2 && dateVal[0] === sY || dateVal[0] === eY) {
606633
uniqueMap[dateVal[0]][dateVal[1]] = uniqueMap[dateVal[0]][dateVal[1]] || {}; //each month
607-
if ((dateVal[0] === sY && dateVal[1] === sM) || (dateVal[0] === eY && dateVal[1] === eM)) {
634+
if ((dateVal[0] === sY && dateVal[1] === sM) || (dateVal[0] === eY && dateVal[1] === eM)) { //bucket is daily
608635
uniqueMap[dateVal[0]][dateVal[1]]["w" + week] = uniqueMap[dateVal[0]][dateVal[1]]["w" + week] || {}; //each week
609-
uniqueMap[dateVal[0]][dateVal[1]]["w" + week][dateVal[2]] = uniqueMap[dateVal[0]][dateVal[1]]["w" + week][dateVal[2]] || {}; //each day
636+
if (dateVal.length >= 2) {
637+
uniqueMap[dateVal[0]][dateVal[1]]["w" + week][dateVal[2]] = uniqueMap[dateVal[0]][dateVal[1]]["w" + week][dateVal[2]] || {}; //each day
638+
}
610639
}
611640
}
641+
var label = dayIt.format(dateFormat);
642+
if (periodObject.currentPeriodArr.length === 0 || periodObject.currentPeriodArr[periodObject.currentPeriodArr.length - 1] !== label) {
643+
periodObject.currentPeriodArr.push(label);
644+
}
612645

613-
periodObject.currentPeriodArr.push(dayIt.format("YYYY.M.D"));
614-
periodObject.previousPeriodArr.push(dayIt.clone().subtract(cycleDuration).format("YYYY.M.D"));
646+
label = dayIt.clone().subtract(cycleDuration).format(dateFormat);
647+
648+
if (periodObject.previousPeriodArr.length === 0 || periodObject.previousPeriodArr[periodObject.previousPeriodArr.length - 1] !== label) {
649+
periodObject.previousPeriodArr.push(label);
650+
}
615651

616652
dateVal = dayIt.clone().subtract(cycleDuration).format("YYYY.M.D");
617653
week = Math.ceil(dayIt.clone().subtract(cycleDuration).format("DDD") / 7);
@@ -687,15 +723,15 @@ function getPeriodObject(prmPeriod) {
687723

688724
let zeroIDs = new Set(),
689725
monthIDs = new Set();
690-
691726
for (let index = 0; index < periodObject.currentPeriodArr.length; index++) {
692-
let [year, month] = periodObject.currentPeriodArr[index].split("."),
693-
[pYear, pMonth] = periodObject.previousPeriodArr[index].split(".");
694-
727+
let [year, month] = periodObject.currentPeriodArr[index].split(".");
695728
zeroIDs.add(year + ":0");
696729
monthIDs.add(year + ":" + month);
697-
zeroIDs.add(pYear + ":0");
698-
monthIDs.add(pYear + ":" + pMonth);
730+
if (periodObject.previousPeriodArr[index]) {
731+
let [pYear, pMonth] = periodObject.previousPeriodArr[index].split(".");
732+
zeroIDs.add(pYear + ":0");
733+
monthIDs.add(pYear + ":" + pMonth);
734+
}
699735
}
700736

701737
periodObject.reqZeroDbDateIds = Array.from(zeroIDs);
@@ -1871,8 +1907,14 @@ countlyCommon.extractData = function(db, clearFunction, dataProperties, periodOb
18711907
dataObj = countlyCommon.getDescendantProp(db, activeDate + "." + i);
18721908
}
18731909
else {
1874-
dateString = "YYYY-M-D";
1875-
formattedDate = moment((activeDateArr[i]).replace(/\./g, "/"), "YYYY/MM/DD");
1910+
if (activeDateArr[i].split('.').length === 2) {
1911+
dateString = "YYYY-M";
1912+
formattedDate = moment((activeDateArr[i]).replace(/\./g, "/"), "YYYY/MM");
1913+
}
1914+
else {
1915+
dateString = "YYYY-M-D";
1916+
formattedDate = moment((activeDateArr[i]).replace(/\./g, "/"), "YYYY/MM/DD");
1917+
}
18761918
dataObj = countlyCommon.getDescendantProp(db, activeDateArr[i]);
18771919
}
18781920

@@ -2634,10 +2676,11 @@ countlyCommon.fixPercentageDelta = function(items, totalPercent) {
26342676
/**
26352677
* Calculate period function
26362678
* @param {object} period - given period
2679+
* @param {string} bucket - bucket for period - monthly or daily. If bucket passed range will be expanded to full months/days
26372680
* @returns {object} returns {@link countlyCommon.periodObj}
26382681
*/
2639-
countlyCommon.calculatePeriodObject = function(period) {
2640-
return getPeriodObject(period);
2682+
countlyCommon.calculatePeriodObject = function(period, bucket) {
2683+
return getPeriodObject(period, bucket);
26412684
};
26422685

26432686
/**

api/lib/countly.event.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ function create() {
2121

2222
/**
2323
* Get event data by periods
24+
* @param {object} options - options object
2425
* @returns {array} with event data objects
2526
*/
26-
countlyEvent.getSubperiodData = function() {
27+
countlyEvent.getSubperiodData = function(options) {
2728

2829
var dataProps = [
2930
{ name: "c" },
3031
{ name: "s" },
3132
{ name: "dur" }
3233
];
33-
34-
return countlyCommon.extractData(countlyEvent.getDb(), countlyEvent.clearObject, dataProps);
34+
options = options || {};
35+
return countlyCommon.extractData(countlyEvent.getDb(), countlyEvent.clearObject, dataProps, countlyCommon.calculatePeriodObject(null, options.bucket));
3536
};
3637

3738
/**

api/lib/countly.users.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ function create() {
8080

8181
/**
8282
* Get metric data by periods
83+
* @param {object} options - options object (options.bucket - daily or monthly)
8384
* @returns {array} with metric data objects
8485
*/
85-
countlySession.getSubperiodData = function() {
86+
countlySession.getSubperiodData = function(options) {
8687

8788
var dataProps = [
8889
{ name: "t" },
@@ -91,8 +92,7 @@ function create() {
9192
{ name: "d" },
9293
{ name: "e" }
9394
];
94-
95-
return countlyCommon.extractData(countlySession.getDb(), countlySession.clearObject, dataProps);
95+
return countlyCommon.extractData(countlySession.getDb(), countlySession.clearObject, dataProps, countlyCommon.calculatePeriodObject(null, options.bucket));
9696
};
9797
return countlySession;
9898
}

api/parts/data/fetch.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,11 @@ fetch.fetchCountries = function(params) {
850850
fetch.fetchSessions = function(params) {
851851
fetchTimeObj('users', params, false, function(usersDoc) {
852852
countlySession.setDb(usersDoc || {});
853-
common.returnOutput(params, countlySession.getSubperiodData());
853+
var options = {};
854+
if (params.qstring.bucket) {
855+
options.bucket = params.qstring.bucket;
856+
}
857+
common.returnOutput(params, countlySession.getSubperiodData(options));
854858
});
855859
};
856860

@@ -1244,12 +1248,16 @@ fetch.fetchEvents = function(params) {
12441248
if (params.qstring.event && params.qstring.event.length) {
12451249
let collectionName = "events" + crypto.createHash('sha1').update(params.qstring.event + params.app_id).digest('hex');
12461250
fetch.getTimeObjForEvents(collectionName, params, function(doc) {
1251+
var options = {};
1252+
if (params.qstring.bucket) {
1253+
options.bucket = params.qstring.bucket;
1254+
}
12471255
countlyEvents.setDb(doc || {});
12481256
if (params.qstring.segmentation && params.qstring.segmentation !== "no-segment") {
12491257
common.returnOutput(params, countlyEvents.getSegmentedData(params.qstring.segmentation));
12501258
}
12511259
else {
1252-
common.returnOutput(params, countlyEvents.getSubperiodData());
1260+
common.returnOutput(params, countlyEvents.getSubperiodData(options));
12531261
}
12541262
});
12551263
}

api/utils/requestProcessor.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,7 @@ const processRequest = (params) => {
27172717
validateUserForDataReadAPI(params, 'core', countlyApi.data.fetch.fetchCountries);
27182718
break;
27192719
case 'sessions':
2720+
//takes also bucket=daily || monthly. extends period to full months if monthly
27202721
validateUserForDataReadAPI(params, 'core', countlyApi.data.fetch.fetchSessions);
27212722
break;
27222723
case 'metric':
@@ -2735,6 +2736,7 @@ const processRequest = (params) => {
27352736
validateUserForDataReadAPI(params, 'core', countlyApi.data.fetch.fetchDurations);
27362737
break;
27372738
case 'events':
2739+
//takes also bucket=daily || monthly. extends period to full months if monthly
27382740
validateUserForDataReadAPI(params, 'core', countlyApi.data.fetch.fetchEvents);
27392741
break;
27402742
default:
@@ -2746,7 +2748,7 @@ const processRequest = (params) => {
27462748
validateUserForDataWriteAPI: validateUserForDataWriteAPI,
27472749
validateUserForGlobalAdmin: validateUserForGlobalAdmin
27482750
})) {
2749-
common.returnMessage(params, 400, 'Invalid path, must be one of /dashboard or /countries');
2751+
common.returnMessage(params, 400, 'Invalid path, must be one of /dashboard, /countries, /sessions, /metric, /tops, /loyalty, /frequency, /durations, /events');
27502752
}
27512753
break;
27522754
}

test/2.api/11.fail.read.app.data.v2.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('Failing app analytics data reading', function() {
5151
return done(err);
5252
}
5353
var ob = JSON.parse(res.text);
54-
ob.should.have.property('result', 'Invalid path, must be one of /dashboard or /countries');
54+
ob.should.have.property('result', 'Invalid path, must be one of /dashboard, /countries, /sessions, /metric, /tops, /loyalty, /frequency, /durations, /events');
5555
done();
5656
});
5757
});

0 commit comments

Comments
 (0)