Show previous value for subscription state

This commit is contained in:
Daniel Compton 2018-01-31 00:49:50 +13:00
parent e0dea398f4
commit d652ef17e3
8 changed files with 286 additions and 227 deletions

View File

@ -10,6 +10,7 @@ This version requires re-frame 0.10.4 to make use of the newly added Event panel
* New event panel. This panel shows the coeffects given to your event handler, the effects your event handler produced, and all of the interceptors in the chain.
* Debugging instructions if re-frame-trace fails to start.
* Setting to drop low level traces. This reduces the memory overhead of re-frame-trace as we can drop more traces that you are unlikely to want most of the time.
* Diff the previous value of a subscription with its current value.
### Changed

View File

@ -513,40 +513,16 @@
new-matches (remove (fn [match]
(let [event (get-in (metam/matched-event match) [:tags :event])]
(contains? events-to-ignore (first event)))) new-matches)
sub-state {}
subscription-match-state (rest
(reductions (fn [state match]
(let [epoch-traces (into []
(comp
(utils/id-between-xf (:id (first match)) (:id (last match)))
(filter metam/subscription?))
filtered-traces)
reset-state (into {}
(map (fn [[k v]]
[k (dissoc v :order :created? :run? :disposed? :previous-value)]))
state)]
(->> epoch-traces
(reduce (fn [state trace]
(let [tags (get trace :tags)
reaction-id (:reaction tags)]
(case (:op-type trace)
:sub/create (assoc state reaction-id {:created? true
:subscription (:query-v tags)
:order [:sub/create]})
:sub/run (update state reaction-id (fn [sub-state]
(-> (if (contains? sub-state :value)
(assoc sub-state :previous-value (:value sub-state))
sub-state)
(assoc :run? true
:value (:value tags))
(update :order (fnil conj []) :sub/run))))
:sub/dispose (-> (assoc-in state [reaction-id :disposed?] true)
(update-in [reaction-id :order] (fnil conj []) :sub/dispose))
(do (js/console.warn "Unhandled sub trace, this is a bug, report to re-frame-trace please" trace)
state))))
reset-state))))
sub-state
new-matches))
;; subscription-info is calculated separately from subscription-match-state because they serve different purposes:
;; - subscription-info collects all the data that we know about the subscription itself, like its layer, inputs and other
;; things that are defined as part of the reg-sub.
;; - subscription-match-state collects all the data that we know about the state of specific instances of subscriptions
;; like its reagent id, when it was created, run, disposed, what values it returned, e.t.c.
subscription-info (metam/subscription-info (get-in db [:epochs :subscription-info] {}) filtered-traces (get-in db [:app-db :reagent-id]))
sub-state (get-in db [:epochs :sub-state] {})
subscription-match-state (metam/subscription-match-state sub-state filtered-traces new-matches)
subscription-matches (rest subscription-match-state)
new-sub-state (last subscription-match-state)
timing (mapv (fn [match]
(let [epoch-traces (into []
(comp
@ -560,27 +536,10 @@
new-matches (map (fn [match sub-match t] {:match-info match
:sub-state sub-match
:timing t}) new-matches subscription-match-state timing)
:timing t})
new-matches subscription-matches timing)
all-matches (reduce conj previous-matches new-matches)
retained-matches (into [] (take-last number-of-epochs-to-retain all-matches))
app-db-id (get-in db [:app-db :reagent-id])
subscription-info (->> filtered-traces
(filter metam/subscription-re-run?)
(reduce (fn [state trace]
;; Can we take any shortcuts by assuming that a sub with
;; multiple input signals is a layer 3? I don't *think* so because
;; one of those input signals could be a naughty subscription to app-db
;; directly.
;; If we knew when subscription handlers were loaded/reloaded then
;; we could avoid doing most of this work, and only check the input
;; signals if we hadn't seen it before, or it had been reloaded.
(assoc-in state
[(:operation trace) :layer]
;; If any of the input signals are app-db, it is a layer 2 sub, else 3
(if (some #(= app-db-id %) (get-in trace [:tags :input-signals]))
2
3)))
(get-in db [:epochs :subscription-info] {})))
first-id-to-retain (first-match-id (first retained-matches))
retained-traces (into [] (comp (drop-while #(< (:id %) first-id-to-retain))
(remove (fn [trace]
@ -594,6 +553,7 @@
:matches-by-id (into {} (map (juxt first-match-id identity)) retained-matches)
:match-ids (mapv first-match-id retained-matches)
:parse-state parse-state
:sub-state new-sub-state
:subscription-info subscription-info)))))
;; Else
db)))

View File

@ -1,5 +1,11 @@
(ns day8.re-frame.trace.metamorphic)
(defn id-between-xf
;; Copied here because I got undeclared Var warnings from figwheel when requiring a CLJC utils ns.
"Returns a transducer that filters for :id between beginning and ending."
[beginning ending]
(filter #(<= beginning (:id %) ending)))
;; What starts an epoch?
;;; idle -> dispatch -> running
@ -245,3 +251,64 @@
(->> match
(filter event-run?)
(first)))
(defn subscription-info
"Collect information about the subscription that we'd like
to know, like its layer."
[initial-state filtered-traces app-db-id]
(->> filtered-traces
(filter subscription-re-run?)
(reduce (fn [state trace]
;; Can we take any shortcuts by assuming that a sub with
;; multiple input signals is a layer 3? I don't *think* so because
;; one of those input signals could be a naughty subscription to app-db
;; directly.
;; If we knew when subscription handlers were loaded/reloaded then
;; we could avoid doing most of this work, and only check the input
;; signals if we hadn't seen it before, or it had been reloaded.
(assoc-in state
[(:operation trace) :layer]
;; If any of the input signals are app-db, it is a layer 2 sub, else 3
(if (some #(= app-db-id %) (get-in trace [:tags :input-signals]))
2
3)))
initial-state)))
(defn subscription-match-state
"Build up the state of re-frame's running subscriptions over each matched epoch.
Returns initial state as first item in list"
[sub-state filtered-traces new-matches]
(reductions (fn [state match]
(let [epoch-traces (into []
(comp
(id-between-xf (:id (first match)) (:id (last match)))
(filter subscription?))
filtered-traces)
reset-state (into {}
(comp
(filter (fn [me] (when-not (:disposed? (val me)) me)))
(map (fn [[k v]]
[k (dissoc v :order :created? :run? :disposed? :previous-value)])))
state)]
(->> epoch-traces
(reduce (fn [state trace]
(let [tags (get trace :tags)
reaction-id (:reaction tags)]
(case (:op-type trace)
:sub/create (assoc state reaction-id {:created? true
:subscription (:query-v tags)
:order [:sub/create]})
:sub/run (update state reaction-id (fn [sub-state]
(-> (if (contains? sub-state :value)
(assoc sub-state :previous-value (:value sub-state))
sub-state)
(assoc :run? true
:value (:value tags))
(update :order (fnil conj []) :sub/run))))
:sub/dispose (-> (assoc-in state [reaction-id :disposed?] true)
(update-in [reaction-id :order] (fnil conj []) :sub/dispose))
(do #?(:cljs (js/console.warn "Unhandled sub trace, this is a bug, report to re-frame-trace please" trace))
state))))
reset-state))))
sub-state
new-matches))

View File

@ -373,6 +373,18 @@
(fn [epoch]
(:subscription-info epoch)))
(rf/reg-sub
:subs/sub-state
:<- [:epochs/epoch-root]
(fn [epochs]
(:sub-state epochs)))
(rf/reg-sub
:subs/current-epoch-sub-state
:<- [:epochs/current-match-state]
(fn [match-state]
(:sub-state match-state)))
(defn sub-sort-val
[sub]
(case (:type sub)
@ -398,26 +410,27 @@
:<- [:subs/all-sub-traces]
:<- [:app-db/reagent-id]
:<- [:subs/subscription-info]
(fn [[traces app-db-id sub-info]]
:<- [:subs/current-epoch-sub-state]
(fn [[traces app-db-id sub-info sub-state]]
(let [raw (map (fn [trace]
(let [pod-type (sub-op-type->type trace)
path-data (get-in trace [:tags :query-v])
;; TODO: detect layer 2/3 for sub/create and sub/destroy
;; This information needs to be accumulated.
layer (if (some #(= app-db-id %) (get-in trace [:tags :input-signals]))
2
3)
sub
(-> {:id (str pod-type (get-in trace [:tags :reaction]))
reagent-id (get-in trace [:tags :reaction])
sub (-> {:id (str pod-type reagent-id)
:reagent-id reagent-id
:type pod-type
:layer (get-in sub-info [(:operation trace) :layer])
:path-data path-data
:path (pr-str path-data)
;; TODO: Get not run subscriptions
})]
(if (contains? (:tags trace) :value)
})
sub (if (contains? (:tags trace) :value)
(assoc sub :value (get-in trace [:tags :value]))
sub)))
sub)
sub (if (contains? (get sub-state reagent-id) :previous-value)
(assoc sub :previous-value (get-in sub-state [reagent-id :previous-value]))
sub)]
sub))
traces)
re-run (->> raw
(filter #(= :re-run (:type %)))

View File

@ -24,8 +24,8 @@
(defn spy
([x]
#?(:cljs (js/console.log x))
(js/console.log x)
x)
([label x]
#?(:cljs (js/console.log label x))
(js/console.log label x)
x))

View File

@ -197,11 +197,7 @@
(defn pod [{:keys [id path open? diff?] :as pod-info}]
(let [render-diff? (and open? diff?)
app-db-after (rf/subscribe [:app-db/current-epoch-app-db-after])
app-db-before (rf/subscribe [:app-db/current-epoch-app-db-before])
[diff-before diff-after _] (when render-diff?
(clojure.data/diff (get-in @app-db-before path)
(get-in @app-db-after path)))]
app-db-after (rf/subscribe [:app-db/current-epoch-app-db-after])]
[rc/v-box
:style {:margin-bottom pod-gap
:margin-right "1px"}
@ -242,6 +238,10 @@
:leave-animation "accordionVertical"
:duration animation-duration})
(when render-diff?
(let [app-db-before (rf/subscribe [:app-db/current-epoch-app-db-before])
[diff-before diff-after _] (when render-diff?
(clojure.data/diff (get-in @app-db-before path)
(get-in @app-db-after path)))]
[rc/v-box
:children [[rc/v-box
:class "app-db-path--link"
@ -276,7 +276,7 @@
:overflow-y "hidden"}
:children [[components/simple-render
diff-after
["app-db-diff" path]]]]]])]
["app-db-diff" path]]]]]]))]
(when open?
[rc/gap-f :size pod-padding])]]]]))

