From ed4e2271449a9d5bd70ab806a6c0b6fa7dcf8211 Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Tue, 6 Apr 2021 18:02:08 +0530 Subject: [PATCH] Add a long running task to transfer events to Go when the user logs in, rename interceptors namespace to core, send events in batches, capture s/on-will-focus, capture navigate-to-cofx, stop long running task if any event fails validation, not quite ready yet, needs more testing Remove redudant println, don't stop the task in case of a bad event, just reject that batch, capture navigate-to-cofx events with an effect, capture parts of screens/on-will-focus event Fix lint Shorten comment widths, update to use less brackets, call events reset once, double run safety for long running task Call onboard after stopping Reset periodic-tasks-chan to on close, formatting Get rid of redundant inline function Update go version to point to 0.75.1 Signed-off-by: Shivek Khurana --- src/quo/previews/main.cljs | 1 + src/status_im/anon_metrics/core.cljs | 97 ++++++++++++++++++++ src/status_im/anon_metrics/interceptors.cljs | 27 ------ src/status_im/anon_metrics/transformers.cljs | 24 +++-- src/status_im/ethereum/json_rpc.cljs | 3 +- src/status_im/events.cljs | 4 +- src/status_im/multiaccounts/login/core.cljs | 3 + src/status_im/multiaccounts/logout/core.cljs | 2 + src/status_im/navigation.cljs | 13 +-- src/status_im/utils/async.cljs | 10 +- 10 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 src/status_im/anon_metrics/core.cljs delete mode 100644 src/status_im/anon_metrics/interceptors.cljs diff --git a/src/quo/previews/main.cljs b/src/quo/previews/main.cljs index 8e72588a9b..f09d182495 100644 --- a/src/quo/previews/main.cljs +++ b/src/quo/previews/main.cljs @@ -74,6 +74,7 @@ [theme-switcher] [rn/view (for [{:keys [name]} screens] + ^{:key name} [rn/touchable-opacity {:on-press #(navigation/navigate-to name nil)} [rn/view {:style {:padding-vertical 8}} [quo/text (str "Preview " name)]]])]]) diff --git a/src/status_im/anon_metrics/core.cljs b/src/status_im/anon_metrics/core.cljs new file mode 100644 index 0000000000..bf537d111f --- /dev/null +++ b/src/status_im/anon_metrics/core.cljs @@ -0,0 +1,97 @@ +(ns status-im.anon-metrics.core + (:require [status-im.ethereum.json-rpc :as json-rpc] + [taoensso.timbre :as log] + [re-frame.core :as re-frame] + [re-frame.interceptor :refer [->interceptor]] + [status-im.utils.async :refer [async-periodic-exec async-periodic-stop!]] + [status-im.utils.platform :as platform] + [status-im.utils.build :as build] + [status-im.utils.fx :as fx] + [status-im.anon-metrics.transformers :as txf])) + +(defonce events-foyer (atom [])) +(defonce periodic-tasks-chan (atom nil)) + +(defn onboard-events + "Check if there are any events in the foyer, + flush them to the backend and clear foyer on-success." + [] + (let [outstanding-events @events-foyer] + (when (seq outstanding-events) + (reset! events-foyer []) + (json-rpc/call + {:method "appmetrics_saveAppMetrics" + :params [outstanding-events] + :on-success #() + :on-error (fn [err] + (log/error {:error err + :events outstanding-events}) + (log/warn "All outstanding events will be rejected"))})))) + +(re-frame/reg-fx + ::transfer-data + (fn [transfer?] + (if (and transfer? + ;; double run safety + (not @periodic-tasks-chan)) + (do + (log/info "[anon-metrics] Start collection service") + (reset! periodic-tasks-chan + ;; interval = 4000 ms (run every `interval` ms) + ;; timeout = 5000 ms (exit if the fn doesn't exit within `timeout` ms) + (async-periodic-exec onboard-events 4000 5000))) + (do + (log/info "[anon-metrics] Stop collection service") + (async-periodic-stop! @periodic-tasks-chan) + (reset! periodic-tasks-chan nil) + + ;; final onboard, will save and clear any pending events + (onboard-events))))) + +(fx/defn start-transferring + [_] + {::transfer-data true}) + +(fx/defn stop-transferring + [_] + {::transfer-data false}) + +(defn transform-and-log [context] + (when-let [transformed-payload (txf/transform context)] + (swap! + events-foyer + conj + {:event (-> context :coeffects :event first) + :value transformed-payload + :app_version build/version + :os platform/os}))) + +(defn catch-events-before [context] + (transform-and-log context) + context) + +(def interceptor + (->interceptor + :id :catch-events + :before catch-events-before)) + +(re-frame/reg-fx ::transform-and-log transform-and-log) + +(fx/defn hoax-capture-event + "Due to usage of fx/defn with fx/merge, it might not be able to + intercept some events (like navigate-to-cofx). In cases like that, + this hoax capture event can be used in conjunction with `fx/merge`" + {:events [::hoax-capture-event]} + [_ {:keys [og-event]}] + ;; re-shape event to look like a context object + {::transform-and-log {:coeffects {:event og-event}}}) + +(comment + ;; read the database + (def events-in-db (atom nil)) + (->> events-in-db + deref + (take-last 5)) + (json-rpc/call {:method "appmetrics_getAppMetrics" + :params [1000 0] ; limit, offset + :on-success #(reset! events-in-db %)})) diff --git a/src/status_im/anon_metrics/interceptors.cljs b/src/status_im/anon_metrics/interceptors.cljs deleted file mode 100644 index fa9afc5df4..0000000000 --- a/src/status_im/anon_metrics/interceptors.cljs +++ /dev/null @@ -1,27 +0,0 @@ -(ns status-im.anon-metrics.interceptors - (:require [status-im.ethereum.json-rpc :as json-rpc] - [taoensso.timbre :as log] - [re-frame.interceptor :refer [->interceptor]] - [status-im.utils.platform :as platform] - [status-im.utils.build :as build] - [status-im.anon-metrics.transformers :as txf])) - -(defn transform-and-log [context] - (log/info :catch-event-fn (get-in context [:coeffects :event])) - (when-let [transformed-payload (txf/transform context)] - (json-rpc/call {:method "appmetrics_saveAppMetrics" - :params [[{:event (-> context :coeffects :event first) - :value transformed-payload - :app_version build/version - :os platform/os}]] - :on-failure #(log/error)}))) - -(defn catch-events-before [context] - (log/info "catch-events/interceptor fired") - (transform-and-log context) - context) - -(def catch-events - (->interceptor - :id :catch-events - :before catch-events-before)) diff --git a/src/status_im/anon_metrics/transformers.cljs b/src/status_im/anon_metrics/transformers.cljs index 266f92b702..07348d260c 100644 --- a/src/status_im/anon_metrics/transformers.cljs +++ b/src/status_im/anon_metrics/transformers.cljs @@ -1,14 +1,24 @@ (ns status-im.anon-metrics.transformers) (defn navigate-to-txf [context] - {:view-id (second context) - :params (-> context - (nth 2) - (select-keys [:screen]))}) + (let [event (-> context :coeffects :event)] + {:view_id (second event) + :params {:screen (get-in event [1 :screen] "")}})) -(def transformations - {:navigate-to navigate-to-txf}) +(defn screens-on-will-focus-txf [context] + (let [view-id (-> context :coeffects :event second)] + ;; we need this check because in some cases, :screens/on-will-focus is dispatched + ;; after navigate to this check ensures that only the events that are not handled + ;; via navigate to are captured hence avoiding duplicates + (when (#{:chat :empty-tab :wallet :status :my-profile} view-id) + {:view_id view-id + :params {:screen ""}}))) + +(def value-transformations + {:navigate-to navigate-to-txf + :screens/on-will-focus screens-on-will-focus-txf}) (defn transform [ctx] - (when-let [txf (-> ctx first transformations)] + (when-let [txf (-> ctx :coeffects :event first value-transformations)] (txf ctx))) + diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index 79cf8a200d..c2fad67798 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -185,7 +185,8 @@ "mailservers_addChatRequestRanges" {} "mailservers_getChatRequestRanges" {} "mailservers_deleteChatRequestRange" {} - "appmetrics_saveAppMetrics" {}}) + "appmetrics_saveAppMetrics" {} + "appmetrics_getAppMetrics" {}}) (defn on-error-retry [call-method {:keys [method number-of-retries delay on-error] :as arg}] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 6eb22b438b..cc376c10ac 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -16,6 +16,7 @@ [status-im.ui.components.permissions :as permissions] [status-im.utils.utils :as utils] [status-im.ethereum.json-rpc :as json-rpc] + [status-im.anon-metrics.core :as anon-metrics] clojure.set status-im.currency.core status-im.navigation @@ -185,7 +186,8 @@ (chat/preload-chat-data cofx constants/timeline-chat-id))) (fx/defn on-will-focus - {:events [:screens/on-will-focus]} + {:events [:screens/on-will-focus] + :interceptors [anon-metrics/interceptor]} [cofx view-id] (fx/merge cofx #(case view-id diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index a4c8622d97..a6d4eec3b8 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -1,5 +1,6 @@ (ns status-im.multiaccounts.login.core (:require [re-frame.core :as re-frame] + [status-im.anon-metrics.core :as anon-metrics] [status-im.chat.models.loading :as chat.loading] [status-im.contact.core :as contact] [status-im.data-store.settings :as data-store.settings] @@ -361,6 +362,8 @@ ::json-rpc/call [{:method "web3_clientVersion" :on-success #(re-frame/dispatch [::initialize-web3-client-version %])}]} + ;; Start tasks to save usage data locally + (anon-metrics/start-transferring) ;;FIXME (when nodes (fleet/set-nodes :eth.contract nodes)) diff --git a/src/status_im/multiaccounts/logout/core.cljs b/src/status_im/multiaccounts/logout/core.cljs index b283e014a6..87fff71b9d 100644 --- a/src/status_im/multiaccounts/logout/core.cljs +++ b/src/status_im/multiaccounts/logout/core.cljs @@ -1,5 +1,6 @@ (ns status-im.multiaccounts.logout.core (:require [re-frame.core :as re-frame] + [status-im.anon-metrics.core :as anon-metrics] [status-im.i18n.i18n :as i18n] [status-im.init.core :as init] [status-im.native-module.core :as status] @@ -20,6 +21,7 @@ :keychain/clear-user-password key-uid ::init/open-multiaccounts #(re-frame/dispatch [::init/initialize-multiaccounts % {:logout? logout?}])} (notifications/logout-disable) + (anon-metrics/stop-transferring) (keychain/save-auth-method key-uid auth-method) (transport/stop-whisper) (wallet/clear-timeouts) diff --git a/src/status_im/navigation.cljs b/src/status_im/navigation.cljs index b872bdab30..5a140324e2 100644 --- a/src/status_im/navigation.cljs +++ b/src/status_im/navigation.cljs @@ -3,7 +3,7 @@ [status-im.ui.screens.routing.core :as navigation] [taoensso.timbre :as log] [status-im.utils.fx :as fx] - [status-im.anon-metrics.interceptors :as anon-metrics])) + [status-im.anon-metrics.core :as anon-metrics])) (re-frame/reg-fx ::navigate-to @@ -38,15 +38,16 @@ (assoc-in [:navigation/screen-params view] screen-params))) (fx/defn navigate-to-cofx - [{:keys [db]} go-to-view-id screen-params] + [{:keys [db] :as cofx} go-to-view-id screen-params] {:db (-> (assoc db :view-id go-to-view-id) (all-screens-params go-to-view-id screen-params)) - ::navigate-to [go-to-view-id screen-params]}) + ::navigate-to [go-to-view-id screen-params] + ;; simulate a navigate-to event so it can be captured be anon-metrics + ::anon-metrics/transform-and-log {:coeffects {:event [:navigate-to go-to-view-id screen-params]}}}) (fx/defn navigate-to - {:events [:navigate-to] - :interceptors [anon-metrics/catch-events]} + {:events [:navigate-to]} [cofx go-to-view-id screen-params] (navigate-to-cofx cofx go-to-view-id screen-params)) @@ -62,7 +63,7 @@ (fx/defn navigate-replace {:events [:navigate-replace] - :interceptors [anon-metrics/catch-events]} + :interceptors [anon-metrics/interceptor]} [{:keys [db]} go-to-view-id screen-params] (let [db (cond-> (assoc db :view-id go-to-view-id) (seq screen-params) diff --git a/src/status_im/utils/async.cljs b/src/status_im/utils/async.cljs index 1b23b06561..fb3a840c95 100644 --- a/src/status_im/utils/async.cljs +++ b/src/status_im/utils/async.cljs @@ -40,7 +40,7 @@ (recur acc (seq acc))))))) (defn task-queue - "Creates `core.async` channel which will process 0 arg functions put there in serial fashon. + "Creates `core.async` channel which will process 0 arg functions put there in serial fashion. Takes the same argument/s as `core.async/chan`, those arguments will be delegated to the channel constructor. Returns task-queue where tasks represented by 0 arg task functions can be put for processing." @@ -100,3 +100,11 @@ (async/alts! [finished-chan (timeout timeout-ms)]) (recur)))) do-now-chan)) + +(comment + (def c (atom nil)) + + (let [periodic-task-chan (async-periodic-exec #(prn :task) 5000 1000)] + (reset! c periodic-task-chan)) + + (async-periodic-stop! @c))