Skip to content

Instantly share code, notes, and snippets.

@realgenekim
Created September 15, 2022 19:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save realgenekim/32188389abc670a16bcd715e41245c62 to your computer and use it in GitHub Desktop.
Save realgenekim/32188389abc670a16bcd715e41245c62 to your computer and use it in GitHub Desktop.
(ns com.example.state-machines.incrementally-loaded-report-gk
;(ns com.fulcrologic.rad.state-machines.incrementally-loaded-report
"Gene modifications to incrementally-loaded-report --
This has the same interface as the 'incrementally-loaded-report' UISM included in RAD.
https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/state_machines/incrementally_loaded_report.cljc
However, instead of rendering only after the resolver resturns the entire data set,
it renders immediately after the first load completes, and use df/load! to asynchronously load
each chunk.
I use df/load! because it's asynchronous, versus uism/load which doesn't allow mutations
or changing the query-params while loading.
Personal note: I'm finding UISM to be a marvel. It's amazing that you can pull out
all the I/O stuff into UISMs, and I'm amazed that I could change the i/o behavior so easily.
END GENE
A Report state machine that will load the data in pages to prevent network timeouts for large result
sets. This requires a resolver that can accept :report/offset and :report/limit parameters and that returns
```
{:report/next-offset n
:report/results data}
```
where `n` is the next offset to use to get the next page of data, and `data` is a vector
of the results in the current fetch.
See incrementally-loaded-report-options for supported additional report options.
"
(:require
[com.fulcrologic.fulcro.algorithms.merge :as merge]
[com.fulcrologic.fulcro.raw.application :as app]
[com.fulcrologic.fulcro.raw.components :as comp]
[com.fulcrologic.fulcro.ui-state-machines :as uism :refer [defstatemachine]]
[com.fulcrologic.fulcro.data-fetch :as df]
[com.fulcrologic.fulcro.mutations :as m]
[com.fulcrologic.fulcro.raw.components :as rc]
[com.fulcrologic.rad.attributes :as attr]
[com.fulcrologic.rad.options-util :as opts :refer [?! debounce]]
[com.fulcrologic.rad.report :as report]
[com.fulcrologic.rad.routing :as rad-routing]
[com.fulcrologic.rad.routing.history :as history]
[com.fulcrologic.rad.type-support.date-time :as dt]
;[com.example.state-machines.tmp :as tmp]
[com.example.fulcro-utils :as futils]
[taoensso.timbre :as log]))
(defn start-load [env]
(let [Report (uism/actor-class env :actor/report)
report-ident (uism/actor->ident env :actor/report)
{::report/keys [source-attribute load-options]
::keys [chunk-size]} (comp/component-options Report)
load-options (?! load-options env)
current-params (->
(report/current-control-parameters env)
(assoc
:report/offset 0
; small chunk here to optimize for fast loading of first page
:report/limit 50)
(dissoc ::chunk-size))
;(or chunk-size 50))
page-path (uism/resolve-alias env :loaded-page)]
(log/warn :start-load "****** UISM/load!!!!")
(-> env
(uism/assoc-aliased :raw-rows [])
(uism/load source-attribute nil (merge
{:params current-params
::uism/ok-event :event/page-loaded
::uism/error-event :event/failed
:marker report-ident
:target page-path}
load-options))
(uism/activate :state/loading))))
(defn finalize-report [{::uism/keys [state-map] :as env}]
(let [Report (uism/actor-class env :actor/report)
{::report/keys [row-pk report-loaded]} (comp/component-options Report)
table-name (::attr/qualified-key row-pk)]
(-> env
(report/preprocess-raw-result)
(report/filter-rows)
(report/sort-rows)
(report/populate-current-page)
(uism/store :last-load-time (inst-ms (dt/now)))
(uism/store :raw-items-in-table (count (keys (get state-map table-name))))
(uism/activate :state/gathering-parameters)
(cond-> report-loaded report-loaded))))
(m/defmutation mutation-resume-load!
[params]
(action [{:keys [app state]}]
(log/warn ::mutation-resume-load! "mutation: mutation-resume-load!: ")
(let [;
{:keys [env current-params report-ident page-path load-options source-attribute
actor-name]} params
state-map @state]
#_(log/warn ::resume-load! :current-param current-params :load-options load-options :page-path page-path
:source-attribute source-attribute :load-options load-options :actor-name actor-name
:state-map state-map
:state state
:env env)
(uism/trigger! app report-ident :event/page-loaded))))
(defn load-next! [{::uism/keys [state-map] :as env}]
(let [Report (uism/actor-class env :actor/report)
{::report/keys [row-pk report-loaded]} (comp/component-options Report)
{:report/keys [next-offset results]} (uism/alias-value env :loaded-page)
{::report/keys [BodyItem source-attribute load-options]
::keys [chunk-size]} (comp/component-options Report)
more? (and (number? next-offset) (pos? next-offset))
current-params (assoc
(report/current-control-parameters env)
:report/offset next-offset
:report/limit (or chunk-size 100))
report-ident (uism/actor->ident env :actor/report)
page-path (uism/resolve-alias env :loaded-page)
load-options (?! load-options env)
table-name (::attr/qualified-key row-pk)]
(log/warn :load-next! :next-offset next-offset)
(if more?
(do
; #object[cljs.core.Atom
; {:val {:load-args {:params {:api/time :year,
; :start-date #inst"2022-01-01T08:00:00.000-00:00",
; :end-date #inst"2023-01-01T08:00:00.000-00:00",
; :report/offset 100,
; :report/limit 100},
; :com.fulcrologic.fulcro.ui-state-machines/ok-event :event/page-loaded,
; :com.fulcrologic.fulcro.ui-state-machines/error-event :event/failed,
(log/warn :load-next! "****** df/load!!!!")
(df/load! (resolve 'com.example.client/app)
source-attribute
(rc/nc ['*])
;Report
(merge
{:params current-params
;::uism/ok-event :event/page-loaded
::uism/ok-event :event/ready-for-next-load
::uism/error-event :event/failed
:marker report-ident
:target page-path
:parallel true
:post-mutation `mutation-resume-load!
:post-mutation-params {:report-ident report-ident}}
load-options))))
(-> env
(report/preprocess-raw-result)
(report/filter-rows)
(report/sort-rows)
(report/populate-current-page)
(uism/activate :state/gathering-parameters)
(cond-> report-loaded report-loaded))))
(defn process-loaded-page [env]
(let [Report (uism/actor-class env :actor/report)
report-ident (uism/actor->ident env :actor/report)
{::report/keys [BodyItem source-attribute load-options]
::keys [chunk-size]} (comp/component-options Report)
load-options (?! load-options env)
{:report/keys [next-offset results]} (uism/alias-value env :loaded-page)
page-path (uism/resolve-alias env :loaded-page)
target-path (uism/resolve-alias env :raw-rows)
current-params (assoc
(report/current-control-parameters env)
:report/offset next-offset
:report/limit (or chunk-size 100))
more? (and (number? next-offset) (pos? next-offset))
append-results (fn [state-map]
(reduce
(fn [s item] (merge/merge-component s BodyItem item :append target-path))
state-map
results))]
(log/warn :process-loaded-page "BEGIN!!! ****** uism/load")
#_(log/warn :process-loaded-page :current-param current-params :load-options load-options :page-path page-path
:source-attribute source-attribute :load-options load-options)
#_(log/warn :apply-action :append-results (-> env (uism/apply-action append-results)))
(-> env
(uism/apply-action append-results)
(uism/trigger report-ident :event/ready-for-next-load)
#_(cond->
more? (uism/load source-attribute nil (merge
{:params current-params
;::uism/ok-event :event/page-loaded
::uism/ok-event :event/ready-for-next-load
::uism/error-event :event/failed
:marker report-ident
:target page-path}
load-options))
(not more?) (uism/trigger report-ident :event/loaded)))))
(defn handle-resume-report
"Internal state machine implementation. Called on :event/resumt to do the steps to resume an already running report
that has just been re-mounted."
[{::uism/keys [state-map] :as env}]
(let [env (report/initialize-parameters env)
Report (uism/actor-class env :actor/report)
{::report/keys [load-cache-seconds
load-cache-expired?
row-pk]} (comp/component-options Report)
now-ms (inst-ms (dt/now))
last-load-time (uism/retrieve env :last-load-time)
last-table-count (uism/retrieve env :raw-items-in-table)
cache-expiration-ms (* 1000 (or load-cache-seconds 0))
table-name (::attr/qualified-key row-pk)
current-table-count (count (keys (get state-map table-name)))
cache-looks-stale? (or
(nil? last-load-time)
(not= current-table-count last-table-count)
(< last-load-time (- now-ms cache-expiration-ms)))
user-cache-expired? (?! load-cache-expired? env cache-looks-stale?)
cache-expired? (if (boolean user-cache-expired?)
user-cache-expired?
cache-looks-stale?)]
(if cache-expired?
(start-load env)
(report/handle-filter-event env))))
(defn start [env]
(let [{::uism/keys [fulcro-app event-data]} env
{::report/keys [run-on-mount?]} (report/report-options env)
page-path (report/route-params-path env ::current-page)
desired-page (-> (history/current-route fulcro-app)
:params
(get-in page-path))
run-now? (or desired-page run-on-mount?)]
(-> env
(uism/store :route-params (:route-params event-data))
(cond->
(nil? desired-page) (uism/assoc-aliased :current-page 1))
(report/initialize-parameters)
(cond->
run-now? (start-load)
(not run-now?) (uism/activate :state/gathering-parameters)))))
(defstatemachine incrementally-loaded-machine
(-> report/report-machine
(assoc-in [::uism/aliases :loaded-page] [:actor/report :ui/incremental-page])
(assoc-in [::uism/states :initial ::uism/handler] start)
(assoc-in [::uism/states :state/loading ::uism/events :event/page-loaded ::uism/handler] process-loaded-page)
(assoc-in [::uism/states :state/loading ::uism/events :event/ready-for-next-load ::uism/handler] load-next!)
(assoc-in [::uism/states :state/loading ::uism/events :event/loaded ::uism/handler] finalize-report)
(assoc-in [::uism/states :state/gathering-parameters ::uism/events :event/run ::uism/handler] start-load)
(assoc-in [::uism/states :state/gathering-parameters ::uism/events :event/resume ::uism/handler] handle-resume-report)
(assoc-in [::uism/states :state/gathering-parameters ::uism/events :event/page-loaded ::uism/handler] process-loaded-page)
(assoc-in [::uism/states :state/gathering-parameters ::uism/events :event/loaded ::uism/handler] finalize-report)
(assoc-in [::uism/states :state/gathering-parameters ::uism/events :event/ready-for-next-load ::uism/handler] load-next!)))
(defn raw-loaded-item-count
"Returns the count of raw loaded items when given the props of the report. Can be used for progress reporting of
the load/refresh/"
[report-instance]
(let [state-map (app/current-state report-instance)
path (conj (comp/get-ident report-instance) :ui/loaded-data)]
(count (get-in state-map path))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment