feat(@desktop/metrics): send basic metrics (#15803) (#16040)

Issue #15737

Co-authored-by: Michał Iskierko <61889657+endulab@users.noreply.github.com>
This commit is contained in:
Jonathan Rainville 2024-08-08 16:53:49 -04:00 committed by GitHub
parent ada348486e
commit 0470723a5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 104 additions and 30 deletions

View File

@ -151,7 +151,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.settingsService = settings_service.newService(statusFoundation.events) result.settingsService = settings_service.newService(statusFoundation.events)
result.appSettingsVariant = newQVariant(result.settingsService) result.appSettingsVariant = newQVariant(result.settingsService)
result.notificationsManager = newNotificationsManager(statusFoundation.events, result.settingsService) result.notificationsManager = newNotificationsManager(statusFoundation.events, result.settingsService)
result.metricsService = metrics_service.newService() result.metricsService = metrics_service.newService(statusFoundation.threadpool)
result.metricsVariant = newQVariant(result.metricsService) result.metricsVariant = newQVariant(result.metricsService)
# Global # Global

View File

@ -55,4 +55,6 @@ QtObject:
proc showCommunityMemberBannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} proc showCommunityMemberBannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.}
proc showCommunityMemberUnbannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} proc showCommunityMemberUnbannedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.}
proc addCentralizedMetric*(self: GlobalEvents, eventName: string, eventValueJson: string) {.signal.}

View File

@ -0,0 +1,39 @@
include ../../common/json_utils
include ../../../app/core/tasks/common
type
AsyncAddCentralizedMetricTaskArg = ref object of QObjectTaskArg
eventName: string
eventValueJson: string
proc asyncAddCentralizedMetricTask(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncAddCentralizedMetricTaskArg](argEncoded)
try:
var metric = CentralizedMetricDto()
metric.eventName = arg.eventName
metric.eventValue = if arg.eventValueJson.len > 0: parseJson(arg.eventValueJson) else: JsonNode()
metric.platform = hostOS
metric.appVersion = APP_VERSION
let payload = %* {"metric": metric.toJsonNode}
let response = status_go.addCentralizedMetric($payload)
try:
let jsonObj = response.parseJson
if jsonObj.hasKey("error"):
arg.finish(%* {
"metricId": "",
"error": jsonObj{"error"}.getStr,
})
return
except Exception:
discard
arg.finish(%* {
"metricId": response,
"error": "",
})
except Exception as e:
arg.finish(%* {
"metricId": "",
"error": e.msg,
})

View File

