Skip to content

Commit 775be2a

Browse files
committed
Merge branch 'master' into next
2 parents a79bd38 + c408f27 commit 775be2a

6 files changed

Lines changed: 118 additions & 84 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## Version 25.03.32
2+
Fixes:
3+
- [core] Filtering out internal events while calculating top events
4+
- [fix] Data Regeneration Error
5+
- [onboarding] Fix redirection to newsletter page
6+
- [push] Message cancellation doesn't work on cohort exit
7+
18
## Version 25.03.31
29
Fixes:
310
- [core] Add null checking for user permission when opening the dashboard

README.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,42 @@
1515

1616
## 🌟 What is Countly?
1717

18-
Countly is a product analytics platform that helps teams track, analyze and act on their user actions and behaviour on mobile, web and desktop applications.
18+
Countly is a **privacy-first, AI-ready analytics and customer engagement platform** built for organizations that require **full data ownership and deployment flexibility**.
1919

20-
Countly is used to track 1.5B unique identities on more than 16,000 applications via 2,000+ servers worldwide. It securely processes billions of data points every day in the cloud and on-premises, enabling teams of all sizes to build better applications and engaging experiences while maintaining full control over their product analytics data flow.
20+
Unlike traditional SaaS-only analytics tools, Countly can be deployed **on-premises or in a private cloud**, giving you complete control over your data, infrastructure, compliance, and security.
2121

22-
## 🚀 What are the Countly editions?
22+
Teams use Countly to:
23+
* Understand user behavior across **mobile, web, desktop, and connected devices**
24+
* Optimize product and customer experiences in **real time**
25+
* Automate and personalize customer engagement across channels
2326

24-
* **Countly Lite** — Essential plugins/features and a free-to-use, open source, non-commercial license. Available as self-hosted. Suitable for individuals and small organizations.
25-
* **Countly Enterprise** — Offers a wider range of plugins/features, granular data, an SLA, and direct support. Available as self-hosted or Countly hosted/managed. Suitable for medium and large organizations.
26-
* **Countly Flex** — Our SaaS platform that offers some Enterprise features as core features, and some others as add-ons. Everyone gets their dedicated and fully-managed Countly server(s) in the region they choose. Suitable for individuals, small and medium-sized organizations.
27+
With **flexible data tracking**, **customizable dashboards**, and a **modular plugin-based architecture**, Countly scales with your product while ensuring long-term autonomy and zero vendor lock-in.
2728

