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.premium-features.core :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)))) |