@ -1,42 +1,55 @@
import NimQml, json, chronicles, times import NimQml, json, chronicles, times
include ../../common/json_utils include ../../common/json_utils
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/global/global_singleton
import backend/response_type import backend/response_type
import status_go import status_go
import constants import constants
import ./dto import ./dto
include async_tasks
logScope: logScope:
topics = "metrics" topics = "metrics"
QtObject: QtObject:
type MetricsService* = ref object of QObject type MetricsService* = ref object of QObject
threadpool: ThreadPool
proc delete*(self: MetricsService) = proc delete*(self: MetricsService) =
self.QObject.delete self.QObject.delete
proc newService*(): MetricsService = proc newService*(threadpool: ThreadPool): MetricsService =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.threadpool = threadpool
# for testing, needs to be discussed signalConnect(singletonInstance.globalEvents, "addCentralizedMetric(QString, QString)",
proc addCentralizedMetric*(self: MetricsService) = result, "addCentralizedMetric(QString, QString)", 2)
# eventValueJson is a json string
proc addCentralizedMetric*(self: MetricsService, eventName: string, eventValueJson: string) {.slot.} =
let arg = AsyncAddCentralizedMetricTaskArg(
tptr: asyncAddCentralizedMetricTask,
vptr: cast[ByteAddress](self.vptr),
slot: "onCentralizedMetricAdded",
eventName: eventName,
eventValueJson: eventValueJson,
)
self.threadpool.start(arg)
proc onCentralizedMetricAdded*(self: MetricsService, response: string) {.slot.} =
try: try:
var metric = CentralizedMetricDto() let responseObj = response.parseJson
metric.userId = "123456" let errorString = responseObj{"error"}.getStr()
metric.eventName = "desktop-event" if errorString != "":
metric.eventValue = parseJson("""{"action": "section-changed"}""") error "onCentralizedMetricAdded", error=errorString
metric.timestamp = now().toTime().toUnix() return
metric.platform = hostOS
metric.appVersion = APP_VERSION
let payload = %* {"metric": metric.toJsonNode} debug "onCentralizedMetricAdded", metricId=responseObj{"metricId"}.getStr()
let response = status_go.addCentralizedMetric($payload) except Exception as e:
let jsonObj = response.parseJson error "onCentralizedMetricAdded", exceptionMsg = e.msg
if jsonObj.hasKey("error"):
error "addCentralizedMetric", errorMsg=jsonObj["error"].getStr
except Exception:
discard
proc centralizedMetricsEnabledChaned*(self: MetricsService) {.signal.} proc centralizedMetricsEnabledChaned*(self: MetricsService) {.signal.}
proc isCentralizedMetricsEnabled*(self: MetricsService): bool {.slot.} = proc isCentralizedMetricsEnabled*(self: MetricsService): bool {.slot.} =

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import shared.popups 1.0 import shared.popups 1.0
import utils 1.0
import Storybook 1.0 import Storybook 1.0
@ -30,7 +31,7 @@ SplitView {
anchors.centerIn: parent anchors.centerIn: parent
modal: false modal: false
visible: true visible: true
isOnboarding: true placement: Constants.metricsEnablePlacement.unknown
} }
} }

View File

@ -28,7 +28,7 @@ Item {
id: d id: d
function showMetricsAndRunAction(action) { function showMetricsAndRunAction(action) {
Global.openMetricsEnablePopupRequested(true, popup => popup.closed.connect(() => action())) Global.openMetricsEnablePopupRequested(Constants.metricsEnablePlacement.welcome, popup => popup.closed.connect(() => action()))
} }
} }

View File

@ -39,12 +39,12 @@ SettingsContentBase {
id: enableMetricsSwitch id: enableMetricsSwitch
checked: root.isCentralizedMetricsEnabled checked: root.isCentralizedMetricsEnabled
onClicked: { onClicked: {
Global.openMetricsEnablePopupRequested(false, popup => popup.toggleMetrics.connect(refreshSwitch)) Global.openMetricsEnablePopupRequested(Constants.metricsEnablePlacement.privacyAndSecurity, popup => popup.toggleMetrics.connect(refreshSwitch))
} }
} }
] ]
onClicked: { onClicked: {
Global.openMetricsEnablePopupRequested(false, popup => popup.toggleMetrics.connect(refreshSwitch)) Global.openMetricsEnablePopupRequested(Constants.metricsEnablePlacement.privacyAndSecurity, popup => popup.toggleMetrics.connect(refreshSwitch))
} }
} }
} }

View File

@ -14,7 +14,7 @@ import utils 1.0
StatusModal { StatusModal {
id: root id: root
property bool isOnboarding: false property string placement: Constants.metricsEnablePlacement.unknown
signal toggleMetrics(bool enabled) signal toggleMetrics(bool enabled)
@ -83,7 +83,7 @@ StatusModal {
Paragraph { Paragraph {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
text: qsTr("Usage data will be shared from all profiles added to device. %1").arg(root.isOnboarding ? "Sharing usage data can be turned off anytime in Settings / Privacy and Security." : "") text: qsTr("Usage data will be shared from all profiles added to device. %1").arg(root.placement !== Constants.metricsEnablePlacement.privacyAndSecurity ? "Sharing usage data can be turned off anytime in Settings / Privacy and Security." : "")
} }
} }
} }

View File

