Implements the ability to dig into history

This commit is contained in:
2023-01-11 12:54:48 -08:00
parent 2facb0c64f
commit 3a2dc07227
7 changed files with 114 additions and 150 deletions

View File

@@ -10,7 +10,6 @@
"@popperjs/core": "^2.11.5", "@popperjs/core": "^2.11.5",
"downshift": "^6.1.3", "downshift": "^6.1.3",
"dropzone": "^4.3.0", "dropzone": "^4.3.0",
"htmx.org": "^1.8.4",
"minisearch": "^3.0.2", "minisearch": "^3.0.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.1", "react": "^17.0.1",

View File

@@ -1,27 +1,17 @@
(ns auto-ap.ssr.admin (ns auto-ap.ssr.admin
(:require (:require
[auto-ap.datomic :refer [conn]]
[auto-ap.graphql.utils :refer [assert-admin]]
[auto-ap.logging :as alog] [auto-ap.logging :as alog]
[auto-ap.time :as atime]
[clj-time.coerce :as coerce]
[clojure.string :as str] [clojure.string :as str]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
#_{:clj-kondo/ignore [:refer-all]} [compojure.core :refer [context defroutes GET POST routes]]
[compojure.core :refer [defroutes GET POST context ANY wrap-routes]]
[compojure.route :as route]
[config.core :refer [env]]
[mount.core :as mount]
[ring.middleware.edn :refer [wrap-edn-params]]
[ring.middleware.multipart-params :as mp]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.reload :refer [wrap-reload]]
[ring.util.response :as response]
[datomic.api :as d] [datomic.api :as d]
[auto-ap.datomic :refer [conn]] [hiccup2.core :as hiccup]))
[clj-time.coerce :as coerce]
[auto-ap.time :as atime]
[hiccup.core :refer [html]]
[hiccup.page :refer [html5]]
[unilog.context :as lc]))
(defn base-page [contents] (defn base-page [request contents]
[:html.has-navbar-fixed-top [:html.has-navbar-fixed-top
[:head [:head
[:meta {:charset "utf-8"}] [:meta {:charset "utf-8"}]
@@ -43,37 +33,57 @@
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV" :integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
:crossorigin="anonymous"}] :crossorigin="anonymous"}]
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]] [:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
[:script {:type "text/javascript"}
(hiccup/raw
(str
"
window.onload = function () {
document.body.addEventListener(\"htmx:configRequest\", function(event) {
event.detail.headers[\"Authorization\"] = \"Token " (get (:query-params request) "jwt")" \" ;
});
}"))]
[:body [:body
[:div {:id "app"} [:div {:id "app"}
[:div [:div
[:nav {:class "navbar has-shadow is-fixed-top"} [:nav {:class "navbar has-shadow is-fixed-top is-grey"}
[:div {:class "container"}
[:div {:class "navbar-brand"} [:div {:class "container"}
[:a {:class "navbar-item", :href "../"} [:div {:class "navbar-brand"}
[:img {:src "/img/logo.png"}]]] [:a {:class "navbar-item", :href "../"}
[:div {:class "navbar-menu"} [:img {:src "/img/logo.png"}]]]
[:div {:class "navbar-burger burger", :data-target "navMenu"} [:div.navbar-menu {:id "navMenu"}
[:span] [:div.navbar-start
[:span] [:a.navbar-item {:href "/"}
[:span]]]]] "Home" ]
[:a.navbar-item {:href "/invoices/"}
"Invoices" ]
[:a.navbar-item {:href "/payments/"}
"Payments" ]
[:a.navbar-item {:href "/pos/sales-orders/"}
"POS" ]
[:a.navbar-item {:href "/transactions/"}
"Transactions" ]
[:a.navbar-item {:href "/ledger/"}
"Ledger" ]]]]]
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"} [:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
[:aside {:class "column aside menu is-2 "} [:aside {:class "column aside menu is-2 "}
[:div {:class "main left-nav"} [:div {:class "main left-nav"}
[:div]]] [:div]]]
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"} [:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
(into [:div {:class "inbox-messages"}] [:div {:class "inbox-messages"}
contents)]] contents]]]
[:div] [:div]
[:div {:id "dz-hidden"}]]]]]) [:div {:id "dz-hidden"}]]]]])
(defn wrap-html [handler] (defn html-response [hiccup]
(fn [request] {:status 200
(doto :headers {"Content-Type" "text/html"}
{:status 200 :body (str
:headers {"Content-Type" "text/html"} (hiccup/html
:body (handler request) {}
} hiccup))})
println)))
(defn inline-add-deletes [history] (defn inline-add-deletes [history]
@@ -121,26 +131,29 @@
(pr-str v))) (pr-str v)))
(comment "_" ) (comment "_" )
(defn page-template [& {:keys [table entity-id]}] (defn page-template [& {:keys [table entity-id]}]
[:div [:div
[:div.columns [:div.columns
[:div.column.is-4 [:div.column.is-4
[:form.hello {"hx-target" "#history-table" [:form.hello {"hx-target" "#history-table"
"hx-post" "/admin/history/search" "hx-post" "/admin/history/search"
"hx-swap" "innerHTML" "hx-swap" "innerHTML"
"_" (hiccup2.core/raw "on submit toggle @disabled on me then toggle .is-loading on <#dig/> end "_" (hiccup/raw "on htmx:beforeRequest toggle @disabled on me then toggle .is-loading on <#dig/> end
on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end") on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end")
} }
[:div.field.is-grouped [:div.field.is-grouped
[:p.control {} [:p.control {}
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" }]] [:input.input {:type "text" :name "entity-id" :placeholder "Entity id" :value entity-id}]]
[:p.control [:p.control
[:button#dig.button.is-primary {} [:button#dig.button.is-primary {}
"Dig"]]]]]] "Dig"]]]]]]
[:div#history-table [:div#history-table
table]]) table]])
(defn history-search [{:keys [form-params params] :as request}] (defn history-search [{:keys [form-params params] identity :identity :as request}]
(assert-admin identity)
(log/info ::request (log/info ::request
request) request)
(try (try
@@ -158,17 +171,18 @@
inline-add-deletes inline-add-deletes
(sort-by (comp :db/txInstant :tx)) (sort-by (comp :db/txInstant :tx))
vec) vec)
best-guess-entity (->> history best-guess-entity (or (->> history
(group-by (group-by
(comp (comp
namespace namespace
second) second)
) )
(map (fn [[k v]] (map (fn [[k v]]
[k v])) [k v]))
(sort-by second) (sort-by second)
last last
first) first)
"?")
table [:div [:h1.title "History for " table [:div [:h1.title "History for "
(str/capitalize best-guess-entity) (str/capitalize best-guess-entity)
" " " "
@@ -181,7 +195,7 @@
[:td {:style "width: 12em"} "Transaction" ] [:td {:style "width: 12em"} "Transaction" ]
[:td {:style "width: 12em"} "Date"] [:td {:style "width: 12em"} "Date"]
[:td {:style "width: 12em"} "User"] [:td {:style "width: 12em"} "User"]
[:td {:style "width: 15em"} "Field"] [:td {:style "width: 10em"} "Field"]
[:td "Change"]]] [:td "Change"]]]
[:tbody [:tbody
(for [[tx a c] history] (for [[tx a c] history]
@@ -193,9 +207,16 @@
(atime/unparse atime/standard-time))] (atime/unparse atime/standard-time))]
[:td (str (:audit/user tx))] [:td (str (:audit/user tx))]
[:td (namespace a) ": " (name a)] [:td (namespace a) ": " (name a)]
[:td (format-value (:removed c)) [:td
" => " [:div.tags
(format-value (:added c))]])] [:div.tag.is-danger.is-light
[:span
"FROM "
(format-value (:removed c))]]
[:div.tag.is-primary.is-light
[:span
"TO "
(format-value (:added c))]]]]])]
]] ]]
[:div.column.is-3 [:div.column.is-3
[:div#inspector.card]]]]] [:div#inspector.card]]]]]
@@ -203,23 +224,24 @@
(alog/info ::trace (alog/info ::trace
:bge best-guess-entity :bge best-guess-entity
:headers (:headers request)) :headers (:headers request))
(html (if (= "history-table" (html-response
(get (:headers request) "hx-target")) (if (get (:headers request) "hx-request")
table table
(do (base-page request
(println "WHOLE PAGOE") (page-template :table table
(page-template :table table :entity-id entity-id)))))
:entity-id entity-id)))))
(catch NumberFormatException e (catch NumberFormatException e
(html [:div.notification.is-danger.is-light (html-response
"Cannot parse the entity-id " (or (:entity-id form-params) (str [:div.notification.is-danger.is-light
(:entity-id params)) "Cannot parse the entity-id " (or (:entity-id form-params)
(:entity-id params))
". It should be a number."])))) ". It should be a number."])))))
(defn inspect [{{:keys [entity-id]} :params :as request}] (defn inspect [{{:keys [entity-id]} :params identity :identity :as request}]
(alog/info ::inspect (alog/info ::inspect
:request request) :request request)
(assert-admin identity)
(try (try
(let [entity-id (Long/parseLong entity-id) (let [entity-id (Long/parseLong entity-id)
data (d/pull (d/db conn) data (d/pull (d/db conn)
@@ -227,34 +249,27 @@
entity-id entity-id
) ] ) ]
(html-response
(html [:div.box [:h1.title "Snapshot of " [:div.box [:h1.title "Snapshot of "
entity-id] entity-id]
[:ul [:ul
(for [[k v] data] (for [[k v] data]
[:li [:strong k] ":" v] [:li [:strong k] ":" v]
)]])) )]]))
(catch NumberFormatException e (catch NumberFormatException e
(html [:div.notification.is-danger.is-light (html-response
"Cannot parse the entity-id " entity-id ". It should be a number."])))) [:div.notification.is-danger.is-light
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
(defn history [request] (defn history [{:keys [identity] :as request}]
(str "<!DOCTYPE html>" (html-response
(html (base-page request (page-template ))))
(base-page (page-template )))))
(defn- with-ignore-trailing-slash [handler]
(fn [request]
(let [uri (request :uri)
clean-uri (str/replace uri #"^(.+?)/+$" "$1")]
(handler (assoc request :uri clean-uri)))))
(defroutes admin-routes (defroutes admin-routes
(wrap-routes (routes
(context "/admin" [] (context "/admin" []
(GET "/history" [] history) (GET "/history" [] history)
(GET "/history/" [] history) (GET "/history/" [] history)
(POST "/history/search" [] history-search) (POST "/history/search" [] history-search)
#_(GET "/history/:entity-id" [entity-id] history-search) (GET "/history/:entity-id" [entity-id] history-search)
#_(GET "/history/inspect/:entity-id" [entity-id] inspect)) (GET "/history/inspect/:entity-id" [entity-id] inspect))))
with-ignore-trailing-slash))

View File

@@ -381,7 +381,7 @@
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn tx-detail [i] (defn tx-detail [i]
(map (juxt :e #(d/ident (d/db (d/connect uri)) (:a %)) :v) (map (juxt :e #(d/ident (d/db (d/connect uri)) (:a %)) :v :added?)
(:data (first (:data (first
(d/tx-range (d/log (d/connect uri)) (d/tx-range (d/log (d/connect uri))
i i

View File

@@ -27,13 +27,7 @@
(defn ^:export init [] (defn ^:export init []
(dev-setup) (dev-setup)
(.addEventListener (.-body js/document)
"htmx:configRequest"
(fn [event]
(aset (.-headers (.-detail event))
"Authorization"
(some->> (.getItem js/localStorage "jwt")
(str "Token ")))))
;; document.body.addEventListener('htmx:configRequest', function(evt) { ;; document.body.addEventListener('htmx:configRequest', function(evt) {

View File

@@ -46,7 +46,7 @@
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}] [:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
[:span {:class "name"} "Rules"]]] [:span {:class "name"} "Rules"]]]
[:li.menu-item [:li.menu-item
[:a {:href "/admin/history", :class (str "item" (active-when ap = :admin-history))} [:a {:href (str "/admin/history?jwt=" (.getItem js/localStorage "jwt")) :class (str "item" (active-when ap = :admin-history))}
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}] [:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
[:span {:class "name"} "History"]]] [:span {:class "name"} "History"]]]
[:ul ]] [:ul ]]

View File

@@ -12,7 +12,6 @@
[auto-ap.views.pages.error :refer [error-page]] [auto-ap.views.pages.error :refer [error-page]]
[auto-ap.views.pages.ledger.balance-sheet :refer [balance-sheet-page]] [auto-ap.views.pages.ledger.balance-sheet :refer [balance-sheet-page]]
[auto-ap.views.pages.admin.jobs :refer [jobs-page]] [auto-ap.views.pages.admin.jobs :refer [jobs-page]]
[auto-ap.views.pages.admin.history :refer [history-page]]
[auto-ap.views.pages.ledger.external-import :refer [external-import-page]] [auto-ap.views.pages.ledger.external-import :refer [external-import-page]]
[auto-ap.views.pages.ledger.external-ledger :refer [external-ledger-page]] [auto-ap.views.pages.ledger.external-ledger :refer [external-ledger-page]]
[auto-ap.views.pages.ledger.profit-and-loss :refer [profit-and-loss-page]] [auto-ap.views.pages.ledger.profit-and-loss :refer [profit-and-loss-page]]
@@ -155,9 +154,6 @@
[admin-excel-import-page]) [admin-excel-import-page])
(defmethod page :admin-history [_]
[history-page])
(defmethod page :initial-error [_] (defmethod page :initial-error [_]
[error-page]) [error-page])

View File

@@ -1,40 +0,0 @@
(ns auto-ap.views.pages.admin.history
(:require
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.pages.admin.jobs.table :as table]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components :as com]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils :refer [dispatch-event with-user]]
[clojure.set :as set]
[re-frame.core :as re-frame]
[vimsical.re-frame.fx.track :as track]
[auto-ap.forms.builder :as form-builder]
[vimsical.re-frame.cofx.inject :as inject]
[auto-ap.forms :as forms]
[clojure.string :as str]
["htmx.org" :as htmx]
))
;; VIEWS
(def history-content
(let [c (atom nil)]
(with-meta
(fn []
(println "bothered?")
[:div {"hx-get" (.-pathname (.-location js/document))
"hx-trigger" "load"
"hx-swap" "outerHTML"
:ref (fn [i] (reset! c i))}
"test"])
{:should-component-update (fn [] false)
:component-did-mount (fn []
(.process htmx @c))})))
(defn history-page []
[side-bar-layout {:side-bar [admin-side-bar {}]
:main [history-content]}])