Skip to content

Commit f8eb89e

Browse files
Merge pull request #6092 from Countly/from_master_25_03_3
Merge 25.03.3 to next
2 parents 9dde236 + 0f266b3 commit f8eb89e

File tree

26 files changed

+610
-79
lines changed

26 files changed

+610
-79
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Features:
77
Enterprise Features:
88
- [journey_engine] Editing/Deleting/Duplication of blocks and version management
99

10+
## Version 25.03.3
11+
Fixes:
12+
- [content_builder] Reformulate asset library and add asset drag and drop upload to builder input
13+
- [content_builder] Fixed content block layout type select visibility
14+
- [user-management] Fixed issue with uploading member icon
15+
1016
## Version 25.03.2
1117
Fixes:
1218
- [user-management] Prevent global admin from self-revoke and self-delete
@@ -21,7 +27,7 @@ Fixes:
2127
- [crashes] Remove memory addresses from stack trace grouping
2228
- [script] Refined delete_custom_events.js to clean up faulty/dead events completely.
2329

24-
Enterprise fixes:
30+
Enterprise Fixes:
2531
- [ab-testing] Fixed bug with variant user filtering
2632
- [license] Fixed issue with handling invalid date periods
2733

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const pluginManager = require('../../../../plugins/pluginManager.js');
2+
3+
/********************************************/
4+
/* 1) With this script, if there is any cooldown defined before in the APPS collection(in the plugins.content), it will be moved to the APPS collection in the plugins.journey_engine */
5+
/* 2) Also, if there is any cooldown defined before in the PLUGINS collection(in the plugins.content), it will be moved to the PLUGINS collection in the plugins.journey_engine */
6+
/********************************************/
7+
8+
console.log("=== Migrating 'content' cooldown to 'journeys' in apps and plugins collections ===");
9+
10+
pluginManager.dbConnection().then(async(countlyDb) => {
11+
try {
12+
/********************************************/
13+
/* 1) in the APPS collection content->journey_engine, APP SETTING */
14+
/********************************************/
15+
console.log("Starting migration in apps collection...");
16+
17+
const apps = await countlyDb.collection("apps").find({"plugins.content": { $exists: true }}).toArray();
18+
if (!apps || apps.length === 0) {
19+
console.log("No apps require content->journeys migration");
20+
}
21+
else {
22+
console.log(`Found ${apps.length} apps to migrate`);
23+
for (let appDoc of apps) {
24+
appDoc.plugins.journey_engine = appDoc.plugins.content;
25+
delete appDoc.plugins.content;
26+
27+
await countlyDb.collection("apps").updateOne(
28+
{ _id: appDoc._id },
29+
{ $set: { plugins: appDoc.plugins }}
30+
);
31+
}
32+
console.log("Apps content->journeys migration DONE");
33+
}
34+
35+
/********************************************/
36+
/* 2) in the PLUGINS collection update the document which _id: "plugins, GLOBAL SETTING"*/
37+
/********************************************/
38+
console.log("Updating '_id: plugins' document in plugins collection...");
39+
40+
const pluginsDoc = await countlyDb.collection("plugins").findOne({ _id: "plugins" });
41+
if (!pluginsDoc) {
42+
console.log("No document found in plugins collection with _id: plugins");
43+
}
44+
else {
45+
let needUpdate = false;
46+
47+
// 2.a) Remove "plugins.content" boolean field
48+
if (pluginsDoc.plugins && Object.prototype.hasOwnProperty.call(pluginsDoc.plugins, "content")) {
49+
delete pluginsDoc.plugins.content;
50+
needUpdate = true;
51+
console.log("Removed plugins.content boolean field.");
52+
}
53+
54+
// 2.b) Move content.cooldown to journey_engine.cooldown
55+
if (pluginsDoc.content && typeof pluginsDoc.content === 'object') {
56+
if (typeof pluginsDoc.content.cooldown !== 'undefined') {
57+
if (!pluginsDoc.journey_engine || typeof pluginsDoc.journey_engine !== 'object') {
58+
pluginsDoc.journey_engine = {};
59+
}
60+
pluginsDoc.journey_engine.cooldown = pluginsDoc.content.cooldown;
61+
console.log(`Copied content.cooldown = ${pluginsDoc.content.cooldown} to journey_engine.cooldown`);
62+
}
63+
64+
// remove the top-level "content" object
65+
delete pluginsDoc.content;
66+
needUpdate = true;
67+
console.log("Removed top-level content object.");
68+
}
69+
70+
// if any changes were made, update the document
71+
if (needUpdate) {
72+
await countlyDb.collection("plugins").updateOne(
73+
{ _id: "plugins" },
74+
{ $set: pluginsDoc }
75+
);
76+
console.log("plugins document updated successfully");
77+
}
78+
else {
79+
console.log("No changes required for _id: plugins document");
80+
}
81+
}
82+
}
83+
catch (err) {
84+
console.error("Error during cooldown setting migration:", err);
85+
}
86+
finally {
87+
countlyDb.close();
88+
console.log("Cooldown migration script finished, DB connection closed.");
89+
}
90+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Modifies all active journey versions to show in-use label on the UI
3+
* Server: countly
4+
* Path: countly dir/bin/upgrade/DEV/scripts/update_journey_versions.js
5+
* Command: node update_journey_versions.js
6+
*/
7+
const pluginManager = require('../../../../plugins/pluginManager.js');
8+
9+
console.log("Modifying active journey versions...");
10+
11+
pluginManager.dbConnection().then(async(countlyDb) => {
12+
try {
13+
const collection = countlyDb.collection('journey_versions');
14+
15+
const result = await collection.updateMany(
16+
{ status: "active" },
17+
{ $set: { lastActivated: true } }
18+
);
19+
20+
console.log(`Successfully modified ${result.modifiedCount} active journey versions.`);
21+
}
22+
catch (error) {
23+
console.error("Error modifying active journey versions: ", error);
24+
}
25+
finally {
26+
countlyDb.close();
27+
}
28+
});

frontend/express/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1701,7 +1701,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_
17011701
try {
17021702
// This is to check that the uploaded image is a real image
17031703
// If jimp cannot read it then it is not a real image
1704-
const image = await jimp.read(tmp_path);
1704+
const image = await jimp.Jimp.read(tmp_path);
17051705

17061706
if (!image) {
17071707
fs.unlink(tmp_path, function() {});

frontend/express/public/core/user-management/templates/drawer.html

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div>
22
<cly-drawer
3+
test-id="user-drawer"
34
toggle-transition="stdt-fade"
45
ref="userDrawer"
56
:title="$props.settings.editMode ? $props.settings.editTitle : $props.settings.createTitle"
@@ -19,33 +20,33 @@
1920
<div class="bu-column">
2021
<div class="user-management-drawer-content__input-wrapper">
2122
<div class="user-management-drawer-content__label">
22-
<div class="text-small text-heading">{{ i18n('management-users.full-name') }}</div>
23+
<div data-test-id="full-name-label" class="text-small text-heading">{{ i18n('management-users.full-name') }}</div>
2324
</div>
2425
<div class="user-management-drawer-content__input">
2526
<validation-provider rules="required" v-slot="v">
26-
<el-input v-model="drawerScope.editedObject.full_name" :placeholder="i18n('management-users.enter-full-name')"></el-input>
27+
<el-input test-id="full-name-input" v-model="drawerScope.editedObject.full_name" :placeholder="i18n('management-users.enter-full-name')"></el-input>
2728
</validation-provider>
2829
</div>
2930
</div>
3031
<div class="user-management-drawer-content__input-wrapper bu-mt-5">
3132
<div class="user-management-drawer-content__label">
32-
<div class="text-small text-heading">{{ i18n('management-users.username') }}</div>
33+
<div data-test-id="user-name-label" class="text-small text-heading">{{ i18n('management-users.username') }}</div>
3334
</div>
3435
<div class="user-management-drawer-content__input">
3536
<validation-provider rules="required" v-slot="v">
36-
<el-input v-model="drawerScope.editedObject.username" :placeholder="i18n('management-users.enter-username')"></el-input>
37+
<el-input test-id="user-name-input" v-model="drawerScope.editedObject.username" :placeholder="i18n('management-users.enter-username')"></el-input>
3738
</validation-provider>
3839
</div>
3940
</div>
4041
<div class="user-management-drawer-content__input-wrapper bu-mt-5">
4142
<div class="user-management-drawer-content__label">
42-
<div class="text-small text-heading">{{ i18n('management-users.password') }}</div>
43+
<div data-test-id="password-label" class="text-small text-heading">{{ i18n('management-users.password') }}</div>
4344
</div>
4445
<div v-if="!$props.settings.editMode" class="user-management-drawer-content__input">
4546
<validation-provider rules="required" v-slot="v">
46-
<el-input v-model="drawerScope.editedObject.password" :placeholder="i18n('management-users.enter-password')"></el-input>
47+
<el-input test-id="password-input" v-model="drawerScope.editedObject.password" :placeholder="i18n('management-users.enter-password')"></el-input>
4748
</validation-provider>
48-
<div @click="generatePassword()" class="user-management-drawer-content__under-input-text bu-mt-1 bu-is-size-7">{{ i18n('management-users.generate-password') }}</div>
49+
<div data-test-id="generate-password-button" @click="generatePassword()" class="user-management-drawer-content__under-input-text bu-mt-1 bu-is-size-7">{{ i18n('management-users.generate-password') }}</div>
4950
</div>
5051
<div v-if="$props.settings.editMode" class="user-management-drawer-content__input">
5152
<validation-provider v-if="changePasswordFlag" rules="required" v-slot="v">
@@ -56,11 +57,11 @@
5657
</div>
5758
<div class="user-management-drawer-content__input-wrapper bu-mt-5">
5859
<div class="user-management-drawer-content__label">
59-
<div class="text-small text-heading">{{ i18n('management-users.email') }}</div>
60+
<div data-test-id="email-label" class="text-small text-heading">{{ i18n('management-users.email') }}</div>
6061
</div>
6162
<div class="user-management-drawer-content__input">
6263
<validation-provider rules="required|email" v-slot="v">
63-
<el-input v-model="drawerScope.editedObject.email" oninput="this.value = this.value.toLowerCase();" :placeholder="i18n('management-users.enter-email')"></el-input>
64+
<el-input test-id="email-input" v-model="drawerScope.editedObject.email" oninput="this.value = this.value.toLowerCase();" :placeholder="i18n('management-users.enter-email')"></el-input>
6465
</validation-provider>
6566
</div>
6667
</div>
@@ -78,6 +79,7 @@
7879
</cly-more-options>
7980
</div>
8081
<cly-dropzone
82+
data-test-id="user-profile-picture-dropzone"
8183
v-if="pictureEditMode || !$props.settings.editMode"
8284
@vdropzone-removed-file="onFileRemoved"
8385
@vdropzone-file-added="onFileAdded"
@@ -107,7 +109,7 @@
107109
</div>
108110
</div>
109111
<div class="user-management-drawer-content__section-content user-management-drawer-content__role-section">
110-
<el-checkbox class="bu-mt-2" v-model="drawerScope.editedObject.global_admin">{{ i18n('management-users.global-administrator') }}</el-checkbox>
112+
<el-checkbox test-id="global-administrator" class="bu-mt-2" v-model="drawerScope.editedObject.global_admin">{{ i18n('management-users.global-administrator') }}</el-checkbox>
111113
<component v-for="roleInput in rolesInput" :is="roleInput.component" :user="drawerScope.editedObject" @role-change="onRoleChange" key="roleInput.key"></component>
112114
</div>
113115
</div>
@@ -128,6 +130,7 @@
128130
</div>
129131
<div class="input" v-if="drawerScope.editedObject.permission">
130132
<cly-select-x
133+
test-id="admin-access-to-app-dropdown"
131134
:collapse-tags="false"
132135
:placeholder="i18n('token_manager.select-apps')"
133136
mode="multi-check"
@@ -156,6 +159,7 @@
156159
</div>
157160
<div class="user-management-drawer-content__input bu-px-4">
158161
<cly-select-x
162+
test-id="user-access-to-app-dropdown"
159163
:collapse-tags="false"
160164
:placeholder="i18n('token_manager.select-apps')"
161165
mode="multi-check"

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

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,20 +288,26 @@
288288
required: true,
289289
default: 'primary',
290290
validator: function(value) {
291-
return ['primary', 'secondary'].includes(value);
291+
return ['primary', 'secondary', 'info'].includes(value);
292292
}
293293
},
294294
label: {
295295
type: String,
296296
required: false,
297297
default: 'Status'
298+
},
299+
showIcon: {
300+
type: Boolean,
301+
required: false,
302+
default: true
298303
}
299304
},
300305
data: function() {
301306
return {
302307
modeConfig: {
303308
primary: { background: '#E2E4E8', color: '#81868D', icon: 'cly-is cly-is-status' },
304-
secondary: { background: '#EBFAEE', color: '#12AF51', icon: 'cly-is cly-is-status' }
309+
secondary: { background: '#EBFAEE', color: '#12AF51', icon: 'cly-is cly-is-status' },
310+
info: { background: '#E1EFFF', color: '#0166D6', icon: 'cly-is cly-is-status' },
305311
// Add more modes here if needed
306312
}
307313
};
@@ -336,7 +342,7 @@
336342
},
337343
template: `
338344
<div :style="badgeStyles">
339-
<i :class="currentConfig.icon" :style="iconStyles"></i>
345+
<i v-if="showIcon" :class="currentConfig.icon" :style="iconStyles"></i>
340346
<span class="text-small" :style="fontStyles">{{ label }}</span>
341347
</div>
342348
`,
@@ -390,6 +396,7 @@
390396
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER = 'swapper';
391397
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH = 'switch';
392398
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB = 'tab';
399+
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD = 'upload';
393400

394401
const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE = {
395402
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER]: 'cly-colorpicker',
@@ -399,15 +406,14 @@
399406
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER]: 'el-slider',
400407
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER]: 'cly-option-swapper',
401408
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH]: 'el-switch',
402-
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB]: 'div'
409+
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB]: 'div',
410+
[COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD]: 'el-upload'
403411
};
404412