View File

@ -1,5 +1,6 @@
(ns day8.re-frame.trace.view.debug
(:require [day8.re-frame.trace.utils.re-com :as rc]
[day8.re-frame.trace.view.components :as components]
[mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
[day8.re-frame.trace.metamorphic :as metam]))
@ -14,6 +15,9 @@
[rc/label :label (str "Ending " (prn-str @(rf/subscribe [:epochs/ending-trace-id])))]
[rc/label :label (str "Current epoch ID " (prn-str @(rf/subscribe [:epochs/current-epoch-id])))]
[:h2 "Subscriptions"]
[components/simple-render @(rf/subscribe [:subs/sub-state]) ["debug-subs"]]
[rc/label :label "Epochs"]
(let [current-match @(rf/subscribe [:epochs/current-match])]
(for [match (:matches @(rf/subscribe [:epochs/epoch-root]))

View File

@ -91,7 +91,7 @@
:children [[rc/checkbox
:model ignore-unchanged?
;; TODO: change from l2 subs to ignored l2 subs
:label [:span "Ignore " [:b {:style {:font-weight "700"}} @ignore-unchanged-l2-count] #_ " unchanged" [:br]
:label [:span "Ignore " [:b {:style {:font-weight "700"}} @ignore-unchanged-l2-count] #_" unchanged" [:br]
[rc/link {:label "layer 2 subs"
:href "https://github.com/Day8/re-frame-trace/blob/master/docs/HyperlinkedInformation/UnchangedLayer2.md"}]]
:style {:margin-top "6px"}
@ -128,15 +128,16 @@
:width "100%"
:model path
:disabled? true]]]
(when @(rf/subscribe [:settings/debug?])
[rc/label :label (str id)])
[rc/gap-f :size common/gs-12s]
[rc/label :label (if (some? layer)
(str "Layer " layer)
[rc/link {:label "Layer ?"
:href "https://github.com/Day8/re-frame-trace/blob/master/docs/HyperlinkedInformation/UnchangedLayer2.md#why-do-i-sometimes-see-layer--when-viewing-a-subscription"}])]
;; TODO: capture previous sub run value and allow diffing it.
#_[rc/gap-f :size common/gs-12s]
#_[rc/box
[rc/gap-f :size common/gs-12s]
[rc/box
:class "bm-muted-button app-db-path--button noselect"
:attr {:title "Show diff"
:on-click #(when open? (rf/dispatch [:subs/diff-pod? id (not diff?)]))}
@ -148,11 +149,8 @@
(defn pod [{:keys [id type layer path open? diff?] :as pod-info}]
(let [render-diff? (and open? diff?)
#_#_app-db-after (rf/subscribe [:app-db/current-epoch-app-db-after])
#_#_app-db-before (rf/subscribe [:app-db/current-epoch-app-db-before])
#_#_[diff-before diff-after _] (when render-diff?
(clojure.data/diff (get-in @app-db-before path)
(get-in @app-db-after path)))]
value? (contains? pod-info :value)
previous-value? (contains? pod-info :previous-value)]
[rc/v-box
:style {:margin-bottom pod-gap
:margin-right "1px"}
@ -164,22 +162,29 @@
:leave-animation "accordionVertical"
:duration animation-duration})
(when open?
(let [main-value (cond value? (:value pod-info)
previous-value? (:previous-value pod-info)
:else nil)]
[rc/v-box
:class (str "data-viewer" (when-not diff? " rounded-bottom"))
:style {:margin (css-join pod-padding pod-padding "0px" pod-padding)
:overflow-x "auto"
:overflow-y "hidden"}
:children [(if (contains? pod-info :value)
:children [(if (or value? previous-value?)
[components/simple-render
(:value pod-info)
main-value
["sub-path" path]]
[rc/label :style {:font-style "italic"} :label "Subscription not run, so no value produced."]
)]])]
)]]))]
[animated/component
(animated/v-box-options {:enter-animation "accordionVertical"
:leave-animation "accordionVertical"
:duration animation-duration})
(when render-diff?
(let [diffable? (and value? previous-value?)
[diff-before diff-after _] (when render-diff?
(clojure.data/diff (:previous-value pod-info)
(:value pod-info)))]
[rc/v-box
:children [[rc/v-box
:class "app-db-path--link"
@ -195,8 +200,11 @@
:class "data-viewer data-viewer--top-rule"
:style {:overflow-x "auto"
:overflow-y "hidden"}
:height "50px"
:children ["---before-diff---"]]
:children [(if diffable?
[components/simple-render
diff-before
["app-db-diff" path]]
[:p {:style {:font-style "italic"}} "No previous value exists to diff"])]]
[rc/v-box
:class "app-db-path--link"
:justify :end
@ -211,8 +219,11 @@
:class "data-viewer data-viewer--top-rule rounded-bottom"
:style {:overflow-x "auto"
:overflow-y "hidden"}
:height "50px"
:children ["---after-diff---"]]]])]
:children [(if diffable?
[components/simple-render
diff-after
["app-db-diff" path]]
[:p {:style {:font-style "italic"}} "No previous value exists to diff"])]]]]))]
(when open?
[rc/gap-f :size pod-padding])]]]]))
@ -226,7 +237,10 @@
(defn pod-section []
(let [all-subs @(rf/subscribe [:subs/visible-subs])
sub-expansions @(rf/subscribe [:subs/sub-expansions])]
sub-expansions @(rf/subscribe [:subs/sub-expansions])
all-subs (if @(rf/subscribe [:settings/debug?])
(cons {:id "debug" :value @(rf/subscribe [:subs/current-epoch-sub-state])} all-subs)
all-subs)]
[rc/v-box
:size "1"
;:gap pod-gap
@ -246,11 +260,11 @@
:style {:flex "1 1 0px"
:overflow-x "hidden"
:overflow-y "auto"}})
(for [p all-subs]
^{:key (:id p)}
[pod (merge p (get sub-expansions (:id p)))])]]
]))
(defn render []