Skip to content

Commit 8d33e51

Browse files
Merge pull request #6514 from Countly/master-v25.03.13-into-next
Master v25.03.13 into next
2 parents f2a5a9f + 9f3aeb8 commit 8d33e51

File tree

10 files changed

+331
-93
lines changed

10 files changed

+331
-93
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
Dependencies:
33
- Remove SQLite
44

5+
## Version 25.03.13
6+
Features:
7+
- [remote-config] Enable comparing newer/older app version in conditions
8+
9+
Fixes:
10+
- [remote-config] Fix condition matching with compound conditions
11+
12+
Enterprise Fixes:
13+
- [flows] Showing correct state for disabled flows
14+
- [surveys] Move "not likely" label next to 0 on mobile screens
15+
516
## Version 25.03.12
617
Features:
718
- [plugins] Add configuration warning tags to settings UI

bin/config/nginx.server.ssl.conf

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ server {
1111
# HTTPS configuration
1212

1313
server {
14-
listen 443;
15-
listen [::]:443 ipv6only=on;
14+
listen 443 ssl;
15+
listen [::]:443 ipv6only=on;
1616
server_name localhost;
1717

1818
access_log off;
1919

20-
ssl on;
21-
2220
# support only known-secure cryptographic protocols
2321
# SSLv3 is broken by POODLE as of October 2014
2422
ssl_protocols TLSv1.2 TLSv1.3;

plugins/remote-config/api/parts/rc.js

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,96 @@
22
* Fetching and processing data for remote config
33
* @module plugins/remote-config/api/parts/data/rc
44
*/
5+
const semver = require('semver');
56
var prng = require('../../../../api/utils/random-sfc32.js');
67
var globalSeed = "Countly_is_awesome";
78

89
/** @lends module:plugins/remote-config/api/parts/data/rc */
910
var remoteConfig = {};
1011

1112
/**
12-
* Function to check if the given query would match the given user
13-
* @param {Object} user - user
14-
* @param {Object} query - query
13+
* Function to check if the given query matches the given user
14+
* @param {Object} inpUser - user object
15+
* @param {Object} inpQuery - condition query
1516
* @returns {Boolean} true if the query matches the user
1617
*/
17-
remoteConfig.processFilter = function(user, query) {
18-
var queryStatus = false, isCohort = false, hasValue = false;
19-
20-
if (Object.keys(query).length) {
21-
queryStatus = true;
22-
23-
for (var prop in query) {
24-
var parts = prop.split(".");
25-
var value;
18+
remoteConfig.processFilter = function(inpUser, inpQuery) {
19+
/**
20+
* Inner function of processFilter for recursion
21+
* @param {Object} user - user object
22+
* @param {Object} query - condition query
23+
* @returns {Boolean} true if the query matches the user
24+
*/
25+
function matchesQuery(user, query) {
26+
for (let key in query) {
27+
if (typeof query[key] === 'object' && query[key] !== null && !Array.isArray(query[key])) {
28+
let qResult = true;
29+
30+
for (let prop in query) {
31+
if (prop === '$or') {
32+
return qResult && query[prop].some((subQuery) => matchesQuery(user, subQuery));
33+
}
34+
else if (prop === '$and') {
35+
return qResult && query[prop].every((subQuery) => matchesQuery(user, subQuery));
36+
}
2637

27-
if (parts[0] === "up" || parts.length === 1) {
28-
var p = parts[0];
29-
if (p === "up") {
30-
p = parts[1];
31-
}
32-
if (user[p]) {
33-
value = user[p];
34-
}
35-
}
36-
else if (user[parts[0]] && user[parts[0]][parts[1]]) {
37-
value = user[parts[0]][parts[1]];
38-
}
38+
let parts = prop.split(".");
39+
let value;
40+
41+
if (parts[0] === "up" || parts.length === 1) {
42+
var p = parts[0];
43+
if (p === "up") {
44+
p = parts[1];
45+
}
46+
if (user[p]) {
47+
value = user[p];
48+
}
49+
}
50+
else if (user[parts[0]] && user[parts[0]][parts[1]]) {
51+
value = user[parts[0]][parts[1]];
52+
}
3953

40-
if (parts[0] !== "chr") {
41-
if (typeof (value) !== "undefined") {
42-
hasValue = true;
43-
queryStatus = queryStatus && processPropertyValues(value, query, prop);
44-
}
45-
else {
46-
//If the type of the user prop is undefined, set query status to false, since data is not available
47-
//In such cases only process if $nin is present otherwise we show the default value to the user
48-
if (query[prop].$nin) {
49-
hasValue = true;
50-
queryStatus = queryStatus && processPropertyValues(value, query, prop);
54+
if (parts[0] !== 'chr') {
55+
if (typeof (value) !== 'undefined') {
56+
const filterType = Object.keys(query[prop])[0];
57+
58+
if (prop === 'up.av' && /^\$(gt|lt)/.test(filterType)) {
59+
qResult = qResult && processAppVersionValues(user.av, { [prop]: query[prop] }, prop);
60+
}
61+
else {
62+
qResult = qResult && processPropertyValues(value, { [prop]: query[prop] }, prop);
63+
}
64+
}
65+
else {
66+
//If data is not available, check for $nin and $exists operator since they can be true
67+
if (query[prop] && ('$nin' in query[prop] || '$exists' in query[prop])) {
68+
qResult = qResult && processPropertyValues(value, { [prop]: query[prop] }, prop);
69+
} // Otherwise return false
70+
else {
71+
qResult = qResult && false;
72+
}
73+
}
5174
}
5275
else {
53-
queryStatus = false;
76+
qResult = qResult && processCohortValues(user, { chr: query[prop] });
5477
}
5578
}
79+
80+
return qResult;
81+
}
82+
else if (key === '$or') {
83+
return query[key].some((subQuery) => matchesQuery(user, subQuery));
84+
}
85+
else if (key === '$and') {
86+
return query[key].every((subQuery) => matchesQuery(user, subQuery));
5687
}
5788
else {
58-
hasValue = true;
59-
isCohort = true;
89+
return false;
6090
}
6191
}
62-
63-
if (isCohort) {
64-
queryStatus = queryStatus && processCohortValues(user, query);
65-
}
66-
67-
if (!hasValue) {
68-
//If the user does not have any user prop value, set query status to false, since data is not available
69-
queryStatus = false;
70-
}
7192
}
7293

73-
return queryStatus;
94+
return matchesQuery(inpUser, inpQuery);
7495
};
7596

7697
/**
@@ -105,12 +126,34 @@ function processPropertyValues(value, query, prop) {
105126
case "$lte": status = status && value <= query[prop].$lte; break;
106127
case "$regex": status = status && query[prop].$regex.test(value); break;
107128
case "$not": status = status && !query[prop].$not.test(value); break;
129+
case '$exists': status = status && (query[prop].$exists === (value !== undefined)); break;
108130
}
109131
}
110132

111133
return status;
112134
}
113135

136+
/**
137+
* Function to process query property value
138+
* @param {Object} inpUserAv - user app version
139+
* @param {Object} query - filter
140+
* @param {String} prop - query property
141+
* @returns {Boolean} property value status
142+
*/
143+
function processAppVersionValues(inpUserAv, query, prop) {
144+
// app version is stored in mongo like 1:1:0 instead of 1.1.0
145+
// the colons have to be replaced with dots so that semver lib can compare the app version
146+
const userAv = inpUserAv.replace(/:/g, '.');
147+
const filterType = Object.keys(query[prop])[0];
148+
const targetAv = query[prop] && query[prop][filterType] && query[prop][filterType].replace(/:/g, '.');
149+
150+
if (!semver.valid(userAv) || !semver.valid(targetAv)) {
151+
return false;
152+
}
153+
154+
return semver[filterType.slice(1)](userAv, targetAv);
155+
}
156+
114157
/**
115158
* Function to process query cohort value
116159
* @param {Object} user - user data
@@ -162,4 +205,4 @@ function processCohortValues(user, query) {
162205
return status;
163206
}
164207

165-
module.exports = remoteConfig;
208+
module.exports = remoteConfig;

plugins/remote-config/frontend/public/javascripts/countly.views.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@
216216
value: 1,
217217
label: "#6C47FF"
218218
},
219-
colorTag: COLOR_TAG
219+
colorTag: COLOR_TAG,
220+
modifyPropType: { 'up.av': countlyQueryBuilder.PropertyType.APP_VERSION_LIST },
220221
};
221222
},
222223
computed: {
@@ -636,7 +637,6 @@
636637
name: CV.i18n("remote-config.conditions.random.percentile"),
637638
type: countlyQueryBuilder.PropertyType.NUMBER,
638639
group: 'User Properties',
639-
640640
}));
641641
return {
642642
remoteConfigFilterRules: remoteConfigFilterRules,
@@ -651,7 +651,8 @@
651651
value: 1,
652652
label: "#6C47FF"
653653
},
654-
colorTag: COLOR_TAG
654+
colorTag: COLOR_TAG,
655+
modifyPropType: { 'up.av': countlyQueryBuilder.PropertyType.APP_VERSION_LIST },
655656
};
656657
},
657658
methods: {
@@ -836,7 +837,7 @@
836837
self.onSubmit();
837838
});
838839

839-
}, [this.i18n["common.no-dont-delete"], this.i18n["remote-config.yes-delete-parameter"]], {title: this.i18n["remote-config.delete-parameter-title"], image: "delete-email-report"});
840+
}, [this.i18n("common.no-dont-delete"), this.i18n("remote-config.yes-delete-parameter")], {title: this.i18n("remote-config.delete-parameter-title"), image: "delete-email-report"});
840841
break;
841842
}
842843
},
@@ -920,7 +921,7 @@
920921
break;
921922

922923
case "remove":
923-
CountlyHelpers.confirm(this.i18n("remote-config.confirm-condition-delete", "<b>" + name + "</b>"), "popStyleGreen", function(result) {
924+
CountlyHelpers.confirm(this.i18n("remote-config.confirm-condition-delete", "<b>" + row.condition_name + "</b>"), "popStyleGreen", function(result) {
924925
if (!result) {
925926
return false;
926927
}
@@ -929,7 +930,7 @@
929930
self.onSubmit();
930931
});
931932

932-
}, [this.i18n["common.no-dont-delete"], this.i18n["remote-config.yes-delete-condition"]], {title: this.i18n["remote-config.delete-condition-title"], image: "delete-email-report"});
933+
}, [this.i18n("common.no-dont-delete"), this.i18n("remote-config.yes-delete-condition")], {title: this.i18n("remote-config.delete-condition-title"), image: "delete-email-report"});
933934
break;
934935
}
935936
},

plugins/remote-config/frontend/public/templates/condition-dialog.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
<cly-confirm-dialog @cancel="cancel" @confirm="submit" :visible.sync="isOpen" :show-close="false" width="674px" class="cly-vue-remote-config-condition-dialog"
2-
:modal="false" center>
1+
<cly-confirm-dialog
2+
@cancel="cancel"
3+
@confirm="submit"
4+
:visible.sync="isOpen"
5+
:show-close="false"
6+
:modal="false"
7+
:title="i18n('remote-config.parameter.conditions.add.new.condition')"
8+
width="674px"
9+
class="cly-vue-remote-config-condition-dialog"
10+
center>
311
<template v-slot:title>
412
<h3 class="color-cool-gray-100">{{i18n('remote-config.parameter.conditions.add.new.condition')}}</h3>
513
</template>
@@ -71,6 +79,7 @@ <h3 class="color-cool-gray-100">{{i18n('remote-config.parameter.conditions.add.n
7179
:add-empty-row-on-empty-query="true"
7280
:allow-breakdown="false"
7381
:orGroupsEnabled="true"
82+
:modifyPropType="modifyPropType"
7483
show-in-the-last-minutes
7584
show-in-the-last-hours
7685
v-model="managedPropertySegmentation">

plugins/remote-config/frontend/public/templates/conditions-drawer.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
:add-empty-row-on-empty-query="true"
6767
:allow-breakdown="false"
6868
:orGroupsEnabled="true"
69+
:modifyPropType="modifyPropType"
6970
show-in-the-last-minutes
7071
show-in-the-last-hours
7172
v-model="managedPropertySegmentation">

0 commit comments

Comments
 (0)