Skip to content

Commit e275acb

Browse files
authored
Merge pull request #6150 from Countly/release_24_10_to_master
Update master from Release.24.10
2 parents a5f6fce + 3c16cf8 commit e275acb

File tree

18 files changed

+519
-148
lines changed

18 files changed

+519
-148
lines changed

CHANGELOG.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
## Version 25.03.x
2-
1+
## Version 25.03.4
32
Features:
43
- Add ability to allow multiple CORS per app for web apps
54
- Add app id and name as view segment for self-tracking
5+
- [dashboards] Added the option to set a refresh rate for dashboards, allowing data to update more frequently for selected dashboards
66

77
Enterprise Features:
88
- [license] Update locking conditions for expired license and over limit usage
99
- [license] Enable force locking with remote config
1010
- [license] Update dashboard lock with redirection to home page for non admin user
1111

12+
Fixes:
13+
- [core] Allow downloading data also from other databases in dbviewer
14+
- [crashes] Fix unescaped SDK logs
15+
- [crash_symbolication] Symbolication server api end point test fix
16+
- [star-rating] Added missing columns to Rating Widgets table edit
17+
- [ui] Fix alignment of drawers title and close icon
18+
- [push] Fixed push notifications title and content text and variables combination
19+
- [reports] Correctly match event for email report if event key contains '.'
20+
21+
Enterprise Fixes:
22+
- [cohorts] Fixed issue with combining multiple cohorts
23+
- [drill] Do not recheck old collections on app_user data deletion if querying from old collections is disabled
24+
1225
Dependencies:
1326
- Bump body-parser from 1.20.3 to 2.2.0
1427
- Bump moment-timezone from 0.5.47 to 0.5.48

api/jobs/topEvents.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,19 @@ class TopEventsJob extends job.Job {
138138
* The errors of all functions will be caught here.
139139
*/
140140
async getAllApps() {
141+
log.d("Fetching all apps");
141142
try {
142143
const getAllApps = await new Promise((res, rej) => common.db.collection("apps").find({}, { _id: 1, timezone: 1 }).toArray((err, apps) => err ? rej(err) : res(apps)));
143-
await Promise.all(getAllApps.map((app) => this.getAppEvents(app)));
144+
for (var z = 0; z < getAllApps.length; z++) {
145+
//Calculating for each app serially.
146+
await this.getAppEvents(getAllApps[z]);
147+
}
144148
}
145149
catch (error) {
146150
log.e("TopEvents Job has a error: ", error);
147151
throw error;
148152
}
153+
log.d("Finished processing");
149154
}
150155

151156
/**
@@ -178,6 +183,7 @@ class TopEventsJob extends job.Job {
178183
* @param {Object} app - saveAppEvents object
179184
*/
180185
async getAppEvents(app) {
186+
log.d(app._id + ": Fetching app events");
181187
const getEvents = await new Promise((res, rej) => common.db.collection("events").findOne({ _id: app._id }, (errorEvents, result) => errorEvents ? rej(errorEvents) : res(result)));
182188
if (getEvents && 'list' in getEvents) {
183189
const eventMap = this.eventsFilter(getEvents.list);
@@ -199,6 +205,7 @@ class TopEventsJob extends job.Job {
199205
let totalDuration = 0;
200206
let prevTotalDuration = 0;
201207
for (const event of eventMap) {
208+
log.d(" getting event data for event: " + event + " (" + period + ")");
202209
const collectionNameEvents = this.eventsCollentions({ event, id: app._id });
203210
await this.getEventsCount({ collectionNameEvents, ob, data, event });
204211
totalCount += data[event].data.count.total;
@@ -208,11 +215,16 @@ class TopEventsJob extends job.Job {
208215
totalDuration += data[event].data.duration.total;
209216
prevTotalDuration += data[event].data.duration["prev-total"];
210217
}
218+
log.d(" getting session count (" + period + ")");
211219
await this.getSessionCount({ ob, sessionData, usersData, usersCollectionName });
220+
log.d(" saving data (" + period + ")");
212221
await this.saveAppEvents({ app, data, sessionData, usersData, period, totalCount, prevTotalCount, totalSum, prevTotalSum, totalDuration, prevTotalDuration });
213222
}
214223
}
215224
}
225+
else {
226+
log.d(" No events found for app");
227+
}
216228

217229
}
218230

api/utils/taskmanager.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,13 @@ taskmanager.rerunTask = function(options, callback) {
984984
});
985985
}
986986

