Provides a very simple Emacs Lisp hook-style events system using Methodical. See https://github.com/metabase/metabase/issues/19812 for more information. Publish an event, which consists of a [[Topic]] keyword and an event map using [[publish-event!]], 'subscribe' to events by writing method implementations of [[publish-event!]]. On launch, all namespaces starting with | (ns metabase.events (:require [clojure.spec.alpha :as s] [metabase.events.schema :as events.schema] [metabase.models.interface :as mi] [metabase.util :as u] [metabase.util.i18n :as i18n] [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.methodical.null-cache :as u.methodical.null-cache] [metabase.util.methodical.unsorted-dispatcher :as u.methodical.unsorted-dispatcher] [methodical.core :as methodical] [potemkin :as p])) |
(p/import-vars [events.schema event-schema]) | |
(set! *warn-on-reflection* true) | |
Malli schema for an event topic keyword. | (def Topic [:and qualified-keyword? [:fn {:error/message "Events should derive from :metabase/event"} #(isa? % :metabase/event)]]) |
(s/def ::publish-event-dispatch-value (s/and (some-fn qualified-keyword? #(= % :default)) #(not= (namespace %) "event"))) | |
'Publish' an event by calling [[publish-event!]] with a [[Topic]] and an (events/publish-event! :event/database-create {:object database :user-id api/current-user-id}) 'Subscribe' to an event by add a Methodical method implementation. Since this uses the [[methodical/do-method-combination]], all multiple method implementations can be called for a single invocation. The order is indeterminate, but return value is ignored. Don't write method implementations for the event names themselves, e.g. ;; bad! If someone else writes a method for Instead, derive the event from another key, and write a method for that ;; Good (derive :event/database-create ::events) (methodical/defmethod events/publish-event! ::events [topic event] ...) The schema for each event topic are defined in | (methodical/defmulti publish-event! {:arglists '([topic event]) :defmethod-arities #{2} :dispatch-value-spec ::publish-event-dispatch-value} :combo (methodical/do-method-combination) ;; work around https://github.com/camsaul/methodical/issues/97 :dispatcher (u.methodical.unsorted-dispatcher/unsorted-dispatcher (fn dispatch-fn [topic _event] (keyword topic))) ;; work around https://github.com/camsaul/methodical/issues/98 :cache (u.methodical.null-cache/null-cache)) |
(methodical/defmethod publish-event! :default [_topic _event] nil) | |
(methodical/defmethod publish-event! :around :default [topic event] (assert (not *compile-files*) "Calls to publish-event! are not allowed in the top level.") (let [{:keys [object]} event] (log/debugf "Publishing %s event (name and id):\n\n%s" (u/colorize :yellow (pr-str topic)) (u/pprint-to-str (let [model (mi/model object)] (cond-> (select-keys object [:name :id]) model (assoc :model model)))))) (assert (and (qualified-keyword? topic) (isa? topic :metabase/event)) (format "Invalid event topic %s: events must derive from :metabase/event" (pr-str topic))) (assert (map? event) (format "Invalid event %s: event must be a map." (pr-str event))) (try (when-let [schema (and (mu/instrument-ns? *ns*) (events.schema/event-schema topic))] (mu/validate-throw schema event)) (next-method topic event) (catch Throwable e (throw (ex-info (i18n/tru "Error publishing {0} event: {1}" topic (ex-message e)) {:topic topic, :event event} e)))) event) | |
Determine metadata, if there is any, for given | (defn object->metadata [object] {:cached (:cached object) :ignore_cache (:ignore_cache object) ;; the :context key comes from qp middleware: ;; [[metabase.query-processor.middleware.process-userland-query/add-and-save-execution-info-xform!]] ;; and is important for distinguishing view events triggered when pinned cards are 'viewed' ;; when a user opens a collection. :context (:context object)}) |