From 0a0591f67a8edcc45bfa8cbae998c2afa7331c3c Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Thu, 4 Jul 2024 16:45:00 +0100 Subject: [PATCH] Add centralized metrics https://github.com/status-im/status-go/compare/6e056348...1ef2434b This commit adds support for pushing centralized metrics to mixpanel. It uses an interceptor and only picks a few selected events to push through. In order to test: 1) Create an account in the app 2) Go to Settings->Privacy 3) Enable metrics You should now see the events on the mixpanel dashboard (login with your status-im account for access). Only some example events are tracked, they are just for testing --- Makefile | 6 + .../status/ethereum/module/AccountManager.kt | 8 ++ .../im/status/ethereum/module/StatusModule.kt | 10 ++ .../ios/RCTStatus/AccountManager.m | 9 ++ .../ios/RCTStatus/RCTStatus.m | 18 +++ modules/react-native-status/nodejs/status.cpp | 33 ++++- scripts/build-android.sh | 2 + shadow-cljs.edn | 6 + src/legacy/status_im/utils/test.cljs | 130 ------------------ src/native_module/core.cljs | 18 ++- src/status_im/config.cljs | 5 + .../contexts/centralized_metrics/effects.cljs | 9 ++ .../contexts/centralized_metrics/events.cljs | 33 +++++ .../centralized_metrics/events_test.cljs | 37 +++++ .../centralized_metrics/tracking.cljs | 40 ++++++ .../centralized_metrics/tracking_test.cljs | 35 +++++ .../actions/community_options/view.cljs | 1 + src/status_im/contexts/profile/config.cljs | 29 ++-- src/status_im/contexts/profile/events.cljs | 44 +++--- .../contexts/profile/settings/list_items.cljs | 13 +- .../contexts/profile/settings/view.cljs | 2 +- .../settings/privacy_and_security/view.cljs | 15 +- src/status_im/setup/interceptors.cljs | 2 + src/status_im/subs/root.cljs | 4 + src/tests/test_utils.cljs | 9 +- status-go-version.json | 6 +- translations/en.json | 1 + 27 files changed, 337 insertions(+), 188 deletions(-) delete mode 100644 src/legacy/status_im/utils/test.cljs create mode 100644 src/status_im/contexts/centralized_metrics/effects.cljs create mode 100644 src/status_im/contexts/centralized_metrics/events.cljs create mode 100644 src/status_im/contexts/centralized_metrics/events_test.cljs create mode 100644 src/status_im/contexts/centralized_metrics/tracking.cljs create mode 100644 src/status_im/contexts/centralized_metrics/tracking_test.cljs diff --git a/Makefile b/Makefile index f6a70f9e2c..256f164bb7 100644 --- a/Makefile +++ b/Makefile @@ -457,6 +457,12 @@ android-tail-geth: export VERSION ?= debug android-tail-geth: adb shell 'while true; do cat; sleep 1; done < /storage/emulated/0/Android/data/im.status.ethereum$$( [ "$(VERSION)" = "release" ] || echo ".$(VERSION)" )/files/Download/geth.log' +android-clean-geth: export TARGET := android-sdk +android-clean-geth: export VERSION ?= debug +android-clean-geth: + adb shell 'rm /storage/emulated/0/Android/data/im.status.ethereum$$( [ "$(VERSION)" = "release" ] || echo ".$(VERSION)" )/files/Download/geth.log' + + android-logcat: export TARGET := android-sdk android-logcat: ##@other Read status-mobile logs from Android phone using adb adb logcat | grep -e RNBootstrap -e ReactNativeJS -e ReactNative -e StatusModule -e StatusNativeLogs -e 'F DEBUG :' -e 'Go :' -e 'GoLog :' -e 'libc :' diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt index 51b8fea480..a3a64d5ef4 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/AccountManager.kt @@ -246,6 +246,14 @@ class AccountManager(private val reactContext: ReactApplicationContext) : ReactC utils.executeRunnableStatusGoMethod({ Statusgo.openAccounts(rootDir) }, callback) } + @ReactMethod + private fun initializeApplication(request: String, callback: Callback) { + Log.d(TAG, "initializeApplication") + Log.d(TAG, "[Initializing application $request") + utils.executeRunnableStatusGoMethod({ Statusgo.initializeApplication(request) }, callback) + } + + @ReactMethod fun logout() { Log.d(TAG, "logout") diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt index af80b40d00..7cbf56f30f 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.kt @@ -76,6 +76,16 @@ class StatusModule(private val reactContext: ReactApplicationContext, private va utils.executeRunnableStatusGoMethod({ Statusgo.getNodeConfig() }, callback) } + @ReactMethod + fun addCentralizedMetric(request: String, callback: Callback) { + utils.executeRunnableStatusGoMethod({ Statusgo.addCentralizedMetric(request) }, callback) + } + + @ReactMethod + fun toggleCentralizedMetrics(request: String, callback: Callback) { + utils.executeRunnableStatusGoMethod({ Statusgo.toggleCentralizedMetrics(request) }, callback) + } + @ReactMethod fun deleteImportedKey(keyUID: String, address: String, password: String, callback: Callback) { val keyStoreDir = utils.getKeyStorePath(keyUID) diff --git a/modules/react-native-status/ios/RCTStatus/AccountManager.m b/modules/react-native-status/ios/RCTStatus/AccountManager.m index 3caafde74c..d8dd8aa556 100644 --- a/modules/react-native-status/ios/RCTStatus/AccountManager.m +++ b/modules/react-native-status/ios/RCTStatus/AccountManager.m @@ -195,6 +195,15 @@ RCT_EXPORT_METHOD(verifyDatabasePassword:(NSString *)keyUID callback(@[result]); } +RCT_EXPORT_METHOD(initializeApplication:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"initializeApplication() method called"); +#endif + NSString *result = StatusgoInitializeApplication(request); + callback(@[result]); +} + RCT_EXPORT_METHOD(openAccounts:(RCTResponseSenderBlock)callback) { #if DEBUG NSLog(@"OpenAccounts() method called"); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 9a9bbf95ba..7149440534 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -176,6 +176,24 @@ RCT_EXPORT_METHOD(appStateChange:(NSString *)type) { StatusgoAppStateChange(type); } +RCT_EXPORT_METHOD(addCentralizedMetric:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"addCentralizedMetric() method called"); +#endif + NSString *result = StatusgoAddCentralizedMetric(request); + callback(@[result]); +} + +RCT_EXPORT_METHOD(toggleCentralizedMetrics:(NSString *)request + callback:(RCTResponseSenderBlock)callback) { +#if DEBUG + NSLog(@"toggleCentralizedMetrics() method called"); +#endif + NSString *result = StatusgoToggleCentralizedMetrics(request); + callback(@[result]); +} + RCT_EXPORT_METHOD(startLocalNotifications) { #if DEBUG NSLog(@"StartLocalNotifications() method called"); diff --git a/modules/react-native-status/nodejs/status.cpp b/modules/react-native-status/nodejs/status.cpp index 620d77032c..126aece4e0 100644 --- a/modules/react-native-status/nodejs/status.cpp +++ b/modules/react-native-status/nodejs/status.cpp @@ -302,6 +302,36 @@ void _MultiAccountStoreAccount(const FunctionCallbackInfo& args) { delete c; } +void _InitializeApplication(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.Length() != 1) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong number of arguments for InitializeApplication"))); + return; + } + + // Check the argument types + + if (!args[0]->IsString()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8Literal(isolate, "Wrong argument type for request"))); + return; + } + + + String::Utf8Value arg0Obj(isolate, args[0]->ToString(context).ToLocalChecked()); + char *arg0 = *arg0Obj; + + // Call exported Go function, which returns a C string + char *c = InitializeApplication(arg0); + + Local ret = String::NewFromUtf8(isolate, c).ToLocalChecked(); + args.GetReturnValue().Set(ret); + delete c; +} void _InitKeystore(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); @@ -1935,6 +1965,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "multiAccountStoreDerivedAccounts", _MultiAccountStoreDerivedAccounts); NODE_SET_METHOD(exports, "multiAccountStoreAccount", _MultiAccountStoreAccount); NODE_SET_METHOD(exports, "initKeystore", _InitKeystore); + NODE_SET_METHOD(exports, "initializeApplication", _InitializeApplication); NODE_SET_METHOD(exports, "fleets", _Fleets); NODE_SET_METHOD(exports, "stopCPUProfiling", _StopCPUProfiling); NODE_SET_METHOD(exports, "encodeTransfer", _EncodeTransfer); @@ -1972,7 +2003,7 @@ void init(Local exports) { NODE_SET_METHOD(exports, "signTypedData", _SignTypedData); NODE_SET_METHOD(exports, "sendTransaction", _SendTransaction); NODE_SET_METHOD(exports, "appStateChange", _AppStateChange); - NODE_SET_METHOD(exports, "setSignalEventCallback", _SetSignalEventCallback); + NODE_SET_METHOD(exports, "setSignalEventCallback", _SetSignalEventCallback); NODE_SET_METHOD(exports, "validateNodeConfig", _ValidateNodeConfig); NODE_SET_METHOD(exports, "hashTypedData", _HashTypedData); NODE_SET_METHOD(exports, "recover", _Recover); diff --git a/scripts/build-android.sh b/scripts/build-android.sh index eeb3a8e573..64470e7e22 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -45,6 +45,8 @@ SECRETS_ENV_VARS=( 'INFURA_TOKEN' 'INFURA_TOKEN_SECRET' 'OPENSEA_API_KEY' + 'MIXPANEL_APP_ID' + 'MIXPANEL_TOKEN' 'POKT_TOKEN' ) diff --git a/shadow-cljs.edn b/shadow-cljs.edn index d9f226b1b8..ad71280492 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -67,6 +67,8 @@ :closure-defines {status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" @@ -102,6 +104,8 @@ {status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN" @@ -143,6 +147,8 @@ status-im.config/POKT_TOKEN #shadow/env "POKT_TOKEN" status-im.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN" status-im.config/OPENSEA_API_KEY #shadow/env "OPENSEA_API_KEY" + status-im.config/MIXPANEL_APP_ID #shadow/env "MIXPANEL_APP_ID" + status-im.config/MIXPANEL_TOKEN #shadow/env "MIXPANEL_TOKEN" status-im.config/RARIBLE_MAINNET_API_KEY #shadow/env "RARIBLE_MAINNET_API_KEY" status-im.config/RARIBLE_TESTNET_API_KEY #shadow/env "RARIBLE_TESTNET_API_KEY" status-im.config/ALCHEMY_ETHEREUM_MAINNET_TOKEN #shadow/env "ALCHEMY_ETHEREUM_MAINNET_TOKEN" diff --git a/src/legacy/status_im/utils/test.cljs b/src/legacy/status_im/utils/test.cljs deleted file mode 100644 index 4bfb83d7b1..0000000000 --- a/src/legacy/status_im/utils/test.cljs +++ /dev/null @@ -1,130 +0,0 @@ -(ns legacy.status-im.utils.test - (:require - [legacy.status-im.utils.deprecated-types :as types] - [re-frame.core :as re-frame])) - -(def native-status (js/require "../../modules/react-native-status/nodejs/bindings")) - -(def fs (js/require "fs")) -(def path (js/require "path")) -(def os (js/require "os")) - -(def tmpdir (.tmpdir os)) - -(def test-dir-prefix (.join path tmpdir "status-mobile-tests")) - -(def test-dir (.mkdtempSync fs test-dir-prefix)) - -(def initialized? (atom false)) - -(defn signal-received-callback - [a] - (re-frame/dispatch [:signals/signal-received a])) - -;; We poll for signals, could not get callback working -(defn init! - [] - (when-not @initialized? - (.setSignalEventCallback native-status) - (reset! initialized? true) - (js/setInterval (fn [] - (.pollSignal native-status signal-received-callback) - 100)))) - -(def ui-helper - (clj->js - {:clearCookies identity - :clearStorageAPIs identity})) - -(def encryption-utils - (clj->js - {:sha3 - (fn [s] (.sha3 native-status s)) - :setBlankPreviewFlag - identity - :encodeTransfer - (fn [to-norm amount-hex] - (.encodeTransfer native-status to-norm amount-hex)) - :hexToNumber - (fn [hex] (.hexToNumber native-status hex)) - :decodeParameters - (fn [decode-param-json] - (.decodeParameters native-status decode-param-json)) - :numberToHex - (fn [num-str] (.numberToHex native-status num-str)) - :initKeystore - (fn [key-uid callback] - (callback (.initKeystore native-status - (str test-dir "/keystore/" key-uid)))) - :multiformatDeserializePublicKey - (fn [public-key deserialization-key callback] - (callback (.multiformatDeserializePublicKey - native-status - public-key - deserialization-key)))})) - -(def account-manager - (clj->js - {:openAccounts - (fn [callback] - (callback (.openAccounts native-status test-dir))) - :createAccountAndLogin - (fn [request] (.createAccountAndLogin native-status request)) - :logout - (fn [] (.logout native-status)) - :multiAccountImportMnemonic - (fn [json callback] - (callback (.multiAccountImportMnemonic native-status json))) - :multiAccountLoadAccount - (fn [json callback] - (callback (.multiAccountLoadAccount native-status json))) - :multiAccountDeriveAddresses - (fn [json callback] - (callback (.multiAccountDeriveAddresses native-status json))) - :multiAccountGenerateAndDeriveAddresses - (fn [json callback] - (callback (.multiAccountGenerateAndDeriveAddresses native-status json))) - :multiAccountStoreDerived - (fn [json callback] - (callback (.multiAccountStoreDerivedAccounts native-status json)))})) - -(def utils - (clj->js - {:backupDisabledDataDir - (fn [] (str test-dir "/backup")) - :keystoreDir (fn [] "") - :toChecksumAddress - (fn [address] (.toChecksumAddress native-status address)) - :checkAddressChecksum - (fn [address] (.checkAddressChecksum native-status address)) - :validateMnemonic - (fn [json callback] (callback (.validateMnemonic native-status json))) - :isAddress - (fn [address] (.isAddress native-status address))})) - -(def log-manager - (clj->js - {:logFileDirectory - (fn [] (str test-dir "/log")) - :initLogging - (fn [enabled mobile-system log-level callback] - (callback (.initLogging native-status - (types/clj->json {:Enabled enabled - :MobileSystem mobile-system - :Level log-level - :File (str test-dir "/geth.log")}))))})) - -(def network - (clj->js - {:callPrivateRPC - (fn [payload callback] - (callback (.callPrivateRPC native-status payload)))})) - -(def status - (clj->js - {:getNodeConfig - (fn [] (types/clj->json {:WakuV2Config ""})) - :fleets - (fn [] (.fleets native-status)) - :startLocalNotifications - identity})) diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 9195bf0f94..44f3f84d24 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -72,10 +72,12 @@ (log/debug "[native-module] init-keystore" key-uid) (.initKeystore ^js (encryption) key-uid callback)) -(defn open-accounts - [callback] - (log/debug "[native-module] open-accounts") - (.openAccounts ^js (account-manager) #(callback (types/json->clj %)))) +(defn initialize-application + [request callback] + (log/debug "[native-module] initialize-application") + (.initializeApplication ^js (account-manager) + (types/clj->json request) + #(callback (types/json->clj %)))) (defn prepare-dir-and-update-config [key-uid config callback] @@ -513,6 +515,14 @@ (let [result (.checkAddressChecksum ^js (utils) address)] (types/json->clj result))) +(defn toggle-centralized-metrics + [enabled callback] + (.toggleCentralizedMetrics ^js (status) (types/clj->json {:enabled enabled}) callback)) + +(defn add-centralized-metric + [metric] + (.addCentralizedMetric ^js (status) (types/clj->json metric) #(log/debug "pushed metric" % metric))) + (defn address? [address] (log/debug "[native-module] address?") diff --git a/src/status_im/config.cljs b/src/status_im/config.cljs index 89e0022297..1213040f6f 100644 --- a/src/status_im/config.cljs +++ b/src/status_im/config.cljs @@ -25,6 +25,8 @@ (goog-define ALCHEMY_OPTIMISM_GOERLI_TOKEN "") (goog-define ALCHEMY_OPTIMISM_SEPOLIA_TOKEN "") (goog-define WALLET_CONNECT_PROJECT_ID "87815d72a81d739d2a7ce15c2cfdefb3") +(goog-define MIXPANEL_APP_ID "3350627") +(goog-define MIXPANEL_TOKEN "5c73bda2d36a9f688a5ee45641fb6775") (def mainnet-rpc-url (str "https://eth-archival.rpc.grove.city/v1/" POKT_TOKEN)) (def goerli-rpc-url (str "https://goerli-archival.gateway.pokt.network/v1/lb/" POKT_TOKEN)) @@ -39,6 +41,9 @@ (def opensea-link "https://opensea.io") (def opensea-tesnet-link "https://testnets.opensea.io") +(def mixpanel-app-id MIXPANEL_APP_ID) +(def mixpanel-token MIXPANEL_TOKEN) + (def opensea-api-key OPENSEA_API_KEY) (def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1"))) (def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED))) diff --git a/src/status_im/contexts/centralized_metrics/effects.cljs b/src/status_im/contexts/centralized_metrics/effects.cljs new file mode 100644 index 0000000000..dfa2db377d --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/effects.cljs @@ -0,0 +1,9 @@ +(ns status-im.contexts.centralized-metrics.effects + (:require + [native-module.core :as native-module] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(rf/reg-fx :effects.centralized-metrics/toggle-metrics + (fn [enabled?] + (native-module/toggle-centralized-metrics enabled? #(log/debug "toggled-metrics" % enabled?)))) diff --git a/src/status_im/contexts/centralized_metrics/events.cljs b/src/status_im/contexts/centralized_metrics/events.cljs new file mode 100644 index 0000000000..a9e303802d --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/events.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.centralized-metrics.events + (:require + [native-module.core :as native-module] + [re-frame.interceptor :as interceptor] + status-im.contexts.centralized-metrics.effects + [status-im.contexts.centralized-metrics.tracking :as tracking] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(defn push-event? + [db] + (or (not (:centralized-metrics/user-confirmed? db)) + (:centralized-metrics/enabled? db))) + +(defn centralized-metrics-interceptor + [context] + (when-let [event (tracking/tracked-event (interceptor/get-coeffect context :event))] + (log/debug "tracking event" event) + (when (push-event? (interceptor/get-coeffect context :db)) + (native-module/add-centralized-metric event))) + context) + +(def interceptor + (interceptor/->interceptor + :id :centralized-metrics + :after centralized-metrics-interceptor)) + +(rf/reg-event-fx :centralized-metrics/toggle-centralized-metrics + (fn [{:keys [db]} [enabled?]] + {:fx [[:effects.centralized-metrics/toggle-metrics enabled?]] + :db (assoc db + :centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?)})) diff --git a/src/status_im/contexts/centralized_metrics/events_test.cljs b/src/status_im/contexts/centralized_metrics/events_test.cljs new file mode 100644 index 0000000000..816609694f --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/events_test.cljs @@ -0,0 +1,37 @@ +(ns status-im.contexts.centralized-metrics.events-test + (:require + [cljs.test :refer-macros [deftest is testing]] + matcher-combinators.test + [status-im.contexts.centralized-metrics.events :as events] + [status-im.contexts.centralized-metrics.tracking :as tracking] + [test-helpers.unit :as h])) + +(deftest push-event-test + (testing "returns correct boolean value" + (is (true? (events/push-event? {:centralized-metrics/user-confirmed? false}))) + (is (true? (events/push-event? {:centralized-metrics/enabled? true}))) + (is (false? (events/push-event? {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? false}))))) + +(deftest centralized-metrics-interceptor-test + (testing "processes context correctly" + (with-redefs [tracking/tracked-event (fn [_] {:metric "mocked-event"}) + events/push-event? (fn [_] true)] + (let [context {:coeffects {:event [:some-event] + :db {:centralized-metrics/enabled? true}}}] + (is (= context (events/centralized-metrics-interceptor context))))))) + +(h/deftest-event :centralized-metrics/toggle-centralized-metrics + [event-id dispatch] + (testing "toggling value to true" + (let [enabled? true + expected-fxs {:db {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?} + :fx [[:effects.centralized-metrics/toggle-metrics enabled?]]}] + (is (match? expected-fxs (dispatch [event-id enabled?]))))) + (testing "toggling value to false" + (let [enabled? false + expected-fxs {:db {:centralized-metrics/user-confirmed? true + :centralized-metrics/enabled? enabled?} + :fx [[:effects.centralized-metrics/toggle-metrics enabled?]]}] + (is (match? expected-fxs (dispatch [event-id enabled?])))))) diff --git a/src/status_im/contexts/centralized_metrics/tracking.cljs b/src/status_im/contexts/centralized_metrics/tracking.cljs new file mode 100644 index 0000000000..df1de0f9b1 --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/tracking.cljs @@ -0,0 +1,40 @@ +(ns status-im.contexts.centralized-metrics.tracking + (:require + [legacy.status-im.utils.build :as build] + [react-native.platform :as platform])) + +(defn user-journey-event + [action] + {:metric + {:eventName "user-journey" + :platform platform/os + :appVersion build/app-short-version + :eventValue {:action action}}}) + +(def ^:const app-started-event "app-started") +(def ^:const navigate-to-create-profile-event "navigate-to-create-profile") +(def ^:const communities-tab-clicked "communities-tab-clicked") +(def ^:const wallet-tab-clicked "wallet-tab-clicked") +(def ^:const chats-tab-clicked "chats-tab-clicked") + +(defn track-view-id-event + [view-id] + (case view-id + :communities-stack (user-journey-event communities-tab-clicked) + :chats-stack (user-journey-event chats-tab-clicked) + :wallet-stack (user-journey-event wallet-tab-clicked) + nil)) + +(defn tracked-event + [[event-name second-parameter]] + (case event-name + :onboarding/navigate-to-create-profile + (user-journey-event navigate-to-create-profile-event) + + :profile/get-profiles-overview-success + (user-journey-event app-started-event) + + :set-view-id + (track-view-id-event second-parameter) + + nil)) diff --git a/src/status_im/contexts/centralized_metrics/tracking_test.cljs b/src/status_im/contexts/centralized_metrics/tracking_test.cljs new file mode 100644 index 0000000000..bf38e42bfb --- /dev/null +++ b/src/status_im/contexts/centralized_metrics/tracking_test.cljs @@ -0,0 +1,35 @@ +(ns status-im.contexts.centralized-metrics.tracking-test + (:require + [cljs.test :refer-macros [deftest is testing]] + [legacy.status-im.utils.build :as build] + [react-native.platform :as platform] + [status-im.contexts.centralized-metrics.tracking :as tracking])) + +(deftest user-journey-event-test + (testing "creates correct metric event" + (let [action "some-action" + expected {:metric {:eventName "user-journey" + :platform platform/os + :appVersion build/app-short-version + :eventValue {:action action}}}] + (is (= expected (tracking/user-journey-event action)))))) + +(deftest track-view-id-event-test + (testing "returns correct event for view-id" + (is (= (tracking/user-journey-event tracking/communities-tab-clicked) + (tracking/track-view-id-event :communities-stack))) + (is (= (tracking/user-journey-event tracking/chats-tab-clicked) + (tracking/track-view-id-event :chats-stack))) + (is (= (tracking/user-journey-event tracking/wallet-tab-clicked) + (tracking/track-view-id-event :wallet-stack))) + (is (nil? (tracking/track-view-id-event :unknown-stack))))) + +(deftest tracked-event-test + (testing "returns correct event for given inputs" + (is (= (tracking/user-journey-event tracking/navigate-to-create-profile-event) + (tracking/tracked-event [:onboarding/navigate-to-create-profile]))) + (is (= (tracking/user-journey-event tracking/app-started-event) + (tracking/tracked-event [:profile/get-profiles-overview-success]))) + (is (= (tracking/track-view-id-event :wallet-stack) + (tracking/tracked-event [:set-view-id :wallet-stack]))) + (is (nil? (tracking/tracked-event [:unknown-event]))))) diff --git a/src/status_im/contexts/communities/actions/community_options/view.cljs b/src/status_im/contexts/communities/actions/community_options/view.cljs index 674de0ba47..88b653b508 100644 --- a/src/status_im/contexts/communities/actions/community_options/view.cljs +++ b/src/status_im/contexts/communities/actions/community_options/view.cljs @@ -139,6 +139,7 @@ [id token-gated? intro-message] [[(when-not token-gated? (view-members id)) (when-not token-gated? (view-rules id intro-message)) + (mark-as-read id) (invite-contacts id) (when token-gated? (view-token-gating id)) (show-qr id) diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index c4b8c16c17..f9e0c3409c 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -28,21 +28,20 @@ (defn create [] (let [log-enabled? (boolean (not-empty config/log-level))] - (merge (login) - {:deviceName (native-module/get-installation-name) - :rootDataDir (native-module/backup-disabled-data-dir) - :rootKeystoreDir (native-module/keystore-dir) - - :logLevel (when log-enabled? config/log-level) - :logEnabled log-enabled? - :logFilePath (native-module/log-file-directory) - :verifyTransactionURL config/verify-transaction-url - :verifyENSURL config/verify-ens-url - :verifyENSContractAddress config/verify-ens-contract-address - :verifyTransactionChainID config/verify-transaction-chain-id - :wakuV2LightClient true - :previewPrivacy config/blank-preview? - :testNetworksEnabled config/test-networks-enabled?}))) + (assoc (login) + :deviceName (native-module/get-installation-name) + :rootDataDir (native-module/backup-disabled-data-dir) + :rootKeystoreDir (native-module/keystore-dir) + :logLevel (when log-enabled? config/log-level) + :logEnabled log-enabled? + :logFilePath (native-module/log-file-directory) + :verifyTransactionURL config/verify-transaction-url + :verifyENSURL config/verify-ens-url + :verifyENSContractAddress config/verify-ens-contract-address + :verifyTransactionChainID config/verify-transaction-chain-id + :wakuV2LightClient true + :previewPrivacy config/blank-preview? + :testNetworksEnabled config/test-networks-enabled?))) (defn strip-file-prefix [path] diff --git a/src/status_im/contexts/profile/events.cljs b/src/status_im/contexts/profile/events.cljs index 337ecbb5de..cb394aa08d 100644 --- a/src/status_im/contexts/profile/events.cljs +++ b/src/status_im/contexts/profile/events.cljs @@ -2,6 +2,7 @@ (:require [legacy.status-im.data-store.settings :as data-store.settings] [native-module.core :as native-module] + [status-im.config :as config] [status-im.contexts.profile.edit.accent-colour.events] [status-im.contexts.profile.edit.bio.events] [status-im.contexts.profile.edit.header.events] @@ -27,7 +28,10 @@ (rf/reg-fx :profile/get-profiles-overview (fn [callback] - (native-module/open-accounts callback))) + (native-module/initialize-application {:dataDir (native-module/backup-disabled-data-dir) + :mixpanelAppId config/mixpanel-app-id + :mixpanelToken config/mixpanel-token} + callback))) (rf/reg-event-fx :profile/profile-selected @@ -36,23 +40,27 @@ (rf/reg-event-fx :profile/get-profiles-overview-success - (fn [{:keys [db]} [profiles-overview]] - (if (seq profiles-overview) - (let [profiles (reduce-profiles profiles-overview) - {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] - {:db (if key-uid - (-> db - (assoc :profile/profiles-overview profiles) - (update :profile/login #(select-profile % key-uid))) - db) - :fx [[:dispatch [:init-root :screen/profile.profiles]] - (when key-uid - [:effects.biometric/check-if-available - {:key-uid key-uid - :on-success (fn [auth-method] - (rf/dispatch [:profile.login/check-biometric-success key-uid - auth-method]))}])]}) - {:fx [[:dispatch [:init-root :screen/onboarding.intro]]]}))) + (fn [{:keys [db]} [{:keys [accounts] {:keys [userConfirmed enabled]} :centralizedMetricsInfo}]] + (let [db-with-settings (assoc db + :centralized-metrics/user-confirmed? userConfirmed + :centralized-metrics/enabled? enabled)] + (if (seq accounts) + (let [profiles (reduce-profiles accounts) + {:keys [key-uid]} (first (sort-by :timestamp > (vals profiles)))] + {:db (if key-uid + (-> db-with-settings + (assoc :profile/profiles-overview profiles) + (update :profile/login #(select-profile % key-uid))) + db-with-settings) + :fx [[:dispatch [:init-root :screen/profile.profiles]] + (when key-uid + [:effects.biometric/check-if-available + {:key-uid key-uid + :on-success (fn [auth-method] + (rf/dispatch [:profile.login/check-biometric-success key-uid + auth-method]))}])]}) + {:db db-with-settings + :fx [[:dispatch [:init-root :screen/onboarding.intro]]]})))) (rf/reg-event-fx :profile/update-setting-from-backup diff --git a/src/status_im/contexts/profile/settings/list_items.cljs b/src/status_im/contexts/profile/settings/list_items.cljs index 78e6c0c6a7..c0a70da6a0 100644 --- a/src/status_im/contexts/profile/settings/list_items.cljs +++ b/src/status_im/contexts/profile/settings/list_items.cljs @@ -61,13 +61,12 @@ :image :icon :blur? true :action :arrow})] - [(when config/show-not-implemented-features? - {:title (i18n/label :t/privacy-and-security) - :on-press #(rf/dispatch [:open-modal :screen/settings-privacy-and-security]) - :image-props :i/privacy - :image :icon - :blur? true - :action :arrow}) + [{:title (i18n/label :t/privacy-and-security) + :on-press #(rf/dispatch [:open-modal :screen/settings-privacy-and-security]) + :image-props :i/privacy + :image :icon + :blur? true + :action :arrow} {:title (i18n/label :t/syncing) :on-press #(rf/dispatch [:open-modal :settings-syncing]) :image-props :i/syncing diff --git a/src/status_im/contexts/profile/settings/view.cljs b/src/status_im/contexts/profile/settings/view.cljs index 59f173ac9d..bbef55bfd8 100644 --- a/src/status_im/contexts/profile/settings/view.cljs +++ b/src/status_im/contexts/profile/settings/view.cljs @@ -79,7 +79,7 @@ profile)}}])}]}]] [rn/flat-list {:header [settings.header/view {:scroll-y scroll-y}] - :data (settings.items/items (boolean (:mnemonic profile))) + :data (settings.items/items (boolean (seq (:mnemonic profile)))) :shows-vertical-scroll-indicator false :render-fn settings-category-view :get-item-layout get-item-layout diff --git a/src/status_im/contexts/settings/privacy_and_security/view.cljs b/src/status_im/contexts/settings/privacy_and_security/view.cljs index c7c87d54df..1ce7641bb8 100644 --- a/src/status_im/contexts/settings/privacy_and_security/view.cljs +++ b/src/status_im/contexts/settings/privacy_and_security/view.cljs @@ -13,8 +13,9 @@ (defn view [] - (let [insets (safe-area/get-insets) - customization-color (rf/sub [:profile/customization-color])] + (let [insets (safe-area/get-insets) + centralized-metrics-enabled? (rf/sub [:centralized-metrics/enabled?]) + customization-color (rf/sub [:profile/customization-color])] [quo/overlay {:type :shell :container-style (style/page-wrapper (:top insets))} @@ -30,13 +31,15 @@ :customization-color customization-color}] [quo/category {:key :category - :data [{:title "Dummy" + :data [{:title (i18n/label :t/share-usage-data) :image-props :i/placeholder - :image :icon :blur? true :action :selector - :action-props {:on-change identity - :checked? false} + :action-props {:on-change #(rf/dispatch + [:centralized-metrics/toggle-centralized-metrics + (not centralized-metrics-enabled?)]) + :customization-color customization-color + :checked? centralized-metrics-enabled?} :on-press identity}] :blur? true :list-type :settings}]])) diff --git a/src/status_im/setup/interceptors.cljs b/src/status_im/setup/interceptors.cljs index 8310f413cd..7c76c24d11 100644 --- a/src/status_im/setup/interceptors.cljs +++ b/src/status_im/setup/interceptors.cljs @@ -2,11 +2,13 @@ (:require [re-frame.core :as re-frame] [re-frame.std-interceptors :as std-interceptors] + [status-im.contexts.centralized-metrics.events :as centralized-metrics] [utils.re-frame :as rf])) (defn register-global-interceptors [] (re-frame/reg-global-interceptor rf/debug-handlers-names) + (re-frame/reg-global-interceptor centralized-metrics/interceptor) (re-frame/reg-global-interceptor (re-frame/inject-cofx :now)) ;; Interceptor `trim-v` removes the first element of the event vector. diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 44ca7a67bf..c84fdd9dfb 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -185,3 +185,7 @@ ;;theme (reg-root-key-sub :theme :theme) + +;; centralized-metrics +(reg-root-key-sub :centralized-metrics/enabled? :centralized-metrics/enabled?) +(reg-root-key-sub :centralized-metrics/user-confirmed? :centralized-metrics/user-confirmed?) diff --git a/src/tests/test_utils.cljs b/src/tests/test_utils.cljs index 85b2dcd742..29b11cb8bb 100644 --- a/src/tests/test_utils.cljs +++ b/src/tests/test_utils.cljs @@ -65,9 +65,9 @@ (def account-manager (clj->js - {:openAccounts - (fn [callback] - (callback (.openAccounts native-status test-dir))) + {:initializeApplication + (fn [request callback] + (callback (.initializeApplication native-status request))) :createAccountAndLogin (fn [request] (.createAccountAndLogin native-status request)) :restoreAccountAndLogin @@ -130,6 +130,9 @@ (clj->js {:getNodeConfig (fn [] (types/clj->json {:WakuV2Config ""})) + :addCentralizedMetric + (fn [_ callback] + (callback)) :fleets (fn [] (.fleets native-status)) :startLocalNotifications diff --git a/status-go-version.json b/status-go-version.json index 62115adb5c..24f27cd75a 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.181.31", - "commit-sha1": "6e056348e6d28f962167118612826f1ef0e47b22", - "src-sha256": "1qvfwk28sg93basjzy8r55qz8pk9xg0h7kxv5ykxkbfh4q17a1ns" + "version": "v0.181.35", + "commit-sha1": "1ef2434b0644581e3c0404d767550cbfee8ad829", + "src-sha256": "1dfw4cff1blg063xxrh78dgivcar92k1fspcr84ra1fxw674kmfk" } diff --git a/translations/en.json b/translations/en.json index dea6d1742e..a4452c46ce 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2749,5 +2749,6 @@ "import-keypair-to-use-account": "Import key pair to use this account", "import-keypair-steps": "{{account-name}} was derived from your {{keypair-name}} key pair, which has not yet been imported to this device. To transact using this account, you will need to import the {{keypair-name}} key pair first.", "not-now": "Not now", + "share-usage-data": "Share usage data with Status", "value-higher-than-send-amount": "This value is higher than entered amount to send" }