diff --git a/api/api.js b/api/api.js index 843deb94f1b..efb4f061c65 100644 --- a/api/api.js +++ b/api/api.js @@ -16,6 +16,7 @@ const {WriteBatcher, ReadBatcher, InsertBatcher} = require('./parts/data/batcher const pack = require('../package.json'); const versionInfo = require('../frontend/express/version.info.js'); const moment = require("moment"); +const tracker = require('./parts/mgmt/tracker.js'); var t = ["countly:", "api"]; common.processRequest = processRequest; @@ -38,6 +39,9 @@ else { process.title = t.join(' '); plugins.connectToAllDatabases().then(function() { + plugins.loadConfigs(common.db, function() { + tracker.enable(); + }); common.writeBatcher = new WriteBatcher(common.db); common.readBatcher = new ReadBatcher(common.db); common.insertBatcher = new InsertBatcher(common.db); @@ -142,6 +146,41 @@ plugins.connectToAllDatabases().then(function() { require('./utils/log.js').ipcHandler(msg); }); + /** + * Set tracking config + */ + plugins.setConfigs("tracking", { + self_tracking_app: "", + self_tracking_url: "", + self_tracking_app_key: "", + self_tracking_id_policy: "_id", + self_tracking_sessions: true, + self_tracking_events: true, + self_tracking_views: true, + self_tracking_feedback: true, + self_tracking_user_details: true, + server_sessions: true, + server_events: true, + server_crashes: true, + server_views: true, + server_feedback: true, + server_user_details: true, + /*user_sessions: true, + user_events: true, + user_crashes: true, + user_views: true, + user_feedback: true, + user_details: true*/ + }); + + /*plugins.setUserConfigs("tracking", { + user_sessions: false, + user_events: false, + user_crashes: false, + user_views: false, + user_feedback: false + });*/ + /** * Initialize Plugins */ @@ -309,7 +348,7 @@ plugins.connectToAllDatabases().then(function() { // Allow configs to load & scanner to find all jobs classes setTimeout(() => { jobs.job('api:topEvents').replace().schedule('at 00:01 am ' + 'every 1 day'); - jobs.job('api:ping').replace().schedule('every 1 day'); + jobs.job('api:ping').replace().schedule('at 00:01 am ' + 'every 1 day'); jobs.job('api:clear').replace().schedule('every 1 day'); jobs.job('api:clearTokens').replace().schedule('every 1 day'); jobs.job('api:clearAutoTasks').replace().schedule('every 1 day'); diff --git a/api/jobs/ping.js b/api/jobs/ping.js index 984e82c566c..efc4e78ceea 100644 --- a/api/jobs/ping.js +++ b/api/jobs/ping.js @@ -1,12 +1,8 @@ 'use strict'; const job = require('../parts/jobs/job.js'), - log = require('../utils/log.js')('job:ping'), - countlyConfig = require("../../frontend/express/config.js"), - versionInfo = require('../../frontend/express/version.info'), plugins = require('../../plugins/pluginManager.js'), - request = require('countly-request')(plugins.getConfig("security")); - + tracker = require('../parts/mgmt/tracker.js'); /** Class for the job of pinging servers **/ class PingJob extends job.Job { @@ -16,86 +12,114 @@ class PingJob extends job.Job { * @param {done} done callback */ run(db, done) { - request({strictSSL: false, uri: (process.env.COUNTLY_CONFIG_PROTOCOL || "http") + "://" + (process.env.COUNTLY_CONFIG_HOSTNAME || "localhost") + (countlyConfig.path || "") + "/configs"}, function() {}); - var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); - var url = "https://count.ly/configurations/ce/tracking"; - if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { - url = "https://count.ly/configurations/ee/tracking"; - } - plugins.loadConfigs(db, function() { + plugins.loadConfigs(db, async function() { const offlineMode = plugins.getConfig("api").offline_mode; - const { countly_tracking } = plugins.getConfig('frontend'); if (!offlineMode) { - request(url, function(err, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } - catch (ex) { - body = null; - } + var server = tracker.getBulkServer(); + var user = tracker.getBulkUser(server); + if (!user) { + return done(); + } + + try { + var custom = await tracker.getAllData(); + if (Object.keys(custom).length) { + user.user_details({"custom": custom }); } - if (body) { - if (countlyConfigOrig.web.use_intercom && typeof body.intercom !== "undefined") { - countlyConfig.web.use_intercom = body.intercom; - } - if (typeof countlyConfigOrig.web.track === "undefined" && typeof body.stats !== "undefined") { - if (body.stats) { - countlyConfig.web.track = null; - } - else { - countlyConfig.web.track = "none"; - } - } + } + catch (ex) { + console.log("Error collecting server data:", ex); + } + var days = 90; + var current_sync = Date.now(); + + // Atomically retrieve old last_sync value and set new one + var syncResult = await db.collection("plugins").findOneAndUpdate( + {_id: "version"}, + {$set: {last_sync: current_sync}}, + { + upsert: true, + returnDocument: 'before', + projection: {last_sync: 1} } - log.d(err, body, countlyConfigOrig, countlyConfig); - if (countly_tracking) { - db.collection("members").findOne({global_admin: true}, function(err2, member) { - if (!err2 && member) { - var date = new Date(); - let domain = plugins.getConfig('api').domain; + ); - try { - // try to extract hostname from full domain url - const urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } + var last_sync = syncResult.value ? syncResult.value.last_sync : null; + if (last_sync) { + days = Math.floor((new Date().getTime() - last_sync) / (1000 * 60 * 60 * 24)); + } - request({ - uri: "https://stats.count.ly/i", - method: "GET", - timeout: 4E3, - qs: { - device_id: domain, - app_key: "e70ec21cbe19e799472dfaee0adb9223516d238f", - timestamp: Math.floor(date.getTime() / 1000), - hour: date.getHours(), - dow: date.getDay(), - no_meta: true, - events: JSON.stringify([ - { - key: "PING", - count: 1 - } - ]) + if (days > 0) { + //calculate seconds timestamp of days before today + var startTs = Math.round((new Date().getTime() - (30 * 24 * 60 * 60 * 1000)) / 1000); + + //sync server events - use aggregation pipeline to group by day and action on MongoDB side + var aggregationPipeline = [ + // Match documents with timestamp greater than startTs and valid action + { + $match: { + ts: { $gt: startTs } + } + }, + // Add calculated fields for day grouping + { + $addFields: { + // Convert timestamp to date and set to noon (12:00:00) + dayDate: { + $dateFromParts: { + year: { $year: { $toDate: { $multiply: ["$ts", 1000] } } }, + month: { $month: { $toDate: { $multiply: ["$ts", 1000] } } }, + day: { $dayOfMonth: { $toDate: { $multiply: ["$ts", 1000] } } }, + hour: 12, + minute: 0, + second: 0 } - }, function(a/*, c, b*/) { - log.d('Done running ping job: %j', a); - done(); - }); + } + } + }, + // Convert back to timestamp in seconds + { + $addFields: { + noonTimestamp: { + $divide: [{ $toLong: "$dayDate" }, 1000] + } } - else { - done(); + }, + // Group by day and action + { + $group: { + _id: { + day: "$noonTimestamp", + action: "$a" + }, + count: { $sum: 1 } } - }); + }, + // Project to final format + { + $project: { + _id: 0, + action: "$_id.action", + timestamp: "$_id.day", + count: 1 + } + } + ]; + + var cursor = db.collection("systemlogs").aggregate(aggregationPipeline); + + while (cursor && await cursor.hasNext()) { + let eventData = await cursor.next(); + user.add_event({key: eventData.action, count: eventData.count, timestamp: eventData.timestamp}); } - else { + server.start(function() { + server.stop(); done(); - } - }); + }); + } + else { + done(); + } } else { done(); @@ -104,4 +128,4 @@ class PingJob extends job.Job { } } -module.exports = PingJob; +module.exports = PingJob; \ No newline at end of file diff --git a/api/parts/mgmt/tracker.js b/api/parts/mgmt/tracker.js index d878ab7188f..d75dce31e1a 100644 --- a/api/parts/mgmt/tracker.js +++ b/api/parts/mgmt/tracker.js @@ -8,176 +8,158 @@ var tracker = {}, stats = require('../data/stats.js'), common = require('../../utils/common.js'), logger = require('../../utils/log.js'), + countlyFs = require('../../utils/countlyFs.js'), //log = logger("tracker:server"), Countly = require('countly-sdk-nodejs'), - countlyConfig = require("../../../frontend/express/config.js"), - versionInfo = require('../../../frontend/express/version.info'), - ip = require('./ip.js'), - cluster = require('cluster'), - os = require('os'), fs = require('fs'), - asyncjs = require('async'), - trial = "79199134e635edb05fc137e8cd202bb8640fb0eb", - app = "e70ec21cbe19e799472dfaee0adb9223516d238f", - server = "e0693b48a5513cb60c112c21aede3cab809d52d0", + path = require('path'), + https = require('https'), + http = require('http'), + FormData = require('form-data'), + { Readable } = require('node:stream'), + versionInfo = require('../../../frontend/express/version.info'), + server = "9c28c347849f2c03caf1b091ec7be8def435e85e", + user = "fa6e9ae7b410cb6d756e8088c5f3936bf1fab5f3", url = "https://stats.count.ly", - plugins = require('../../../plugins/pluginManager.js'), - request = require('countly-request')(plugins.getConfig("security")), - offlineMode = plugins.getConfig("api").offline_mode, - domain = plugins.getConfig("api").domain; - + plugins = require('../../../plugins/pluginManager.js'); -//update configs -var cache = {}; -if (countlyConfig.web && countlyConfig.web.track === "all") { - countlyConfig.web.track = null; -} -var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); -var url_check = "https://count.ly/configurations/ce/tracking"; -if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { - url_check = "https://count.ly/configurations/ee/tracking"; -} +var IS_FLEX = false; -if (!offlineMode) { - request(url_check, function(err, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } - catch (ex) { - body = null; - } +if (fs.existsSync(path.resolve('/opt/deployment_env.json'))) { + var deploymentConf = fs.readFileSync('/opt/deployment_env.json', 'utf8'); + try { + if (JSON.parse(deploymentConf).DEPLOYMENT_ID) { + IS_FLEX = true; } - if (body) { - if (countlyConfigOrig.web.use_intercom && typeof body.intercom !== "undefined") { - countlyConfig.web.use_intercom = body.intercom; - } - if (typeof countlyConfigOrig.web.track === "undefined" && typeof body.stats !== "undefined") { - if (body.stats) { - countlyConfig.web.track = null; - } - else { - countlyConfig.web.track = "none"; - } - } - if (typeof countlyConfigOrig.web.server_track === "undefined" && typeof body.server !== "undefined") { - if (body.server) { - countlyConfig.web.server_track = null; - } - else { - countlyConfig.web.server_track = "none"; - } - } - } - }); + } + catch (e) { + IS_FLEX = false; + } } +//update configs var isEnabled = false; /** * Enable tracking for this server **/ tracker.enable = function() { + if (isEnabled) { + return; + } + var config = { - app_key: (versionInfo.trial) ? trial : server, + app_key: server, url: url, app_version: versionInfo.version, storage_path: "../../../.sdk/", interval: 10000, fail_timeout: 600, - session_update: 120, + session_update: 60 * 60 * 12, remote_config: true, debug: (logger.getLevel("tracker:server") === "debug") }; + + var domain = plugins.getConfig("api").domain; + //set static device id if domain is defined if (domain) { config.device_id = stripTrailingSlash((domain + "").split("://").pop()); } - Countly.init(config); - //change device id if is it not domain - if (domain && Countly.get_device_id() !== domain) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - } - else if (!domain) { - checkDomain(); - } + if (config.device_id && config.device_id !== "localhost") { + Countly.init(config); - isEnabled = true; - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.track_errors(); - } - if (cluster.isMaster) { - setTimeout(function() { - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.begin_session(true); - setTimeout(function() { - collectServerStats(); - collectServerData(); - }, 20000); - } - }, 1000); - //report app start trace - if (Countly.report_app_start) { - Countly.report_app_start(); + //change device id if is it not domain + if (Countly.get_device_id() !== domain) { + Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); + } + + isEnabled = true; + Countly.user_details({"name": config.device_id }); + if (plugins.getConfig("white-labeling") && (plugins.getConfig("white-labeling").favicon || plugins.getConfig("white-labeling").stopleftlogo || plugins.getConfig("white-labeling").prelogo)) { + var id = plugins.getConfig("white-labeling").favicon || plugins.getConfig("white-labeling").stopleftlogo || plugins.getConfig("white-labeling").prelogo; + countlyFs.gridfs.getDataById("white-labeling", id, function(errWhitelabel, data) { + if (!errWhitelabel && data) { + tracker.uploadBase64FileFromGridFS(data).catch(() => {}); + } + }); } + else { + Countly.user_details({"picture": "./images/favicon.png" }); + } + if (plugins.getConfig("tracking").server_sessions) { + Countly.begin_session(true); + } + if (plugins.getConfig("tracking").server_crashes) { + Countly.track_errors(); + } + setTimeout(function() { + tracker.getAllData().then((custom) => { + if (Object.keys(custom).length) { + Countly.user_details({"custom": custom }); + } + }); + }, 20000); } }; /** -* Enable tracking for dashboard process -**/ -tracker.enableDashboard = function() { - var config = { - app_key: (versionInfo.trial) ? trial : server, - url: url, - app_version: versionInfo.version, - storage_path: "../../../.sdk/", - interval: 60000, - fail_timeout: 600, - session_update: 120, - debug: (logger.getLevel("tracker:server") === "debug") - }; - //set static device id if domain is defined - if (domain) { - config.device_id = stripTrailingSlash((domain + "").split("://").pop()); - } - Countly.init(config); + * Get bulk server instance + * @returns {Object} Countly Bulk instance + */ +tracker.getBulkServer = function() { + return new Countly.Bulk({ + app_key: server, + url: url + }); +}; - //change device id if is it not domain - if (domain && Countly.get_device_id() !== domain) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - } - else if (!domain) { - checkDomain(); - } - isEnabled = true; - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.track_errors(); +/** + * Get bulk user instance + * @param {Object} serverInstance - Countly Bulk server instance + * @returns {Object} Countly Bulk User instance + */ +tracker.getBulkUser = function(serverInstance) { + var domain = stripTrailingSlash((plugins.getConfig("api").domain + "").split("://").pop()); + if (domain && domain !== "localhost") { + return serverInstance.add_user({device_id: domain}); } }; - /** * Report server level event * @param {object} event - event object **/ tracker.reportEvent = function(event) { - if (isEnabled && countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { + if (isEnabled && plugins.getConfig("tracking").server_events) { Countly.add_event(event); } }; +/** +* Report server level event in bulk +* @param {Array} events - array of event objects +**/ +tracker.reportEventBulk = function(events) { + if (isEnabled && plugins.getConfig("tracking").server_events) { + Countly.request({ + app_key: server, + device_id: Countly.get_device_id(), + events: JSON.stringify(events) + }); + } +}; + /** * Report user level event * @param {string} id - id of the device * @param {object} event - event object -* @param {string} level - tracking level **/ -tracker.reportUserEvent = function(id, event, level) { - if (isEnabled && countlyConfig.web.track !== "none" && (!level || countlyConfig.web.track === level) && countlyConfig.web.server_track !== "none") { +tracker.reportUserEvent = function(id, event) { + if (isEnabled && plugins.getConfig("tracking").user_events) { Countly.request({ - app_key: app, + app_key: user, device_id: id, events: JSON.stringify([event]) }); @@ -186,11 +168,10 @@ tracker.reportUserEvent = function(id, event, level) { /** * Check if tracking enabled -* @param {boolean|string} level - level of tracking * @returns {boolean} if enabled **/ -tracker.isEnabled = function(level) { - return (isEnabled && countlyConfig.web.track !== "none" && (!level || countlyConfig.web.track === level) && countlyConfig.web.server_track !== "none"); +tracker.isEnabled = function() { + return isEnabled; }; /** @@ -203,164 +184,403 @@ tracker.getSDK = function() { /** * Get server stats +* @returns {Promise} server stats **/ -function collectServerStats() { // eslint-disable-line no-unused-vars - stats.getServer(common.db, function(data) { - common.db.collection("apps").aggregate([{$project: {last_data: 1}}, {$sort: {"last_data": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errApps, resApps) { - common.db.collection("members").aggregate([{$project: {last_login: 1}}, {$sort: {"last_login": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errLogin, resLogin) { - if (resApps && resApps[0]) { - Countly.userData.set("last_data", resApps[0].last_data || 0); - } - if (resLogin && resLogin[0]) { - Countly.userData.set("last_login", resLogin[0].last_login || 0); - } - if (data) { - if (data.app_users) { - Countly.userData.set("app_users", data.app_users); - } - if (data.apps) { - Countly.userData.set("apps", data.apps); - } - if (data.users) { - Countly.userData.set("users", data.users); - } - } - Countly.userData.save(); +tracker.collectServerStats = function() { + var props = {}; + return new Promise((resolve) => { + stats.getServer(common.db, function(data) { + common.db.collection("apps").aggregate([{$project: {last_data: 1}}, {$sort: {"last_data": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errApps, resApps) { + common.db.collection("members").aggregate([{$project: {last_login: 1}}, {$sort: {"last_login": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errLogin, resLogin) { + // Aggregate total list lengths across all documents in events collection + common.db.collection("events").aggregate([ + { + $group: { + _id: null, + totalListLength: { $sum: { $size: "$list" } } + } + } + ], {allowDiskUse: true}, function(errEvents, resEvents) { + + if (resApps && resApps[0]) { + props.last_data = resApps[0].last_data || 0; + } + if (resLogin && resLogin[0]) { + props.last_login = resLogin[0].last_login || 0; + } + if (resEvents && resEvents[0]) { + props.events = resEvents[0].totalListLength || 0; + } + if (data) { + if (data.app_users) { + props.app_users = data.app_users; + } + if (data.apps) { + props.apps = data.apps; + } + if (data.users) { + props.users = data.users; + } + } + resolve(props); + }); + }); }); }); }); -} +}; /** * Get server data +* @returns {Object} server data **/ -function collectServerData() { - Countly.userData.set("plugins", plugins.getPlugins()); - var cpus = os.cpus(); - if (cpus && cpus.length) { - Countly.userData.set("cores", cpus.length); +tracker.collectServerData = async function() { + var props = {}; + props.trial = versionInfo.trial ? true : false; + props.plugins = plugins.getPlugins(); + props.nodejs = process.version; + props.countly = versionInfo.version; + props.docker = hasDockerEnv() || hasDockerCGroup() || hasDockerMountInfo(); + var edition = "Lite"; + if (IS_FLEX) { + edition = "Flex"; + } + else if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { + edition = "Enterprise"; } - Countly.userData.set("nodejs_version", process.version); + props.edition = edition; if (common.db.build && common.db.build.version) { - Countly.userData.set("db_version", common.db.build.version); + props.mongodb = common.db.build.version; } - common.db.command({ serverStatus: 1 }, function(errCmd, res) { - if (res && res.storageEngine && res.storageEngine.name) { - Countly.userData.set("db_engine", res.storageEngine.name); + const sdkData = await tracker.getSDKData(); + if (sdkData && sdkData.sdk_versions && Object.keys(sdkData.sdk_versions).length) { + props.sdks = Object.keys(sdkData.sdk_versions); + for (const [key, value] of Object.entries(sdkData.sdk_versions)) { + props[key] = value; } - getDomain(function(err, domainname) { - if (!err) { - Countly.userData.set("domain", domainname); - Countly.user_details({"name": stripTrailingSlash((domainname + "").split("://").pop())}); + } + + return props; +}; + +/** + * Get all eligible data + * @returns {Object} all eligible data + */ +tracker.getAllData = async function() { + var props = {}; + if (plugins.getConfig("tracking").server_user_details) { + Object.assign(props, await tracker.collectServerStats()); + } + Object.assign(props, await tracker.collectServerData()); + return props; +}; + +/** + * Query sdks collection for current and previous year (month 0) and combine meta_v2 data + * @returns {Promise} Combined meta_v2 data from all matching documents + */ +tracker.getSDKData = async function() { + var currentYear = new Date().getFullYear(); + var previousYear = currentYear - 1; + + // Build regex pattern to match: appid_YYYY:0_shard + // Matches any app ID, year (current or previous), month 0, and any shard number + var yearPattern = `(${currentYear}|${previousYear})`; + var pattern = new RegExp(`^[a-f0-9]{24}_${yearPattern}:0_\\d+$`); + + try { + // Use aggregation pipeline to combine meta_v2 data on MongoDB side + var pipeline = [ + // Match documents for current and previous year, month 0, any shard + { + $match: { + _id: pattern + } + }, + // Project only meta_v2 field and convert to array of key-value pairs + { + $project: { + meta_v2: { $objectToArray: "$meta_v2" } + } + }, + // Unwind meta_v2 array to process each meta key separately + { + $unwind: "$meta_v2" + }, + // Convert nested objects to arrays for merging + { + $project: { + metaKey: "$meta_v2.k", + metaValue: { $objectToArray: "$meta_v2.v" } + } + }, + // Unwind nested values + { + $unwind: "$metaValue" + }, + // Group by meta key and inner key to collect all unique combinations + { + $group: { + _id: { + metaKey: "$metaKey", + innerKey: "$metaValue.k" + }, + value: { $first: "$metaValue.v" } + } + }, + // Group by meta key to rebuild nested structure + { + $group: { + _id: "$_id.metaKey", + values: { + $push: { + k: "$_id.innerKey", + v: "$value" + } + } + } + }, + // Convert arrays back to objects + { + $project: { + _id: 0, + k: "$_id", + v: { $arrayToObject: "$values" } + } + }, + // Group all into single document + { + $group: { + _id: null, + meta_v2: { + $push: { + k: "$k", + v: "$v" + } + } + } + }, + // Convert final array to object + { + $project: { + _id: 0, + meta_v2: { $arrayToObject: "$meta_v2" } + } } - getDistro(function(err2, distro) { - if (!err2) { - Countly.userData.set("distro", distro); + ]; + + var result = await common.db.collection("sdks").aggregate(pipeline).toArray(); + + // Extract combined meta_v2 or return empty object if no results + var combinedMeta = (result && result[0] && result[0].meta_v2) ? result[0].meta_v2 : {}; + + // Process sdk_version to extract highest version per SDK + var sdkVersions = {}; + if (combinedMeta.sdk_version) { + for (var versionKey in combinedMeta.sdk_version) { + // Parse SDK version format: [sdk_name]_major:minor:patch + var match = versionKey.match(/^\[([^\]]+)\]_(\d+):(\d+):(\d+)$/); + if (match) { + var sdkName = match[1]; + var major = parseInt(match[2], 10); + var minor = parseInt(match[3], 10); + var patch = parseInt(match[4], 10); + + // Check if this SDK exists and compare versions + if (!sdkVersions[sdkName]) { + sdkVersions[sdkName] = { + version: `${major}.${minor}.${patch}`, + major: major, + minor: minor, + patch: patch + }; + } + else { + var current = sdkVersions[sdkName]; + // Compare versions (major.minor.patch) + if (major > current.major || + (major === current.major && minor > current.minor) || + (major === current.major && minor === current.minor && patch > current.patch)) { + sdkVersions[sdkName] = { + version: `${major}.${minor}.${patch}`, + major: major, + minor: minor, + patch: patch + }; + } + } } - getHosting(function(err3, hosting) { - if (!err3) { - Countly.userData.set("hosting", hosting); + } + } + + // Convert to simple object with just SDK name -> version string + var simpleSdkVersions = {}; + for (var sdk in sdkVersions) { + simpleSdkVersions[`sdk_${sdk}`] = sdkVersions[sdk].version; + } + + return { + meta_v2: combinedMeta, + sdk_versions: simpleSdkVersions, + years: [previousYear, currentYear], + month: 0 + }; + } + catch (error) { + logger("tracker:server").error("Error querying SDK data:", error); + return { + meta_v2: {}, + error: error.message + }; + } +}; + +/** + * Upload a base64-encoded file from GridFS to the stats server + * This function handles files stored in GridFS as base64 strings (e.g., data URIs) + * and decodes them before uploading + * + * @param {Object} base64String - Picture data + * @returns {Promise} Upload result + */ +tracker.uploadBase64FileFromGridFS = function(base64String) { + return new Promise((resolve, reject) => { + var domain = stripTrailingSlash((plugins.getConfig("api").domain + "").split("://").pop()); + if (domain && domain !== "localhost") { + try { + let mimeType = "image/png"; + // Strip data URI prefix if present and stripDataURI is true + if (base64String.includes('base64,')) { + // Extract MIME type from data URI if not provided + const dataURIMatch = base64String.match(/^data:([^;]+);base64,/); + if (dataURIMatch) { + mimeType = dataURIMatch[1]; } - Countly.userData.save(); + // Remove data URI prefix + base64String = base64String.split('base64,')[1]; + } + + // Decode base64 to binary buffer + const binaryBuffer = Buffer.from(base64String, 'base64'); + + // Create a readable stream from the decoded buffer + const decodedStream = Readable.from(binaryBuffer); + + // Parse the URL + const statsUrl = new URL(url); + const protocol = statsUrl.protocol === 'https:' ? https : http; + + // Build query parameters + const queryParams = new URLSearchParams({ + device_id: domain, + app_key: server, + user_details: "" }); - }); - }); + + // Create form data + const form = new FormData(); + + // Prepare form options with MIME type if available + const formOptions = { filename: "profile" }; + if (mimeType) { + formOptions.contentType = mimeType; + } + + form.append('file', decodedStream, formOptions); + + // Prepare request options + const requestOptions = { + hostname: statsUrl.hostname, + port: statsUrl.port || (statsUrl.protocol === 'https:' ? 443 : 80), + path: `/i?${queryParams.toString()}`, + method: 'POST', + headers: form.getHeaders() + }; + + // Make the request + const req = protocol.request(requestOptions, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + const result = JSON.parse(data); + resolve({ + success: true, + statusCode: res.statusCode, + data: result + }); + } + catch (e) { + resolve({ + success: true, + statusCode: res.statusCode, + data: data + }); + } + } + else { + reject(new Error(`Upload failed with status ${res.statusCode}: ${data}`)); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + // Pipe the form data to the request + form.pipe(req); + } + catch (error) { + reject(error); + } + } }); -} +}; /** -* Get server domain or ip -* @param {function} callback - callback to get results -**/ -function getDomain(callback) { - if (cache.domain) { - callback(false, cache.domain); + * Check if running in Docker environment + * @returns {boolean} if running in docker + */ +function hasDockerEnv() { + try { + fs.statSync('/.dockerenv'); + return true; } - else { - ip.getHost(function(err, host) { - cache.domain = host; - callback(err, host); - }); + catch { + return false; } } -/** -* Get server hosting provider -* @param {function} callback - callback to get results -**/ -function getHosting(callback) { - if (cache.hosting) { - callback(cache.hosting.length === 0, cache.hosting); +/** + * Check if running in Docker by inspecting cgroup info + * @returns {boolean} if running in docker + */ +function hasDockerCGroup() { + try { + return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker'); } - else { - var hostings = { - "Digital Ocean": "http://169.254.169.254/metadata/v1/hostname", - "Google Cloud": "http://metadata.google.internal", - "AWS": "http://169.254.169.254/latest/dynamic/instance-identity/" - }; - asyncjs.eachSeries(Object.keys(hostings), function(host, done) { - request(hostings[host], function(err, response) { - if (response && response.statusCode >= 200 && response.statusCode < 300) { - cache.hosting = host; - callback(false, cache.hosting); - done(true); - } - else { - done(); - } - }); - }, function() { - if (!cache.hosting) { - callback(true, cache.hosting); - } - }); + catch { + return false; } } -/** -* Get OS distro -* @param {function} callback - callback to get results -**/ -function getDistro(callback) { - if (cache.distro) { - callback(cache.distro.length === 0, cache.distro); +/** + * Check if running in Docker by inspecting mountinfo + * @returns {boolean} if running in docker + */ +function hasDockerMountInfo() { + try { + return fs.readFileSync('/proc/self/mountinfo', 'utf8').includes('/docker/containers/'); } - else { - var oses = {"win32": "Windows", "darwin": "macOS"}; - var osName = os.platform(); - // Linux is a special case. - if (osName !== 'linux') { - cache.distro = oses[osName] ? oses[osName] : osName; - cache.distro += " " + os.release(); - callback(false, cache.distro); - } - else { - var distros = { - "/etc/lsb-release": {name: "Ubuntu", regex: /distrib_release=(.*)/i}, - "/etc/redhat-release": {name: "RHEL/Centos", regex: /release ([^ ]+)/i} - }; - asyncjs.eachSeries(Object.keys(distros), function(distro, done) { - //check ubuntu - fs.readFile(distro, 'utf8', (err, data) => { - if (!err && data) { - cache.distro = distros[distro].name; - var match = data.match(distros[distro].regex); - if (match[1]) { - cache.distro += " " + match[1]; - } - callback(false, cache.distro); - done(true); - } - else { - done(null); - } - }); - }, function() { - if (!cache.distro) { - callback(true, cache.distro); - } - }); - } + catch { + return false; } } @@ -376,20 +596,4 @@ function stripTrailingSlash(str) { return str; } -//check every hour if domain was provided -var checkDomain = function() { - if (!domain && domain !== plugins.getConfig("api").domain) { - domain = plugins.getConfig("api").domain; - if (Countly && isEnabled) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - Countly.userData.set("domain", domain); - Countly.user_details({"name": stripTrailingSlash((domain + "").split("://").pop())}); - Countly.userData.save(); - } - } - else if (!domain) { - setTimeout(checkDomain, 3600000); - } -}; - module.exports = tracker; \ No newline at end of file diff --git a/frontend/express/app.js b/frontend/express/app.js index 9680f48bb00..5fe426f0cb4 100644 --- a/frontend/express/app.js +++ b/frontend/express/app.js @@ -72,7 +72,8 @@ var versionInfo = require('./version.info'), argon2 = require('argon2'), countlyCommon = require('../../api/lib/countly.common.js'), timezones = require('../../api/utils/timezones.js').getTimeZones, - { validateCreate } = require('../../api/utils/rights.js'); + { validateCreate } = require('../../api/utils/rights.js'), + tracker = require('../../api/parts/mgmt/tracker.js'); console.log("Starting Countly", "version", versionInfo.version, "package", pack.version); @@ -136,21 +137,9 @@ plugins.setConfigs("frontend", { session_timeout: 30, use_google: true, code: true, - offline_mode: false, - self_tracking: "", + offline_mode: false }); -if (!plugins.isPluginEnabled('tracker')) { - plugins.setConfigs('frontend', { - countly_tracking: null, - }); -} -else { - plugins.setConfigs('frontend', { - countly_tracking: true, - }); -} - plugins.setUserConfigs("frontend", { production: false, theme: false, @@ -195,16 +184,13 @@ if (countlyConfig.web && countlyConfig.web.track === "all") { countlyConfig.web.track = null; } -var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); - Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_fs")]).then(function(dbs) { var countlyDb = dbs[0]; //reference for consistency between app and api processes membersUtility.db = common.db = countlyDb; countlyFs.setHandler(dbs[1]); + tracker.enable(); - //checking remote configuration - membersUtility.recheckConfigs(countlyConfigOrig, countlyConfig); /** * Create sha1 hash string * @param {string} str - string to hash @@ -417,6 +403,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ }; plugins.loadConfigs(countlyDb, function() { + tracker.enable(); curTheme = plugins.getConfig("frontend").theme; app.loadThemeFiles(curTheme); app.dashboard_headers = plugins.getConfig("security").dashboard_additional_headers; @@ -834,11 +821,6 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ res.send(plugins.getConfig("security").robotstxt); }); - app.get(countlyConfig.path + '/configs', function(req, res) { - membersUtility.recheckConfigs(countlyConfigOrig, countlyConfig); - res.send("Success"); - }); - app.get(countlyConfig.path + '/session', function(req, res, next) { if (req.session.auth_token) { authorize.verify_return({ @@ -931,7 +913,6 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ **/ function renderDashboard(req, res, next, member, adminOfApps, userOfApps, countlyGlobalApps, countlyGlobalAdminApps) { var configs = plugins.getConfig("frontend", member.settings), - countly_tracking = plugins.isPluginEnabled('tracker') ? true : plugins.getConfig('frontend').countly_tracking, countly_domain = plugins.getConfig('api').domain, licenseNotification, licenseError; var isLocked = false; @@ -1008,6 +989,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ member: member, config: req.config, security: plugins.getConfig("security"), + tracking: plugins.getConfig("tracking"), plugins: plugins.getPlugins(), pluginsFull: plugins.getPlugins(true), path: countlyConfig.path || "", @@ -1020,9 +1002,8 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ countlyTypeName: overriddenCountlyNamedType, countlyTypeTrack: COUNTLY_TRACK_TYPE, countlyTypeCE: COUNTLY_TYPE_CE, - countly_tracking, countly_domain, - frontend_app: versionInfo.frontend_app || 'e70ec21cbe19e799472dfaee0adb9223516d238f', + frontend_app: versionInfo.frontend_app || "9c28c347849f2c03caf1b091ec7be8def435e85e", frontend_server: versionInfo.frontend_server || 'https://stats.count.ly/', usermenu: { feedbackLink: COUNTLY_FEEDBACK_LINK, diff --git a/frontend/express/libs/members.js b/frontend/express/libs/members.js old mode 100755 new mode 100644 index a9c42ad2f83..ec99041c7ff --- a/frontend/express/libs/members.js +++ b/frontend/express/libs/members.js @@ -12,18 +12,12 @@ var authorize = require('./../../../api/utils/authorizer.js'); //for token validations var common = require('./../../../api/utils/common.js'); var plugins = require('./../../../plugins/pluginManager.js'); -var { getUserApps } = require('./../../../api/utils/rights.js'); var configs = require('./../config', 'dont-enclose'); var countlyMail = require('./../../../api/parts/mgmt/mail.js'); -var countlyStats = require('./../../../api/parts/data/stats.js'); -var request = require('countly-request')(plugins.getConfig("security")); var url = require('url'); var crypto = require('crypto'); var argon2 = require('argon2'); -var versionInfo = require('./../version.info'), - COUNTLY_TYPE = versionInfo.type; - /** @lends module:frontend/express/libs/members */ var membersUtility = { }; //Helper functions @@ -55,44 +49,6 @@ membersUtility.emptyPermission = { } }; -/** Checks remote configuration and sets variables to configuration object - * @param {object} countlyConfigOrig - configuration settings object. Original(ar read from file) - * @param {object} countlyConfig - contiguration. Changes if are done on this object. -*/ -membersUtility.recheckConfigs = function(countlyConfigOrig, countlyConfig) { - var checkUrl = "https://count.ly/configurations/ce/tracking"; - if (COUNTLY_TYPE !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { - checkUrl = "https://count.ly/configurations/ee/tracking"; - } - if (!plugins.getConfig("api").offline_mode) { - request(checkUrl, function(error, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } - catch (ex) { - body = null; - } - } - if (body) { - if (countlyConfigOrig.web.use_intercom && typeof body.intercom !== "undefined") { - countlyConfig.web.use_intercom = body.intercom; - } - if (typeof countlyConfigOrig.web.track === "undefined" && typeof body.stats !== "undefined") { - if (body.stats) { - countlyConfig.web.track = null; - } - else { - countlyConfig.web.track = "none"; - } - } - } - }); - } -}; -var origConf = JSON.parse(JSON.stringify(membersUtility.countlyConfig)); -membersUtility.recheckConfigs(origConf, membersUtility.countlyConfig); - /** * Is hashed string argon2? * @param {string} hashedStr | argon2 hashed string @@ -334,58 +290,6 @@ membersUtility.verifyCredentials = function(username, password, callback) { } }; -/** -* Update Stats for member. -* -* @param {object} member - member properties -* @example -* membersUtility.updateStats(member ); -**/ -membersUtility.updateStats = function(member) { - if (plugins.getConfig('frontend').countly_tracking && !plugins.getConfig("api").offline_mode) { - countlyStats.getUser(membersUtility.db, member, function(statsObj) { - const userApps = getUserApps(member); - var custom = { - apps: (userApps) ? userApps.length : 0, - platforms: {"$addToSet": statsObj["total-platforms"]}, - events: statsObj["total-events"], - pushes: statsObj["total-msg-sent"], - crashes: statsObj["total-crash-groups"], - users: statsObj["total-users"] - }; - var date = new Date(); - let domain = plugins.getConfig('api').domain; - - try { - // try to extract hostname from full domain url - const urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } - - request({ - uri: "https://stats.count.ly/i", - method: "GET", - timeout: 4E3, - qs: { - device_id: domain, - app_key: "e70ec21cbe19e799472dfaee0adb9223516d238f", - timestamp: Math.round(date.getTime() / 1000), - hour: date.getHours(), - dow: date.getDay(), - user_details: JSON.stringify( - { - custom: custom - } - ) - } - }, function() {}); - }); - } -}; - /** * Tries to log in user based passed userame and password. Calls "plugins" * methods to notify successful and unsucessful logging in attempts. If @@ -422,9 +326,6 @@ membersUtility.login = function(req, res, callback) { else { plugins.callMethod("loginSuccessful", {req: req, data: member}); - // update stats - membersUtility.updateStats(member); - req.session.regenerate(function() { // will have a new session here @@ -495,9 +396,6 @@ membersUtility.loginWithExternalAuthentication = function(req, res, callback) { else { plugins.callMethod("loginSuccessful", {req: req, data: member}); - // update stats - membersUtility.updateStats(member); - req.session.regenerate(function() { // will have a new session here diff --git a/frontend/express/public/core/onboarding/javascripts/countly.views.js b/frontend/express/public/core/onboarding/javascripts/countly.views.js index e8a147cd5b7..3e9739f208f 100644 --- a/frontend/express/public/core/onboarding/javascripts/countly.views.js +++ b/frontend/express/public/core/onboarding/javascripts/countly.views.js @@ -179,9 +179,8 @@ template: CV.T('/core/onboarding/templates/consent.html'), data: function() { return { - isCountlyHosted: countlyGlobal.plugins.includes('tracker'), + isCountlyHosted: true, newConsent: { - countly_tracking: true, countly_newsletter: true, }, }; @@ -226,43 +225,8 @@ }; countlyPlugins.updateConfigs(configs); - var domain = countlyGlobal.countly_domain || window.location.origin; - - try { - // try to extract hostname from full domain url - var urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } - - var statsUrl = 'https://stats.count.ly/i'; - - try { - var uObj = new URL(countlyGlobal.frontend_server); - uObj.pathname = '/i'; - statsUrl = uObj.href; - } - catch (_) { - // do nothing, statsUrl will be used as is - } - - CV.$.ajax({ - type: 'GET', - url: statsUrl, - data: { - consent: JSON.stringify({countly_tracking: doc.countly_tracking}), - app_key: countlyGlobal.frontend_app, - device_id: (window.Countly && window.Countly.device_id) || domain, - }, - dataType: 'json', - complete: function() { - // go home - window.location.href = '#/home'; - window.location.reload(); - } - }); + window.location.href = '#/home'; + window.location.reload(); }, } }); @@ -271,7 +235,7 @@ template: CV.T('/core/onboarding/templates/consent.html'), data: function() { return { - isCountlyHosted: countlyGlobal.plugins.includes('tracker'), + isCountlyHosted: true, newConsent: { countly_newsletter: true, }, @@ -323,88 +287,6 @@ } }); - var notRespondedConsentView = CV.views.create({ - template: CV.T('/core/onboarding/templates/consent.html'), - data: function() { - return { - isCountlyHosted: countlyGlobal.plugins.includes('tracker'), - newConsent: { - countly_tracking: null, - }, - }; - }, - mounted: function() { - this.$store.dispatch('countlyOnboarding/fetchConsentItems'); - }, - computed: { - consentItems: function() { - return this.$store.getters['countlyOnboarding/consentItems'] - .filter(function(item) { - return item.type === 'tracking'; - }); - }, - }, - methods: { - decodeHtmlEntities: function(inp) { - var el = document.createElement('p'); - el.innerHTML = inp; - - var result = el.textContent || el.innerText; - el = null; - - return result; - }, - handleSubmit: function(doc) { - var configs = { - frontend: doc, - }; - - if (this.consentItems.length === 0) { - configs.frontend.countly_tracking = false; - } - - countlyPlugins.updateConfigs(configs); - var domain = countlyGlobal.countly_domain || window.location.origin; - - try { - // try to extract hostname from full domain url - var urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } - - var statsUrl = 'https://stats.count.ly/i'; - - try { - var uObj = new URL(countlyGlobal.frontend_server); - uObj.pathname = '/i'; - statsUrl = uObj.href; - } - catch (_) { - // do nothing, statsUrl will be used as is - } - - CV.$.ajax({ - type: 'GET', - url: statsUrl, - data: { - consent: JSON.stringify({countly_tracking: doc.countly_tracking}), - app_key: countlyGlobal.frontend_app, - device_id: (window.Countly && window.Countly.device_id) || domain, - }, - dataType: 'json', - complete: function() { - // go home - window.location.href = '#/home'; - window.location.reload(); - } - }); - }, - } - }); - app.route('/initial-setup', 'initial-setup', function() { this.renderWhenReady(new CV.views.BackboneWrapper({ component: appSetupView, @@ -419,13 +301,6 @@ })); }); - app.route('/not-responded-consent', 'not-responded-consent', function() { - this.renderWhenReady(new CV.views.BackboneWrapper({ - component: notRespondedConsentView, - vuex: [{ clyModel: countlyOnboarding }], - })); - }); - var hasNewsLetter = typeof countlyGlobal.newsletter === "undefined" ? true : countlyGlobal.newsletter; app.route('/not-subscribed-newsletter', 'not-subscribed-newsletter', function() { @@ -470,12 +345,7 @@ } }); - if (typeof countlyGlobal.countly_tracking !== 'boolean' && isGlobalAdmin && !countlyGlobal.plugins.includes('tracker')) { - if (Backbone.history.fragment !== '/not-responded-consent' && !/initial-setup|initial-consent/.test(window.location.hash)) { - app.navigate("/not-responded-consent", true); - } - } - else if (hasNewsLetter && (typeof countlyGlobal.member.subscribe_newsletter !== 'boolean' && !store.get('disable_newsletter_prompt') && (countlyGlobal.member.login_count === 3 || moment().dayOfYear() % 90 === 0))) { + if (hasNewsLetter && (typeof countlyGlobal.member.subscribe_newsletter !== 'boolean' && !store.get('disable_newsletter_prompt') && (countlyGlobal.member.login_count === 3 || moment().dayOfYear() % 90 === 0))) { if (Backbone.history.fragment !== '/not-subscribed-newsletter' && !/initial-setup|initial-consent/.test(window.location.hash)) { app.navigate("/not-subscribed-newsletter", true); } diff --git a/frontend/express/views/dashboard.html b/frontend/express/views/dashboard.html index 1c6ee63a39c..3b4cb7c32e5 100644 --- a/frontend/express/views/dashboard.html +++ b/frontend/express/views/dashboard.html @@ -1886,187 +1886,138 @@

{{this.nam <% } %> - <% if (!offline_mode) { %> - - - + - <% } %> + } + diff --git a/plugins/plugins/frontend/public/javascripts/countly.views.js b/plugins/plugins/frontend/public/javascripts/countly.views.js index 27bae3d1761..9bf0f63590d 100644 --- a/plugins/plugins/frontend/public/javascripts/countly.views.js +++ b/plugins/plugins/frontend/public/javascripts/countly.views.js @@ -298,7 +298,7 @@ back: this.$route.params.namespace === "search", configsData: {}, configsList: [], - coreDefaults: ['api', 'frontend', 'logs', 'security'], + coreDefaults: ['api', 'frontend', 'logs', 'security', 'tracking'], diff: [], diff_: {}, selectedConfig: this.$route.params.namespace || "api", @@ -344,10 +344,7 @@ app.configurationsView.registerInput("frontend.__user", {input: "el-select", attrs: {multiple: true}, list: list}); } - if (self.configsData.frontend && countlyGlobal.plugins.includes('tracker')) { - // disable countly tracking config for countly hosted instances - delete self.configsData.frontend.countly_tracking; - } + delete self.configsData.frontend.countly_tracking; self.configsList.push({ "label": self.getLabel("core"), @@ -513,7 +510,7 @@ }, getLabelName: function(id, ns) { ns = ns || this.selectedConfig; - if (ns !== "frontend" && ns !== "api" && ns !== "apps" && ns !== "logs" && ns !== "security" && ns !== "feedback" && countlyGlobal.plugins.indexOf(ns) === -1) { + if (this.coreDefaults.indexOf(ns) === -1 && ns !== "feedback" && countlyGlobal.plugins.indexOf(ns) === -1) { return null; } @@ -1182,17 +1179,25 @@ } }); - var appList = [{value: "", label: jQuery.i18n.map["configs.frontend-self_tracking.none"]}]; + var appList = [{value: "", label: jQuery.i18n.map["configs.tracking.self_tracking.none"]}]; for (var a in countlyGlobal.apps) { appList.push({value: countlyGlobal.apps[a].key, label: countlyGlobal.apps[a].name}); } - app.configurationsView.registerInput("frontend.self_tracking", { + app.configurationsView.registerInput("tracking.self_tracking_app", { input: "el-select", attrs: {}, list: appList }); + var idList = [{value: "_id", label: "_id"}, {value: "email", label: "email"}]; + + app.configurationsView.registerInput("tracking.self_tracking_id_policy", { + input: "el-select", + attrs: {}, + list: idList + }); + app.configurationsView.registerStructure("api", { description: "configs.api.description", groups: [ @@ -1203,6 +1208,15 @@ ] }); + app.configurationsView.registerStructure("tracking", { + description: "configs.tracking.description", + groups: [ + {label: "configs.tracking.self_tracking", list: ["self_tracking_app", "self_tracking_url", "self_tracking_app_key", "self_tracking_id_policy", "self_tracking_sessions", "self_tracking_events", "self_tracking_crashes", "self_tracking_views", "self_tracking_feedback", "self_tracking_user_details"]}, + {label: "configs.tracking.user", list: ["user_sessions", "user_events", "user_crashes", "user_views", "user_feedback", "user_details"]}, + {label: "configs.tracking.server", list: ["server_sessions", "server_events", "server_crashes", "server_views", "server_feedback", "server_user_details"]}, + ] + }); + app.configurationsView.registerStructure("logs", { description: "", groups: [ diff --git a/plugins/plugins/frontend/public/localization/plugins.properties b/plugins/plugins/frontend/public/localization/plugins.properties index eb10290d9c6..aecc967e390 100644 --- a/plugins/plugins/frontend/public/localization/plugins.properties +++ b/plugins/plugins/frontend/public/localization/plugins.properties @@ -64,8 +64,6 @@ configs.no-theme = Default Theme configs.frontend-code = Show Code Generator for SDK integration configs.frontend-offline_mode = Offline mode configs.frontend-countly_tracking = Countly -configs.frontend-self_tracking = Self-tracking using Countly -configs.frontend-self_tracking.none = -- Not tracked -- configs.security-login_tries = Allowed login attempts configs.security-login_wait = Incorrect login block time increment configs.security-dashboard_additional_headers = Additional Dashboard HTTP Response headers @@ -179,7 +177,6 @@ configs.help.frontend-countly_tracking = When enabled, Countly will be activated configs.help.frontend-production = Initial load of dashboard should be faster, due to smaller files and smaller file amount, but when developing a plugin, you need to regenerate them to see changes configs.help.frontend-theme = Selected theme will be available server-wide, for all apps and users configs.help.frontend-session_timeout = User will be forced to logout after session timeout (in minutes) of inactivity. If you want to disable force logout, set to 0. -configs.help.frontend-self_tracking = If you want to track usage of this server and users that are using the dashboard, select an app where to collect this data. Make sure to create a new app specifically for this purpose, or else you would merge collected data with existing. Data will only be stored on this server and will not be sent anywhere else. By selecting an app, you will enabling tracking of this server and it will count towards your datapoint quota. The scale of datapoints will depend on your user count and often usage of this dashboard. configs.help.security-login_tries = Account will be blocked for some time after provided number of incorrect login attempts. See below for time increments. configs.help.security-login_wait = Incremental period of time account is blocked after provided number of incorrect login attempts (in seconds) configs.help.security-password_rotation = Amount of previous passwords user should not be able to reuse @@ -252,3 +249,53 @@ configs.tooltip.server-performance = Performance systemlogs.action.change_configs = Setting Changed systemlogs.action.change_plugins = Plugins Changed + +configs.tracking = Tracking +configs.tracking.description = Control the tracking settings of this server +configs.tracking.server = Server level tracking that helps Countly improve their product. All data is collected on the server level and cannot be tied to any specific user +configs.tracking.user = User level tracking that is enabled only with consent from the user +configs.tracking.self_tracking = Self-tracking using Countly +configs.tracking.self_tracking.none = -- Not tracked -- +configs.tracking.self_tracking_app = Track using this same server by selecting an app +configs.tracking.self_tracking_url = Track using other server, provide Server URL +configs.tracking.self_tracking_app_key = Tracking using other server, provide App Key +configs.tracking.self_tracking_id_policy = What user identifier to use for self tracking +configs.tracking.self_tracking_sessions = Allow tracking self tracking sessions +configs.tracking.self_tracking_events = Allow tracking self tracking events +configs.tracking.self_tracking_crashes = Allow tracking self tracking crashes +configs.tracking.self_tracking_views = Allow tracking self tracking views +configs.tracking.self_tracking_feedback = Allow tracking self tracking feedback +configs.tracking.self_tracking_user_details = Allow tracking self tracking user details +configs.tracking.server_sessions = Allow tracking server uptime +configs.tracking.server_events = Allow tracking feature usage +configs.tracking.server_crashes = Allow tracking server errors +configs.tracking.server_views = Allow tracking visited sections +configs.tracking.server_feedback = Allow us to ask for feedback periodically +configs.tracking.server_user_details = Allow collecting server specific properties +configs.tracking.user_sessions = Allow tracking user sessions +configs.tracking.user_events = Allow tracking feature usage on user level +configs.tracking.user_crashes = Allow tracking errors +configs.tracking.user_views = Allow tracking visited sections of the dashboard on user level +configs.tracking.user_feedback = Allow asking users for feedback +configs.tracking.user_details = Allow tracking ownership of features +configs.help.tracking-self_tracking_app = If you want to track usage of this server and users that are using the dashboard, select an app where to collect this data. Make sure to create a new app specifically for this purpose, or else you would merge collected data with existing. Data will only be stored on this server and will not be sent anywhere else. By selecting an app, you will enabling tracking of this server and it will count towards your datapoint quota. The scale of datapoints will depend on your user count and often usage of this dashboard. +configs.help.tracking.self_tracking_url = If you want to report self tracking to some other server, instead of selecting an app, you can provide Countly server URL here. Make sure to also provide App Key below. Data will only be stored on the provided server and will not be sent anywhere else. By providing a URL, you will enabling tracking of this server and it will count towards your datapoint quota on the provided server. The scale of datapoints will depend on your user count and often usage of this dashboard. +configs.help.tracking.self_tracking_app_key = If you want to report self tracking to some other server, instead of selecting an app, you can provide Countly App Key here. Make sure to also provide Server URL above. Data will only be stored on the provided server and will not be sent anywhere else. By providing an App Key, you will enabling tracking of this server and it will count towards your datapoint quota on the provided server. The scale of datapoints will depend on your user count and often usage of this dashboard. +configs.help.tracking.self_tracking_id_policy = You can choose which user property to use to identify users. Selecting _id would provide more anonymous tracking, while selecting email address would allow you to see which of your users are using the dashboard most often. No personal data will be collected or sent anywhere else, but if you are using email address, it will be stored on this server and would be visible to your server administrator. +configs.help.tracking.self_tracking_sessions = Tracking how often users use dashboard +configs.help.tracking.self_tracking_events = Tracking which features users use in the dashboard +configs.help.tracking.self_tracking_views = Tracking which sections users visit during the time the use dashboard +configs.help.tracking.self_tracking_feedback = Displaying NPS, ratings and Surveys targeted to specific users +configs.help.tracking.self_tracking_user_details = Populating user properties with information we know about users +configs.help.tracking-server_sessions = Tracking server uptime and restarts. Also collects OS, OS version +configs.help.tracking-server_events = Tracking feature usability. We collect aggregated data per day on how many actions were performed on the server, like creating, editing and deleting something +configs.help.tracking-server_crashes = Tracking server errors. If any error happens on the backend that may impact server usability, we would see the stack trace of that problem and time when it happened. +configs.help.tracking-server_views = Tracking which sections of the dashboard are visited and how often in aggregate from all users +configs.help.tracking-server_feedback = Receiving some server wide periodic feedback checks from us, like asking you to rate your experience or notify of issues. Feedback will be collected anonymously and in aggregate +configs.help.tracking-server_user_details = Allow tracking some properties that define how server is used. How many apps are on the server, how many users using the server (only numbers), when somebody logged in to server last time, and when was the last time server received any data +configs.help.tracking-user_sessions = Tracking how often users use dashboard +configs.help.tracking-user_events = Tracking which features users use in the dashboard +configs.help.tracking-user_crashes = Tracking errors in user interaction with UI +configs.help.tracking-user_views = Tracking which sections users visit during the time the use dashboard +configs.help.tracking-user_feedback = Displaying NPS, ratings and Surveys targeted to specific users +configs.help.tracking-user_details = Tracking how many cohorts, funnels, journeys, surveys, NPS, ratings, alerts, dashboards, widgets, email_reports, hooks, formulas, drill queries users have created \ No newline at end of file diff --git a/plugins/server-stats/api/api.js b/plugins/server-stats/api/api.js index abd498dd544..09ca36ce3ea 100644 --- a/plugins/server-stats/api/api.js +++ b/plugins/server-stats/api/api.js @@ -21,7 +21,7 @@ const internalEventsSkipped = ["[CLY]_orientation"]; plugins.register("/master", function() { // Allow configs to load & scanner to find all jobs classes setTimeout(() => { - require('../../../api/parts/jobs').job('server-stats:stats').replace().schedule('every 1 day'); + require('../../../api/parts/jobs').job('server-stats:stats').replace().schedule('at 00:01 am ' + 'every 1 day'); }, 10000); }); diff --git a/plugins/server-stats/api/jobs/stats.js b/plugins/server-stats/api/jobs/stats.js index 894ca43e416..95ecf8cb88e 100644 --- a/plugins/server-stats/api/jobs/stats.js +++ b/plugins/server-stats/api/jobs/stats.js @@ -3,11 +3,9 @@ const job = require('../../../../api/parts/jobs/job.js'), tracker = require('../../../../api/parts/mgmt/tracker.js'), log = require('../../../../api/utils/log.js')('job:stats'), - config = require('../../../../frontend/express/config.js'), pluginManager = require('../../../pluginManager.js'), serverStats = require('../parts/stats.js'), - moment = require('moment-timezone'), - request = require('countly-request')(pluginManager.getConfig('security')); + moment = require('moment-timezone'); let drill; try { @@ -18,14 +16,6 @@ catch (ex) { drill = null; } -const promisedLoadConfigs = function(db) { - return new Promise((resolve) => { - pluginManager.loadConfigs(db, () => { - resolve(); - }); - }); -}; - /** Representing a StatsJob. Inherits api/parts/jobs/job.js (job.Job) */ class StatsJob extends job.Job { /** @@ -73,9 +63,19 @@ class StatsJob extends job.Job { const ids6 = {}; const ids0 = {}; const order = []; + var thisMonth = ""; + var lastMonth = ""; + var thisMonthDP = 0; + var lastMonthDP = 0; for (let i = 0; i < 12; i++) { order.push(utcMoment.format('MMM YYYY')); + if (i === 0) { + thisMonth = utcMoment.format('YYYY:M'); + } + if (i === 1) { + lastMonth = utcMoment.format('YYYY:M'); + } ids[utcMoment.format('YYYY:M')] = utcMoment.format('MMM YYYY'); if (i < 7) { ids6[utcMoment.format('YYYY:M')] = utcMoment.format('MMM YYYY'); @@ -105,6 +105,12 @@ class StatsJob extends job.Job { avg6monthDP += DP[ids[key]]; avg6++; } + if (key === thisMonth) { + thisMonthDP = value; + } + if (key === lastMonth) { + lastMonthDP = value; + } } } @@ -112,10 +118,16 @@ class StatsJob extends job.Job { data.DP.push((i < 9 ? '0' + (i + 1) : i + 1) + '. ' + order[i] + ': ' + ((DP[order[i]] || 0).toLocaleString())); } if (avg12) { - data['Last 12 months'] = Math.round(avg12monthDP / avg12).toLocaleString(); + data.DPAvg12months = Math.round(avg12monthDP / avg12); } if (avg6) { - data['Last 6 months'] = Math.round(avg6monthDP / avg6).toLocaleString(); + data.DPAvg6months = Math.round(avg6monthDP / avg6); + } + if (lastMonthDP) { + data.DPLastMonth = lastMonthDP; + } + if (thisMonthDP) { + data.DPThisMonth = thisMonthDP; } return data; @@ -128,94 +140,80 @@ class StatsJob extends job.Job { * @returns {undefined} Returns nothing, only callback **/ run(db, done) { - if (config.web.track !== 'none') { - db.collection('members').find({global_admin: true}).toArray(async(err, members) => { - if (!err && members.length > 0) { - let license = {}; - if (drill) { - try { - license = await drill.loadLicense(undefined, db); - } - catch (error) { - log.e(error); - // do nothing, most likely there is no license - } - } - - const options = { - monthlyBreakdown: true, - license_hosting: license.license_hosting, - }; - - serverStats.fetchDatapoints(db, {}, options, async(allData) => { - const dataSummary = StatsJob.generateDataSummary(allData); - - let date = new Date(); - const usersData = []; + pluginManager.loadConfigs(db, async function() { + let license = {}; + if (drill) { + try { + license = await drill.loadLicense(undefined, db); + } + catch (error) { + log.e(error); + // do nothing, most likely there is no license + } + } - await promisedLoadConfigs(db); + var server = tracker.getBulkServer(); + var user = tracker.getBulkUser(server); + if (!user) { + return done(); + } + var days = 30; + var current_sync = Date.now(); + + //generate dates in YYYY:M:D format for dates from "days" variable back up to today + const specificDates = []; + const utcMoment = moment.utc(); + for (let i = 0; i < days; i++) { + specificDates.push(utcMoment.format('YYYY:M:D')); + utcMoment.subtract(1, 'days'); + } - let domain = ''; + const options = { + dailyDates: specificDates, + monthlyBreakdown: true, + license_hosting: license?.license_hosting, + }; + + // Atomically retrieve old last_sync value and set new one + var syncResult = await db.collection("plugins").findOneAndUpdate( + {_id: "version"}, + {$set: {last_dp_sync: current_sync}}, + { + upsert: true, + returnDocument: 'before', + projection: {last_dp_sync: 1} + } + ); - try { - // try to extract hostname from full domain url - const urlObj = new URL(pluginManager.getConfig('api').domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } + var last_dp_sync = syncResult.value ? syncResult.value.last_dp_sync : null; + if (last_dp_sync) { + days = Math.floor((new Date().getTime() - last_dp_sync) / (1000 * 60 * 60 * 24)); + } - usersData.push({ - device_id: domain, - timestamp: Math.floor(date.getTime() / 1000), - hour: date.getHours(), - dow: date.getDay(), - user_details: JSON.stringify({ - custom: { - dataPointsAll: dataSummary.all, - dataPointsMonthlyAvg: dataSummary.avg, - dataPointsLast3Months: dataSummary.month3, - }, - }), - }); - - var formData = { - app_key: 'e70ec21cbe19e799472dfaee0adb9223516d238f', - requests: JSON.stringify(usersData) - }; - - request.post({ - url: 'https://stats.count.ly/i/bulk', - json: formData - }, function(a) { - log.d('Done running stats job: %j', a); - done(); - }); - - if (tracker.isEnabled()) { - const dataMonthly = StatsJob.generateDataMonthly(allData); - - const Countly = tracker.getSDK(); - Countly.user_details({ - 'custom': dataMonthly, - }); - - Countly.userData.save(); + if (days > 0) { + serverStats.fetchDatapoints(db, {}, options, async(allData) => { + const dataMonthly = StatsJob.generateDataMonthly(allData); + + if (allData.daily) { + for (const key in allData.daily) { + var parts = key.split(':'); + //convert date in YYYY:M:D format to timestamp for noon (12:00:00) of that day in UTC + const timestamp = moment.tz(parts[0] + '-' + parts[1] + '-' + parts[2] + ' 12:00:00', 'YYYY-M-D HH:mm:ss', 'UTC').valueOf(); + //send datapoint event with timestamp for noon of that day + user.add_event({key: "DP", count: allData.daily[key], timestamp: timestamp}); } + } + user.user_details({'custom': dataMonthly}); + server.start(function() { + server.stop(); + done(); }); - } - else { - done(); - } - }); - } - else { - db.collection('plugins').updateOne({_id: 'plugins'}, {$unset: {remoteConfig: 1}}).catch(dbe => { - log.e('Db error', dbe); - }); - done(); - } + }); + } + else { + done(); + } + }); } } diff --git a/plugins/server-stats/api/parts/stats.js b/plugins/server-stats/api/parts/stats.js index da95217a233..1cc51278f04 100644 --- a/plugins/server-stats/api/parts/stats.js +++ b/plugins/server-stats/api/parts/stats.js @@ -236,6 +236,7 @@ function punchCard(db, filter, options) { * @param {object} options - array with periods * @param {boolean} options.monthlyBreakdown - if true, will calculate monthly data points breakdown for all apps (used to get license metric) * @param {string} options.license_hosting - client hosting type, could be countly hosted or self hosted. This will determine how consolidated data points should be added to total data points + * @param {boolean} options.dailyDates - array of dates in YYYY:M:D format for daily data points (used to get data points for last 30 days) * @param {function} callback - callback */ function fetchDatapoints(db, filter, options, callback) { @@ -274,6 +275,22 @@ function fetchDatapoints(db, filter, options, callback) { acc[current.m] = dp; } + if (options.dailyDates && options.dailyDates.length && current.m && (!/^\[CLY\]_consolidated/.test(current._id) || options.license_hosting === 'Countly-Hosted')) { + if (!acc.daily) { + acc.daily = {}; + } + options.dailyDates.forEach(date => { + if (date.startsWith(current.m)) { + var day = date.split(":")[2]; + if (current.d && current.d[day] && Object.keys(current.d[day]).length) { + for (var hour in current.d[day]) { + acc.daily[date] = (acc.daily[date] || 0) + (current.d[day][hour].e || 0) + (current.d[day][hour].s || 0); + } + } + } + }); + } + return acc; }, {}); diff --git a/plugins/server-stats/frontend/app.js b/plugins/server-stats/frontend/app.js index 32a27465b01..e69de29bb2d 100644 --- a/plugins/server-stats/frontend/app.js +++ b/plugins/server-stats/frontend/app.js @@ -1,86 +0,0 @@ -var pluginExported = {}; -var versionInfo = require('../../../frontend/express/version.info'); -var moment = require('moment'); -const plugins = require('../../pluginManager.js'); -const request = require('countly-request')(plugins.getConfig("security")); -const { getUserApps } = require('../../../api/utils/rights'); - -(function(plugin) { - plugin.init = function(app, countlyDb) { - plugin.loginSuccessful = function(ob) { - var member = ob.data; - if (plugins.getConfig('frontend').countly_tracking) { - var match = {}; - if (versionInfo.trial) { - match.a = {$in: getUserApps(member) || []}; - } - countlyDb.collection("server_stats_data_points").aggregate([ - { - $match: match - }, - { - $group: { - _id: "$m", - e: { $sum: "$e"}, - s: { $sum: "$s"} - } - } - ], { allowDiskUse: true }, function(error, allData) { - var custom = {}; - if (!error && allData && allData.length) { - var data = {}; - data.all = 0; - data.month3 = []; - var utcMoment = moment.utc(); - var months = {}; - for (let i = 0; i < 3; i++) { - months[utcMoment.format("YYYY:M")] = true; - utcMoment.subtract(1, 'months'); - } - for (let i = 0; i < allData.length; i++) { - data.all += allData[i].e + allData[i].s; - if (months[allData[i]._id]) { - data.month3.push(allData[i]._id + " - " + (allData[i].e + allData[i].s)); - } - } - data.avg = Math.round((data.all / allData.length) * 100) / 100; - custom.dataPointsAll = data.all; - custom.dataPointsMonthlyAvg = data.avg; - custom.dataPointsLast3Months = data.month3; - var date = new Date(); - let domain = plugins.getConfig('api').domain; - - try { - // try to extract hostname from full domain url - const urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } - - request({ - uri: "https://stats.count.ly/i", - method: "GET", - timeout: 4E3, - qs: { - device_id: domain, - app_key: "e70ec21cbe19e799472dfaee0adb9223516d238f", - timestamp: Math.round(date.getTime() / 1000), - hour: date.getHours(), - dow: date.getDay(), - user_details: JSON.stringify( - { - custom: custom - } - ) - } - }, function(/*error, response, body*/) {}); - } - }); - } - }; - }; -}(pluginExported)); - -module.exports = pluginExported; \ No newline at end of file diff --git a/plugins/server-stats/tests/job.js b/plugins/server-stats/tests/job.js index 0657d113fda..179160b32c1 100644 --- a/plugins/server-stats/tests/job.js +++ b/plugins/server-stats/tests/job.js @@ -15,6 +15,11 @@ for (let count = 0; count < 12; count += 1) { describe('Stats job', () => { it('Generates data summary', () => { const { all, avg, month3 } = StatsJob.generateDataSummary(allData); + console.log('All Data:', allData); + console.log('Data Summary:'); + console.log(`- All: ${all}`); + console.log(`- Avg: ${avg}`); + console.log(`- Month 3: ${month3}`); should(all).equal(12000); should(avg).equal(1000); @@ -32,9 +37,11 @@ describe('Stats job', () => { it('Generates data monthly', () => { const monthlyData = StatsJob.generateDataMonthly(allData); + console.log('All Data:', allData); + console.log(monthlyData); - should(monthlyData['Last 6 months']).equal((1000).toLocaleString()); - should(monthlyData['Last 12 months']).equal((1000).toLocaleString()); + should(monthlyData['DPAvg6months']).equal(1000); + should(monthlyData['DPAvg12months']).equal(1000); const expectedDP = []; for (let count = 0; count < 12; count += 1) { diff --git a/plugins/system-utility/api/api.js b/plugins/system-utility/api/api.js index 436ea2af9f9..ef04f854d40 100644 --- a/plugins/system-utility/api/api.js +++ b/plugins/system-utility/api/api.js @@ -1,6 +1,6 @@ var plugin = {}, common = require('../../../api/utils/common.js'), - tracker = require('../../../api/parts/mgmt/tracker.js'), + //tracker = require('../../../api/parts/mgmt/tracker.js'), plugins = require('../../pluginManager.js'), systemUtility = require('./system.utility'), log = common.log('system-utility:api'), @@ -261,7 +261,7 @@ function stopWithTimeout(type, fromTimeout = false) { }); - plugins.register("/master", function() { + /*plugins.register("/master", function() { //ping server stats periodically if (tracker.isEnabled()) { var timeout = 1000 * 60 * 15; @@ -326,7 +326,7 @@ function stopWithTimeout(type, fromTimeout = false) { }; getServerStats(); } - }); + });*/ }(plugin)); module.exports = plugin; \ No newline at end of file diff --git a/ui-tests/cypress/lib/dashboard/manage/configurations/configurations.js b/ui-tests/cypress/lib/dashboard/manage/configurations/configurations.js index e19040fea5a..92655d9bc7e 100755 --- a/ui-tests/cypress/lib/dashboard/manage/configurations/configurations.js +++ b/ui-tests/cypress/lib/dashboard/manage/configurations/configurations.js @@ -514,21 +514,6 @@ const verifyPageElements = () => { isChecked: true }); - cy.verifyElement({ - labelElement: configurationsListBoxElements({ subFeature: SETTINGS.FRONTED.COUNTLY_TRACKING }).SELECTED_SUBFEATURE_TITLE, - labelText: "Countly", - }); - - cy.verifyElement({ - labelElement: configurationsListBoxElements({ subFeature: SETTINGS.FRONTED.COUNTLY_TRACKING }).SELECTED_SUBFEATURE_DESCRIPTION, - labelText: "When enabled, Countly will be activated on this server to perform server-level analytics and gather user feedback to aid us in continuous product improvement. Personal user data/details or the data you process using this server will never be collected or analyzed. All data is sent exclusively to our dedicated Countly server located in Europe.", - }); - - cy.verifyElement({ - element: configurationsListBoxElements({ subFeature: SETTINGS.FRONTED.COUNTLY_TRACKING }).SELECTED_SUBFEATURE_CHECKBOX, - //isChecked: true //TODO: if empty data, it should be false - }); - cy.verifyElement({ labelElement: configurationsListBoxElements({ subFeature: SETTINGS.FRONTED.OFFLINE_MODE }).SELECTED_SUBFEATURE_TITLE, labelText: "Offline mode", diff --git a/ui-tests/cypress/lib/onboarding/initialConsent.js b/ui-tests/cypress/lib/onboarding/initialConsent.js index 57afc8a10f1..c1b02f5cf45 100644 --- a/ui-tests/cypress/lib/onboarding/initialConsent.js +++ b/ui-tests/cypress/lib/onboarding/initialConsent.js @@ -1,13 +1,5 @@ import initialConsentPageElements from "../../support/elements/onboarding/initialConsent"; -const enableTracking = () => { - cy.clickElement(initialConsentPageElements.ENABLE_TRACKING_RADIO_BUTTON); -}; - -const dontEnableTracking = () => { - cy.clickElement(initialConsentPageElements.DONT_ENABLE_TRACKING_RADIO_BUTTON); -}; - const subscribeToNewsletter = () => { cy.clickElement(initialConsentPageElements.ENABLE_NEWSLETTER_RADIO_BUTTON); }; @@ -33,16 +25,6 @@ const verifyDefaultPageElements = () => { labelText: "Before we start..." }); - cy.verifyElement({ - labelElement: initialConsentPageElements.PAGE_DESC_TRACKING, - labelText: "We utilize Countly to understand user interactions and collect feedback, helping us enhance our product continuously. However, your privacy remains our priority. This analysis is done on the server level, so we won't see or collect any individual details or any data you record. The data is reported back only to our dedicated Countly server based in Europe. Please note, you can change your mind at any time in the settings." - }); - - cy.verifyElement({ - labelElement: initialConsentPageElements.PAGE_QUESTION_TRACKING, - labelText: "Considering our commitment to maintaining your privacy and the potential benefits for product enhancement, would you be comfortable enabling Countly on this server?" - }); - cy.verifyElement({ labelElement: initialConsentPageElements.PAGE_DESC_NEWSLETTER, labelText: "We offer a newsletter brimming with recent updates about our product, news from Countly, and information on product analytics. We assure you - our aim is to provide value and insights, not clutter your inbox with unwanted emails." @@ -53,20 +35,6 @@ const verifyDefaultPageElements = () => { labelText: "Would you be interested in subscribing to our newsletter?" }); - cy.verifyElement({ - element: initialConsentPageElements.ENABLE_TRACKING_RADIO_BUTTON, - isChecked: true, - labelElement: initialConsentPageElements.ENABLE_TRACKING_RADIO_BUTTON_LABEL, - labelText: "Yes, enable tracking on this server" - }); - - cy.verifyElement({ - element: initialConsentPageElements.DONT_ENABLE_TRACKING_RADIO_BUTTON, - isChecked: false, - labelElement: initialConsentPageElements.DONT_ENABLE_TRACKING_RADIO_BUTTON_LABEL, - labelText: "No, maybe later" - }); - cy.verifyElement({ element: initialConsentPageElements.ENABLE_NEWSLETTER_RADIO_BUTTON, //isChecked: true, @@ -88,17 +56,13 @@ const verifyDefaultPageElements = () => { }; const completeOnboardingInitialConsent = ({ - isEnableTacking, isSubscribeToNewsletter, }) => { - isEnableTacking ? enableTracking() : dontEnableTracking(); isSubscribeToNewsletter ? subscribeToNewsletter() : dontSubscribeToNewsletter(); clickContinue(); }; module.exports = { - enableTracking, - dontEnableTracking, subscribeToNewsletter, dontSubscribeToNewsletter, verifyDefaultPageElements, diff --git a/ui-tests/cypress/support/constants.js b/ui-tests/cypress/support/constants.js index 59d1d2721a1..a1377dfbf45 100644 --- a/ui-tests/cypress/support/constants.js +++ b/ui-tests/cypress/support/constants.js @@ -162,7 +162,6 @@ module.exports.SETTINGS = { FRONTED: { CODE: 'Code', - COUNTLY_TRACKING: 'Countly Tracking', OFFLINE_MODE: 'Offline Mode', PRODUCTION: 'Production', SESSION_TIMEOUT: 'Session Timeout', diff --git a/ui-tests/cypress/support/elements/onboarding/initialConsent.js b/ui-tests/cypress/support/elements/onboarding/initialConsent.js index 6bfb97831a9..69ad6656332 100644 --- a/ui-tests/cypress/support/elements/onboarding/initialConsent.js +++ b/ui-tests/cypress/support/elements/onboarding/initialConsent.js @@ -1,12 +1,6 @@ export default { LOGO: 'countly-logo', PAGE_TITLE: 'page-title', - PAGE_DESC_TRACKING: 'page-description-tracking', - PAGE_QUESTION_TRACKING: 'page-question-tracking', - ENABLE_TRACKING_RADIO_BUTTON_LABEL: 'enable-tracking-el-radio-label', - ENABLE_TRACKING_RADIO_BUTTON: 'enable-tracking-el-radio', - DONT_ENABLE_TRACKING_RADIO_BUTTON_LABEL: 'dont-enable-tracking-el-radio-label', - DONT_ENABLE_TRACKING_RADIO_BUTTON: 'dont-enable-tracking-el-radio', PAGE_DESC_NEWSLETTER: 'page-description-newsletter', PAGE_QUESTION_NEWSLETTER: 'page-question-newsletter', ENABLE_NEWSLETTER_RADIO_BUTTON_LABEL: 'enable-newsletter-el-radio-label',