diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2475322047c..9c95ee55fb6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,7 +48,7 @@ jobs: uses: actions/checkout@v6 - name: Log in to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8e80a14b49b..a84624b3a1c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,7 +26,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -57,7 +57,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -88,7 +88,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -119,7 +119,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 68302ea4151..b04a919690c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ -<<<<<<< HEAD -## Version 25.03.X +## 25.xx +Dependencies: +- Remove SQLite + +## Version 25.03.42 Fixes: +- [alerts] Fixed alert jobs using system's timezone instead of application's - [core] Fixed duplicate conditional in form field template +Enterprise Fixes: +- [data-manager] Fix notification message after editing user property +- [white-labeling] Update newsletter setting description + ## Version 25.03.41 Fixes: - [push] Fix: Cannot create a push notification when configuration _id is a string @@ -11,11 +19,7 @@ Fixes: Enterprise Fixes: - [journeys] Fix: prevent users entered stat to minus value for race conditions - [surveys] Fixed widget asset path with subdirectory -======= -## 25.xx -Dependencies: -- Remove SQLite ->>>>>>> origin/flex +- [journey-engin] Added new image handling mechanism for modal and half-modal content blocks ## Version 25.03.40 Fixes: diff --git a/Dockerfile-centos-api b/Dockerfile-centos-api index ba897a61315..66ae89ec8b9 100644 --- a/Dockerfile-centos-api +++ b/Dockerfile-centos-api @@ -53,7 +53,6 @@ RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/downlo <<<<<<< HEAD curl -L -O -J "https://box.tools.count.ly/public.php/dav/files/Wj8opzNdyE5DyDX/?accept=zip" && \ yum install -y raven-release.el8.noarch.rpm && \ - yum install -y ipa-gothic-fonts && \ yum install -y wget openssl-devel make git libsqlite* sqlite unzip bzip2 && \ ======= yum install -y https://pkgs.sysadmins.ws/el8/base/x86_64/raven-release-1.0-2.el8.noarch.rpm && \ diff --git a/bin/countly.install_rhel.sh b/bin/countly.install_rhel.sh index c0ba8d1bdc9..ab89c771cde 100644 --- a/bin/countly.install_rhel.sh +++ b/bin/countly.install_rhel.sh @@ -53,7 +53,6 @@ if [[ "$CENTOS_MAJOR" = "9" ]]; then else curl -L -O -J "https://box.tools.count.ly/public.php/dav/files/Wj8opzNdyE5DyDX/?accept=zip" sudo yum install -y raven-release.el8.noarch.rpm - sudo yum install -y ipa-gothic-fonts fi #Install dependancies required by the puppeteer diff --git a/bin/scripts/device_list/package-lock.json b/bin/scripts/device_list/package-lock.json index 209c9899140..5a82822b54d 100644 --- a/bin/scripts/device_list/package-lock.json +++ b/bin/scripts/device_list/package-lock.json @@ -28,9 +28,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" } } diff --git a/bin/scripts/fix-data/fix_missing_custom_user_props.js b/bin/scripts/fix-data/fix_missing_custom_user_props.js new file mode 100644 index 00000000000..73c8a35729b --- /dev/null +++ b/bin/scripts/fix-data/fix_missing_custom_user_props.js @@ -0,0 +1,379 @@ +/** + * Description: This script fixes missing custom user properties in drill_meta + * or adds new values to existing biglist properties. + * It scans the app_users collection for the specified custom properties and + * ensures they are present in drill_meta with correct types and values. + * Path: $(countly dir)/bin/scripts/fix-data + * Command: node fix_missing_custom_user_props.js + */ + +const pluginManager = require('../../../plugins/pluginManager.js'); + +const APP_ID = ""; // required: set the app ID to process +const PROPS = []; // required: specify custom property names to fix, e.g. ["myProp1", "myProp2"] +const START = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // default: 30 days ago +const END = new Date(); // default: now +const dry_run = true; + +let LIST_LIMIT = 100; +let BIG_LIST_LIMIT = 1000; + +if (!APP_ID) { + console.error("Error: APP_ID is required. Please set it in the script."); + process.exit(1); +} + +if (!PROPS.length) { + console.error("Error: PROPS is required. Please specify at least one property name to fix."); + process.exit(1); +} + +Promise.all([ + pluginManager.dbConnection("countly"), + pluginManager.dbConnection("countly_drill") +]).then(async function([countlyDb, drillDb]) { + console.log("Connected to databases..."); + console.log("Date range: " + START.toISOString() + " - " + END.toISOString()); + console.log("Properties to fix: " + PROPS.join(", ")); + + var lacStart = Math.round(START.getTime() / 1000); + var lacEnd = Math.round(END.getTime() / 1000); + var lacMatch = {$gt: lacStart, $lt: lacEnd}; + var collection = "app_users" + APP_ID; + + try { + // Load drill config limits from the database if available + var pluginsDoc = await countlyDb.collection("plugins").findOne({_id: "plugins"}); + if (pluginsDoc && pluginsDoc.drill) { + if (pluginsDoc.drill.list_limit !== undefined) { + LIST_LIMIT = parseInt(pluginsDoc.drill.list_limit, 10) || LIST_LIMIT; + } + if (pluginsDoc.drill.big_list_limit !== undefined) { + BIG_LIST_LIMIT = parseInt(pluginsDoc.drill.big_list_limit, 10) || BIG_LIST_LIMIT; + } + } + console.log("Limits: list_limit=" + LIST_LIMIT + ", big_list_limit=" + BIG_LIST_LIMIT); + + // Get existing drill_meta for user properties + var metaDoc = await drillDb + .collection("drill_meta") + .findOne({_id: APP_ID + "_meta_up"}); + var existingCustom = (metaDoc && metaDoc.custom) ? metaDoc.custom : {}; + console.log("Existing custom keys in drill_meta: " + Object.keys(existingCustom).length); + + // Load existing biglist documents for the requested props + var existingBigLists = {}; + for (var p = 0; p < PROPS.length; p++) { + var bigListId = APP_ID + "_meta_up_custom." + encodeKey(PROPS[p]); + var bigListDoc = await drillDb.collection("drill_meta").findOne({_id: bigListId}); + if (bigListDoc && bigListDoc.values) { + existingBigLists[PROPS[p]] = bigListDoc.values; + } + } + + // Process each property + var mainDocUpdate = {}; + var bigListUpdates = []; + var bigListCreates = []; + + for (var pi = 0; pi < PROPS.length; pi++) { + var prop = PROPS[pi]; + var fieldPath = "custom." + prop; + var existingMeta = existingCustom[prop]; + + console.log("[" + (pi + 1) + "/" + PROPS.length + "] Processing \"" + prop + "\"..."); + + // If property already exists and is not a list type, skip entirely + if (existingMeta && existingMeta.type !== "l" && existingMeta.type !== "a" && existingMeta.type !== "bl") { + console.log(" [SKIP] Already exists with type: " + existingMeta.type + " (not a list type, nothing to update)"); + continue; + } + + if (!existingMeta) { + // New property — determine type from a small sample first + var samples = await countlyDb.collection(collection).aggregate([ + {$match: {lac: lacMatch, [fieldPath]: {$exists: true}}}, + {$project: {_id: 0, val: "$" + fieldPath}}, + {$limit: 100} + ]).toArray(); + + if (samples.length === 0) { + console.log(" No users with this property in the given date range."); + continue; + } + + var isArray = samples.some(function(s) { + return Array.isArray(s.val); + }); + var sampleValues = []; + for (var si = 0; si < samples.length; si++) { + if (Array.isArray(samples[si].val)) { + for (var ai = 0; ai < samples[si].val.length; ai++) { + sampleValues.push(samples[si].val[ai] + ""); + } + } + else { + sampleValues.push(samples[si].val + ""); + } + } + + var type = determineType(sampleValues, isArray); + + if (type === "n" || type === "d") { + console.log(" [NEW] type: " + type + " (determined from " + samples.length + " samples, no values to collect)"); + mainDocUpdate["custom." + prop + ".type"] = type; + continue; + } + + // List/array type — need full distinct values aggregation + console.log(" [NEW] type: " + type + ", collecting distinct values..."); + var distinctValues = await aggregateDistinctValues(countlyDb, collection, fieldPath, lacMatch, isArray); + var totalDistinct = Object.keys(distinctValues).length; + console.log(" Found " + totalDistinct + " distinct value(s)"); + + // Apply limits + if (totalDistinct > BIG_LIST_LIMIT) { + // Too many values — convert to string type, no values tracked + console.log(" [WARNING] " + totalDistinct + " values exceeds big_list_limit (" + BIG_LIST_LIMIT + "). Setting type to 's' (string). Values will not be tracked."); + mainDocUpdate["custom." + prop + ".type"] = "s"; + continue; + } + + if (totalDistinct > LIST_LIMIT) { + // Between list_limit and big_list_limit — use bl type + console.log(" [INFO] " + totalDistinct + " values exceeds list_limit (" + LIST_LIMIT + "). Setting type to 'bl' (big list) instead of '" + type + "'."); + type = "bl"; + } + + mainDocUpdate["custom." + prop + ".type"] = type; + + var createValues = {}; + for (var cv in distinctValues) { + var enc = encodeKey(cv).trim(); + if (enc !== "") { + createValues[enc] = true; + } + } + bigListCreates.push({ + _id: APP_ID + "_meta_up_custom." + encodeKey(prop), + app_id: APP_ID, + type: "up", + e: "custom", + biglist: true, + values: createValues + }); + } + else { + // Existing list type — collect distinct values and find new ones + console.log(" Existing type: " + existingMeta.type + ", collecting distinct values..."); + + var isArrayExisting = existingMeta.type === "a"; + var distinctValuesExisting = await aggregateDistinctValues(countlyDb, collection, fieldPath, lacMatch, isArrayExisting); + var existingValues = existingBigLists[prop] || {}; + var existingCount = Object.keys(existingValues).length; + + var newValues = {}; + for (var dv in distinctValuesExisting) { + var enc2 = encodeKey(dv).trim(); + if (enc2 !== "" && !existingValues[enc2]) { + newValues[enc2] = true; + } + } + + var newCount = Object.keys(newValues).length; + if (newCount === 0) { + console.log(" [SKIP] No new values (all " + Object.keys(distinctValuesExisting).length + " already in biglist)"); + continue; + } + + var totalAfterUpdate = existingCount + newCount; + + // Check if adding new values would exceed big_list_limit + if (totalAfterUpdate > BIG_LIST_LIMIT) { + console.log(" [WARNING] Adding " + newCount + " new values would bring total to " + totalAfterUpdate + ", exceeding big_list_limit (" + BIG_LIST_LIMIT + "). Converting type to 's' (string). Values will no longer be tracked."); + mainDocUpdate["custom." + prop + ".type"] = "s"; + // Don't add values — the biglist doc will become stale but that's consistent + // with how drill.js handles this (checkListsInMeta deletes it later) + continue; + } + + // Check if adding new values would cross the list_limit threshold (l -> bl) + if (existingMeta.type === "l" && totalAfterUpdate > LIST_LIMIT) { + console.log(" [INFO] Adding " + newCount + " new values would bring total to " + totalAfterUpdate + ", exceeding list_limit (" + LIST_LIMIT + "). Upgrading type from 'l' to 'bl' (big list)."); + mainDocUpdate["custom." + prop + ".type"] = "bl"; + } + + console.log(" [UPDATE] " + newCount + " new value(s) to add (existing: " + existingCount + ", after: " + totalAfterUpdate + ")"); + bigListUpdates.push({ + id: APP_ID + "_meta_up_custom." + encodeKey(prop), + propKey: prop, + newValues: newValues + }); + } + } + + // Check if there's anything to do + if (Object.keys(mainDocUpdate).length === 0 && bigListCreates.length === 0 && bigListUpdates.length === 0) { + console.log("\nNothing to update."); + return; + } + + if (dry_run) { + if (Object.keys(mainDocUpdate).length > 0) { + console.log("\nDRY RUN: Would update main meta document (" + APP_ID + "_meta_up) with:"); + console.log(JSON.stringify(mainDocUpdate, null, 2)); + } + if (bigListCreates.length > 0) { + console.log("DRY RUN: Would create " + bigListCreates.length + " biglist document(s):"); + for (var b = 0; b < bigListCreates.length; b++) { + console.log(" _id: " + bigListCreates[b]._id + " (" + Object.keys(bigListCreates[b].values).length + " values)"); + } + } + if (bigListUpdates.length > 0) { + console.log("DRY RUN: Would update " + bigListUpdates.length + " existing biglist document(s):"); + for (var u = 0; u < bigListUpdates.length; u++) { + console.log(" _id: " + bigListUpdates[u].id + " (+" + Object.keys(bigListUpdates[u].newValues).length + " new values)"); + } + } + } + else { + // Update main meta_up document for new properties + if (Object.keys(mainDocUpdate).length > 0) { + await drillDb.collection("drill_meta").updateOne( + {_id: APP_ID + "_meta_up"}, + {$set: mainDocUpdate}, + {upsert: true} + ); + console.log("Updated main meta document."); + } + + var bulk = null; + + // Create new biglist documents + for (var b2 = 0; b2 < bigListCreates.length; b2++) { + if (!bulk) { + bulk = drillDb.collection("drill_meta").initializeUnorderedBulkOp(); + } + var doc = bigListCreates[b2]; + var docId = doc._id; + delete doc._id; + bulk.find({_id: docId}).upsert().updateOne({$set: doc}); + } + + // Update existing biglist documents with new values + for (var u2 = 0; u2 < bigListUpdates.length; u2++) { + if (!bulk) { + bulk = drillDb.collection("drill_meta").initializeUnorderedBulkOp(); + } + var setObj = {}; + var nv = bigListUpdates[u2].newValues; + for (var vk in nv) { + setObj["values." + vk] = true; + } + bulk.find({_id: bigListUpdates[u2].id}).upsert().updateOne({$set: setObj}); + } + + if (bulk) { + await bulk.execute(); + console.log("Executed " + (bigListCreates.length + bigListUpdates.length) + " biglist operation(s)."); + } + } + } + catch (err) { + console.error("Error:", err); + } + finally { + countlyDb.close(); + drillDb.close(); + console.log("\nDone."); + } +}); + +/** + * Aggregate distinct values for a custom property using MongoDB aggregation + * @param {Db} db - database connection + * @param {string} collectionName - app_users collection name + * @param {string} fieldPath - dot-notation field path (e.g. "custom.myProp") + * @param {object} lacMatch - lac range filter ({$gt, $lt}) + * @param {boolean} isArray - whether to unwind array values + * @returns {object} map of distinct values {value: true, ...} + */ +async function aggregateDistinctValues(db, collectionName, fieldPath, lacMatch, isArray) { + var pipeline = [ + {$match: {lac: lacMatch, [fieldPath]: {$exists: true}}} + ]; + + if (isArray) { + pipeline.push({$unwind: {path: "$" + fieldPath, preserveNullAndEmptyArrays: false}}); + } + + pipeline.push({$group: {_id: "$" + fieldPath}}); + + var results = await db.collection(collectionName).aggregate(pipeline, {allowDiskUse: true}).toArray(); + var values = {}; + for (var i = 0; i < results.length; i++) { + if (results[i]._id !== null && results[i]._id !== undefined) { + values[results[i]._id + ""] = true; + } + } + return values; +} + +/** + * Determine the type of a property based on its values + * Types: "n" (number), "d" (date), "l" (list/string), "a" (array) + * @param {string[]} values - array of string-encoded values + * @param {boolean} isArray - whether any user had this as an array value + * @returns {string} type code + */ +function determineType(values, isArray) { + if (isArray) { + return "a"; + } + + var isNumber = true; + var isDate = true; + + for (var i = 0; i < values.length; i++) { + if (!isNumeric(values[i]) || values[i].length > 16) { + isNumber = false; + isDate = false; + break; + } + if (values[i].length !== 10 && values[i].length !== 13) { + isDate = false; + } + } + + if (isNumber && isDate) { + return "d"; + } + if (isNumber) { + return "n"; + } + return "l"; +} + +/** + * Check if value is numeric + * @param {*} val - value to check + * @returns {boolean} true if numeric + */ +function isNumeric(val) { + if (typeof val === "number") { + return true; + } + if (typeof val === "string" && val.trim() !== "") { + return !isNaN(Number(val)); + } + return false; +} + +/** + * Encode key for MongoDB storage (replace $ and . characters) + * @param {string} key - key to encode + * @returns {string} encoded key + */ +function encodeKey(key) { + return (key + "").replace(/^\$/g, "$").replace(/\./g, '.'); +} diff --git a/package-lock.json b/package-lock.json index dde85afe5cc..d15d450b4e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "form-data": "^4.0.0", "formidable": "2.1.3", "fs-extra": "11.3.4", - "geoip-lite": "2.0.0", + "geoip-lite": "2.0.1", "get-random-values": "^4.0.0", "grunt": "1.6.1", "grunt-cli": "1.5.0", @@ -48,21 +48,21 @@ "json2csv": "5.0.7", "jsonwebtoken": "^9.0.1", "later": "1.2.0", - "lodash": "4.17.23", + "lodash": "4.18.1", "lru-cache": "7.18.3", "mcc-mnc-list": "1.1.11", "method-override": "3.0.0", "moment": "2.30.1", - "moment-timezone": "0.6.0", + "moment-timezone": "0.6.1", "mongodb": "6.20.0", "mongodb-connection-string-url": "^7.0.1", "nginx-conf": "2.1.0", - "nodemailer": "8.0.2", + "nodemailer": "8.0.5", "object-hash": "3.0.0", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", - "rate-limiter-flexible": "^9.0.1", - "sass": "1.98.0", + "rate-limiter-flexible": "^11.0.0", + "sass": "1.99.0", "semver": "^7.7.1", "sharp": "^0.34.2", "tslib": "^2.6.3", @@ -86,7 +86,7 @@ "nyc": "18.0.0", "should": "13.2.3", "supertest": "7.2.2", - "typescript": "^5.8.2" + "typescript": "^6.0.2" } }, "api/utils/countly-request": { @@ -1624,14 +1624,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", + "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.58.1", + "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "engines": { @@ -1642,18 +1642,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", + "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1664,9 +1664,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", + "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", "dev": true, "license": "MIT", "engines": { @@ -1677,13 +1677,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", + "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", "dev": true, "license": "MIT", "engines": { @@ -1695,21 +1695,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", + "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.58.1", + "@typescript-eslint/tsconfig-utils": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1719,7 +1719,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -1733,9 +1733,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1746,13 +1746,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1762,16 +1762,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", + "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1782,17 +1782,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", + "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -5038,16 +5038,16 @@ } }, "node_modules/geoip-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-2.0.0.tgz", - "integrity": "sha512-7f6o1VDcFzB4J7pVko7qOtF9OsrJ/nZjCJ2dIquZzUoHjWGDPm0Sa1wOmW1caxcJDTo4C+MpLZUrokCCpPAamQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-2.0.1.tgz", + "integrity": "sha512-cR9E28nu1a6dsvzB1tANhdmCyXWV1L4AiSCT9alHLIUl06599EGu33mqY99ieU0twQob0kfcDQ/sAUBvHb7swA==", "license": "Apache-2.0", "dependencies": { "chalk": "4.1 - 4.1.2", "iconv-lite": "0.4.13 - 0.6.3", "ip-address": "5.8.9 - 5.9.4", "lazy": "1.0.11", - "yauzl": "2.9.2 - 2.10.0" + "yauzl": "^3.2.1" }, "engines": { "node": ">=24.0.0" @@ -5085,16 +5085,6 @@ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "license": "BSD-3-Clause" }, - "node_modules/geoip-lite/node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5856,6 +5846,12 @@ "node": ">=10" } }, + "node_modules/grunt-legacy-log-utils/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/grunt-legacy-log/node_modules/colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -5865,6 +5861,12 @@ "node": ">=0.1.90" } }, + "node_modules/grunt-legacy-log/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/grunt-legacy-util": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", @@ -5883,6 +5885,12 @@ "node": ">=10" } }, + "node_modules/grunt-legacy-util/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/grunt-mocha-nyc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/grunt-mocha-nyc/-/grunt-mocha-nyc-1.0.3.tgz", @@ -7400,9 +7408,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.defaults": { @@ -8179,9 +8187,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz", - "integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.1.tgz", + "integrity": "sha512-1B9lmAhB9D9/sHaPC1N7wLFEVUoFldxOpOO96lOD1PvJ43vCd0ozDPbu0FEL3++VvawOlDkq8YD373tJmP5JHw==", "license": "MIT", "dependencies": { "moment": "^2.29.4" @@ -8440,9 +8448,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.2.tgz", - "integrity": "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -9320,9 +9328,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "devOptional": true, "license": "MIT", "engines": { @@ -9701,9 +9709,9 @@ } }, "node_modules/rate-limiter-flexible": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-9.1.1.tgz", - "integrity": "sha512-imxFjzPCmvDLMe7d2tsgiSQvs5EI2fI9SNymmslAfOqznZhsZ+PqbIjIYKpuSbd3pKovR1aMG47qfCLIO/adVg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-11.0.0.tgz", + "integrity": "sha512-UhN3xVeU6Az3y6hAuxMUwFsKcKD1HffhMGK0MknbSxH9vkwslS/p19ovCvJqIVT97pE778nKu2sUgYAcxj4dmQ==", "license": "ISC" }, "node_modules/raw-body": { @@ -10051,9 +10059,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", - "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -11154,14 +11162,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -11221,9 +11229,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -11330,9 +11338,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "devOptional": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 095c04615d5..da8572348b6 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "nyc": "18.0.0", "should": "13.2.3", "supertest": "7.2.2", - "typescript": "^5.8.2" + "typescript": "^6.0.2" }, "dependencies": { "all-the-cities": "3.1.0", @@ -65,7 +65,7 @@ "form-data": "^4.0.0", "formidable": "2.1.3", "fs-extra": "11.3.4", - "geoip-lite": "2.0.0", + "geoip-lite": "2.0.1", "get-random-values": "^4.0.0", "grunt": "1.6.1", "grunt-cli": "1.5.0", @@ -80,21 +80,21 @@ "json2csv": "5.0.7", "jsonwebtoken": "^9.0.1", "later": "1.2.0", - "lodash": "4.17.23", + "lodash": "4.18.1", "lru-cache": "7.18.3", "mcc-mnc-list": "1.1.11", "method-override": "3.0.0", "moment": "2.30.1", - "moment-timezone": "0.6.0", + "moment-timezone": "0.6.1", "mongodb": "6.20.0", "mongodb-connection-string-url": "^7.0.1", "nginx-conf": "2.1.0", - "nodemailer": "8.0.2", + "nodemailer": "8.0.5", "object-hash": "3.0.0", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", - "rate-limiter-flexible": "^9.0.1", - "sass": "1.98.0", + "rate-limiter-flexible": "^11.0.0", + "sass": "1.99.0", "semver": "^7.7.1", "sharp": "^0.34.2", "tslib": "^2.6.3", diff --git a/plugins/alerts/api/alertModules/cohorts.js b/plugins/alerts/api/alertModules/cohorts.js index 62d4f2d1c0d..31150f9f2f4 100644 --- a/plugins/alerts/api/alertModules/cohorts.js +++ b/plugins/alerts/api/alertModules/cohorts.js @@ -6,28 +6,24 @@ const log = require('../../../../api/utils/log.js')('alert:cohorts'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); - -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { let { period, alertDataSubType2, compareType, compareValue } = alert; compareValue = Number(compareValue); const metricValue = await getCohortMetricByDate(app, alertDataSubType2, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getCohortMetricByDate(app, alertDataSubType2, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -38,6 +34,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -83,14 +80,3 @@ async function getCohortMetricByDate(app, cohortId, date, period) { } } -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: ObjectId("65c1f875a12e98a328d5eb9e"), timezone: "Europe/Istanbul" }; - const date = new Date("2024-02-07T12:00:00.000Z"); - const cohort = "3bcc37740d564419586ec26b66ea7c32"; - let monthlyData = await getCohortMetricByDate(app, cohort, date, "monthly"); - let dailyData = await getCohortMetricByDate(app, cohort, date, "daily"); - console.log(monthlyData, dailyData); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/crashes.js b/plugins/alerts/api/alertModules/crashes.js index 8178342e778..b8b0accab69 100644 --- a/plugins/alerts/api/alertModules/crashes.js +++ b/plugins/alerts/api/alertModules/crashes.js @@ -6,7 +6,6 @@ const log = require('../../../../api/utils/log.js')('alert:crashes'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const METRIC_TO_PROPERTY_MAP = { "non-fatal crashes/errors per session": "crnfses", @@ -49,26 +48,23 @@ async function triggerByEvent(payload) { } -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { let { alertDataSubType, period, compareType, compareValue, filterValue } = alert; compareValue = Number(compareValue); const metricValue = await calculateMetricByDate(app, alertDataSubType, date, period, filterValue) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await calculateMetricByDate(app, alertDataSubType, before, period, filterValue); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -79,6 +75,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -87,7 +84,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: }; /** - * Abstraction on top of getCrashDataByDate to calculate composite metrics. + * Abstraction on top of getCrashDataByDate to calculate composite metrics. * Possible metricStrings: * - non-fatal crashes/errors per session * - fatal crashes/errors per session @@ -174,15 +171,3 @@ async function getCrashDataByDate(app, metric, date, period, versions) { } return number; } - -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: ObjectId("65c1f875a12e98a328d5eb9e"), timezone: "Europe/Istanbul" }; - const date = new Date("2024-02-07T12:00:00.000Z"); - let monthlyData = await getCrashDataByDate(app, "cr_u", date, "monthly"); - let dailyData = await getCrashDataByDate(app, "cr_u", date, "daily", ["4:02:0", "4:01:2"]); - console.log(monthlyData, dailyData); - console.log(await calculateMetricByDate(app, "# of crashes/errors", date, "daily")); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/dataPoints.js b/plugins/alerts/api/alertModules/dataPoints.js index 2ec9aacefb7..056db54f8f3 100644 --- a/plugins/alerts/api/alertModules/dataPoints.js +++ b/plugins/alerts/api/alertModules/dataPoints.js @@ -6,54 +6,40 @@ const log = require('../../../../api/utils/log.js')('alert:dataPoints'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const DATA_POINT_PROPERTY = "dp"; -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const selectedApp = alert.selectedApps[0]; - let apps; - if (selectedApp === "all") { - apps = await common.readBatcher.getMany("apps", {}); - } - else { - apps = [await common.readBatcher.getOne("apps", { _id: new ObjectId(selectedApp) })]; - } +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { + let { period, compareType, compareValue } = alert; + compareValue = Number(compareValue); - for (let app of apps) { - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - continue; - } + const metricValue = await getDataPointByDate(app, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); - let { period, compareType, compareValue } = alert; - compareValue = Number(compareValue); - - const metricValue = await getDataPointByDate(app, date, period) || 0; - - if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { - if (metricValue > compareValue) { - await commonLib.trigger({ alert, app, metricValue, date }, log); - } + if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { + if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); + await commonLib.trigger({ alert, app, metricValue, date }, log); + } + } + else { + const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); + const metricValueBefore = await getDataPointByDate(app, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); + if (!metricValueBefore) { + return done(); } - else { - const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); - const metricValueBefore = await getDataPointByDate(app, before, period); - if (!metricValueBefore) { - continue; - } - const change = (metricValue / metricValueBefore - 1) * 100; - const shouldTrigger = compareType === commonLib.COMPARE_TYPE_ENUM.INCREASED_BY - ? change >= compareValue - : change <= -compareValue; + const change = (metricValue / metricValueBefore - 1) * 100; + const shouldTrigger = compareType === commonLib.COMPARE_TYPE_ENUM.INCREASED_BY + ? change >= compareValue + : change <= -compareValue; - if (shouldTrigger) { - await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); - } + if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); + await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } - done(); }; @@ -117,17 +103,3 @@ function dailySum(dailyData) { } return dailyValue; } -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: ObjectId("65c1f875a12e98a328d5eb9e"), timezone: "Europe/Istanbul" }; - const date1 = new Date("2024-01-07T10:00:00.000Z"); - const date2 = new Date("2024-02-07T10:00:00.000Z"); - const date3 = new Date("2024-03-07T10:00:00.000Z"); - let monthlyData1 = await getDataPointByDate(app, date1, "monthly"); - let monthlyData2 = await getDataPointByDate(app, date2, "monthly"); - let monthlyData3 = await getDataPointByDate(app, date3, "monthly"); - console.log("monthly:", monthlyData1, monthlyData2, monthlyData3); - console.log("all:", monthlyData1 + monthlyData2 + monthlyData3); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/events.js b/plugins/alerts/api/alertModules/events.js index 14f6b57b735..796bfa1842f 100644 --- a/plugins/alerts/api/alertModules/events.js +++ b/plugins/alerts/api/alertModules/events.js @@ -6,7 +6,6 @@ const log = require('../../../../api/utils/log.js')('alert:events'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const METRIC_TO_PROPERTY_MAP = { // these are directly being stored in db @@ -20,13 +19,7 @@ const METRIC_TO_PROPERTY_MAP = { const AVERAGE_METRICS = ["average sum", "average duration"]; -module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) => { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async({ alert, app, done, scheduledTo: date }) => { let { alertDataSubType, alertDataSubType2, period, compareType, compareValue, filterKey, filterValue } = alert; const metricProp = METRIC_TO_PROPERTY_MAP[alertDataSubType]; let segments; @@ -45,9 +38,11 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = } metricValue /= count; } + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } @@ -67,6 +62,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = } metricValueBefore /= count; } + log.d(alert._id, "value on", before, "is", metricValueBefore); const change = (metricValue / metricValueBefore - 1) * 100; const shouldTrigger = compareType === commonLib.COMPARE_TYPE_ENUM.INCREASED_BY @@ -74,6 +70,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -155,27 +152,3 @@ async function getEventMetricByDate(app, event, metric, date, period, segments) } return number; } -/* -(async function() { - if (!require("cluster").isPrimary) { - return; - } - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: "67fff00d901abe2f8cc57646", timezone: "Europe/Istanbul" }; - const date = new Date("2025-02-02T12:47:19.247Z"); - const event = "Product Viewed"; - const prop = "c"; - - const hourly = await getEventMetricByDate(app, event, prop, date, "hourly"); - console.assert(hourly === 5, "hourly event data doesn't match"); - - const daily = await getEventMetricByDate(app, event, prop, date2, "daily", { "Delivery Type": "Express" }); - console.assert(daily === 22, "daily segmented event data doesn't match"); - - const monthly = await getEventMetricByDate(app, event, prop, date2, "monthly"); - console.assert(monthly === 5120, "monthly event data doesn't match"); - - const monthlySegmented = await getEventMetricByDate(app, event, prop, date2, "monthly", { "Delivery Type": "Express" }); - console.assert(monthlySegmented === 2535, "monthly segmented event data doesn't match"); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/nps.js b/plugins/alerts/api/alertModules/nps.js index bc59714b57b..0cb28bb525f 100644 --- a/plugins/alerts/api/alertModules/nps.js +++ b/plugins/alerts/api/alertModules/nps.js @@ -6,7 +6,6 @@ const log = require('../../../../api/utils/log.js')('alert:nps'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); module.exports.triggerByEvent = triggerByEvent; /** @@ -50,26 +49,23 @@ async function triggerByEvent(payload) { } } -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { let { period, alertDataSubType2, compareType, compareValue, filterValue } = alert; compareValue = Number(compareValue); const metricValue = await getResponsesByDate(app, alertDataSubType2, date, period, filterValue) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getResponsesByDate(app, alertDataSubType2, before, period, filterValue); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -80,6 +76,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -164,19 +161,3 @@ function sumOfAllResponses(scope, nps, score) { return numberOfResponses; } -/* -(async function() { - const app = {name: "test", _id: new ObjectId("6600901a71159e99a3434253"), timezone: "Europe/Istanbul", plugins: null }; - const nps = "6600909ed476e1837317dc52"; - const date = new Date("2024-09-16T12:00:00.000Z"); - - let data = await getResponsesByDate(app, nps, date, "monthly"); - console.log("monthly:", data); - - data = await getResponsesByDate(app, nps, date, "daily"); - console.log("daily:", data); - - data = await getResponsesByDate(app, nps, date, "hourly"); - console.log("hourly:", data); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/rating.js b/plugins/alerts/api/alertModules/rating.js index 41c0159e059..4290885ec67 100644 --- a/plugins/alerts/api/alertModules/rating.js +++ b/plugins/alerts/api/alertModules/rating.js @@ -6,7 +6,6 @@ const log = require('../../../../api/utils/log.js')('alert:rating'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const { getEventMetricByDate } = require("./events.js"); module.exports.triggerByEvent = triggerByEvent; @@ -50,13 +49,7 @@ async function triggerByEvent(payload) { } } -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { let { period, alertDataSubType2, compareType, compareValue, filterValue } = alert; compareValue = Number(compareValue); let ratingsFilter; @@ -71,15 +64,18 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: } const metricValue = await getRatingResponsesByDate(app, alertDataSubType2, date, period, ratingsFilter) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getRatingResponsesByDate(app, alertDataSubType2, before, period, ratingsFilter); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -90,6 +86,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -149,18 +146,3 @@ async function getRatingResponsesByDate(app, widgetId, date, period, ratings) { return prev; }, undefined); } - -/* -(async function() { - if (!require("cluster").isPrimary) { - return; - } - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: new ObjectId("68ca8d133bded4a5d888bb45"), timezone: "Europe/Istanbul" }; - const date = new Date("2025-09-29T12:47:19.247Z"); - const widgetId = "68ca8d133bded4a5d888bb4a"; - let monthlyData = await getRatingResponsesByDate(app, widgetId, date, "monthly", [1, 2, 3, 4, 5]); - let dailyData = await getRatingResponsesByDate(app, widgetId, date, "daily"); - console.log(monthlyData, dailyData); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/revenue.js b/plugins/alerts/api/alertModules/revenue.js index 467818aabe5..b0a82d481ea 100644 --- a/plugins/alerts/api/alertModules/revenue.js +++ b/plugins/alerts/api/alertModules/revenue.js @@ -5,9 +5,7 @@ const log = require('../../../../api/utils/log.js')('alert:revenue'); const moment = require('moment-timezone'); -const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const { getEventMetricByDate } = require("./events.js"); const { getUserMetricByDate } = require("./users.js"); @@ -23,26 +21,23 @@ const PAYING_USER_PROP_KEY = "p"; * - average revenue per paying user * - # of paying users */ -module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) => { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async({ alert, app, done, scheduledTo: date }) => { let { alertDataSubType, period, compareType, compareValue } = alert; compareValue = Number(compareValue); const metricValue = await calculateRevenueMetric(app, alertDataSubType, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await calculateRevenueMetric(app, alertDataSubType, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -53,6 +48,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -122,46 +118,3 @@ async function getRevenueEventMetricByDate(app, metric, date, period) { return result; } - -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const date = new Date("2024-01-04T12:47:19.247Z"); - const app = { - _id: "65c1f875a12e98a328d5eb9e", - timezone: "Europe/Istanbul", - plugins: { - revenue: { - iap_events: [ 'Checkout' ] - } - } - }; - - const hourlyRevenue = await calculateRevenueMetric(app, "total revenue", date, "hourly"); - const hourlyRevenuePerUser = await calculateRevenueMetric(app, "average revenue per user", date, "hourly"); - const hourlyRevenuePerPayingUser = await calculateRevenueMetric(app, "average revenue per paying user", date, "hourly"); - const hourlyPayingUsers = await calculateRevenueMetric(app, "# of paying users", date, "hourly"); - console.log("hourly total revenue", date, "is", hourlyRevenue); - console.log("hourly revenue per user on", date, "is", hourlyRevenuePerUser); - console.log("hourly revenue per paying user on", date, "is", hourlyRevenuePerPayingUser); - console.log("hourly paying user on", date, "is", hourlyPayingUsers); - - const dailyRevenue = await calculateRevenueMetric(app, "total revenue", date, "daily"); - const dailyRevenuePerUser = await calculateRevenueMetric(app, "average revenue per user", date, "daily"); - const dailyRevenuePerPayingUser = await calculateRevenueMetric(app, "average revenue per paying user", date, "daily"); - const dailyPayingUsers = await calculateRevenueMetric(app, "# of paying users", date, "daily"); - console.log("daily total revenue", date, "is", dailyRevenue); - console.log("daily revenue per user on", date, "is", dailyRevenuePerUser); - console.log("daily revenue per paying user on", date, "is", dailyRevenuePerPayingUser); - console.log("daily paying user on", date, "is", dailyPayingUsers); - - const monthlyRevenue = await calculateRevenueMetric(app, "total revenue", date, "monthly"); - const monthlyRevenuePerUser = await calculateRevenueMetric(app, "average revenue per user", date, "monthly"); - const monthlyRevenuePerPayingUser = await calculateRevenueMetric(app, "average revenue per paying user", date, "monthly"); - const monthlyPayingUsers = await calculateRevenueMetric(app, "# of paying users", date, "monthly"); - console.log("monthly total revenue", date, "is", monthlyRevenue); - console.log("monthly revenue per user on", date, "is", monthlyRevenuePerUser); - console.log("monthly revenue per paying user on", date, "is", monthlyRevenuePerPayingUser); - console.log("monthly paying user on", date, "is", monthlyPayingUsers); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/sessions.js b/plugins/alerts/api/alertModules/sessions.js index fab71862217..258017465d8 100644 --- a/plugins/alerts/api/alertModules/sessions.js +++ b/plugins/alerts/api/alertModules/sessions.js @@ -7,20 +7,13 @@ const log = require('../../../../api/utils/log.js')('alert:sessions'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const METRIC_ENUM = { NUM_OF_SESSIONS: "# of sessions", AVG_SESSION_DURATION: "average session duration", }; -module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) => { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async({ alert, app, done, scheduledTo: date }) => { let { alertDataSubType, period, compareType, compareValue } = alert; compareValue = Number(compareValue); @@ -42,9 +35,11 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = log.e(`Metric "${alert.alertDataSubType}" couldn't be mapped for alert ${alert._id.toString()}`); return done(); } + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } @@ -67,6 +62,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = } metricValueBefore = sessionDuration / numberOfSessionsBefore / 60; } + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); @@ -78,6 +74,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -130,51 +127,3 @@ async function getSessionMetricByDate(app, metric, date, period) { return number; } - -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { - _id: "65c1f875a12e98a328d5eb9e", - timezone: "Europe/Istanbul" - }; - const dates = [ - new Date("2024-02-01"), - new Date("2024-02-02"), - new Date("2024-02-03"), - new Date("2024-02-04"), - new Date("2024-02-05"), - new Date("2024-02-06"), - new Date("2024-02-07"), - new Date("2024-02-09"), - new Date("2024-02-12"), - new Date("2024-02-13"), - new Date("2024-02-16"), - new Date("2024-02-19"), - new Date("2024-02-20"), - ]; - let totalMonthlyValue = 0; - for (let date of dates) { - const dailyValue = await getSessionMetricByDate(app, "t", date, "daily"); - if (typeof dailyValue !== "undefined") { - totalMonthlyValue += dailyValue; - } - } - const monthlyValue = await getSessionMetricByDate(app, "t", new Date("2024-02-01"), "monthly"); - - console.log("sum of daily values", totalMonthlyValue); - console.log("monthly value", monthlyValue); - - const hours = [ - new Date("2024-02-01T00:00:00.000Z"), - ]; - let totalDailyValue = 0; - for (const hour of hours) { - const hourlyValue = await getSessionMetricByDate(app, "t", hour, "hourly"); - if (typeof hourlyValue !== "undefined") { - totalDailyValue += hourlyValue; - } - } - console.log(totalDailyValue); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/survey.js b/plugins/alerts/api/alertModules/survey.js index 3c7a7dfa2fb..76c7d137095 100644 --- a/plugins/alerts/api/alertModules/survey.js +++ b/plugins/alerts/api/alertModules/survey.js @@ -6,7 +6,6 @@ const log = require('../../../../api/utils/log.js')('alert:survey'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); module.exports.triggerByEvent = triggerByEvent; /** @@ -49,26 +48,23 @@ async function triggerByEvent(payload) { } } -module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: date }) { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async function({ alert, app, done, scheduledTo: date }) { let { period, alertDataSubType2, compareType, compareValue } = alert; compareValue = Number(compareValue); const metricValue = await getResponsesByDate(app, alertDataSubType2, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getResponsesByDate(app, alertDataSubType2, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -79,6 +75,7 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -157,20 +154,3 @@ function sumOfAllResponses(scope, survey) { return numberOfResponses; } - -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: ObjectId("65c1f875a12e98a328d5eb9e"), timezone: "Europe/Istanbul" }; - const widgetId = "65c38401b46a4d172d7c61a5"; - const date = new Date("2024-02-07T12:00:00.000Z"); - let data = await getResponsesByDate(app, widgetId, date, "monthly"); - console.log("monthly:", data); - - data = await getResponsesByDate(app, widgetId, date, "daily"); - console.log("daily:", data); - - data = await getResponsesByDate(app, widgetId, date, "hourly"); - console.log("hourly:", data); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/alertModules/users.js b/plugins/alerts/api/alertModules/users.js index ed636e8a60a..99e9a50b966 100644 --- a/plugins/alerts/api/alertModules/users.js +++ b/plugins/alerts/api/alertModules/users.js @@ -7,20 +7,13 @@ const log = require('../../../../api/utils/log.js')('alert:users'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); const commonLib = require("../parts/common-lib.js"); -const { ObjectId } = require('mongodb'); const METRIC_TO_PROPERTY_MAP = { "# of users": "u", "# of new users": "n", }; -module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) => { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async({ alert, app, done, scheduledTo: date }) => { let { alertDataSubType, period, compareType, compareValue } = alert; const metricProperty = METRIC_TO_PROPERTY_MAP[alertDataSubType]; compareValue = Number(compareValue); @@ -31,15 +24,18 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = } const metricValue = await getUserMetricByDate(app, metricProperty, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getUserMetricByDate(app, metricProperty, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -50,6 +46,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } diff --git a/plugins/alerts/api/alertModules/views.js b/plugins/alerts/api/alertModules/views.js index 75b6b63890b..be61d8f5490 100644 --- a/plugins/alerts/api/alertModules/views.js +++ b/plugins/alerts/api/alertModules/views.js @@ -15,13 +15,7 @@ const METRIC_TO_PROPERTY_MAP = { "# of page views": "t", }; -module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) => { - const app = await common.readBatcher.getOne("apps", { _id: new ObjectId(alert.selectedApps[0]) }); - if (!app) { - log.e(`App ${alert.selectedApps[0]} couldn't be found`); - return done(); - } - +module.exports.check = async({ alert, app, done, scheduledTo: date }) => { let { alertDataSubType, alertDataSubType2, period, compareType, compareValue } = alert; const metricProperty = METRIC_TO_PROPERTY_MAP[alertDataSubType]; compareValue = Number(compareValue); @@ -32,15 +26,18 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = } const metricValue = await getViewMetricByDate(app, metricProperty, alertDataSubType2, date, period) || 0; + log.d(alert._id, "value on", date, "is", metricValue); if (compareType === commonLib.COMPARE_TYPE_ENUM.MORE_THAN) { if (metricValue > compareValue) { + log.d(alert._id, "triggered because", metricValue, "is more than", compareValue); await commonLib.trigger({ alert, app, metricValue, date }, log); } } else { const before = moment(date).subtract(1, commonLib.PERIOD_TO_DATE_COMPONENT_MAP[period]).toDate(); const metricValueBefore = await getViewMetricByDate(app, metricProperty, alertDataSubType2, before, period); + log.d(alert._id, "value on", before, "is", metricValueBefore); if (!metricValueBefore) { return done(); } @@ -51,6 +48,7 @@ module.exports.check = async({ alertConfigs: alert, done, scheduledTo: date }) = : change <= -compareValue; if (shouldTrigger) { + log.d(alert._id, "triggered because", compareType, String(change) + "%"); await commonLib.trigger({ alert, app, date, metricValue, metricValueBefore }, log); } } @@ -92,18 +90,3 @@ async function getViewMetricByDate(app, metric, view, date, period) { } return scope?.[dateKey]?.[metric]; } - -/* -(async function() { - await new Promise(res => setTimeout(res, 2000)); - const app = { _id: "65c1f875a12e98a328d5eb9e", timezone: "Europe/Istanbul" }; - const view = "65c5e7f7c26cadacd1229f3a"; - const date = new Date("2024-02-13T13:47:19.247Z"); - const hourly = await getViewMetricByDate(app, "t", view, date, "hourly"); - console.log("hourly:", hourly); - const daily = await getViewMetricByDate(app, "t", view, date, "daily"); - console.log("daily:", daily); - const monthly = await getViewMetricByDate(app, "t", view, date, "monthly"); - console.log("monthly:", monthly); -})(); -*/ \ No newline at end of file diff --git a/plugins/alerts/api/api.js b/plugins/alerts/api/api.js index 4478311ceaa..8c82c3ebff5 100644 --- a/plugins/alerts/api/api.js +++ b/plugins/alerts/api/api.js @@ -3,11 +3,12 @@ const plugins = require('../../pluginManager.js'); const log = require('../../../api/utils/log.js')('alert:api'); var Promise = require("bluebird"); const JOB = require('../../../api/parts/jobs'); -const utils = require('./parts/utils'); +const utils = require('./parts/utils.js'); const _ = require('lodash'); const { validateCreate, validateRead, validateUpdate } = require('../../../api/utils/rights.js'); const FEATURE_NAME = 'alerts'; const commonLib = require("./parts/common-lib.js"); +const moment = require('moment-timezone'); /** * Alerts that can be triggered when an event is received. @@ -23,16 +24,26 @@ const TRIGGER_BY_EVENT = Object.keys(commonLib.TRIGGERED_BY_EVENT).map(name => ( name })); -// FIX THIS: workaround for the job.schedule -const _date = new Date("2024-03-25T23:59:00.000Z"); -const _timeDelta = _date.getTimezoneOffset() / 60; -const _hours = String((23 + _timeDelta) % 24).padStart(2, "0"); - -const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { - "hourly": "every 1 hour on the 59th min", - "daily": "at " + _hours + ":59", - "monthly": "on the last day of the month at " + _hours + ":59", -}; +/** + * Returns the text expression build from period for later.js. + * Takes the timezone offset into account while calculating the trigger time. + * @param {string} period - "hourly"|"daily"|"monthly" + * @param {number} offset - timezone offset in minutes + * @returns {string} schedule text + */ +function getScheduleTextExpression(period, offset) { + if (period === "hourly") { + return "every 1 hour on the 59th min"; + } + const utcClock = moment("2026-02-01T23:59:00.000Z") + .tz("UTC") + .subtract(offset, "minutes") + .format("HH:mm"); + if (period === "daily") { + return "at " + utcClock; + } + return "on the last day of the month at " + utcClock; +} (function() { /** @@ -59,12 +70,17 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { * update alert job * @param {object} alert - alert record data */ - function updateJobForAlert(alert) { - if (alert.enabled) { - const textExpression = PERIOD_TO_TEXT_EXPRESSION_MAPPER[alert.period]; - if (textExpression) { - JOB.job('alerts:monitor', { alertID: alert._id }).replace().schedule(textExpression); - // JOB.job('alerts:monitor', { alertID: alert._id }).replace().schedule("every seconds"); + async function updateJobForAlert(alert) { + if (alert.enabled && Object.keys(commonLib.PERIOD_TO_DATE_COMPONENT_MAP).includes(alert.period)) { + const apps = await commonLib.loadAlertAppsWithTimezoneOffsets(alert); + for (const app of apps) { + const textExpression = getScheduleTextExpression(alert.period, app.offset); + if (textExpression) { + JOB.job('alerts:monitor', { + alertID: alert._id, + appID: app._id + }).replace().schedule(textExpression); + } } } else { @@ -74,16 +90,17 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { /** * load job list */ - function loadJobs() { - common.readBatcher.getMany("alerts", {}, function(err, alertsList) { - log.d(alertsList, "get alert configs"); - alertsList && alertsList.forEach(t => { - //period type - if (t.period) { - updateJobForAlert(t); - } - }); + async function loadJobs() { + // delete and then re-create all jobs + await common.db.collection("jobs").deleteMany({ + name: "alerts:monitor" }); + const alerts = await common.readBatcher.getMany("alerts", { + enabled: true, + period: { $exists: true } + }); + log.d("loaded", alerts); + await Promise.all(alerts.map(updateJobForAlert)); } plugins.register("/i", async function(ob) { @@ -124,22 +141,20 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { }); plugins.register("/master", function() { - loadJobs(); + setTimeout(function() { + plugins.dispatch("/updateAlert", { method: "alertTrigger" }); + }, 10000); }); plugins.register("/updateAlert", function(ob) { - setTimeout(() => { - if (ob && (ob.method === "alertTrigger")) { - if (ob.alert) { - deleteJob(ob.alert, function() { - updateJobForAlert(ob.alert); - }); - } - else { - loadJobs(); - } + if (ob && ob.method === "alertTrigger") { + if (ob.alert) { + updateJobForAlert(ob.alert); } - }, 2000); + else { + loadJobs(); + } + } }); @@ -148,21 +163,18 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { utils.addAlertCount(ob); }); - setTimeout(function() { - plugins.dispatch("/updateAlert", { method: "alertTrigger" }); - }, 10000); /** - * @api {get} /i/alert/save save new create or updated alert data. - * @apiName saveAlert - * @apiGroup alerts + * @api {get} /i/alert/save save new create or updated alert data. + * @apiName saveAlert + * @apiGroup alerts * - * @apiDescription create or update alert. - * @apiQuery {string} alert_config alert Configuration JSON object string. + * @apiDescription create or update alert. + * @apiQuery {string} alert_config alert Configuration JSON object string. * if contains "_id" will update related alert in DB. - * @apiQuery {String} app_id target app id of the alert. + * @apiQuery {String} app_id target app id of the alert. * * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK @@ -229,7 +241,6 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { if (result && result.value) { plugins.dispatch("/updateAlert", { method: "alertTrigger", alert: result.value }); } - plugins.dispatch("/updateAlert", { method: "alertTrigger" }); common.returnOutput(params, result && result.value); } @@ -268,13 +279,13 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { /** - * @api {get} /i/alert/delete delete alert by alert ID - * @apiName deleteAlert - * @apiGroup alerts + * @api {get} /i/alert/delete delete alert by alert ID + * @apiName deleteAlert + * @apiGroup alerts * * @apiDescription delete alert by id. * @apiQuery {string} alertID target alert id from db. - * @apiQuery {String} app_id target app id of the alert. + * @apiQuery {String} app_id target app id of the alert. * * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK @@ -322,13 +333,13 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { /** * @api {post} /i/alert/status change alert status - * @apiName changeAlertStatus - * @apiGroup alerts + * @apiName changeAlertStatus + * @apiGroup alerts * * @apiDescription change alerts status by boolean flag. * @apiQuery {string} JSON string of status object for alerts record want to update. * for example: {"626270afbf7392a8bfd8c1f3":false, "42dafbf7392a8bfd8c1e1": true} - * @apiQuery {String} app_id target app id of the alert. + * @apiQuery {String} app_id target app id of the alert. * * @apiSuccessExample {text} Success-Response: * HTTP/1.1 200 OK @@ -361,14 +372,19 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { qquery, {}, { $set: { enabled: statusList[alertID] } }, - { new: false, upsert: false } + { new: true, upsert: false } ) ); } - Promise.all(batch).then(function() { - log.d("alert all updated."); + Promise.all(batch).then(function(result) { + let updatedAlerts = []; + if (Array.isArray(result)) { + updatedAlerts = result + .filter(({ ok }) => !!ok) + .map(({ value }) => value); + } common.readBatcher.invalidate("alerts", {}, {}, true); - plugins.dispatch("/updateAlert", { method: "alertTrigger" }); + updatedAlerts.map(alert => plugins.dispatch("/updateAlert", { method: "alertTrigger", alert })); common.returnOutput(params, true); }); }); @@ -376,13 +392,13 @@ const PERIOD_TO_TEXT_EXPRESSION_MAPPER = { }); /** - * @api {post} /i/alert/list get alert list - * @apiName getAlertList - * @apiGroup alerts + * @api {post} /i/alert/list get alert list + * @apiName getAlertList + * @apiGroup alerts * - * @apiDescription get Alert List user can view. + * @apiDescription get Alert List user can view. * - * @apiQuery {String} app_id target app id of the alert. + * @apiQuery {String} app_id target app id of the alert. * * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK diff --git a/plugins/alerts/api/jobs/monitor.js b/plugins/alerts/api/jobs/monitor.js index debe90c19c8..fab1bf99b65 100644 --- a/plugins/alerts/api/jobs/monitor.js +++ b/plugins/alerts/api/jobs/monitor.js @@ -30,40 +30,31 @@ class MonitorJob extends Job { * @param {object} _db - db object * @param {function} done - callback function */ - run(_db, done) { - const alertID = this._json.data.alertID; + async run(_db, done) { + let { alertID, appID } = this._json.data; const scheduledTo = this._json.next; - const self = this; - common.db.collection("alerts").findOne({ - _id: common.db.ObjectID(alertID), - // these are being triggered by the event listener in api.js - alertDataSubType: { $nin: Object.values(TRIGGERED_BY_EVENT) } - }, async function(err, alertConfigs) { - if (err) { - log.e(err); - return; + try { + const alert = await common.db.collection("alerts").findOne({ + _id: common.db.ObjectID(alertID), + // these are being triggered by the event listener in api.js + alertDataSubType: { $nin: Object.values(TRIGGERED_BY_EVENT) } + }); + const app = await common.db.collection("apps").findOne({ + _id: common.db.ObjectID(appID), + }); + log.d("alert job info:", this._json, alert, app); + if (!alert || !app) { + throw new Error("Alert", alertID, "or App", appID, "couldn't be found"); } - if (!alertConfigs) { - return; + if (alert.alertDataType === 'profile_groups') { + alert.alertDataType = 'cohorts'; } - log.d('Runing alerts Monitor Job ....'); - log.d("job info:", self._json, alertConfigs); - if (alertConfigs.alertDataType === 'profile_groups') { - alertConfigs.alertDataType = 'cohorts'; - } - const module = ALERT_MODULES[alertConfigs.alertDataType]; - if (module) { - try { - await module.check({ alertConfigs, done, scheduledTo }); - } - catch (error) { - log.e("Error while running " + alertConfigs.alertDataType + " alert check", error); - } - } - else { - log.e("Alert module " + alertConfigs.alertDataType + " not found"); - } - }); + const module = ALERT_MODULES[alert.alertDataType]; + await module.check({ alert, app, done, scheduledTo }); + } + catch (err) { + log.e(err); + } } } diff --git a/plugins/alerts/api/parts/common-lib.js b/plugins/alerts/api/parts/common-lib.js index 7eda0c8eec7..5840708d1e5 100644 --- a/plugins/alerts/api/parts/common-lib.js +++ b/plugins/alerts/api/parts/common-lib.js @@ -18,7 +18,7 @@ * @property {boolean} enabled - true|false * @property {string} compareDescribe - text to show on lists for this alert * @property {Array} alertValues - audience e.g. for alertBy="email", list of e-mails - * @property {Array} allGroups - + * @property {Array} allGroups - * @property {string} createdBy - creation time */ @@ -28,6 +28,7 @@ * @property {string} name - name identifier * @property {ObjectId} _id - document id * @property {string} timezone - timezone string (e.g. Europe/Istanbul) + * @property {number} offset - timezone offset (e.g. 120 for Europe/Berlin, -240 for US/Eastern) * @property {any} plugins */ @@ -57,6 +58,7 @@ const moment = require('moment-timezone'); const path = require("path"); const fs = require("fs"); const ejs = require("ejs"); +const { ObjectId } = require("mongodb"); const EMAIL_TEMPLATE = ejs.compile( fs.readFileSync( path.resolve(__dirname, '../../frontend/public/templates/email.html'), @@ -92,6 +94,7 @@ module.exports = { determineAudience, compileEmail, trigger, + loadAlertAppsWithTimezoneOffsets, }; /** @@ -206,7 +209,7 @@ async function compileEmail(result) { * Formats the metric value to ensure it maintains its type. * If the value is a number, it rounds to 2 decimal places if necessary. * Otherwise, it returns the value as is. - * + * * @param {number|string} value - The value to be formatted. * @returns {number|string} The formatted value, maintaining the original type. */ @@ -257,4 +260,33 @@ async function trigger(result, log) { log.e("Alert e-mail couldn't be send to " + email, err); } } +} + +/** + * Returns all of the app objects from "apps" collection with "offset" + * property populated for the given alert. + * @param {Alert} alert - alert object + * @returns {App[]} App objects with offset property + */ +async function loadAlertAppsWithTimezoneOffsets(alert) { + const selectedApp = alert.selectedApps[0]; + // there can only be a single app selected. + // or all of the apps are selected (only available for data points): + let apps; + if (selectedApp === "all") { + apps = await common.readBatcher.getMany("apps", {}); + } + else { + apps = [ + await common.readBatcher.getOne("apps", { + _id: new ObjectId(selectedApp) + }) + ]; + } + for (const app of apps) { + app.offset = app.timezone + ? moment.tz(app.timezone).utcOffset() + : moment().utcOffset(); // fallback to system timezone + } + return apps; } \ No newline at end of file diff --git a/plugins/data-manager/frontend/public/localization/data-manager.properties b/plugins/data-manager/frontend/public/localization/data-manager.properties index 69377808b86..c8fc93ed2ea 100644 --- a/plugins/data-manager/frontend/public/localization/data-manager.properties +++ b/plugins/data-manager/frontend/public/localization/data-manager.properties @@ -26,6 +26,8 @@ data-manager.validations = Validations data-manager.user-properties = User Properties data-manager.custom-property = Custom Property data-manager.user-property = User Property +data-manager.user-property.update-success = User property changes saved successfully +data-manager.user-property.update-fail = Failed saving user property changes data-manager.all-data-sources = All data sources data-manager.created = Transformation Created! data-manager.saved = Transformation Saved! diff --git a/plugins/hooks/package-lock.json b/plugins/hooks/package-lock.json index 86369330e25..127661a553e 100644 --- a/plugins/hooks/package-lock.json +++ b/plugins/hooks/package-lock.json @@ -161,9 +161,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -730,9 +730,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lru-cache": { diff --git a/ui-tests/README.md b/ui-tests/README.md index a2b03d70864..44804e4e020 100644 --- a/ui-tests/README.md +++ b/ui-tests/README.md @@ -65,4 +65,4 @@ Cypress.SelectorPlayground.defaults({ selectorPriority: ['data-test-id', 'id', 'class'] }) -``` \ No newline at end of file +``` diff --git a/ui-tests/package-lock.json b/ui-tests/package-lock.json index 12b38960975..1f57dcfa7be 100644 --- a/ui-tests/package-lock.json +++ b/ui-tests/package-lock.json @@ -83,9 +83,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.3.0.tgz", - "integrity": "sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.4.0.tgz", + "integrity": "sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==", "funding": [ { "type": "opencollective", @@ -589,9 +589,9 @@ } }, "node_modules/@napi-rs/canvas": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.95.tgz", - "integrity": "sha512-lkg23ge+rgyhgUwXmlbkPEhuhHq/hUi/gXKH+4I7vO+lJrbNfEYcQdJLIGjKyXLQzgFiiyDAwh5vAe/tITAE+w==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.97.tgz", + "integrity": "sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==", "dev": true, "license": "MIT", "optional": true, @@ -606,23 +606,23 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.95", - "@napi-rs/canvas-darwin-arm64": "0.1.95", - "@napi-rs/canvas-darwin-x64": "0.1.95", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.95", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.95", - "@napi-rs/canvas-linux-arm64-musl": "0.1.95", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.95", - "@napi-rs/canvas-linux-x64-gnu": "0.1.95", - "@napi-rs/canvas-linux-x64-musl": "0.1.95", - "@napi-rs/canvas-win32-arm64-msvc": "0.1.95", - "@napi-rs/canvas-win32-x64-msvc": "0.1.95" + "@napi-rs/canvas-android-arm64": "0.1.97", + "@napi-rs/canvas-darwin-arm64": "0.1.97", + "@napi-rs/canvas-darwin-x64": "0.1.97", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.97", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.97", + "@napi-rs/canvas-linux-arm64-musl": "0.1.97", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.97", + "@napi-rs/canvas-linux-x64-gnu": "0.1.97", + "@napi-rs/canvas-linux-x64-musl": "0.1.97", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.97", + "@napi-rs/canvas-win32-x64-msvc": "0.1.97" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.95.tgz", - "integrity": "sha512-SqTh0wsYbetckMXEvHqmR7HKRJujVf1sYv1xdlhkifg6TlCSysz1opa49LlS3+xWuazcQcfRfmhA07HxxxGsAA==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.97.tgz", + "integrity": "sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==", "cpu": [ "arm64" ], @@ -641,9 +641,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.95.tgz", - "integrity": "sha512-F7jT0Syu+B9DGBUBcMk3qCRIxAWiDXmvEjamwbYfbZl7asI1pmXZUnCOoIu49Wt0RNooToYfRDxU9omD6t5Xuw==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.97.tgz", + "integrity": "sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==", "cpu": [ "arm64" ], @@ -662,9 +662,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.95.tgz", - "integrity": "sha512-54eb2Ho15RDjYGXO/harjRznBrAvu+j5nQ85Z4Qd6Qg3slR8/Ja+Yvvy9G4yo7rdX6NR9GPkZeSTf2UcKXwaXw==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.97.tgz", + "integrity": "sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==", "cpu": [ "x64" ], @@ -683,9 +683,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.95.tgz", - "integrity": "sha512-hYaLCSLx5bmbnclzQc3ado3PgZ66blJWzjXp0wJmdwpr/kH+Mwhj6vuytJIomgksyJoCdIqIa4N6aiqBGJtJ5Q==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.97.tgz", + "integrity": "sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==", "cpu": [ "arm" ], @@ -704,9 +704,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.95.tgz", - "integrity": "sha512-J7VipONahKsmScPZsipHVQBqpbZx4favaD8/enWzzlGcjiwycOoymL7f4tNeqdjK0su19bDOUt6mjp9gsPWYlw==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.97.tgz", + "integrity": "sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==", "cpu": [ "arm64" ], @@ -725,9 +725,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.95.tgz", - "integrity": "sha512-PXy0UT1J/8MPG8UAkWp6Fd51ZtIZINFzIjGH909JjQrtCuJf3X6nanHYdz1A+Wq9o4aoPAw1YEUpFS1lelsVlg==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.97.tgz", + "integrity": "sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==", "cpu": [ "arm64" ], @@ -746,9 +746,9 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.95.tgz", - "integrity": "sha512-2IzCkW2RHRdcgF9W5/plHvYFpc6uikyjMb5SxjqmNxfyDFz9/HB89yhi8YQo0SNqrGRI7yBVDec7Pt+uMyRWsg==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.97.tgz", + "integrity": "sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==", "cpu": [ "riscv64" ], @@ -767,9 +767,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.95.tgz", - "integrity": "sha512-OV/ol/OtcUr4qDhQg8G7SdViZX8XyQeKpPsVv/j3+7U178FGoU4M+yIocdVo1ih/A8GQ63+LjF4jDoEjaVU8Pw==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.97.tgz", + "integrity": "sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==", "cpu": [ "x64" ], @@ -788,9 +788,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.95.tgz", - "integrity": "sha512-Z5KzqBK/XzPz5+SFHKz7yKqClEQ8pOiEDdgk5SlphBLVNb8JFIJkxhtJKSvnJyHh2rjVgiFmvtJzMF0gNwwKyQ==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.97.tgz", + "integrity": "sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==", "cpu": [ "x64" ], @@ -809,9 +809,9 @@ } }, "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.95.tgz", - "integrity": "sha512-aj0YbRpe8qVJ4OzMsK7NfNQePgcf9zkGFzNZ9mSuaxXzhpLHmlF2GivNdCdNOg8WzA/NxV6IU4c5XkXadUMLeA==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.97.tgz", + "integrity": "sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==", "cpu": [ "arm64" ], @@ -830,9 +830,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.95", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.95.tgz", - "integrity": "sha512-GA8leTTCfdjuHi8reICTIxU0081PhXvl3lzIniLUjeLACx9GubUiyzkwFb+oyeKLS5IAGZFLKnzAf4wm2epRlA==", + "version": "0.1.97", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.97.tgz", + "integrity": "sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==", "cpu": [ "x64" ], @@ -1351,9 +1351,9 @@ } }, "node_modules/cypress": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.12.0.tgz", - "integrity": "sha512-B2BRcudLfA4NZZP5QpA45J70bu1heCH59V1yKRLHAtiC49r7RV03X5ifUh7Nfbk8QNg93RAsc6oAmodm/+j0pA==", + "version": "15.13.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.13.1.tgz", + "integrity": "sha512-jLkgo75zlwo7PhXp0XJot+zIfFSDzN1SvTml6Xf3ETM1XHRWnH3Q4LAR3orCo/BsnxPnhjG3m5HYSvn9DAtwBg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2103,9 +2103,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.once": { @@ -2335,16 +2335,16 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.5.207", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz", - "integrity": "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==", + "version": "5.6.205", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.6.205.tgz", + "integrity": "sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=20.19.0 || >=22.13.0 || >=24" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.95", + "@napi-rs/canvas": "^0.1.96", "node-readable-to-web-readable-stream": "^0.4.2" } },