diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 50e0f18db..9fb795d9c 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -53,7 +53,7 @@ ptk/WatchEvent (watch [_ _ stream] (rx/merge - (rx/of (ptk/event ::ev/initialize) + (rx/of (ev/initialize) (du/initialize-profile)) (->> stream (rx/filter du/profile-fetched?) @@ -79,6 +79,7 @@ [] (mf/unmount (dom/get-element "app")) (mf/unmount (dom/get-element "modal")) + (st/emit! (ev/initialize)) (init-ui)) (defn ^:dev/after-load after-load @@ -91,4 +92,3 @@ (fn [_ _ old-value current-value] (when (not= old-value current-value) (reinit)))) - diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index d6696bffe..bcb6dc548 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -8,6 +8,7 @@ (:require ["ua-parser-js" :as UAParser] [app.common.data :as d] + [app.common.logging :as l] [app.config :as cf] [app.main.repo :as rp] [app.util.globals :as g] @@ -20,6 +21,8 @@ [lambdaisland.uri :as u] [potok.core :as ptk])) +(l/set-level! :info) + ;; Defines the maximum buffer size, after events start discarding. (def max-buffer-size 1024) @@ -94,11 +97,13 @@ (derive :app.main.data.fonts/add-font ::generic-action) (derive :app.main.data.fonts/delete-font ::generic-action) (derive :app.main.data.fonts/delete-font-variant ::generic-action) +(derive :app.main.data.modal/show-modal ::generic-action) (derive :app.main.data.users/logout ::generic-action) (derive :app.main.data.users/request-email-change ::generic-action) (derive :app.main.data.users/update-password ::generic-action) (derive :app.main.data.users/update-photo ::generic-action) (derive :app.main.data.workspace.comments/open-comment-thread ::generic-action) +(derive :app.main.data.workspace.guides/update-guides ::generic-action) (derive :app.main.data.workspace.libraries/add-color ::generic-action) (derive :app.main.data.workspace.libraries/add-media ::generic-action) (derive :app.main.data.workspace.libraries/add-typography ::generic-action) @@ -111,8 +116,6 @@ (derive :app.main.data.workspace/create-page ::generic-action) (derive :app.main.data.workspace/set-workspace-layout ::generic-action) (derive :app.main.data.workspace/toggle-layout-flag ::generic-action) -(derive :app.main.data.modal/show-modal ::generic-action) -(derive :app.main.data.workspace.guides/update-guides ::generic-action) (defmulti process-event ptk/type) (defmethod process-event :default [_] nil) @@ -133,17 +136,20 @@ (dissoc ::context) (cond-> origin (assoc :origin origin)))})))) -(defn- filter-props +(defn- normalize-props "Removes complex data types from props." [data] (into {} - (map (fn [[k v :as kv]] - (cond - (map? v) [k :placeholder/map] - (vector? v) [k :placeholder/vec] - (set? v) [k :placeholder/set] - (coll? v) [k :placeholder/coll] - :else kv))) + (comp + (remove (fn [[_ v]] (nil? v))) + (map (fn [[k v :as kv]] + (cond + (map? v) [k :placeholder/map] + (vector? v) [k :placeholder/vec] + (set? v) [k :placeholder/set] + (coll? v) [k :placeholder/coll] + (fn? v) [k :placeholder/fn] + :else kv)))) data)) (defmethod process-event ::generic-action @@ -156,9 +162,8 @@ {:type "action" :name (or (::name mdata) (name type)) - :props (-> (merge (d/without-nils data) - (d/without-nils (::props mdata))) - (filter-props)) + :props (-> (merge data (::props mdata)) + (normalize-props)) :context (d/without-nils {:event-origin (::origin mdata) :event-namespace (namespace type) @@ -174,8 +179,7 @@ :project-id (get-in match [:path-params :project-id])}] {:name "navigate" :type "action" - :timestamp (dt/now) - :props (d/without-nils props)})) + :props (normalize-props props)})) (defmethod process-event :app.main.data.users/logged-in [event] @@ -191,7 +195,7 @@ {:name "signin" :type "identify" :profile-id (:id data) - :props (d/without-nils props)})) + :props (normalize-props props)})) ;; --- MAIN LOOP @@ -218,60 +222,72 @@ (defn initialize [] - (ptk/reify ::initialize - ptk/EffectEvent - (effect [_ _ stream] - (let [session (atom nil) - buffer (atom #queue []) - profile (->> (rx/from-atom storage {:emit-current-value? true}) - (rx/map :profile) - (rx/map :id) - (rx/dedupe))] + (when (contains? @cf/flags :audit-log) + (ptk/reify ::initialize + ptk/EffectEvent + (effect [_ _ stream] + (let [session (atom nil) + stoper (rx/filter (ptk/type? ::initialize) stream) + buffer (atom #queue []) + profile (->> (rx/from-atom storage {:emit-current-value? true}) + (rx/map :profile) + (rx/map :id) + (rx/dedupe))] - (->> (rx/merge - (->> (rx/from-atom buffer) - (rx/filter #(pos? (count %))) - (rx/debounce 2000)) - (->> stream - (rx/filter (ptk/type? :app.main.data.users/logout)) - (rx/observe-on :async))) - (rx/map (fn [_] - (into [] (take max-buffer-size) @buffer))) - (rx/with-latest-from profile) - (rx/mapcat (fn [[chunk profile-id]] - (let [events (filterv #(= profile-id (:profile-id %)) chunk)] - (->> (persist-events events) - (rx/map (constantly chunk)))))) - (rx/subs (fn [chunk] - (swap! buffer remove-from-buffer (count chunk))))) + (l/debug :hint "event instrumentation initialized") + (->> (rx/merge + (->> (rx/from-atom buffer) + (rx/filter #(pos? (count %))) + (rx/debounce 2000)) + (->> stream + (rx/filter (ptk/type? :app.main.data.users/logout)) + (rx/observe-on :async))) + (rx/map (fn [_] + (into [] (take max-buffer-size) @buffer))) + (rx/with-latest-from profile) + (rx/mapcat (fn [[chunk profile-id]] + (let [events (filterv #(= profile-id (:profile-id %)) chunk)] + (->> (persist-events events) + (rx/tap (fn [_] + (l/debug :hint "events chunk persisted" :total (count chunk)))) + (rx/map (constantly chunk)))))) + (rx/take-until stoper) + (rx/subs (fn [chunk] + (swap! buffer remove-from-buffer (count chunk))) + (fn [cause] + (l/error :hint "unexpected error on audit persistence" :cause cause)) + (fn [] + (l/debug :hint "audit persistence terminated")))) - (->> stream - (rx/with-latest-from profile) - (rx/map (fn [result] - (let [event (aget result 0) - profile-id (aget result 1)] - (some-> (process-event event) - (update :profile-id #(or % profile-id)))))) - (rx/filter :profile-id) - (rx/map (fn [event] - (let [session* (or @session (dt/now)) - context (-> @context - (d/merge (:context event)) - (assoc :session session*))] - (reset! session session*) - (-> event - (assoc :timestamp (dt/now)) - (assoc :context context))))) + (->> stream + (rx/with-latest-from profile) + (rx/map (fn [result] + (let [event (aget result 0) + profile-id (aget result 1)] + (some-> (process-event event) + (update :profile-id #(or % profile-id)))))) + (rx/filter :profile-id) + (rx/map (fn [event] + (let [session* (or @session (dt/now)) + context (-> @context + (d/merge (:context event)) + (assoc :session session*))] + (reset! session session*) + (-> event + (assoc :timestamp (dt/now)) + (assoc :context context))))) - (rx/tap (fn [event] - (swap! buffer append-to-buffer event))) + (rx/tap (fn [event] + (l/debug :hint "event enqueued") + (swap! buffer append-to-buffer event))) - (rx/switch-map #(rx/timer (inst-ms session-timeout))) - (rx/subs #(reset! session nil))))))) - -(defmethod ptk/resolve ::initialize - [_ params] - (if (contains? @cf/flags :audit-log) - (initialize) - (ptk/data-event ::initialize params))) + (rx/switch-map #(rx/timer (inst-ms session-timeout))) + (rx/take-until stoper) + (rx/subs (fn [_] + (l/debug :hint "session reinitialized") + (reset! session nil)) + (fn [cause] + (l/error :hint "error on event batching stream" :cause cause)) + (fn [] + (l/debug :hitn "events batching stream terminated")))))))))