28-
For a detailed comparison of different editions [please check here](https://countly.com/pricing). To try the Countly Flex [please visit this page]([https://countly.com/flex](https://countly.com/flex)).
29+
**Built for privacy. Designed for flexibility. Ready for AI-driven innovation.**
30+
31+
## 🚀 Countly Plans
32+
33+
**Countly Lite**
34+
* Core analytics and essential features
35+
* Free to use under an open-source, non-commercial license
36+
* Self-hosted deployment
37+
* Ideal for individuals and small teams
38+
39+
**Countly Enterprise**
40+
* Full analytics and engagement suite
41+
* Advanced features, granular data access, SLA, and direct support
42+
* Available as self-hosted or managed/private cloud
43+
* Ideal for medium and large organizations with advanced compliance needs
2944

30-
Also, please note that SDKs of Countly are the same for all editions.
45+
**Countly Flex**
46+
* Fully managed SaaS experience with dedicated Countly servers
47+
* Region-based hosting selection
48+
* Enterprise-grade features included, with optional add-ons
49+
* Ideal for individuals and small-to-medium organizations wanting flexibility without infrastructure management
50+
51+
:pushpin: **Note**: Countly SDKs are identical across all editions.
52+
53+
For a detailed comparison of different editions [please check here](https://countly.com/pricing). To try the Countly Flex [please visit this page]([https://countly.com/flex](https://countly.com/flex)).
3154

3255
## 📦 What is included in this repository?
3356

api/jobs/topEvents.js

Lines changed: 47 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,7 @@ class TopEventsJob extends job.Job {
3535
}
3636

3737
/**
38-
* If the event's name include [CLY], removed them.
39-
* @param {array} eventsData - events list.
40-
* @return {array} filtered data.
41-
*/
42-
eventsFilter(eventsData) {
43-
return eventsData.filter(l => !l.includes('[CLY]'));
44-
}
45-
46-
/**
47-
*
38+
*
4839
* @param {object} params - params object
4940
* @param {object} data - object where to collect data
5041
* @param {boolean} previous - if fetching for previous period
@@ -205,51 +196,55 @@ class TopEventsJob extends job.Job {
205196
async getAppEvents(app) {
206197
log.d(app._id + ": Fetching app events");
207198
const getEvents = await new Promise((res, rej) => common.db.collection("events").findOne({ _id: app._id }, (errorEvents, result) => errorEvents ? rej(errorEvents) : res(result)));
208-
if (getEvents && 'list' in getEvents) {
209-
const eventMap = this.eventsFilter(getEvents.list);
210-
if (eventMap && eventMap instanceof Array) {
211-
for (const period of TopEventsJob.PERIODS) {
212-
const data = {};
213-
const sessionData = {};
214-
const usersData = {};
215-
const usersCollectionName = "users";
216-
const ob = { app_id: app._id, appTimezone: app.timezone, qstring: { period: period } };
217-
// if (period === "hour") {
218-
// ob.time = common.initTimeObj(app.timezone, new Date().getTime());
219-
// ob.qstring.action = "refresh";
220-
// }
221-
let totalCount = 0;
222-
let prevTotalCount = 0;
223-
let totalSum = 0;
224-
let prevTotalSum = 0;
225-
let totalDuration = 0;
226-
let prevTotalDuration = 0;
227-
228-
//Fetching totals for this period
229-
await this.fetchEventTotalCounts({ app_id: app._id, appTimezone: app.timezone, qstring: { period: period } }, data, false);
230-
var period2 = countlyCommon.getPeriodObj({appTimezone: app.timezone, qstring: {}}, period);
231-
var newPeriod = [period2.start - (period2.end - period2.start), period2.start];
232-
//Fetching totals for previous period
233-
await this.fetchEventTotalCounts({ app_id: app._id, appTimezone: app.timezone, qstring: { period: newPeriod } }, data, true);
199+
if (Array.isArray(getEvents?.list)) {
200+
for (const period of TopEventsJob.PERIODS) {
201+
const data = {};
202+
const sessionData = {};
203+
const usersData = {};
204+
const usersCollectionName = "users";
205+
const ob = { app_id: app._id, appTimezone: app.timezone, qstring: { period: period } };
206+
// if (period === "hour") {
207+
// ob.time = common.initTimeObj(app.timezone, new Date().getTime());
208+
// ob.qstring.action = "refresh";
209+
// }
210+
let totalCount = 0;
211+
let prevTotalCount = 0;
212+
let totalSum = 0;
213+
let prevTotalSum = 0;
214+
let totalDuration = 0;
215+
let prevTotalDuration = 0;
234216

217+
//Fetching totals for this period
218+
await this.fetchEventTotalCounts({ app_id: app._id, appTimezone: app.timezone, qstring: { period: period } }, data, false);
219+
var period2 = countlyCommon.getPeriodObj({appTimezone: app.timezone, qstring: {}}, period);
220+
var newPeriod = [period2.start - (period2.end - period2.start), period2.start];
221+
//Fetching totals for previous period
222+
await this.fetchEventTotalCounts({ app_id: app._id, appTimezone: app.timezone, qstring: { period: newPeriod } }, data, true);
223+
// filter out the internal events
224+
if (typeof data === "object") {
225+
Object.keys(data).forEach((key) => {
226+
if (key.startsWith('[CLY]_')) {
227+
delete data[key];
228+
}
229+
});
230+
}
235231

236-
for (var event in data) {
237-
//Calculating trend
238-
var trend = countlyCommon.getPercentChange(data[event].data.count["prev-total"], data[event].data.count.total);
239-
data[event].data.count.change = trend.percent;
240-
data[event].data.count.trend = trend.trend;
241-
totalCount += data[event].data.count.total;
242-
prevTotalCount += data[event].data.count["prev-total"];
243-
totalSum += data[event].data.sum.total;
244-
prevTotalSum += data[event].data.sum["prev-total"];
245-
totalDuration += data[event].data.duration.total;
246-
prevTotalDuration += data[event].data.duration["prev-total"];
247-
}
248-
log.d(" getting session count (" + period + ")");
249-
await this.getSessionCount({ ob, sessionData, usersData, usersCollectionName });
250-
log.d(" saving data (" + period + ")");
251-
await this.saveAppEvents({ app, data, sessionData, usersData, period, totalCount, prevTotalCount, totalSum, prevTotalSum, totalDuration, prevTotalDuration });
232+
for (var event in data) {
233+
//Calculating trend
234+
var trend = countlyCommon.getPercentChange(data[event].data.count["prev-total"], data[event].data.count.total);
235+
data[event].data.count.change = trend.percent;
236+
data[event].data.count.trend = trend.trend;
237+
totalCount += data[event].data.count.total;
238+
prevTotalCount += data[event].data.count["prev-total"];
239+
totalSum += data[event].data.sum.total;
240+
prevTotalSum += data[event].data.sum["prev-total"];
241+
totalDuration += data[event].data.duration.total;
242+
prevTotalDuration += data[event].data.duration["prev-total"];
252243
}
244+
log.d(" getting session count (" + period + ")");
245+
await this.getSessionCount({ ob, sessionData, usersData, usersCollectionName });
246+
log.d(" saving data (" + period + ")");
247+
await this.saveAppEvents({ app, data, sessionData, usersData, period, totalCount, prevTotalCount, totalSum, prevTotalSum, totalDuration, prevTotalDuration });
253248
}
254249
}
255250
else {

frontend/express/public/core/onboarding/javascripts/countly.views.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,6 @@
267267
var countly_newsletter = doc.countly_newsletter;
268268
delete doc.countly_newsletter;
269269

270-
this.$store.dispatch('countlyOnboarding/updateUserNewsletter', {
271-
user_id: countlyGlobal.member._id,
272-
subscribe_newsletter: countly_newsletter,
273-
});
274-
275270
if (countly_newsletter) {
276271
this.$store.dispatch('countlyOnboarding/sendNewsletterSubscription', {
277272
name: countlyGlobal.member.full_name.split(' ')[0],
@@ -280,9 +275,15 @@
280275
});
281276
}
282277

283-
// go home
284-
window.location.href = '#/home';
285-
window.location.reload();
278+
this.$store.dispatch('countlyOnboarding/updateUserNewsletter', {
279+
user_id: countlyGlobal.member._id,
280+
subscribe_newsletter: countly_newsletter,
281+
}).finally(function() {
282+
countlyGlobal.member.subscribe_newsletter = countly_newsletter;
283+
// go home
284+
window.location.href = '#/home';
285+
window.location.reload();
286+
});
286287
},
287288
}
288289
});
@@ -345,12 +346,19 @@
345346
}
346347
});
347348

348-
if (hasNewsLetter && (typeof countlyGlobal.member.subscribe_newsletter !== 'boolean' && !store.get('disable_newsletter_prompt') && (countlyGlobal.member.login_count === 3 || moment().dayOfYear() % 90 === 0))) {
349+
if (
350+
hasNewsLetter &&
351+
(
352+
typeof countlyGlobal.member.subscribe_newsletter !== 'boolean' &&
353+
store.get('disable_newsletter_prompt') === false &&
354+
(countlyGlobal.member.login_count === 3 || moment().dayOfYear() % 90 === 0)
355+
)
356+
) {
349357
if (Backbone.history.fragment !== '/not-subscribed-newsletter' && !/initial-setup|initial-consent/.test(window.location.hash)) {
350358
app.navigate("/not-subscribed-newsletter", true);
351359
}
352360
}
353361
else if (!countlyGlobal.member.subscribe_newsletter && (countlyGlobal.member.login_count !== 3 && moment().dayOfYear() % 90 !== 0)) {
354362
store.set('disable_newsletter_prompt', false);
355363
}
356-
})();
364+
})();

plugins/push/api/api-auto.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,8 @@ module.exports.autoOnCohort = function(entry, cohort, uids) {
4848
// adding messages to queue
4949
if (trigger) {
5050
logCohorts.d('processing %s %s', typ, msg._id);
51-
audience.getApp().then(() => {
52-
audience.push(trigger).setUIDs(uids).setStart(new Date()).run().then(result => {
53-
logCohorts.d('processing %s %s, result: %j', typ, msg._id, result);
54-
if (result.total) {
55-
return msg.update({$inc: {'result.total': result.total}}, () => {
56-
msg.result.total += result.total;
57-
});
58-
}
59-
}).then(() => {
51+
audience.getApp().then(async() => {
52+
audience.push(trigger).setUIDs(uids).setStart(new Date()).run().then(() => {
6053
logCohorts.d('done processing %s %s', typ, msg._id);
6154
}).catch(error => {
6255
logCohorts.e('Error while pushing users to cohorted message queue %s %s', typ, msg._id, error);

plugins/push/api/send/audience.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -756,16 +756,22 @@ class Popper extends PusherPopper {
756756
*/
757757
async run() {
758758
this.audience.log.i('popping %d uids from %s', this.uids.length, this.audience.message._id);
759+
return this.clear(this.uids);
759760
}
760761

761762
/**
762-
* Remove all message pushes
763-
*
763+
* Remove all message pushes or those for specified uids
764+
*
765+
* @param {string[]} uids optional array of uids to remove pushes for
764766
* @returns {number} number of records removed
765767
*/
766-
async clear() {
768+
async clear(uids) {
767769
let deleted = await Promise.all(this.audience.platformsWithVirtuals().map(async p => {
768-
let res = await common.db.collection('push').deleteMany({m: this.audience.message._id, p});
770+
const query = {m: this.audience.message._id, p};
771+
if (uids) {
772+
query.u = { $in: uids };
773+
}
774+
let res = await common.db.collection('push').deleteMany(query);
769775
return {p, deleted: res.deletedCount};
770776
}));
771777
let update;
@@ -775,7 +781,9 @@ class Popper extends PusherPopper {
775781
}
776782
update.$inc['result.processed'] = (update.$inc['result.processed'] || 0) + obj.deleted;
777783
update.$inc['result.errored'] = (update.$inc['result.errored'] || 0) + obj.deleted;
778-
update.$inc[`result.errors.${obj.p}.cancelled`] = (update.$inc[`result.errors.${obj.p}.cancelled`] || 0) + obj.deleted;
784+
update.$inc[`result.errors.cancelled`] = (update.$inc[`result.errors.cancelled`] || 0) + obj.deleted;
785+
update.$inc[`result.subs.${obj.p}.errors.cancelled`] = (update.$inc[`result.subs.${obj.p}.errors.cancelled`] || 0) + obj.deleted;
786+
update.$inc[`result.subs.${obj.p}.errored`] = (update.$inc[`result.subs.${obj.p}.errored`] || 0) + obj.deleted;
779787
}
780788
if (update) {
781789
await this.audience.message.update(update, () => {

0 commit comments

Comments
 (0)