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. * 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. * 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. * 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 ### Changed

View File

@ -513,40 +513,16 @@
new-matches (remove (fn [match] new-matches (remove (fn [match]
(let [event (get-in (metam/matched-event match) [:tags :event])] (let [event (get-in (metam/matched-event match) [:tags :event])]
(contains? events-to-ignore (first event)))) new-matches) (contains? events-to-ignore (first event)))) new-matches)
sub-state {} ;; subscription-info is calculated separately from subscription-match-state because they serve different purposes:
subscription-match-state (rest ;; - subscription-info collects all the data that we know about the subscription itself, like its layer, inputs and other
(reductions (fn [state match] ;; things that are defined as part of the reg-sub.
(let [epoch-traces (into [] ;; - subscription-match-state collects all the data that we know about the state of specific instances of subscriptions
(comp ;; like its reagent id, when it was created, run, disposed, what values it returned, e.t.c.
(utils/id-between-xf (:id (first match)) (:id (last match))) subscription-info (metam/subscription-info (get-in db [:epochs :subscription-info] {}) filtered-traces (get-in db [:app-db :reagent-id]))
(filter metam/subscription?)) sub-state (get-in db [:epochs :sub-state] {})
filtered-traces) subscription-match-state (metam/subscription-match-state sub-state filtered-traces new-matches)
reset-state (into {} subscription-matches (rest subscription-match-state)
(map (fn [[k v]] new-sub-state (last subscription-match-state)
[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))
timing (mapv (fn [match] timing (mapv (fn [match]
(let [epoch-traces (into [] (let [epoch-traces (into []
(comp (comp
@ -560,27 +536,10 @@
new-matches (map (fn [match sub-match t] {:match-info match new-matches (map (fn [match sub-match t] {:match-info match
:sub-state sub-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) all-matches (reduce conj previous-matches new-matches)
retained-matches (into [] (take-last number-of-epochs-to-retain all-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)) first-id-to-retain (first-match-id (first retained-matches))
retained-traces (into [] (comp (drop-while #(< (:id %) first-id-to-retain)) retained-traces (into [] (comp (drop-while #(< (:id %) first-id-to-retain))
(remove (fn [trace] (remove (fn [trace]
@ -594,6 +553,7 @@
:matches-by-id (into {} (map (juxt first-match-id identity)) retained-matches) :matches-by-id (into {} (map (juxt first-match-id identity)) retained-matches)
:match-ids (mapv first-match-id retained-matches) :match-ids (mapv first-match-id retained-matches)
:parse-state parse-state :parse-state parse-state
:sub-state new-sub-state
:subscription-info subscription-info))))) :subscription-info subscription-info)))))
;; Else ;; Else
db))) db)))

View File

@ -1,5 +1,11 @@
(ns day8.re-frame.trace.metamorphic) (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? ;; What starts an epoch?
;;; idle -> dispatch -> running ;;; idle -> dispatch -> running
@ -245,3 +251,64 @@
(->> match (->> match
(filter event-run?) (filter event-run?)
(first))) (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] (fn [epoch]
(:subscription-info 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 (defn sub-sort-val
[sub] [sub]
(case (:type sub) (case (:type sub)
@ -398,26 +410,27 @@
:<- [:subs/all-sub-traces] :<- [:subs/all-sub-traces]
:<- [:app-db/reagent-id] :<- [:app-db/reagent-id]
:<- [:subs/subscription-info] :<- [: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 [raw (map (fn [trace]
(let [pod-type (sub-op-type->type trace) (let [pod-type (sub-op-type->type trace)
path-data (get-in trace [:tags :query-v]) path-data (get-in trace [:tags :query-v])
;; TODO: detect layer 2/3 for sub/create and sub/destroy reagent-id (get-in trace [:tags :reaction])
;; This information needs to be accumulated. sub (-> {:id (str pod-type reagent-id)
layer (if (some #(= app-db-id %) (get-in trace [:tags :input-signals])) :reagent-id reagent-id
2
3)
sub
(-> {:id (str pod-type (get-in trace [:tags :reaction]))
:type pod-type :type pod-type
:layer (get-in sub-info [(:operation trace) :layer]) :layer (get-in sub-info [(:operation trace) :layer])
:path-data path-data :path-data path-data
:path (pr-str path-data) :path (pr-str path-data)
;; TODO: Get not run subscriptions ;; TODO: Get not run subscriptions
})] })
(if (contains? (:tags trace) :value) sub (if (contains? (:tags trace) :value)
(assoc sub :value (get-in trace [:tags :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) traces)
re-run (->> raw re-run (->> raw
(filter #(= :re-run (:type %))) (filter #(= :re-run (:type %)))

View File

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

View File

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

View File

@ -1,5 +1,6 @@
(ns day8.re-frame.trace.view.debug (ns day8.re-frame.trace.view.debug
(:require [day8.re-frame.trace.utils.re-com :as rc] (: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] [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
[day8.re-frame.trace.metamorphic :as metam])) [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 "Ending " (prn-str @(rf/subscribe [:epochs/ending-trace-id])))]
[rc/label :label (str "Current epoch ID " (prn-str @(rf/subscribe [:epochs/current-epoch-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"] [rc/label :label "Epochs"]
(let [current-match @(rf/subscribe [:epochs/current-match])] (let [current-match @(rf/subscribe [:epochs/current-match])]
(for [match (:matches @(rf/subscribe [:epochs/epoch-root])) (for [match (:matches @(rf/subscribe [:epochs/epoch-root]))

View File

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