Middleware that handles special {:type :internal :fn "metabase-enterprise.audit-app.pages.dashboards/table" :args []} ; optional vector of args to pass to the fn above To run an (defmethod audit.i/internal-query ::table [_] {:metadata ..., :results ...}) The function should return a map with two keys, LEGACY FORMAT:
REDUCIBLE FORMAT:
| (ns metabase-enterprise.audit-app.query-processor.middleware.handle-audit-queries (:require [clojure.data :as data] [metabase-enterprise.audit-app.interface :as audit.i] [metabase.api.common.validation :as validation] [metabase.public-settings.premium-features :as premium-features :refer [defenterprise]] [metabase.query-processor.error-type :as qp.error-type] [metabase.query-processor.pipeline :as qp.pipeline] [metabase.query-processor.schema :as qp.schema] [metabase.query-processor.util :as qp.util] [metabase.util.i18n :refer [tru]] [metabase.util.malli :as mu])) |
Primarily for dev and debugging purposes. We can probably take this out when shipping the finished product. | (defn- check-results-and-metadata-keys-match [results metadata] (let [results-keys (set (keys (first results))) metadata-keys (set (map (comp keyword first) metadata))] (when (and (seq results-keys) (not= results-keys metadata-keys)) (let [[only-in-results only-in-metadata] (data/diff results-keys metadata-keys)] (throw (Exception. (str "results-keys and metadata-keys differ.\n" "results-keys: " results-keys "\n" "metadata-keys: " metadata-keys "\n" "in results, but not metadata: " only-in-results "\n" "in metadata, but not results: " only-in-metadata))))))) |
(defn- metadata->cols [metadata] (for [[k v] metadata] (assoc v :name (name k)))) | |
(mu/defn- format-results [{:keys [results metadata]} :- [:map [:results [:sequential :map]] [:metadata audit.i/ResultsMetadata]]] (check-results-and-metadata-keys-match results metadata) {:cols (metadata->cols metadata) :rows (for [row results] (for [[k] metadata] (get row (keyword k))))}) | |
Schema for a valid | (def InternalQuery [:map [:type [:enum :internal "internal"]] [:fn [:and :string [:fn {:error/message "namespace-qualified symbol serialized as a string"} (fn [s] (try (when-let [symb (symbol s)] (qualified-symbol? symb)) (catch Throwable _)))]]] [:args {:optional true} [:sequential :any]]]) |
Additional | (def ^:dynamic *additional-query-params* nil) |
(defn- reduce-reducible-results [rff {:keys [metadata results xform], :or {xform identity}}] (let [cols (metadata->cols metadata) reducible-rows (results) rff* (fn [metadata] (xform (rff metadata)))] (assert (some? cols)) (assert (instance? clojure.lang.IReduceInit reducible-rows)) (qp.pipeline/*reduce* rff* {:cols cols} reducible-rows))) | |
(defn- reduce-legacy-results [rff results] (let [{:keys [cols rows]} (format-results results)] (assert (some? cols)) (assert (some? rows)) (qp.pipeline/*reduce* rff {:cols cols} rows))) | |
(defn- reduce-results [rff {rows :results, :as results}] ((if (fn? rows) reduce-reducible-results reduce-legacy-results) rff results)) | |
(mu/defn- process-internal-query [{qualified-fn-str :fn, args :args, :as query} :- InternalQuery rff :- ::qp.schema/rff] ;; Make sure current user is a superuser or has monitoring permissions (validation/check-has-application-permission :monitoring) ;; Make sure audit app is enabled (currently the only use case for internal queries). We can figure out a way to ;; allow non-audit-app queries if and when we add some (when-not (premium-features/enable-audit-app?) (throw (ex-info (tru "Audit App queries are not enabled on this instance.") {:type qp.error-type/invalid-query}))) (binding [*additional-query-params* (dissoc query :fn :args)] (let [resolved (apply audit.i/resolve-internal-query qualified-fn-str args)] (reduce-results rff resolved)))) | |
Middleware that handles | (defenterprise handle-audit-app-internal-queries :feature :audit-app [qp] (fn [query rff] (if (qp.util/internal-query? query) (process-internal-query query rff) (qp query rff)))) |