Skip to content

Commit 2bc610f

Browse files
committed
Merge branch 'release.24.10'
# Conflicts: # CHANGELOG.md # frontend/express/public/javascripts/countly/vue/components/dropdown.js # plugins/star-rating/frontend/public/templates/star-consent-link.html # ui-tests/cypress/lib/dashboard/feedback/ratings/widgets.js
2 parents ff85a16 + bc73702 commit 2bc610f

17 files changed

Lines changed: 516 additions & 187 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11

22
## Version 25.03.x
33
Fixes:
4+
- [core] Changes for event omit script to validate data in new model and use countly-request.
5+
- [core] Changes to top events job. Fetching data from aggregated event totals.
6+
- [crashes] Fix unescaped SDK logs
47
- [feedback] Uniformize drawer internal name input texts
8+
- [feedback] Uniformize feedback widgets status tag
59
- [star-rating] Added missing columns to Rating Widgets table edit
10+
- [star-rating] Allow bulk update of widget status
611
- [star-rating] Fix rating score and responses table sorting
712
- [ui] Fix alignment of drawers title and close icon
13+
- [UI] Remove white background from input character amount suffix
814
- [heatmaps] Get heatmap data from new drill events collection
915

16+
Enterprise Fixes:
17+
- [retention] Fixed report loading
1018
Fixes:
1119
- [localization] Fixed grammatical errors
1220

api/jobs/topEvents.js

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,49 @@ class TopEventsJob extends job.Job {
4444
}
4545