987-
options.db.collection("long_tasks").findOne({_id: options.id}, function(err, res) {
987+
var qq = {_id: options.id};
988+
if (options.additionalQuery) {
989+
qq = options.additionalQuery;
990+
qq._id = options.id;
991+
}
992+
log.d("Fetching from long_tasks to rerun: " + JSON.stringify(qq));
993+
options.db.collection("long_tasks").findOne(qq, function(err, res) {
988994
if (!err && res && res.request) {
989995
var reqData = {};
990996
try {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
'is-open': this.isOpened,
6363
'has-sidecars': this.hasSidecars
6464
};
65+
// NOTE: currentScreenMode variable seems to be not defined it should be defined or removed
6566
classes["cly-vue-drawer--" + this.currentScreenMode + "-screen"] = true;
6667
if (this.currentScreenMode === 'half') {
6768
classes["cly-vue-drawer--half-screen-" + this.size] = true;

frontend/express/public/javascripts/countly/vue/templates/drawer.html

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@
2020
</div>
2121
<div class="cly-vue-drawer__header" :class="{ 'is-full-screen': currentScreenMode === 'full'}">
2222
<div class="cly-vue-drawer__title">
23-
<div v-if="currentScreenMode !== 'full'">
24-
<div v-if="hasBackLink" class="cly-vue-drawer__title-container bu-is-flex bu-is-justify-content-space-between">
25-
<div class="bu-is-flex bu-is-flex-direction-column bu-is-justify-content-center bu-p-1 bu-ml-5 bu-mt-5">
26-
<a style="cursor: pointer;" @click="doClose" :data-test-id="testId + '-drawer-back-link'">
27-
<span class="text-medium bu-is-capitalized"><i class="fas fa-arrow-left bu-pr-3"></i>{{i18n('plugins.back')}}</span>
28-
</a>
29-
</div>
30-
<div class="bu-is-flex" :style="hasBackLink.style" :class="{ 'bu-is-justify-content-flex-start': hasBackLink, 'bu-is-justify-content-center': !hasBackLink }">
31-
<h3 :data-test-id="testId + '-header-title'" class="cly-vue-drawer__title-header">{{title}}</h3>
32-
</div>
33-
<span :data-test-id="testId + '-close-button'" class="cly-vue-drawer__close-button bu-p-1 bu-mr-5" v-on:click="doClose">
34-
<i class="ion-ios-close-empty"></i>
35-
</span>
36-
</div>
37-
<div v-else class="cly-vue-drawer__title-container bu-is-flex bu-is-justify-content-space-between">
38-
<h3 :data-test-id="testId + '-header-title'" class="cly-vue-drawer__title-header">{{title}}</h3>
39-
<span :data-test-id="testId + '-close-button'" class="cly-vue-drawer__close-button" v-on:click="doClose">
40-
<i class="ion-ios-close-empty"></i>
23+
<div
24+
v-if="currentScreenMode !== 'full'"
25+
class="cly-vue-drawer__title-container bu-is-flex bu-is-justify-content-space-between"
26+
:class="{ 'bu-is-align-items-center': !hasBackLink }"
27+
>
28+
<a
29+
v-if="hasBackLink"
30+
:data-test-id="testId + '-drawer-back-link'"
31+
style="cursor: pointer;"
32+
@click="doClose"
33+
>
34+
<span class="text-medium bu-is-capitalized">
35+
<i class="fas fa-arrow-left bu-pr-3" />
36+
{{ i18n('plugins.back') }}
4137
</span>
38+
</a>
39+
<h3
40+
class="cly-vue-drawer__title-header"
41+
:data-test-id="testId + '-header-title'"
42+
:style="hasBackLink.style"
43+
>
44+
{{ title }}
45+
</h3>
46+
<div
47+
class="cly-vue-drawer__close-button"
48+
:data-test-id="testId + '-close-button'"
49+
@click="doClose"
50+
>
51+
<i class="ion-ios-close-empty" />
4252
</div>
4353
</div>
4454
<div v-if="isMultiStep" class="bu-columns bu-is-gapless bu-is-mobile cly-vue-drawer__subtitle">
@@ -51,7 +61,7 @@ <h3 :data-test-id="testId + '-header-title'" class="cly-vue-drawer__title-header
5161
<span class="index text-small" :data-test-id="testId + '-current-step-index-' + (i + 1)" :class="{'color-white': i === currentStepIndex}">{{i + 1}}</span>
5262
<span class="done-icon text-small color-white bu-pt-0" :data-test-id="testId + '-current-step-index-img-container' + (i + 1)">
5363
<img :data-test-id="testId + '-step-' + (i + 1)" src="images/icons/check-icon.svg">
54-
</span>
64+
</span>
5565
</div>
5666
<div :data-test-id="testId + '-' + (currentContent && currentContent.name ? currentContent.name.toLowerCase().replaceAll(/[\s&]+/g, '-') + '-label' : 'content-label')" class="cly-vue-drawer__step-title text-small font-weight-bold color-cool-gray-40">{{currentContent.name}}</div>
5767
</div>

frontend/express/public/stylesheets/vue/clyvue.scss

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,31 +1899,29 @@
18991899
box-sizing: border-box;
19001900
}
19011901

1902+
// .cly-vue-drawer__title-container
19021903
&__title-container {
1903-
width: 100%;
1904-
margin-bottom: 24px;
1905-
}
1906-
1907-
&__title-header {
1908-
margin-top: 24px;
1909-
margin-left: 32px;
1904+
margin: 24px 32px;
19101905
}
19111906

19121907
&__subtitle {
19131908
margin-left: 32px !important;
19141909
margin-bottom: 24px !important;
19151910
}
19161911

1912+
// .cly-vue-drawer__close-button
19171913
&__close-button {
1914+
display: flex;
1915+
align-items: center;
1916+
justify-content: center;
1917+
19181918
font-size: 30px;
19191919
color: #81868D;
19201920
transition: color 1s;
19211921
cursor: pointer;
1922-
//width: 30px;
1923-
height: 15px;
1924-
//margin-block: auto;
1925-
margin-right: 32px;
1926-
margin-top: 24px;
1922+
height: 20px;
1923+
1924+
// .cly-vue-drawer__close-button:hover
19271925
&:hover {
19281926
color: #333;
19291927
transition: color 1s;

plugins/crashes/frontend/public/javascripts/countly.models.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,9 @@ function transformAppVersion(inpVersion) {
808808
}
809809

810810
crashgroupJson.data.forEach(function(crash, crashIndex) {
811+
// unescape logs
812+
crashgroupJson.data[crashIndex].logs = countlyCommon.unescapeHtml(crash.logs);
813+
811814
if (crash.uid in userIds) {
812815
userIds[crash.uid].push(crashIndex);
813816
}
@@ -1327,4 +1330,4 @@ function transformAppVersion(inpVersion) {
13271330
return resultQuery;
13281331
};
13291332

1330-
}(window.countlyCrashes = window.countlyCrashes || {}));
1333+
}(window.countlyCrashes = window.countlyCrashes || {}));

plugins/dashboards/api/api.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ plugins.setConfigs("dashboards", {
2222

2323
(function() {
2424

25+
plugins.register("/master", function() {
26+
setTimeout(() => {
27+
require('../../../api/parts/jobs').job('dashboards:refreshDashboards').replace().schedule('every 5 minutes');
28+
}, 1000);
29+
});
30+
2531
/**
2632
* @api {get} /o/dashboards Get dashboard
2733
* @apiName GetDashboard
@@ -162,6 +168,11 @@ plugins.setConfigs("dashboards", {
162168
}
163169
}
164170

171+
if (dashboard.refreshRate) {
172+
dashboard.refreshRate = dashboard.refreshRate / 60; //Convert to minutes
173+
dashboard.use_refresh_rate = true;
174+
}
175+
165176
if (canSeeDashboardShares(params.member, dashboard)) {
166177
parallelTasks.push(fetchSharedUsersInfo.bind(null, dashboard));
167178
}
@@ -597,6 +608,19 @@ plugins.setConfigs("dashboards", {
597608
shareWith = params.qstring.share_with || "",
598609
copyDashId = params.qstring.copy_dash_id;
599610

611+
var refreshRate = 0;
612+
if (params.qstring.use_refresh_rate && params.qstring.use_refresh_rate !== "false" && params.qstring.refreshRate > 0) {
613+
try {
614+
refreshRate = parseInt(params.qstring.refreshRate, 10);
615+
refreshRate = Math.max(5, refreshRate) * 60; //Convert to seconds
616+
}
617+
catch (ex) {
618+
refreshRate = 0;
619+
log.e("passed unexpected refresh rate");
620+
}
621+
622+
}
623+
600624
try {
601625
sharedEmailEdit = JSON.parse(sharedEmailEdit);
602626
}
@@ -711,6 +735,9 @@ plugins.setConfigs("dashboards", {
711735
theme: theme,
712736
created_at: new Date().getTime()
713737
};
738+
if (refreshRate > 0) {
739+
dashData.refreshRate = refreshRate;
740+
}
714741

715742
var widgets = dataObj.newWidgetIds;
716743
if (widgets && widgets.length) {
@@ -846,6 +873,19 @@ plugins.setConfigs("dashboards", {
846873
send_email_invitation = params.qstring.send_email_invitation,
847874
memberId = params.member._id + "";
848875

876+
var refreshRate = 0;
877+
if (params.qstring.use_refresh_rate && params.qstring.use_refresh_rate !== "false" && params.qstring.refreshRate > 0) {
878+
try {
879+
refreshRate = parseInt(params.qstring.refreshRate, 10);
880+
refreshRate = Math.max(5, refreshRate) * 60; //Convert to seconds
881+
}
882+
catch (ex) {
883+
refreshRate = 0;
884+
log.e("passed unexpected refresh rate");
885+
}
886+
}
887+
888+
849889
if (!dashboardId || dashboardId.length !== 24) {
850890
common.returnMessage(params, 400, 'Invalid parameter: dashboard_id');
851891
return true;
@@ -972,12 +1012,19 @@ plugins.setConfigs("dashboards", {
9721012
changedFields.shared_user_groups_view = sharedUserGroupView;
9731013
}
9741014
}
1015+
var unset = {shared_with_view: "", shared_with_edit: ""};
1016+
if (refreshRate && refreshRate > 0 && refreshRate !== dashboard.refreshRate) {
1017+
changedFields.refreshRate = refreshRate;
1018+
}
1019+
else if (refreshRate === 0) {
1020+
unset.refreshRate = ""; //unset refresh rate for dashboard
1021+
}
9751022

9761023
common.db.collection("dashboards").update(
9771024
filterCond,
9781025
{
9791026
$set: changedFields,
980-
$unset: {shared_with_view: "", shared_with_edit: ""}
1027+
$unset: unset
9811028
},
9821029
async function(e, res) {
9831030
if (!e && res) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const job = require('../../../../api/parts/jobs/job.js');
2+
const log = require('../../../../api/utils/log.js')('job:dashboards:refreshDashboards');
3+
const pluginManager = require('../../../pluginManager.js');
4+
var customDashboards = require('./../parts/dashboards.js');
5+
6+
7+
/** class RefreshDashboardsJob */
8+
class RefreshDashboardsJob extends job.Job {
9+
/** function run
10+
* @param {object} countlyDb - db connection object
11+
* @param {function} doneJob - function to call when finishing Job
12+
* @param {function} progressJob - fnction to call while running job
13+
*/
14+
run(countlyDb, doneJob, progressJob) {
15+
var total = 0;
16+
var current = 0;
17+
var bookmark = '';
18+
log.d('Starting dashboards refresh job');
19+
20+
/**
21+
* check job status periodically
22+
*/
23+
function ping() {
24+
log.d('Pinging dashboards refresh job');
25+
if (pingTimeout) {
26+
progressJob(total, current, bookmark);
27+
pingTimeout = setTimeout(ping, 10000);
28+
}
29+
}
30+
var pingTimeout = setTimeout(ping, 10000);
31+
32+
/**
33+
* end job
34+
* @returns {varies} job done
35+
*/
36+
function endJob() {
37+
log.d('Ending dashboards refresh job');
38+
clearTimeout(pingTimeout);
39+
pingTimeout = 0;
40+
return doneJob();
41+
}
42+
43+
pluginManager.loadConfigs(countlyDb, async() => {
44+
//Fetch all sashboards.
45+
//Check for the ones that have set refresh rate.
46+
//Trigger regeneration for those dashboards.
47+
try {
48+
var dashboards = await countlyDb.collection('dashboards').find({}).toArray();
49+
for (var z = 0; z < dashboards.length; z++) {
50+
if (dashboards[z].refreshRate && dashboards[z].refreshRate > 0) {
51+
if (dashboards[z].refreshRate < 300) {
52+
dashboards[z].refreshRate = 300;
53+
}
54+
log.d('Refreshing dashboard: ' + dashboards[z]._id);
55+
await customDashboards.refreshDashboard(countlyDb, dashboards[z]);
56+
}
57+
}
58+
}
59+
catch (error) {
60+
log.e('Error while refreshing dashboards: ' + error);
61+
}
62+
return endJob();
63+
});
64+
}
65+
}
66+
67+
module.exports = RefreshDashboardsJob;

0 commit comments

Comments
 (0)