405413
const COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_HORIZONTAL = 'horizontal';
406414
const COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_VERTICAL = 'vertical';
407415

408416
Vue.component("cly-content-builder-sidebar-input", countlyVue.components.create({
409-
template: CV.T('/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html'),
410-
411417
props: {
412418
componentTooltip: {
413419
default: null,
@@ -428,13 +434,19 @@
428434
default: 'cly-io cly-io-question-mark-circle',
429435
type: String
430436
},
437+
431438
labelTooltip: {
432439
default: null,
433440
type: String
434441
},
435442

443+
loading: {
444+
default: false,
445+
type: Boolean
446+
},
447+
436448
options: {
437-
default: () => [],
449+
default: () => null,
438450
type: Array
439451
},
440452

@@ -485,6 +497,8 @@
485497
},
486498

487499
emits: [
500+
'add-asset',
501+
'delete-asset',
488502
'input'
489503
],
490504

@@ -510,6 +524,20 @@
510524
}
511525
},
512526

527+
computedAttrs() {
528+
if (this.isUploadInput) {
529+
return {
530+
action: '',
531+
drag: true,
532+
multiple: false,
533+
showFileList: false,
534+
...this.$attrs
535+
};
536+
}
537+
538+
return this.$attrs;
539+
},
540+
513541
controlsProp() {
514542
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER ? false : null;
515543
},
@@ -541,6 +569,10 @@
541569
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER;
542570
},
543571

572+
isUploadInput() {
573+
return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_UPLOAD;
574+
},
575+
544576
isVerticalInput() {
545577
return this.position === COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_VERTICAL;
546578
},
@@ -555,7 +587,19 @@
555587
}
556588
return null;
557589
}
558-
}
590+
},
591+
592+
methods: {
593+
onUploadAddButtonClick() {
594+
this.$emit('add-asset');
595+
},
596+
597+
onUploadDeleteButtonClick() {
598+
this.$emit('delete-asset');
599+
}
600+
},
601+
602+
template: CV.T('/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html')
559603
}));
560604

561605
Vue.component("cly-option-swapper", countlyVue.components.create({

0 commit comments

Comments
 (0)