4646
/**
47-
* async
48-
* Get events count.
49-
* @param {Object} params - getEventsCount object
50-
* @param {String} params.collectionNameEvents - event collection name
51-
* @param {Object} params.ob - it contains all necessary info
52-
* @param {string} params.event - event name
53-
* @param {Object} params.data - dummy event data
54-
* @returns {Promise.<boolean>} true.
47+
*
48+
* @param {object} params - params object
49+
* @param {object} data - object where to collect data
50+
* @param {boolean} previous - if fetching for previous period
51+
* @returns {Promise} promise
5552
*/
56-
async getEventsCount(params) {
57-
const { collectionNameEvents, ob, data, event } = params;
53+
async fetchEventTotalCounts(params, data, previous) {
54+
let collectionName = "all";
55+
params.qstring.segmentation = "key";
5856
return await new Promise((resolve) => {
59-
countlyApi.data.fetch.getTimeObjForEvents(collectionNameEvents, ob, (doc) => {
57+
countlyApi.data.fetch.getTimeObjForEvents("events_data", params, {'id_prefix': params.app_id + "_" + collectionName + '_'}, function(doc) {
6058
countlyEvents.setDb(doc || {});
61-
const countProp = countlyEvents.getNumber("c", true);
62-
const sumProp = countlyEvents.getNumber("s", true);
63-
const durationProp = countlyEvents.getNumber("dur", true);
64-
data[event] = {};
65-
data[event].data = {
66-
count: countProp,
67-
sum: sumProp,
68-
duration: durationProp
69-
};
59+
60+
var dd = countlyEvents.getSegmentedData(params.qstring.segmentation);
61+
for (var z = 0; z < dd.length;z++) {
62+
var key = dd[z]._id;
63+
data[key] = data[key] || {};
64+
data[key].data = data[key].data || {};
65+
data[key].data.count = data[key].data.count || {"total": 0, "prev-total": 0, "change": "NA", "trend": "u"};
66+
if (previous) {
67+
data[key].data.count["prev-total"] = dd[z].c;
68+
}
69+
else {
70+
data[key].data.count.total = dd[z].c;
71+
}
72+
73+
data[key].data.sum = data[key].data.sum || {"total": 0, "prev-total": 0, "change": "NA", "trend": "u"};
74+
if (previous) {
75+
data[key].data.sum["prev-total"] = dd[z].s;
76+
}
77+
else {
78+
data[key].data.sum.total = dd[z].s;
79+
}
80+
81+
data[key].data.duration = data[key].data.duration || {"total": 0, "prev-total": 0, "change": "NA", "trend": "u"};
82+
if (previous) {
83+
data[key].data.duration["prev-total"] = dd[z].dur;
84+
}
85+
else {
86+
data[key].data.duration.total = dd[z].dur;
87+
}
88+
}
89+
//data.all = countlyEvents.getSegmentedData(params.qstring.segmentation);
7090
resolve(true);
7191
});
7292
});
@@ -204,10 +224,20 @@ class TopEventsJob extends job.Job {
204224
let prevTotalSum = 0;
205225
let totalDuration = 0;
206226
let prevTotalDuration = 0;
207-
for (const event of eventMap) {
208-
log.d(" getting event data for event: " + event + " (" + period + ")");
209-
const collectionNameEvents = this.eventsCollentions({ event, id: app._id });
210-
await this.getEventsCount({ collectionNameEvents, ob, data, event });
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);
234+
235+
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;
211241
totalCount += data[event].data.count.total;
212242
prevTotalCount += data[event].data.count["prev-total"];
213243
totalSum += data[event].data.sum.total;
Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
11
/**
22
* Checks aggregated data for all events of all apps and outputs the ones that should need omitting segments.
3+
* For deletion to work SERVER_URL and API_KEY should be set.
34
* If DRY_RUN is false, will also omit those segmentes
45
* Server: countly
56
* Path: $(countly dir)/bin/scripts/modify-data
67
* Command: nodejs omit_events.js
78
*/
89

9-
//API key here with permission to delete events
10-
var API_KEY = "0d87fb49bd48ddc510306b7b4faf209a";
11-
10+
//API key with global admin rights
11+
var API_KEY = "";
1212
//dry run without deleting events
1313
var DRY_RUN = true;
1414
var SERVER_URL = "https://yourserver.count.ly";
1515

1616
var requestsToRun = [];
17-
17+
var omitLimit = 100; //Segment value limit
18+
var failedReqs = 0;
1819

1920
var plugins = require("../../../plugins/pluginManager");
2021
var crypto = require('crypto');
21-
var request = require('request');
22-
var requestOptions = {
23-
uri: (process.env.COUNTLY_CONFIG_PROTOCOL || "http") + "://" + (process.env.COUNTLY_CONFIG_HOSTNAME || "localhost") + "/i/events/edit_map",
24-
method: 'POST'
25-
};
22+
var request = require('countly-request')(plugins.getConfig("security"));
23+
2624
if (!SERVER_URL) {
2725
SERVER_URL = (process.env.COUNTLY_CONFIG_PROTOCOL || "http") + "://" + (process.env.COUNTLY_CONFIG_HOSTNAME || "localhost");
2826
}
2927
plugins.dbConnection().then(async function(db) {
30-
31-
var date = new Date();
32-
var yy = date.getFullYear();
33-
3428
var apps = await db.collection("apps").find().toArray();
3529
var appCheck = {};
3630
for (var l = 0; l < apps.length; l++) {
@@ -42,48 +36,78 @@ plugins.dbConnection().then(async function(db) {
4236
var omitted_segments = {};
4337
var event_map = {};
4438
if (events[i] && events[i].list && events[i].list.length) {
39+
console.log("Checking app:", events[i]._id);
4540
for (var j = 0; j < events[i].list.length; j++) {
4641
if (events[i].list[j]) {
42+
console.log(" Checking event:", events[i].list[j]);
4743
var eventSegmentCounts = {};
48-
var eventMeta = await db.collection("events" + crypto.createHash('sha1').update(events[i].list[j] + events[i]._id).digest('hex')).find({"m": yy + ":0"}).toArray();
49-
for (var k = 0; k < eventMeta.length; k++) {
50-
if (eventMeta[k] && eventMeta[k].meta_v2 && eventMeta[k].meta_v2.segments) {
51-
for (let segment in eventMeta[k].meta_v2.segments) {
52-
if (eventMeta[k].meta_v2[segment]) {
53-
if (typeof eventSegmentCounts[segment] === "undefined") {
54-
eventSegmentCounts[segment] = 0;
44+
var hash = crypto.createHash('sha1').update(events[i].list[j] + events[i]._id).digest('hex');
45+
var pipeline = [
46+
{"$match": {"_id": {"$regex": "^" + events[i]._id + "_" + hash + "_no-segment_.*"}, "meta_v2": {"$exists": true}}},
47+
{
48+
"$project": {
49+
"m": "$m",
50+
"meta_v2": {
51+
"$map": {
52+
"input": {"$objectToArray": "$meta_v2"},
53+
"as": "seg",
54+
"in": {"k": "$$seg.k", "v": {"$size": {"$objectToArray": "$$seg.v"}}}
5555
}
56-
eventSegmentCounts[segment] += Object.keys(eventMeta[k].meta_v2[segment]).length;
5756
}
5857
}
58+
},
59+
{"$unwind": "$meta_v2"},
60+
{
61+
"$group": {
62+
"_id": {"key": "$meta_v2.k", "m": "$m"},
63+
"count": {"$sum": "$meta_v2.v"}
64+
}
65+
}
66+
];
67+
var eventMeta = await db.collection("events_data").aggregate(pipeline).toArray();
68+
for (var k = 0; k < eventMeta.length; k++) {
69+
if (eventMeta[k]._id.key !== "segments") {
70+
eventSegmentCounts[eventMeta[k]._id.key] = eventSegmentCounts[eventMeta[k]._id.key] || 0;
71+
eventSegmentCounts[eventMeta[k]._id.key] = Math.max(eventMeta[k].count, eventSegmentCounts[eventMeta[k]._id.key]);
5972
}
73+
6074
}
6175
var first = true;
6276
//console.log("Event:", events[i].list[j], "for app:", events[i]._id);
6377
for (let segment in eventSegmentCounts) {
64-
if (eventSegmentCounts[segment] >= 1000) {
78+
if (eventSegmentCounts[segment] >= omitLimit) {
6579
if (first) {
6680
if (firstApp) {
67-
console.log("For app:", appCheck[events[i]._id] || events[i]._id);
6881
firstApp = false;
6982
}
70-
console.log("", "Event:", events[i].list[j]);
7183
first = false;
7284
}
73-
console.log("", "", segment, ":", eventSegmentCounts[segment]);
85+
if (events[i] && events[i].omitted_segments && events[i].omitted_segments[events[i].list[j]] && Array.isArray(events[i].omitted_segments[events[i].list[j]])) {
86+
omitted_segments[events[i].list[j]] = events[i].omitted_segments[events[i].list[j]];
87+
}
7488
if (!omitted_segments[events[i].list[j]]) {
7589
omitted_segments[events[i].list[j]] = [];
7690
}
77-
omitted_segments[events[i].list[j]].push(segment);
91+
if (omitted_segments[events[i].list[j]].indexOf(segment) === -1) {
92+
omitted_segments[events[i].list[j]].push(segment);
93+
}
94+
95+
if (events[i] && events[i].map && events[i].map[events[i].list[j]]) {
96+
event_map[events[i].list[j]] = events[i].map[events[i].list[j]];
97+
event_map[events[i].list[j]].omit_list = events[i].map[events[i].list[j]].omit_list || [];
98+
}
7899
if (!event_map[events[i].list[j]]) {
79100
event_map[events[i].list[j]] = {key: events[i].list[j], is_visible: true, omit_list: []};
80101
}
81-
event_map[events[i].list[j]].omit_list.push(segment);
102+
if (event_map[events[i].list[j]] && event_map[events[i].list[j]].omit_list && event_map[events[i].list[j]].omit_list.indexOf(segment) === -1) {
103+
event_map[events[i].list[j]].omit_list.push(segment);
104+
}
82105
}
83106
}
84107
}
85108
}
86109
}
110+
console.log(" Current event map:", JSON.stringify(event_map));
87111
if (Object.keys(omitted_segments).length) {
88112
if (DRY_RUN) {
89113
//as it is dry run - output request
@@ -96,25 +120,41 @@ plugins.dbConnection().then(async function(db) {
96120
requestsToRun.push(SERVER_URL + "/i/events/edit_map?" + props.join("&"));
97121
}
98122
else {
99-
requestOptions.json = {
100-
omitted_segments: JSON.stringify(omitted_segments),
101-
event_map: JSON.stringify(event_map),
102-
app_id: events[i]._id,
103-
api_key: API_KEY
123+
console.log("Omitting segments for app:", events[i]._id, "for events:", JSON.stringify(omitted_segments));
124+
const options = {
125+
url: SERVER_URL + "/i/events/edit_map",
126+
uri: SERVER_URL + "/i/events/edit_map",
127+
method: "POST",
128+
json: {
129+
omitted_segments: JSON.stringify(omitted_segments),
130+
event_map: JSON.stringify(event_map),
131+
app_id: events[i]._id + "",
132+
api_key: API_KEY
133+
},
134+
strictSSL: false
104135
};
105136
await new Promise(function(resolve) {
106-
request(requestOptions, function(error, response, body) {
107-
console.log("request finished", body);
137+
request(SERVER_URL + "/i/events/edit_map", options, function(error) {
138+
if ((error && error.name)) {
139+
failedReqs++;
140+
console.log(JSON.stringify(error.message));
141+
console.log({err: 'There was an error while sending a request.'});
142+
}
143+
108144
resolve();
109145
});
110146
});
111147
}
112148
}
113149
}
150+
if (failedReqs) {
151+
console.log("There were " + failedReqs + " failed requests. Please check your API KEY and url for server");
152+
}
114153
if (requestsToRun.length) {
115154
for (var z = 0; z < requestsToRun.length; z++) {
116155
console.log(requestsToRun[z]);
117156
}
118157
}
158+
console.log("Done");
119159
db.close();
120160
});

frontend/express/public/javascripts/countly/vue/components/dropdown.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@
622622
else {
623623
this.$emit('command', command, instance);
624624
}
625-
this.$refs.dropdown.handleClose();
625+
this.$refs?.dropdown?.handleClose();
626626
}
627627
},
628628
toggleArrowState: function() {

frontend/express/public/javascripts/countly/vue/components/helpers.js

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,63 @@
8686
}
8787
}));
8888

89-
Vue.component("cly-status-tag", countlyBaseComponent.extend({
90-
template: '<div class="cly-vue-status-tag" :class="dynamicClasses">\n' +
91-
'<div class="cly-vue-status-tag__blink"></div>\n' +
92-
'{{text}}\n' +
93-
'</div>',
94-
mixins: [countlyVue.mixins.i18n],
89+
Vue.component('cly-status-tag', countlyBaseComponent.extend({
9590
props: {
96-
text: { required: true, type: String },
97-
color: { default: "green", type: String},
98-
size: { default: "unset", type: String},
91+
color: {
92+
default: 'green',
93+
type: String
94+
},
95+
96+
loading: {
97+
default: false,
98+
type: Boolean
99+
},
100+
101+
size: {
102+
default: 'unset',
103+
type: String
104+
},
105+
106+
text: {
107+
required: true,
108+
type: String
109+
}
99110
},
111+
100112
computed: {
101-
dynamicClasses: function() {
102-
if (this.size === "small") {
103-
return ["cly-vue-status-tag--small", "cly-vue-status-tag--" + this.color];
113+
dynamicClasses() {
114+
const classes = [];
115+
116+
if (this.size === 'small') {
117+
classes.push('cly-vue-status-tag--small');
118+
}
119+
120+
if (this.loading) {
121+
classes.push('cly-vue-status-tag--gray');
104122
}
105-
return "cly-vue-status-tag--" + this.color;
123+
else {
124+
classes.push(`cly-vue-status-tag--${this.color}`);
125+
}
126+
127+
return classes;
106128
}
107129
},
130+
131+
template: `
132+
<div
133+
class="cly-vue-status-tag"
134+
:class="dynamicClasses"
135+
>
136+
<div class="cly-vue-status-tag__blink" />
137+
<div
138+
v-if="loading"
139+
class="cly-vue-status-tag__skeleton"
140+
/>
141+
<template v-else>
142+
{{ text }}
143+
</template>
144+
</div>
145+
`
108146
}));
109147

110148
Vue.component("cly-diff-helper", countlyBaseComponent.extend({

0 commit comments

Comments
 (0)