Skip to content

Commit 1a46de5

Browse files
authored
Merge branch 'next' into SER-2116
2 parents e930451 + 0fc41e9 commit 1a46de5

File tree

7 files changed

+149
-61
lines changed

7 files changed

+149
-61
lines changed

frontend/express/public/javascripts/countly/countly.auth.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
}
4040

4141
if (!member.global_admin) {
42-
var isPermissionObjectExistForAccessType = (typeof member.permission[accessType] === "object" && typeof member.permission[accessType][app_id] === "object");
42+
var isPermissionObjectExistForAccessType = (member.permission && typeof member.permission[accessType] === "object" && typeof member.permission[accessType][app_id] === "object");
4343

4444
var memberHasAllFlag = member.permission && member.permission[accessType] && member.permission[accessType][app_id] && member.permission[accessType][app_id].all;
4545
var memberHasAllowedFlag = false;
@@ -96,7 +96,7 @@
9696
return false;
9797
}
9898
if (!member.global_admin) {
99-
var isPermissionObjectExistForRead = (typeof member.permission.r === "object" && typeof member.permission.r[app_id] === "object");
99+
var isPermissionObjectExistForRead = (member.permission && typeof member.permission.r === "object" && typeof member.permission.r[app_id] === "object");
100100
// TODO: make here better. create helper method for these checks
101101
var memberHasAllFlag = member.permission && member.permission.r && member.permission.r[app_id] && member.permission.r[app_id].all;
102102
var memberHasAllowedFlag = false;

plugins/alerts/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Alerts plugin
22

3-
The Alerts plugin is designed to monitor various metrics and events within the Countly analytics platform. It provides a structured way to set up alerts for different types of data, ensuring that users are notified when specific conditions are met. Below is a detailed breakdown of the plugin's file structure and its components:
3+
The Alerts plugin in Countly is a reactive tool designed to keep you informed about critical changes in your application’s metrics, even when you’re not monitoring dashboards. It sends email notifications when specific conditions on important metrics are met, enabling you to respond quickly to potential issues. This feature helps ensure your app maintains high performance and provides a positive user experience by alerting you to areas that may need immediate attention.
44

55
## File structure
66
File structure follows usual Countly plugin structure
@@ -41,6 +41,17 @@ alerts/
4141
└── tests.js # plugin tests
4242
```
4343

44+
## Key Features
45+
46+
- **Customizable Alerts:** Define specific conditions for metrics such as crashes, cohorts, data points, events, Net Promoter Score (NPS), online users, rating, revenue, sessions, surveys, users, and views. Get notified whenever these conditions are met.
47+
- **Real-Time Notifications:** Receive email alerts for immediate awareness of changes in your metrics.
48+
- **Detailed Monitoring:** Track a broad range of metrics, including user engagement, performance, user feedback, and error rates.
49+
- **Easy Setup:** Simple configuration allows you to set and customize alerts quickly to fit your needs.
50+
51+
## Example Use Case
52+
53+
Imagine you’ve released a new version of your app. Although it passed all tests, some critical bugs may still slip through. These bugs might prevent users from fully using the app. By setting up alerts for sudden spikes in crashes or decreased user activity, you can catch these issues early and work to resolve them, ensuring minimal disruption.
54+
4455
## Generate alerts job
4556

4657
Job name: alerts:monitor

plugins/alerts/api/api.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = {
4444
if (typeof alertID === 'string') {
4545
alertID = common.db.ObjectID(alertID);
4646
}
47-
common.db.collection("jobs").remove({ 'data.alertID': alertID }, function() {
47+
common.db.collection("jobs").remove({ 'data.alertID': alertID }, function(err) {
48+
if (err) {
49+
log.e('delete job failed, alertID:', alertID, err);
50+
return;
51+
}
4852
log.d('delete job, alertID:', alertID);
4953
if (callback) {
5054
callback();
@@ -241,8 +245,8 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = {
241245
);
242246
}
243247
catch (err) {
244-
log.e('Parse alert failed', alertConfig);
245-
common.returnMessage(params, 500, "Failed to create an alert");
248+
log.e('Parse alert failed', alertConfig, err);
249+
common.returnMessage(params, 500, "Failed to create an alert" + err.message);
246250
}
247251
});
248252
return true;
@@ -284,8 +288,8 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = {
284288
);
285289
}
286290
catch (err) {
287-
log.e('delete alert failed', alertID);
288-
common.returnMessage(params, 500, "Failed to delete an alert");
291+
log.e('delete alert failed', alertID, err);
292+
common.returnMessage(params, 500, "Failed to delete an alert" + err.message);
289293
}
290294
});
291295
return true;
@@ -411,8 +415,8 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = {
411415
});
412416
}
413417
catch (err) {
414-
log.e('get alert list failed');
415-
common.returnMessage(params, 500, "Failed to get alert list");
418+
log.e('get alert list failed', err);
419+
common.returnMessage(params, 500, "Failed to get alert list" + err.message);
416420
}
417421
});
418422
return true;

plugins/compliance-hub/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,18 @@ compliance-hub/
2727
├── package.json
2828
├── README.md
2929
└── tests.js # plugin tests
30-
```
30+
```
31+
32+
## Key Features
33+
34+
- **Collect User Consents:** Prompt first-time users to consent to data collection, detailing which types of data (e.g., sessions, crashes, views) will be collected. No data is sent unless the user opts in.
35+
- **Manage User Requests:** The "Consents" tab (available in Enterprise Edition) lets admins view and fulfill user requests for data export or deletion.
36+
- **SDK Integration:** Countly SDKs (iOS, Android, Node.js, Web) support flexible consent management, allowing opt-in/opt-out on a per-feature basis. SDKs default to opt-in for backward compatibility but can be configured to require opt-in consent at initialization.
37+
38+
## Using the Compliance Hub
39+
40+
Access the Compliance Hub via Main Menu > Utilities > Compliance Hub. The Compliance Hub offers the following views:
41+
1. **Metrics View:** Track opt-ins and opt-outs across various features (e.g., sessions, crashes) over time in a time-series graph.
42+
2. **Users View:** List users with consent histories. Each entry shows user ID, device info, app version, and consent types. Options include viewing consent history, exporting user data, and purging data if required.
43+
3. **Consent History:** A complete list of all opt-in and opt-out actions across metrics, allowing for easy tracking.
44+
4. **Export/Purge History:** See a record of all data export and deletion actions for compliance tracking.

plugins/hooks/api/api.js

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ plugins.register("/permissions/features", function(ob) {
272272
plugins.register("/i/hook/save", function(ob) {
273273
let paramsInstance = ob.params;
274274

275-
276275
validateCreate(ob.params, FEATURE_NAME, function(params) {
277276
let hookConfig = params.qstring.hook_config;
278277
if (!hookConfig) {
@@ -283,50 +282,57 @@ plugins.register("/i/hook/save", function(ob) {
283282
try {
284283
hookConfig = JSON.parse(hookConfig);
285284
hookConfig = sanitizeConfig(hookConfig);
286-
if (!(common.validateArgs(hookConfig, CheckHookProperties(hookConfig)))) {
287-
common.returnMessage(params, 400, 'Not enough args');
288-
return true;
289-
}
285+
if (hookConfig) {
286+
// Null check for hookConfig
287+
if (!(common.validateArgs(hookConfig, CheckHookProperties(hookConfig)))) {
288+
common.returnMessage(params, 400, 'Not enough args');
289+
return true;
290+
}
290291

291-
if (hookConfig.effects && !validateEffects(hookConfig.effects)) {
292-
common.returnMessage(params, 400, 'Invalid configuration for effects');
293-
return true;
294-
}
292+
if (hookConfig.effects && !validateEffects(hookConfig.effects)) {
293+
common.returnMessage(params, 400, 'Invalid configuration for effects');
294+
return true;
295+
}
295296

296-
if (hookConfig._id) {
297-
const id = hookConfig._id;
298-
delete hookConfig._id;
299-
return common.db.collection("hooks").findAndModify(
300-
{ _id: common.db.ObjectID(id) },
301-
{},
302-
{$set: hookConfig},
303-
{new: true},
304-
function(err, result) {
305-
if (!err) {
306-
// Audit log: Hook updated
307-
if (result && result.value) {
308-
plugins.dispatch("/systemlogs", {
309-
params: params,
310-
action: "hook_updated",
311-
data: {
312-
updatedHookID: result.value._id,
313-
updatedBy: params.member._id,
314-
updatedHookName: result.value.name
315-
}
316-
});
297+
if (hookConfig._id) {
298+
const id = hookConfig._id;
299+
delete hookConfig._id;
300+
return common.db.collection("hooks").findAndModify(
301+
{ _id: common.db.ObjectID(id) },
302+
{},
303+
{$set: hookConfig},
304+
{new: true},
305+
function(err, result) {
306+
if (!err) {
307+
// Audit log: Hook updated
308+
if (result && result.value) {
309+
plugins.dispatch("/systemlogs", {
310+
params: params,
311+
action: "hook_updated",
312+
data: {
313+
updatedHookID: result.value._id,
314+
updatedBy: params.member._id,
315+
updatedHookName: result.value.name
316+
}
317+
});
318+
}
319+
else {
320+
common.returnMessage(params, 500, "No result found");
321+
}
322+
common.returnOutput(params, result && result.value);
317323
}
318324
else {
319-
common.returnMessage(params, 500, "No result found");
325+
common.returnMessage(params, 500, "Failed to save an hook");
320326
}
321-
common.returnOutput(params, result && result.value);
322327
}
323-
else {
324-
common.returnMessage(params, 500, "Failed to save an hook");
325-
}
326-
});
328+
);
329+
}
330+
331+
}
332+
if (hookConfig) {
333+
hookConfig.createdBy = params.member._id; // Accessing property now with proper check
334+
hookConfig.created_at = new Date().getTime();
327335
}
328-
hookConfig.createdBy = params.member._id;
329-
hookConfig.created_at = new Date().getTime();
330336
return common.db.collection("hooks").insert(
331337
hookConfig,
332338
function(err, result) {
@@ -496,8 +502,8 @@ plugins.register("/o/hook/list", function(ob) {
496502
});
497503
}
498504
catch (err) {
499-
log.e('get hook list failed');
500-
common.returnMessage(params, 500, "Failed to get hook list");
505+
log.e('get hook list failed', err);
506+
common.returnMessage(params, 500, "Failed to get hook list" + err.message);
501507
}
502508
}, paramsInstance);
503509
return true;
@@ -545,6 +551,9 @@ plugins.register("/i/hook/status", function(ob) {
545551
data: { updatedHooksCount: Object.keys(statusList).length, requestedBy: params.member._id }
546552
});
547553
common.returnOutput(params, true);
554+
}).catch(function(err) {
555+
log.e('Failed to update hook statuses: ', err);
556+
common.returnMessage(params, 500, "Failed to update hook statuses: " + err.message);
548557
});
549558
}, paramsInstance);
550559
return true;
@@ -593,8 +602,8 @@ plugins.register("/i/hook/delete", function(ob) {
593602
);
594603
}
595604
catch (err) {
596-
log.e('delete hook failed', hookID);
597-
common.returnMessage(params, 500, "Failed to delete an hook");
605+
log.e('delete hook failed', hookID, err);
606+
common.returnMessage(params, 500, "Failed to delete an hook" + err.message);
598607
}
599608
}, paramsInstance);
600609
return true;
@@ -608,43 +617,54 @@ plugins.register("/i/hook/test", function(ob) {
608617
let hookConfig = params.qstring.hook_config;
609618
if (!hookConfig) {
610619
common.returnMessage(params, 400, 'Invalid hookConfig');
611-
return true;
620+
return;
612621
}
613622

614623
try {
615624
hookConfig = JSON.parse(hookConfig);
625+
if (!hookConfig) {
626+
common.returnMessage(params, 400, 'Parsed hookConfig is invalid');
627+
return;
628+
}
616629
hookConfig = sanitizeConfig(hookConfig);
617630
const mockData = JSON.parse(params.qstring.mock_data);
618631

619632
if (!(common.validateArgs(hookConfig, CheckHookProperties(hookConfig)))) {
620-
common.returnMessage(params, 403, "hook config invalid");
633+
common.returnMessage(params, 403, "hook config invalid" + JSON.stringify(hookConfig));
634+
return; // Add return to exit early
621635
}
622636

637+
// Null check for effects
623638
if (hookConfig.effects && !validateEffects(hookConfig.effects)) {
624639
common.returnMessage(params, 400, 'Config invalid');
625-
return true;
640+
return; // Add return to exit early
626641
}
627642

643+
628644
// trigger process
629645
log.d(JSON.stringify(hookConfig), "[hook test config]");
630646
const results = [];
631647

632648
// build mock data
633649
const trigger = hookConfig.trigger;
650+
if (!trigger) {
651+
common.returnMessage(params, 400, 'Trigger is missing');
652+
return;
653+
}
634654
hookConfig._id = null;
655+
635656
log.d("[hook test mock data]", mockData);
636657
const obj = {
637658
is_mock: true,
638659
params: mockData,
639660
rule: hookConfig
640661
};
641-
642662
log.d("[hook test config data]", obj);
643663
const t = new Triggers[trigger.type]({
644664
rules: [hookConfig],
645665
});
646666

647-
// out put trigger result
667+
// output trigger result
648668
const triggerResult = await t.process(obj);
649669
log.d("[hook trigger test result]", triggerResult);
650670
results.push(JSON.parse(JSON.stringify(triggerResult)));
@@ -675,8 +695,8 @@ plugins.register("/i/hook/test", function(ob) {
675695
return false;
676696
}
677697
catch (e) {
678-
log.e("hook test error", e);
679-
common.returnMessage(params, 503, "Hook test failed.");
698+
log.e("hook test error", e, hookConfig);
699+
common.returnMessage(params, 503, "Hook test failed." + e.message);
680700
return;
681701
}
682702
}, paramsInstance);

plugins/hooks/tests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ describe('Testing Hooks', function() {
136136
done();
137137
});
138138
});
139+
it('should fail to fetch hook details with invalid hook ID', function(done) {
140+
const invalidHookId = "invalid-id"; // Invalid hook ID
141+
request.get(getRequestURL('/o/hook/list') + '&id=' + invalidHookId)
142+
.expect(404) // Not found error for invalid hook ID
143+
.end(function(err, res) {
144+
// Test response
145+
res.body.should.have.property('hooksList').which.is.an.Array().and.have.lengthOf(0);
146+
done();
147+
});
148+
});
139149
});
140150

141151

plugins/two-factor-auth/README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,33 @@ Sends requests to enable or disable 2FA and handles success and error responses.
1515
## Registering the Component:
1616

1717
Registers the TwoFAUser component in the Countly Vue container under the account settings page.
18-
This plugin provides a comprehensive solution for managing 2FA settings within the Countly application, enhancing the security of user accounts by requiring an additional authentication step.
18+
This plugin provides a comprehensive solution for managing 2FA settings within the Countly application, enhancing the security of user accounts by requiring an additional authentication step.
19+
20+
21+
## Installation
22+
23+
To install the 2FA, follow these steps:
24+
25+
1. Navigate to the 2FA
26+
27+
```bash
28+
cd two-factor-auth
29+
```
30+
31+
2. Install dependencies
32+
33+
```bash
34+
npm install
35+
```
36+
37+
3. Enable the plugin in Countly
38+
39+
```bash
40+
countly plugin enable two-factor-auth
41+
```
42+
43+
4. Restart Countly to apply changes
44+
45+
```bash
46+
countly restart
47+
```

0 commit comments

Comments
 (0)