@ -7,5 +7,10 @@ QtObject {
metrics.toggleCentralizedMetrics(enabled) metrics.toggleCentralizedMetrics(enabled)
} }
function addCentralizedMetric(eventName, eventValue = null) {
let eventValueJsonStr = !!eventValue ? JSON.stringify(eventValue) : ""
metrics.addCentralizedMetric(eventName, eventValueJsonStr)
}
readonly property bool isCentralizedMetricsEnabled : metrics.isCentralizedMetricsEnabled readonly property bool isCentralizedMetricsEnabled : metrics.isCentralizedMetricsEnabled
} }

View File

@ -1367,6 +1367,13 @@ QtObject {
readonly property string undefinedAccount: "undefined" readonly property string undefinedAccount: "undefined"
} }
readonly property QtObject metricsEnablePlacement: QtObject {
readonly property string unknown: "unknown"
readonly property string welcome: "welcome_view"
readonly property string privacyAndSecurity: "privacy_and_security_view"
readonly property string startApp: "start_app_after_upgrade"
}
enum MutingVariations { enum MutingVariations {
For15min = 1, For15min = 1,
For1hr = 2, For1hr = 2,

View File

@ -108,7 +108,8 @@ QtObject {
signal openBuyCryptoModalRequested() signal openBuyCryptoModalRequested()
// Metrics // Metrics
signal openMetricsEnablePopupRequested(bool isOnboarding, var cb) signal openMetricsEnablePopupRequested(string placement, var cb)
signal addCentralizedMetric(string eventName, var eventValue)
signal openAddEditSavedAddressesPopup(var params) signal openAddEditSavedAddressesPopup(var params)
signal openDeleteSavedAddressesPopup(var params) signal openDeleteSavedAddressesPopup(var params)

View File

@ -289,6 +289,7 @@ StatusWindow {
restoreAppState(); restoreAppState();
Global.openMetricsEnablePopupRequested.connect(openMetricsEnablePopup) Global.openMetricsEnablePopupRequested.connect(openMetricsEnablePopup)
Global.addCentralizedMetric.connect(metricsStore.addCentralizedMetric)
} }
signal navigateTo(string path) signal navigateTo(string path)
@ -300,10 +301,10 @@ StatusWindow {
applicationWindow.requestActivate() applicationWindow.requestActivate()
} }
function openMetricsEnablePopup(isOnboarding, cb = null) { function openMetricsEnablePopup(placement, cb = null) {
metricsPopupLoader.active = true metricsPopupLoader.active = true
metricsPopupLoader.item.visible = true metricsPopupLoader.item.visible = true
metricsPopupLoader.item.isOnboarding = isOnboarding metricsPopupLoader.item.placement = placement
if (cb) if (cb)
cb(metricsPopupLoader.item) cb(metricsPopupLoader.item)
if(!localAppSettings.metricsPopupSeen) { if(!localAppSettings.metricsPopupSeen) {
@ -360,7 +361,7 @@ StatusWindow {
// animation is finished, app main will be shown // animation is finished, app main will be shown
// open metrics popup only if it has not been seen // open metrics popup only if it has not been seen
if(!localAppSettings.metricsPopupSeen) { if(!localAppSettings.metricsPopupSeen) {
openMetricsEnablePopup(true, null) openMetricsEnablePopup(Constants.metricsEnablePlacement.startApp, null)
} }
} }
} }
@ -378,7 +379,12 @@ StatusWindow {
sourceComponent: MetricsEnablePopup { sourceComponent: MetricsEnablePopup {
visible: true visible: true
onClosed: metricsPopupLoader.active = false onClosed: metricsPopupLoader.active = false
onToggleMetrics: applicationWindow.metricsStore.toggleCentralizedMetrics(enabled) onToggleMetrics: {
applicationWindow.metricsStore.toggleCentralizedMetrics(enabled)
if(enabled) {
Global.addCentralizedMetric("usage_data_shared", {placement: metricsPopupLoader.item.placement})
}
}
} }
} }