Compare commits
2 Commits
visualizer
...
fix-sql-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a403e51022 | ||
|
|
3263ca1f66 |
31
.babelrc
Normal file
31
.babelrc
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@emotion",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }]
|
||||
],
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": []
|
||||
},
|
||||
"extract": {
|
||||
"plugins": [
|
||||
[
|
||||
"ttag",
|
||||
{
|
||||
"extract": {
|
||||
"output": "locales/metabase-frontend.pot"
|
||||
},
|
||||
"discover": ["t", "jt"],
|
||||
"numberedExpressions": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
1297
.circleci/config.yml
Normal file
1297
.circleci/config.yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
Update `clj-kondo` configs for libraries using
|
||||
|
||||
```sh
|
||||
clj-kondo --copy-configs --dependencies --lint "$(clojure -Spath -A:dev)"
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
23
.clj-kondo/hooks/metabase/api/common.clj
Normal file
23
.clj-kondo/hooks/metabase/api/common.clj
Normal file
@@ -0,0 +1,23 @@
|
||||
(ns hooks.metabase.api.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn route-fn-name
|
||||
[method route]
|
||||
(let [route (if (vector? route) (first route) route)]
|
||||
(-> (str (name method) route)
|
||||
(str/replace #"/" "_")
|
||||
symbol)))
|
||||
|
||||
(defn defendpoint [{:keys [node]}]
|
||||
(let [[method route & body] (rest (:children node))]
|
||||
{:node
|
||||
(api/list-node [(api/token-node 'do)
|
||||
;; register usage of compojure core var
|
||||
(api/token-node (symbol (str "compojure.core")
|
||||
(str method)))
|
||||
;; define function with route-fn-name
|
||||
(api/list-node (list* (api/token-node 'clojure.core/defn)
|
||||
(route-fn-name (api/sexpr method) (api/sexpr route))
|
||||
body))])}))
|
||||
55
.clj-kondo/hooks/metabase/test/data.clj
Normal file
55
.clj-kondo/hooks/metabase/test/data.clj
Normal file
@@ -0,0 +1,55 @@
|
||||
(ns hooks.metabase.test.data
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as string]
|
||||
[clojure.walk :as walk]))
|
||||
|
||||
(defn dataset [{:keys [node]}]
|
||||
(let [[dataset & body] (rest (:children node))]
|
||||
{:node (with-meta
|
||||
(hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'clojure.test/testing)
|
||||
(hooks/string-node (str dataset))
|
||||
body))
|
||||
(meta dataset))}))
|
||||
|
||||
(defn $ids [{:keys [node]}]
|
||||
(let [args (rest (:children node))
|
||||
[table-name & body] args
|
||||
unused-node (hooks/token-node '_)
|
||||
vars (atom #{})
|
||||
_ (walk/postwalk (fn [node]
|
||||
(when (hooks/token-node? node)
|
||||
(let [str-node (str (hooks/sexpr node))]
|
||||
(when (or (string/starts-with? str-node "$")
|
||||
(string/starts-with? str-node "!")
|
||||
(string/starts-with? str-node "&")
|
||||
(string/starts-with? str-node "*")
|
||||
(string/starts-with? str-node "%"))
|
||||
(swap! vars conj node))))
|
||||
node)
|
||||
body)
|
||||
nil-bindings (vec (interpose (hooks/token-node nil) @vars))
|
||||
unused-bindings (vec (interpose unused-node @vars))
|
||||
final-bindings (concat
|
||||
(when-not (= "nil" (str table-name))
|
||||
[table-name (hooks/token-node nil)])
|
||||
[unused-node table-name]
|
||||
(if (seq nil-bindings)
|
||||
(conj nil-bindings (hooks/token-node nil))
|
||||
[])
|
||||
(if (seq unused-bindings)
|
||||
(conj (next unused-bindings)
|
||||
(first unused-bindings)
|
||||
unused-node)
|
||||
[]))]
|
||||
{:node (with-meta
|
||||
(hooks/list-node
|
||||
(with-meta
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node (vec final-bindings))
|
||||
body)
|
||||
(meta body)))
|
||||
(meta body))}))
|
||||
11
.clj-kondo/hooks/toucan/util/test.clj
Normal file
11
.clj-kondo/hooks/toucan/util/test.clj
Normal file
@@ -0,0 +1,11 @@
|
||||
(ns hooks.toucan.util.test
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn with-temp [{:keys [node]}]
|
||||
(let [[_ db-ref binding+opts & body] (:children node)
|
||||
[args opts] (:children binding+opts)]
|
||||
{:node (api/vector-node [db-ref
|
||||
opts
|
||||
(api/list-node (list* (api/token-node 'fn)
|
||||
(api/vector-node [args])
|
||||
body))])}))
|
||||
@@ -1,5 +0,0 @@
|
||||
(ns macros.common
|
||||
"Helpers for common Metabase macro shapes so we don't have to define the same macroexpansions over and over.")
|
||||
|
||||
(defn ignore-unused [symb]
|
||||
(vary-meta symb assoc :clj-kondo/ignore [:unused-binding]))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns macros.metabase.api.card-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-ordered-items
|
||||
[collection model-and-name-syms & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[model binding]]
|
||||
[(macros.common/ignore-unused binding) model])))
|
||||
model-and-name-syms)
|
||||
~collection
|
||||
~@body))
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns macros.metabase.api.collection-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
`(let [~(macros.common/ignore-unused 'a) nil
|
||||
~(macros.common/ignore-unused 'b) nil
|
||||
~(macros.common/ignore-unused 'c) nil
|
||||
~(macros.common/ignore-unused 'd) nil
|
||||
~(macros.common/ignore-unused 'e) nil
|
||||
~(macros.common/ignore-unused 'f) nil
|
||||
~(macros.common/ignore-unused 'g) nil]
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
`(let [~(macros.common/ignore-unused '&ids) ~collection-or-id-or-nil]
|
||||
~@body))
|
||||
@@ -1,7 +1,5 @@
|
||||
(ns macros.metabase.api.common)
|
||||
|
||||
(defmacro define-routes
|
||||
"Macro for api.common/define-routes"
|
||||
[& args]
|
||||
`(do (def ~'routes "docstring" nil)
|
||||
(defmacro define-routes [& args]
|
||||
`(do (def ~'routes)
|
||||
~@args))
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.api.embed-test)
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.api.public-test)
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-card-referencing!
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
~(or dashcard-binding '_) nil]
|
||||
~@body))
|
||||
@@ -1,24 +0,0 @@
|
||||
(ns macros.metabase.lib.common)
|
||||
|
||||
(defmacro defop
|
||||
[op-name & argvecs]
|
||||
`(do
|
||||
(defn ~(symbol (str (name op-name) "-clause"))
|
||||
~(format "Create a standalone clause of type `%s`." (name op-name))
|
||||
~@(for [argvec argvecs
|
||||
:let [arglist-expr (if (contains? (set argvec) '&)
|
||||
(cons 'list* (remove #{'&} argvec))
|
||||
argvec)]]
|
||||
`([~'query ~'stage-number ~@argvec]
|
||||
[~(keyword op-name) ~'query ~'stage-number ~@arglist-expr])))
|
||||
|
||||
(defn ~op-name
|
||||
~(format "Create a closure of clause of type `%s`." (name op-name))
|
||||
~@(for [argvec argvecs
|
||||
:let [arglist-expr (if (contains? (set argvec) '&)
|
||||
(cons 'list* (remove #{'&} argvec))
|
||||
argvec)]]
|
||||
`([~@argvec]
|
||||
(fn ~(symbol (str (name op-name) "-closure"))
|
||||
[~'query ~'stage-number]
|
||||
(apply ~(symbol (str (name op-name) "-clause")) ~'query ~'stage-number ~@arglist-expr)))))))
|
||||
@@ -1,7 +0,0 @@
|
||||
(ns macros.metabase.lib.filter)
|
||||
|
||||
(defmacro deffilter
|
||||
[filter-name argv]
|
||||
(let [argv (into ['query 'stage] (remove #{'&} argv))]
|
||||
`(defn ~filter-name "docstring" ~argv
|
||||
~argv)))
|
||||
@@ -1,4 +0,0 @@
|
||||
(ns macros.metabase.models.params.chain-filter-test)
|
||||
|
||||
(defmacro chain-filter [& _]
|
||||
`(metabase.models.params.chain-filter/chain-filter nil nil))
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns macros.metabase.models.query-analysis-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup [& body]
|
||||
`(let [~(macros.common/ignore-unused 'card-id) 1
|
||||
~(macros.common/ignore-unused 'table-id) 2
|
||||
~(macros.common/ignore-unused 'tax-id) 3
|
||||
~(macros.common/ignore-unused 'total-id) 4]
|
||||
~@body))
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns macros.metabase.models.user-test)
|
||||
|
||||
(defmacro with-groups [bindings & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 3)
|
||||
(mapcat (fn [[group-binding properties members]]
|
||||
[group-binding [properties members]])))
|
||||
bindings)
|
||||
~@body))
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns macros.metabase.query-processor.streaming)
|
||||
(ns metabase.query-processor.streaming)
|
||||
|
||||
(defmacro streaming-response [[x y z] & body]
|
||||
`(let [~x [~y ~z]]
|
||||
`(clojure.core/let [~x ~y]
|
||||
~z
|
||||
~@body))
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.related-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-world [& body]
|
||||
`(let [~(macros.common/ignore-unused 'collection-id) nil
|
||||
~(macros.common/ignore-unused 'metric-id-a) nil
|
||||
~(macros.common/ignore-unused 'metric-id-b) nil
|
||||
~(macros.common/ignore-unused 'segment-id-a) nil
|
||||
~(macros.common/ignore-unused 'segment-id-b) nil
|
||||
~(macros.common/ignore-unused 'card-id-a) nil
|
||||
~(macros.common/ignore-unused 'card-id-b) nil
|
||||
~(macros.common/ignore-unused 'card-id-c) nil]
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.task.setup.query-analysis-setup
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup!
|
||||
"Set up the data required to test the Query Analysis related tasks"
|
||||
[& body]
|
||||
`(let [~(macros.common/ignore-unused 'c1) 1
|
||||
~(macros.common/ignore-unused 'c2) 2
|
||||
~(macros.common/ignore-unused 'c3) 3
|
||||
~(macros.common/ignore-unused 'c4) 4
|
||||
~(macros.common/ignore-unused 'archived) 5
|
||||
~(macros.common/ignore-unused 'invalid) 6]
|
||||
~@body))
|
||||
@@ -1,6 +0,0 @@
|
||||
(ns macros.metabase.test.data.users)
|
||||
|
||||
(defmacro with-group-for-user
|
||||
[[group-binding test-user-name-or-user-id group] & body]
|
||||
`(let [~group-binding [~test-user-name-or-user-id ~group]]
|
||||
~@body))
|
||||
@@ -1,5 +0,0 @@
|
||||
(ns macros.metabase.test.persistence)
|
||||
|
||||
(defmacro with-persistence-enabled! [[persist-fn-sym] & body]
|
||||
`(clojure.core/let [~persist-fn-sym (fn [])]
|
||||
~@body))
|
||||
@@ -1,11 +0,0 @@
|
||||
(ns macros.metabase.test.util)
|
||||
|
||||
(defmacro with-temp-env-var-value! [bindings & body]
|
||||
`((constantly nil)
|
||||
~@(map second (partition-all 2 bindings))
|
||||
~@body))
|
||||
|
||||
(defmacro with-temporary-raw-setting-values [bindings & body]
|
||||
`((constantly nil)
|
||||
~@(map second (partition-all 2 bindings))
|
||||
~@body))
|
||||
@@ -1,8 +0,0 @@
|
||||
(ns macros.metabase.util.namespaces)
|
||||
|
||||
(defmacro import-fn
|
||||
"Kondo macro replacement for [[metabase.util.namespaces/import-fn]]."
|
||||
([sym]
|
||||
`(def ~(-> sym name symbol) "docstring" ~sym))
|
||||
([sym name]
|
||||
`(def ~name "docstring" ~sym)))
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns macros.metabase.xrays.domain-entities.malli)
|
||||
|
||||
(defmacro define-getters-and-setters [schema & defs]
|
||||
`(do
|
||||
(comment ~schema) ;; Reference the schema
|
||||
~@(for [[sym _path] (partition 2 defs)]
|
||||
`(do
|
||||
(defn ~(vary-meta sym
|
||||
assoc :export true)
|
||||
"docs" [_x#] nil)
|
||||
(defn ~(vary-meta (symbol (str "with-" (name sym)))
|
||||
assoc :export true)
|
||||
"docs" [x# _new#] x#)
|
||||
(defn ~(vary-meta (symbol (str (name sym) "-js"))
|
||||
assoc :export true)
|
||||
"docs" [x#] x#)))))
|
||||
@@ -1,6 +0,0 @@
|
||||
(ns macros.metabase-enterprise.advanced-permissions.api.util-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-impersonations! [impersonations-and-attributes-map & body]
|
||||
`(let [~(macros.common/ignore-unused '&group) ~impersonations-and-attributes-map]
|
||||
~@body))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns macros.metabase-enterprise.query-reference-validation.api-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup! [& body]
|
||||
`(let [~(macros.common/ignore-unused 'card-1) 1
|
||||
~(macros.common/ignore-unused 'card-2) 2
|
||||
~(macros.common/ignore-unused 'card-3) 3
|
||||
~(macros.common/ignore-unused 'card-4) 4
|
||||
~(macros.common/ignore-unused 'card-5) 5
|
||||
~(macros.common/ignore-unused 'coll-2) 100
|
||||
~(macros.common/ignore-unused 'coll-3) 200]
|
||||
~@body))
|
||||
@@ -1,6 +0,0 @@
|
||||
(ns macros.metabase-enterprise.sandbox.test-util
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-gtaps! [gtaps-definition & body]
|
||||
`(let [~(macros.common/ignore-unused '&group) ~gtaps-definition]
|
||||
~@body))
|
||||
@@ -1,59 +0,0 @@
|
||||
(ns macros.metabase-enterprise.serialization.test-util
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-world [& body]
|
||||
`(let [~(macros.common/ignore-unused 'db-id) nil
|
||||
~(macros.common/ignore-unused 'table-id) nil
|
||||
~(macros.common/ignore-unused 'table) nil
|
||||
~(macros.common/ignore-unused 'table-id-categories) nil
|
||||
~(macros.common/ignore-unused 'table-id-users) nil
|
||||
~(macros.common/ignore-unused 'table-id-checkins) nil
|
||||
~(macros.common/ignore-unused 'venues-pk-field-id) nil
|
||||
~(macros.common/ignore-unused 'numeric-field-id) nil
|
||||
~(macros.common/ignore-unused 'name-field-id) nil
|
||||
~(macros.common/ignore-unused 'latitude-field-id) nil
|
||||
~(macros.common/ignore-unused 'longitude-field-id) nil
|
||||
~(macros.common/ignore-unused 'category-field-id) nil
|
||||
~(macros.common/ignore-unused 'category-pk-field-id) nil
|
||||
~(macros.common/ignore-unused 'date-field-id) nil
|
||||
~(macros.common/ignore-unused 'users-pk-field-id) nil
|
||||
~(macros.common/ignore-unused 'user-id-field-id) nil
|
||||
~(macros.common/ignore-unused 'checkins->venues-field-id) nil
|
||||
~(macros.common/ignore-unused 'last-login-field-id) nil
|
||||
~(macros.common/ignore-unused 'collection-id) nil
|
||||
~(macros.common/ignore-unused 'collection-id-nested) nil
|
||||
~(macros.common/ignore-unused 'user-id-temp) nil
|
||||
~(macros.common/ignore-unused 'personal-collection-id) nil
|
||||
~(macros.common/ignore-unused 'pc-felicia-nested-id) nil
|
||||
~(macros.common/ignore-unused 'pc-nested-id) nil
|
||||
~(macros.common/ignore-unused 'pc-deeply-nested-id) nil
|
||||
~(macros.common/ignore-unused 'metric-id) nil
|
||||
~(macros.common/ignore-unused 'segment-id) nil
|
||||
~(macros.common/ignore-unused 'dashboard-id) nil
|
||||
~(macros.common/ignore-unused 'root-dashboard-id) nil
|
||||
~(macros.common/ignore-unused 'card-id) nil
|
||||
~(macros.common/ignore-unused 'card-arch-id) nil
|
||||
~(macros.common/ignore-unused 'card-id-root) nil
|
||||
~(macros.common/ignore-unused 'card-id-nested) nil
|
||||
~(macros.common/ignore-unused 'card-id-nested-query) nil
|
||||
~(macros.common/ignore-unused 'card-id-native-query) nil
|
||||
~(macros.common/ignore-unused 'card-id-pivot-table) nil
|
||||
~(macros.common/ignore-unused 'dashcard-id) nil
|
||||
~(macros.common/ignore-unused 'dashcard-top-level-click-id) nil
|
||||
~(macros.common/ignore-unused 'dashcard-with-click-actions) nil
|
||||
~(macros.common/ignore-unused 'dashcard-with-textbox-id) nil
|
||||
~(macros.common/ignore-unused 'card-id-root-to-collection) nil
|
||||
~(macros.common/ignore-unused 'card-id-collection-to-root) nil
|
||||
~(macros.common/ignore-unused 'pulse-id) nil
|
||||
~(macros.common/ignore-unused 'pulsecard-root-id) nil
|
||||
~(macros.common/ignore-unused 'pulsecard-collection-id) nil
|
||||
~(macros.common/ignore-unused 'card-id-template-tags) nil
|
||||
~(macros.common/ignore-unused 'card-id-filter-agg) nil
|
||||
~(macros.common/ignore-unused 'card-id-temporal-unit) nil
|
||||
~(macros.common/ignore-unused 'snippet-id) nil
|
||||
~(macros.common/ignore-unused 'snippet-collection-id) nil
|
||||
~(macros.common/ignore-unused 'snippet-nested-collection-id) nil
|
||||
~(macros.common/ignore-unused 'nested-snippet-id) nil
|
||||
~(macros.common/ignore-unused 'card-id-with-native-snippet) nil
|
||||
~(macros.common/ignore-unused 'card-join-card-id) nil]
|
||||
~@body))
|
||||
@@ -10,10 +10,6 @@
|
||||
`(let [tb# (org.quartz.TriggerBuilder/newTrigger)]
|
||||
(clojurewerkz.quartzite.triggers/finalize (-> tb# ~@body))))
|
||||
|
||||
(defmacro simple-schedule
|
||||
[& body]
|
||||
`(-> {} ~@body))
|
||||
|
||||
(defmacro schedule
|
||||
[& body]
|
||||
`(let [s# ~(first body)]
|
||||
|
||||
8
.clj-kondo/macros/toucan/models.clj
Normal file
8
.clj-kondo/macros/toucan/models.clj
Normal file
@@ -0,0 +1,8 @@
|
||||
(ns toucan.models)
|
||||
|
||||
(defmacro defmodel [model-name & ks]
|
||||
`(do (clojure.core/defrecord ~model-name ~(vec (map (comp symbol name) ks)))
|
||||
(clojure.core/defrecord
|
||||
~(symbol (str model-name "Instance"))
|
||||
~(vec (map (comp symbol name) ks)))
|
||||
~(symbol (str model-name "Instance"))))
|
||||
@@ -1,329 +0,0 @@
|
||||
(ns hooks.clojure.core
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
;;; TODO -- seems silly to maintain different blacklists and whitelists here than we use for the deftest `^:parallel`
|
||||
;;; checker... those lists live in the Clj Kondo config file
|
||||
(def ^:private symbols-allowed-in-fns-not-ending-in-an-exclamation-point
|
||||
'#{;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
|
||||
toucan2.core/delete!
|
||||
toucan2.core/update!
|
||||
toucan2.core/insert!
|
||||
toucan2.core/insert-returning-instance!
|
||||
toucan2.core/insert-returning-instances!
|
||||
toucan2.core/insert-returning-pk!
|
||||
toucan2.core/insert-returning-pks!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.memoize/memo-clear!
|
||||
clojure.core/conj!
|
||||
clojure.core/persistent!
|
||||
clojure.core/reset!
|
||||
clojure.core/swap!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.java.jdbc/execute!
|
||||
methodical.core/add-aux-method-with-unique-key!
|
||||
methodical.core/remove-aux-method-with-unique-key!
|
||||
next.jdbc/execute!
|
||||
|
||||
;; Definitely thread safe
|
||||
metabase.test.util.dynamic-redefs/patch-vars!
|
||||
|
||||
;; TODO: most of these symbols shouldn't be here, we should go through them and
|
||||
;; find the functions/macros that use them and make sure their names end with !
|
||||
;; best way to do this is try remove each of these and rely on kondo output to find places where it's used
|
||||
clojure.test/grant-collection-perms!
|
||||
clojure.test/grant-collection-perms-fn!
|
||||
clojure.test/grant-perms-fn!
|
||||
clojure.test/purge-old-entries!
|
||||
clojure.test/revoke-collection-perms!
|
||||
clojure.test/save-results!
|
||||
metabase-enterprise.advanced-permissions.models.permissions/update-db-download-permissions!
|
||||
metabase-enterprise.internal-user/install-internal-user!
|
||||
metabase-enterprise.sso.integrations.saml-test/call-with-login-attributes-cleared!
|
||||
metabase.actions/perform-action!
|
||||
metabase.analytics.snowplow-test/fake-track-event-impl!
|
||||
metabase.analytics.snowplow/track-event-impl!
|
||||
metabase.api.public-test/add-card-to-dashboard!
|
||||
metabase.cmd.dump-to-h2/dump-to-h2!
|
||||
metabase.cmd.load-from-h2/load-from-h2!
|
||||
metabase.core/ensure-audit-db-installed!
|
||||
metabase.db.schema-migrations-test.impl/run-migrations-in-range!
|
||||
metabase.db.setup/migrate!
|
||||
metabase.db.setup/setup-db!
|
||||
metabase.db/migrate!
|
||||
metabase.db/setup-db!
|
||||
metabase.driver.mongo-test/create-database-from-row-maps!
|
||||
metabase.driver.postgres-test/create-enums-db!
|
||||
metabase.driver.postgres-test/drop-if-exists-and-create-db!
|
||||
metabase.driver.sql-jdbc.execute/execute-statement!
|
||||
metabase.email-test/reset-inbox!
|
||||
metabase.email/send-email!
|
||||
metabase.models.action/insert!
|
||||
metabase.models.collection.graph-test/clear-graph-revisions!
|
||||
metabase.models.collection.graph-test/do-with-n-temp-users-with-personal-collections!
|
||||
metabase.models.field-values/create-or-update-full-field-values!
|
||||
metabase.models.model-index/add-values!
|
||||
metabase.models.moderation-review/create-review!
|
||||
metabase.models.on-demand-test/add-dashcard-with-parameter-mapping!
|
||||
metabase.models.permissions/grant-application-permissions!
|
||||
metabase.models.permissions/grant-collection-read-permissions!
|
||||
metabase.models.permissions/grant-collection-readwrite-permissions!
|
||||
metabase.models.permissions/grant-full-data-permissions!
|
||||
metabase.models.permissions/grant-native-readwrite-permissions!
|
||||
metabase.models.permissions/grant-permissions!
|
||||
metabase.models.permissions/revoke-application-permissions!
|
||||
metabase.models.permissions/revoke-data-perms!
|
||||
metabase.models.permissions/update-data-perms-graph!
|
||||
metabase.models.permissions/update-group-permissions!
|
||||
metabase.models.persisted-info/ready-database!
|
||||
metabase.models.setting-test/test-user-local-allowed-setting!
|
||||
metabase.models.setting-test/test-user-local-only-setting!
|
||||
metabase.models.setting.cache/restore-cache!
|
||||
metabase.models.setting/set!
|
||||
metabase.models.setting/validate-settings-formatting!
|
||||
metabase.permissions.test-util/with-restored-perms!
|
||||
metabase.pulse.send/send-notifications!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase.query-processor.streaming.interface/begin!
|
||||
metabase.query-processor.streaming.interface/finish!
|
||||
metabase.query-processor.streaming.interface/write-row!
|
||||
metabase.sample-data/try-to-extract-sample-database!
|
||||
metabase.setup/create-token!
|
||||
metabase.sync.sync-metadata.fields.sync-metadata/update-field-metadata-if-needed!
|
||||
metabase.sync.sync-metadata/sync-db-metadata!
|
||||
metabase.sync.util-test/sync-database!
|
||||
metabase.sync.util/store-sync-summary!
|
||||
metabase.sync/sync-database!
|
||||
metabase.task.index-values/job-init!
|
||||
metabase.task.persist-refresh/job-init!
|
||||
metabase.task.persist-refresh/refresh-tables!
|
||||
metabase.task.persist-refresh/schedule-persistence-for-database!
|
||||
metabase.task/delete-task!
|
||||
metabase.test.data.bigquery-cloud-sdk/execute!
|
||||
metabase.test.data.impl/copy-db-tables-and-fields!
|
||||
metabase.test.data.impl/get-or-create-database!
|
||||
metabase.test.data.impl/get-or-create-default-dataset!
|
||||
metabase.test.data.impl.get-or-create/set-test-db-permissions!
|
||||
metabase.test.data.interface/create-db!
|
||||
metabase.test.data.interface/destroy-db!
|
||||
metabase.test.data.oracle/create-user!
|
||||
metabase.test.data.oracle/drop-user!
|
||||
metabase.test.data.sql-jdbc.load-data/make-insert!
|
||||
metabase.test.data.users/clear-cached-session-tokens!
|
||||
metabase.test.initialize/do-initialization!
|
||||
metabase.test.initialize/initialize-if-needed!
|
||||
metabase.test.integrations.ldap/start-ldap-server!
|
||||
metabase.test.util.log/ensure-unique-logger!
|
||||
metabase.test.util.log/set-ns-log-level!
|
||||
metabase.test.util/do-with-temp-env-var-value!
|
||||
metabase.test.util/restore-raw-setting!
|
||||
metabase.test.util/upsert-raw-setting!
|
||||
metabase.test/initialize-if-needed!
|
||||
metabase.test/test-helpers-set-global-values!
|
||||
metabase.test/with-temp-env-var-value!
|
||||
metabase.upload-test/set-local-infile!
|
||||
metabase.util.files/create-dir-if-not-exists!
|
||||
metabase.util.ssh-test/start-mock-servers!
|
||||
metabase.util.ssh-test/stop-mock-servers!})
|
||||
|
||||
(defn- end-with-exclamation?
|
||||
[s]
|
||||
(str/ends-with? s "!"))
|
||||
|
||||
(defn- explicitly-safe? [qualified-symbol]
|
||||
(contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol))
|
||||
|
||||
(defn- explicitly-unsafe? [config qualified-symbol]
|
||||
(contains? (get-in config [:linters :metabase/validate-deftest :parallel/unsafe]) qualified-symbol))
|
||||
|
||||
(defn- unsafe? [config qualified-symbol]
|
||||
(and (or (end-with-exclamation? qualified-symbol)
|
||||
(explicitly-unsafe? config qualified-symbol))
|
||||
(not (explicitly-safe? qualified-symbol))))
|
||||
|
||||
(defn- non-thread-safe-form-should-end-with-exclamation*
|
||||
[{[defn-or-defmacro form-name] :children, :as node} config]
|
||||
(when-not (and (:string-value form-name)
|
||||
(end-with-exclamation? (:string-value form-name)))
|
||||
(letfn [(walk [f form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk f child)))
|
||||
(check-node [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(when (unsafe? config qualified-symbol)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta form-name)
|
||||
:message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`. [:metabase/test-helpers-use-non-thread-safe-functions]"
|
||||
(:string-value defn-or-defmacro) qualified-symbol)
|
||||
:type :metabase/test-helpers-use-non-thread-safe-functions)))))]
|
||||
(walk check-node node))
|
||||
node))
|
||||
|
||||
(defn non-thread-safe-form-should-end-with-exclamation
|
||||
"Used to ensure defn and defmacro in test namespace to have name ending with `!` if it's non-thread-safe.
|
||||
A function or a macro can be defined as 'not thread safe' when their funciton name ends with a `!`.
|
||||
|
||||
Only used in tests to identify thread-safe/non-thread-safe test helpers. See #37126"
|
||||
[{:keys [node cljc lang config]}]
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(non-thread-safe-form-should-end-with-exclamation* node config))
|
||||
{:node node})
|
||||
|
||||
(comment
|
||||
(require '[clj-kondo.core :as clj-kondo])
|
||||
(def form (str '(defmacro a
|
||||
[x]
|
||||
`(fun-call x))))
|
||||
|
||||
(def form "(defmacro a
|
||||
[x]
|
||||
`(some! ~x))")
|
||||
|
||||
(def form "(defun f
|
||||
[x]
|
||||
(let [g! (fn [] 1)]
|
||||
(g!)))")
|
||||
|
||||
(str (hooks/parse-string form))
|
||||
(hooks/sexpr (hooks/parse-string form))
|
||||
|
||||
(binding [hooks/*reload* true]
|
||||
(-> form
|
||||
(with-in-str (clj-kondo/run! {:lint ["-"]}))
|
||||
:findings))
|
||||
|
||||
(do (non-thread-safe-form-should-end-with-exclamation* (hooks/parse-string form)) nil))
|
||||
|
||||
(defn- ns-form-node->require-node [ns-form-node]
|
||||
(some (fn [node]
|
||||
(when (and (hooks/list-node? node)
|
||||
(let [first-child (first (:children node))]
|
||||
(and (hooks/keyword-node? first-child)
|
||||
(= (hooks/sexpr first-child) :require))))
|
||||
node))
|
||||
(:children ns-form-node)))
|
||||
|
||||
(defn- lint-require-shapes [ns-form-node]
|
||||
(doseq [node (-> ns-form-node
|
||||
ns-form-node->require-node
|
||||
:children
|
||||
rest)]
|
||||
(cond
|
||||
(not (hooks/vector-node? node))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "All :required namespaces should be wrapped in vectors [:metabase/require-shape-checker]"
|
||||
:type :metabase/require-shape-checker))
|
||||
|
||||
(hooks/vector-node? (second (:children node)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Don't use prefix forms inside :require [:metabase/require-shape-checker]"
|
||||
:type :metabase/require-shape-checker)))))
|
||||
|
||||
(defn- require-node->namespace-symb-nodes [require-node]
|
||||
(let [[_ns & args] (:children require-node)]
|
||||
(into []
|
||||
;; prefixed namespace forms are NOT SUPPORTED!!!!!!!!1
|
||||
(keep (fn [node]
|
||||
(cond
|
||||
(hooks/vector-node? node)
|
||||
;; propagate the metadata attached to this vector in case there's a `:clj-kondo/ignore` form.
|
||||
(vary-meta (first (:children node)) (partial merge (meta require-node) (meta node)))
|
||||
|
||||
;; this should also be dead code since we require requires to be vectors
|
||||
(hooks/token-node? node)
|
||||
(vary-meta node (partial merge (meta require-node)))
|
||||
|
||||
:else
|
||||
(printf "Don't know how to figure out what namespace is being required in %s\n" (pr-str node)))))
|
||||
args)))
|
||||
|
||||
(defn- ns-form-node->ns-symb [ns-form-node]
|
||||
(some-> (some (fn [node]
|
||||
(when (and (hooks/token-node? node)
|
||||
(not= (hooks/sexpr node) 'ns))
|
||||
node))
|
||||
(:children ns-form-node))
|
||||
hooks/sexpr))
|
||||
|
||||
(defn- module
|
||||
"E.g.
|
||||
|
||||
(module 'metabase.qp.middleware.wow) => 'metabase.qp"
|
||||
[ns-symb]
|
||||
(some-> (re-find #"^metabase\.[^.]+" (str ns-symb)) symbol))
|
||||
|
||||
(defn- ignored-namespace? [ns-symb config]
|
||||
(some
|
||||
(fn [pattern-str]
|
||||
(re-find (re-pattern pattern-str) (str ns-symb)))
|
||||
(:ignored-namespace-patterns config)))
|
||||
|
||||
(defn- module-api-namespaces
|
||||
"Set API namespaces for a given module. `:any` means you can use anything, there are no API namespaces for this
|
||||
module (yet). If unspecified, the default is just the namespace with the same name as the module e.g.
|
||||
`metabase.db`."
|
||||
[module config]
|
||||
(let [module-config (get-in config [:api-namespaces module])]
|
||||
(cond
|
||||
(= module-config :any)
|
||||
nil
|
||||
|
||||
(set? module-config)
|
||||
module-config
|
||||
|
||||
:else
|
||||
#{module})))
|
||||
|
||||
(defn- lint-modules [ns-form-node config]
|
||||
(let [ns-symb (ns-form-node->ns-symb ns-form-node)]
|
||||
(when-not (ignored-namespace? ns-symb config)
|
||||
(when-let [current-module (module ns-symb)]
|
||||
(let [allowed-modules (get-in config [:allowed-modules current-module])
|
||||
required-namespace-symb-nodes (-> ns-form-node
|
||||
ns-form-node->require-node
|
||||
require-node->namespace-symb-nodes)]
|
||||
(doseq [node required-namespace-symb-nodes
|
||||
:let [clj-kondo-ignore (some-> (meta node) :clj-kondo/ignore hooks/sexpr set)]
|
||||
:when (not (contains? clj-kondo-ignore :metabase/ns-module-checker))
|
||||
:let [required-namespace (hooks/sexpr node)
|
||||
required-module (module required-namespace)]
|
||||
;; ignore stuff not in a module i.e. non-Metabase stuff.
|
||||
:when required-module
|
||||
:let [in-current-module? (= required-module current-module)]
|
||||
:when (not in-current-module?)
|
||||
:let [allowed-module? (or (= allowed-modules :any)
|
||||
(contains? (set allowed-modules) required-module))
|
||||
module-api-namespaces (module-api-namespaces required-module config)
|
||||
allowed-module-namespace? (or (empty? module-api-namespaces)
|
||||
(contains? module-api-namespaces required-namespace))]]
|
||||
(when-let [error (cond
|
||||
(not allowed-module?)
|
||||
(format "Module %s should not be used in the %s module. [:metabase/ns-module-checker :allowed-modules %s]"
|
||||
required-module
|
||||
current-module
|
||||
current-module)
|
||||
|
||||
(not allowed-module-namespace?)
|
||||
(format "Namespace %s is not an allowed external API namespace for the %s module. [:metabase/ns-module-checker :api-namespaces %s]"
|
||||
required-namespace
|
||||
required-module
|
||||
required-module))]
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message error
|
||||
:type :metabase/ns-module-checker)))))))))
|
||||
|
||||
(defn lint-ns [x]
|
||||
(lint-require-shapes (:node x))
|
||||
(lint-modules (:node x) (get-in x [:config :linters :metabase/ns-module-checker]))
|
||||
x)
|
||||
@@ -1,160 +0,0 @@
|
||||
(ns hooks.clojure.test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- warn-about-disallowed-parallel-forms [form config]
|
||||
(letfn [(error! [form message]
|
||||
(hooks/reg-finding! (assoc (meta form)
|
||||
:message message
|
||||
:type :metabase/validate-deftest)))
|
||||
(f [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(cond
|
||||
(contains? (:parallel/unsafe config) qualified-symbol)
|
||||
(error! form (format "%s is not allowed inside a ^:parallel test or test fixture [:metabase/validate-deftest]" qualified-symbol))
|
||||
|
||||
(and (not (contains? (:parallel/safe config) qualified-symbol))
|
||||
(str/ends-with? (name qualified-symbol) "!"))
|
||||
(error! form (format "destructive functions like %s are not allowed inside a ^:parallel test or test fixture. If this should be allowed, add it to the whitelist in the Kondo config file [:metabase/validate-deftest]"
|
||||
qualified-symbol)))))
|
||||
(walk [form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk child)))]
|
||||
(walk form)))
|
||||
|
||||
(defn- deftest-check-parallel
|
||||
"1. Check if test is marked ^:parallel / ^:synchronized correctly
|
||||
2. Make sure disallowed forms are not used in ^:parallel tests"
|
||||
[{[_ test-name & body] :children, :as _node} config]
|
||||
(let [test-metadata (:meta test-name)
|
||||
metadata-sexprs (map hooks/sexpr test-metadata)
|
||||
combined-metadata (transduce
|
||||
(map (fn [x]
|
||||
(if (map? x)
|
||||
x
|
||||
{x true})))
|
||||
(completing merge)
|
||||
{}
|
||||
metadata-sexprs)
|
||||
parallel? (:parallel combined-metadata)
|
||||
synchronized? (:synchronized combined-metadata)]
|
||||
(when (and parallel? synchronized?)
|
||||
(hooks/reg-finding! (assoc (meta test-name)
|
||||
:message "Test should not be marked both ^:parallel and ^:synchronized"
|
||||
:type :metabase/validate-deftest)))
|
||||
;; only when the custom `:metabase/deftest-not-marked-parallel-or-synchronized` is enabled: complain if tests are
|
||||
;; not explicitly marked `^:parallel` or `^:synchronized`. This is mostly to encourage people to mark everything
|
||||
;; `^:parallel` in places like `metabase.lib` tests unless there is a really good reason not to.
|
||||
(when-not (or parallel? synchronized?)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta test-name)
|
||||
:message "Test should be marked either ^:parallel or ^:synchronized"
|
||||
:type :metabase/deftest-not-marked-parallel-or-synchronized)))
|
||||
(when parallel?
|
||||
(doseq [form body]
|
||||
(warn-about-disallowed-parallel-forms form config)))))
|
||||
|
||||
(defn- deftest-check-not-horrifically-long
|
||||
[node {:keys [max-length], :as _config}]
|
||||
(let [{:keys [row end-row]} (meta node)]
|
||||
(when (and row end-row)
|
||||
(let [num-lines (- end-row row)]
|
||||
(when (and max-length
|
||||
(>= num-lines max-length))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (str (format "This test is horrifically long, it's %d lines! 😱 " num-lines)
|
||||
"Do you really want to try to debug it if it fails? 💀 "
|
||||
"Split it up into smaller tests! 🥰")
|
||||
:type :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests)))))))
|
||||
|
||||
(defn- ignore? [node error-type]
|
||||
(when-let [ignores (some-> node meta :clj-kondo/ignore hooks/sexpr)]
|
||||
(when-let [ignores (cond
|
||||
(coll? ignores) (set ignores)
|
||||
(keyword? ignores) #{ignores})]
|
||||
(contains? ignores error-type))))
|
||||
|
||||
(defn deftest-check-no-driver-keywords [node {:keys [drivers], :as _config}]
|
||||
;; fail fast after we see the first error, where we see one hardcoded driver name there are likely several more and we
|
||||
;; don't need multiple warnings for a single test.
|
||||
(letfn [(f [node]
|
||||
(when (and (hooks/keyword-node? node)
|
||||
(set? drivers)
|
||||
(contains? drivers (hooks/sexpr node)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (format "Do not hardcode driver name %s in driver tests! [:metabase/disallow-hardcoded-driver-names-in-tests]"
|
||||
(hooks/sexpr node))
|
||||
:type :metabase/disallow-hardcoded-driver-names-in-tests))
|
||||
::error))
|
||||
(walk [node]
|
||||
(cond
|
||||
(ignore? node :metabase/disallow-hardcoded-driver-names-in-tests)
|
||||
nil
|
||||
|
||||
(= (f node) ::error)
|
||||
::error
|
||||
|
||||
:else
|
||||
(reduce
|
||||
(fn [_acc child]
|
||||
(when (= (walk child) ::error)
|
||||
(reduced ::error)))
|
||||
nil
|
||||
(:children node))))]
|
||||
(walk node)))
|
||||
|
||||
(defn deftest [{:keys [node cljc lang config]}]
|
||||
;; run [[deftest-check-parallel]] only once... if this is a `.cljc` file only run it for the `:clj` analysis, no point
|
||||
;; in running it twice.
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(deftest-check-parallel node (get-in config [:linters :metabase/validate-deftest])))
|
||||
(deftest-check-not-horrifically-long node (get-in config [:linters :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests]))
|
||||
(deftest-check-no-driver-keywords node (get-in config [:linters :metabase/disallow-hardcoded-driver-names-in-tests]))
|
||||
{:node node})
|
||||
|
||||
;;; this is a hacky way to determine whether these namespaces are required in the `ns` form or not... basically `:ns`
|
||||
;;; will come back as `nil` if they are not.
|
||||
(defn- approximately-equal-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.approximately-equal/=?-report}))
|
||||
'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
(defn- malli-equals-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.malli-equals/malli=-report}))
|
||||
'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
(defn- warn-about-missing-test-expr-requires-in-cljs [{:keys [children], :as _is-node}]
|
||||
(let [[_is assertion-node] children]
|
||||
(when (hooks/list-node? assertion-node)
|
||||
(let [[assertion-symb-node] (:children assertion-node)]
|
||||
(when (hooks/token-node? assertion-symb-node)
|
||||
(let [assertion-symb-token (hooks/sexpr assertion-symb-node)
|
||||
warn! (fn [ns-to-require]
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta assertion-symb-node)
|
||||
:message (format "You must require %s to use %s in ClojureScript"
|
||||
ns-to-require
|
||||
assertion-symb-token)
|
||||
:type :metabase/missing-test-expr-requires-in-cljs)))]
|
||||
(condp = assertion-symb-token
|
||||
'=?
|
||||
(when-not (approximately-equal-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
'malli=
|
||||
(when-not (malli-equals-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
nil)))))))
|
||||
|
||||
(defn is [{:keys [node lang]}]
|
||||
(when (= lang :cljs)
|
||||
(warn-about-missing-test-expr-requires-in-cljs node))
|
||||
{:node node})
|
||||
|
||||
(defn use-fixtures [{:keys [node config]}]
|
||||
(warn-about-disallowed-parallel-forms node config)
|
||||
{:node node})
|
||||
@@ -1,447 +0,0 @@
|
||||
(ns hooks.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.pprint]))
|
||||
|
||||
(defn with-macro-meta
|
||||
"When introducing internal nodes (let, defn, etc) it is important to provide a meta of an existing token
|
||||
as the current version of kondo will use the whole form by default."
|
||||
[new-node hook-node]
|
||||
(with-meta new-node (meta (first (:children hook-node)))))
|
||||
|
||||
;;; This stuff is to help debug hooks. Trace a function and it will pretty-print the before & after sexprs.
|
||||
;;;
|
||||
;;; (hooks.common/trace #'calculate-bird-scarcity)
|
||||
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(defn- trace* [f]
|
||||
{:pre [(fn? f)]}
|
||||
(fn traced-fn [node]
|
||||
(println \newline)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node)))
|
||||
(let [node* (f node)]
|
||||
(println '=>)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node*)))
|
||||
node*)))
|
||||
|
||||
;;; this doesn't seem to work with SCI. Use [[defn-traced]] instead.
|
||||
|
||||
;; (defn trace [varr]
|
||||
;; {:pre [(var? varr)]}
|
||||
;; (when-not (::traced? (meta varr))
|
||||
;; (alter-var-root varr trace*)
|
||||
;; (alter-meta! varr assoc ::traced? true)
|
||||
;; (println "Traced" varr)))
|
||||
|
||||
(defmacro defn-traced
|
||||
"Replace a hook [[defn]] with this to trace the before and after sexprs."
|
||||
[fn-name & fn-tail]
|
||||
`(do
|
||||
(defn fn# ~@fn-tail)
|
||||
(def ~fn-name (trace* fn#))))
|
||||
|
||||
;;;; Common hook definitions
|
||||
|
||||
(defn do*
|
||||
"This is the same idea as [[clojure.core/do]] but doesn't cause Kondo to complain about redundant dos or unused
|
||||
values."
|
||||
[{{[_ & args] :children, :as node} :node}]
|
||||
(let [node* (-> (hooks/list-node
|
||||
(list*
|
||||
(with-meta (hooks/token-node 'do) {:clj-kondo/ignore [:redundant-do]})
|
||||
(for [arg args]
|
||||
(vary-meta arg update :clj-kondo/ignore #(conj (vec %) :unused-value)))))
|
||||
(with-meta (meta node)))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)
|
||||
|
||||
Binding is optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-three-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y z]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil, z nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y z] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or z (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-four-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-five-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-six-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-seven-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f g]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil, g nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f g] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or g (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-top-level-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)"
|
||||
[{{[_ x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-top-level-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x y
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)"
|
||||
[{{[_ x y & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)
|
||||
y (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn let-one-with-optional-value
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the binding, `value`, is optional, and only one
|
||||
binding can be established.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {[binding value] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[binding (or value (hooks/token-node 'nil))])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn- let-second-inner [body bindings]
|
||||
(let [binding-infos (for [[model {[binding value] :children}] (partition 2 bindings)]
|
||||
{:model model
|
||||
:binding binding
|
||||
:value (or value
|
||||
(hooks/token-node 'nil))})]
|
||||
(-> (hooks/vector-node
|
||||
[(hooks/vector-node (map :model binding-infos))
|
||||
(-> (hooks/list-node (list* (hooks/token-node `let)
|
||||
(hooks/vector-node (mapcat (juxt :binding :value) binding-infos))
|
||||
body))
|
||||
(with-meta (meta body)))])
|
||||
(with-meta (meta body)))))
|
||||
|
||||
(defn let-second
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x [y]
|
||||
...)
|
||||
|
||||
where the let is for second arg.
|
||||
|
||||
=>
|
||||
|
||||
(let [y nil]
|
||||
...)"
|
||||
[{:keys [node]}]
|
||||
(let [[_ first-arg-ref binding+opts & body] (:children node)]
|
||||
{:node (let-second-inner body [first-arg-ref binding+opts])}))
|
||||
|
||||
(defn let-with-optional-value-for-last-binding
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the *last* binding, `value`, is optional.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding value]]
|
||||
[binding (or value (hooks/token-node 'nil))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-ignored-first-arg
|
||||
"For macros like
|
||||
|
||||
(discard-setting-changes [setting-1 setting-2]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(do ...)
|
||||
|
||||
where the first arg ought to be ignored for linting purposes."
|
||||
[{{[_ _x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-used-first-arg
|
||||
"For macros like
|
||||
|
||||
(with-drivers (filter pred? some-drivers)
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [_1234 (filter pred? some-drivers)]
|
||||
...)
|
||||
|
||||
where the first arg should be linted and appear to be used."
|
||||
[{{[_ arg & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node (gensym "_"))
|
||||
arg])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-vec-first-binding
|
||||
"For macros like
|
||||
|
||||
(with-temp-file [binding \"temp.txt\" \"content\"]
|
||||
(slurp binding))
|
||||
|
||||
=>
|
||||
(let [binding nil]
|
||||
(slurp binding))
|
||||
|
||||
where the first arg is a vector of bindings."
|
||||
[{{[_ {[x & _args] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn node->qualified-symbol
|
||||
"If the node is a symbol, return it as a qualified symbol if it's not already. Otherwise return nil."
|
||||
[node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(let [resolved (hooks/resolve {:name sexpr})]
|
||||
(cond
|
||||
(and resolved (:ns resolved))
|
||||
(symbol (name (:ns resolved)) (name (:name resolved)))
|
||||
|
||||
;; if it wasn't resolved but is still qualified it's probably using the full namespace name rather than an
|
||||
;; alias.
|
||||
(qualified-symbol? sexpr)
|
||||
sexpr)))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(defn format-string-specifier-count
|
||||
"Number of things like `%s` in a format string, not counting newlines (`%n`) or escaped percent signs (`%%`). For
|
||||
checking the number of args to something that takes a format string."
|
||||
[format-string]
|
||||
(count (re-seq #"(?<!%)%(?![%n])" format-string)))
|
||||
|
||||
(comment
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%s %%")
|
||||
|
||||
;; should be 2
|
||||
(format-string-specifier-count "%s %%%n%s")
|
||||
|
||||
;; should be 0
|
||||
(format-string-specifier-count "%n%%%%")
|
||||
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%-02d"))
|
||||
|
||||
(defn add-lsp-ignore-unused-public-var-metadata
|
||||
"Add
|
||||
|
||||
^{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
|
||||
metadata to a node to suppress LSP warnings."
|
||||
[node]
|
||||
(letfn [(update-ignores [existing-ignores]
|
||||
(hooks/vector-node
|
||||
(cons
|
||||
(hooks/keyword-node :clojure-lsp/unused-public-var)
|
||||
(:children existing-ignores))))]
|
||||
(vary-meta node update :clj-kondo/ignore update-ignores)))
|
||||
@@ -1,28 +0,0 @@
|
||||
(ns hooks.metabase.api.automagic-dashboards-test
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(comment
|
||||
|
||||
(def input (hooks/parse-string
|
||||
"(with-indexed-model! [{:keys [model model-index model-index-value]}
|
||||
{:query :some-query}]
|
||||
:foo :bar :body)"))
|
||||
|
||||
(println (hooks/sexpr input))
|
||||
|
||||
(let [f (load-file ".clj-kondo/hooks/metabase/api/automagic_dashboards_test.clj")]
|
||||
(f {:node input})))
|
||||
|
||||
(defn with-indexed-model!
|
||||
[{node :node}]
|
||||
(let [[_macro-call binding-and-query-info & body] (:children node)
|
||||
[binding query-info] (:children binding-and-query-info)
|
||||
node* (hooks/list-node
|
||||
[(hooks/token-node 'do-with-indexed-model!)
|
||||
query-info
|
||||
(hooks/list-node
|
||||
(list* (hooks/token-node 'fn)
|
||||
(hooks/vector-node [binding])
|
||||
body))])]
|
||||
#_(println (hooks/sexpr node*))
|
||||
{:node node*}))
|
||||
@@ -1,32 +0,0 @@
|
||||
(ns hooks.metabase.api.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(defn route-fn-name
|
||||
"route fn hook"
|
||||
[method route]
|
||||
(let [route (if (vector? route) (first route) route)]
|
||||
(-> (str (name method) route)
|
||||
(str/replace #"/" "_")
|
||||
symbol)))
|
||||
|
||||
(defn defendpoint
|
||||
[arg]
|
||||
(letfn [(update-defendpoint [node]
|
||||
(let [[_defendpoint method route & body] (:children node)]
|
||||
(-> (api/list-node
|
||||
(list
|
||||
(api/token-node 'do)
|
||||
(-> (api/token-node (symbol "compojure.core" (str (api/sexpr method))))
|
||||
(with-meta (meta method)))
|
||||
(api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/defn)
|
||||
(-> (api/token-node (route-fn-name (api/sexpr method) (api/sexpr route)))
|
||||
(with-meta (meta route)))
|
||||
body))))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)))]
|
||||
(update arg :node update-defendpoint)))
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns hooks.metabase.api.search-test
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn with-search-items-in-collection [{{[_ & args] :children, :as node} :node}]
|
||||
;; (println \newline)
|
||||
;; (clojure.pprint/pprint (hooks/sexpr node))
|
||||
(let [[created-items-sym search-string & body] args
|
||||
node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[created-items-sym (hooks/token-node 'nil)])
|
||||
search-string
|
||||
body))]
|
||||
;; (println '=>)
|
||||
;; (clojure.pprint/pprint (hooks/sexpr node*))
|
||||
{:node node*}))
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns hooks.metabase.channel.template.handlebars-helper
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn defhelper
|
||||
"defhelper hook"
|
||||
[{:keys [node]}]
|
||||
(let [[_ helper-name description argvec & body] (:children node)
|
||||
helper-fn-name (symbol (str (hooks/sexpr helper-name) "*"))]
|
||||
{:node (hooks/list-node
|
||||
(list (hooks/token-node 'do)
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'defn)
|
||||
(hooks/token-node helper-fn-name)
|
||||
description
|
||||
argvec
|
||||
(hooks/list-node body)))
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'def)
|
||||
helper-name
|
||||
description
|
||||
(hooks/list-node
|
||||
(into [(hooks/token-node 'fn)
|
||||
argvec]
|
||||
body))))))}))
|
||||
@@ -1,14 +0,0 @@
|
||||
(ns hooks.metabase.db.schema-migrations-test.impl
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn test-migrations
|
||||
[{{[_ migration-range {[binding] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node `let)
|
||||
(hooks/vector-node
|
||||
[binding (hooks/list-node (list (hooks/token-node `fn)
|
||||
(hooks/vector-node [(hooks/token-node '&) (hooks/token-node '_args)])))])
|
||||
migration-range
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,18 +0,0 @@
|
||||
(ns hooks.metabase.driver
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn register!
|
||||
"Register the driver keyword so LSP can jump to it."
|
||||
[x]
|
||||
(letfn [(update-driver-keyword [k]
|
||||
(api/reg-keyword! k 'metabase.driver/register!))
|
||||
(update-children [[_register! k :as children]]
|
||||
(if (api/keyword-node? k)
|
||||
(update (vec children) 1 update-driver-keyword)
|
||||
children))
|
||||
(update-node [node]
|
||||
(if (api/list-node? node)
|
||||
(update node :children update-children)
|
||||
node))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,69 +0,0 @@
|
||||
(ns hooks.metabase.legacy-mbql.schemas.macros
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn- unwrap-defclause-clause-name
|
||||
"The `clause-name-form` can be either a plain symbol, or a vector like `[var-name mbql-clause-name]`. For the vector
|
||||
form, we are only interested in the `var-name`. For a plain symbol, the two names are the same. Return a token node."
|
||||
[clause-name-form]
|
||||
(-> (if (hooks/vector-node? clause-name-form)
|
||||
(first (:children clause-name-form))
|
||||
clause-name-form)
|
||||
(with-meta (meta clause-name-form))))
|
||||
|
||||
(defn- collect-defclause-body-schemas
|
||||
"Collect the argument schemas in the body of a `defclause` form into a vector node.
|
||||
|
||||
The body is a sequence of <argument-name> <argument-schema> pairs. We can ignore the argument names.
|
||||
|
||||
<argument-schema> can optionally be wrapped be `(optional ...)` or `(rest ...)`... we can ignore these."
|
||||
[arg-specs]
|
||||
(hooks/vector-node
|
||||
(for [[_arg-name arg-schema] (partition-all 2 arg-specs)
|
||||
:let [arg-schema (if (and (hooks/list-node? arg-schema)
|
||||
('#{optional rest} (hooks/sexpr (first (:children arg-schema)))))
|
||||
(-> (second (:children arg-schema))
|
||||
(with-meta (meta arg-schema)))
|
||||
arg-schema)]]
|
||||
arg-schema)))
|
||||
|
||||
(defn defclause
|
||||
"e.g.
|
||||
|
||||
(defclause [ag:var var] field-or-expression FieldOrExpressionDef)
|
||||
=>
|
||||
(def ag:var [FieldOrExpressionDef])"
|
||||
[{:keys [node]}]
|
||||
(let [[_defclause clause-name-form & arg-specs] (:children node)]
|
||||
{:node (-> (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'def)
|
||||
(unwrap-defclause-clause-name clause-name-form)
|
||||
(hooks/string-node "Docstring.")
|
||||
(collect-defclause-body-schemas arg-specs)))
|
||||
(with-meta (meta node)))}))
|
||||
|
||||
(comment
|
||||
(defn- defclause* [form]
|
||||
(hooks/sexpr
|
||||
(:node
|
||||
(defclause
|
||||
{:node
|
||||
(hooks/parse-string
|
||||
(with-out-str
|
||||
(clojure.pprint/pprint
|
||||
form)))}))))
|
||||
|
||||
(defn- x []
|
||||
(defclause* '(defclause [ag:var var]
|
||||
field-or-expression FieldOrExpressionDef)))
|
||||
;; =>
|
||||
(def ag:var "Docstring." [FieldOrExpressionDef])
|
||||
|
||||
(defn- y []
|
||||
(defclause* '(defclause var
|
||||
x (some-list-schema)
|
||||
y (optional FieldOrExpressionDef)
|
||||
z (rest (some-other-schema)))))
|
||||
;; =>
|
||||
(def var "Docstring." [(some-list-schema) FieldOrExpressionDef (some-other-schema)]))
|
||||
@@ -1,21 +0,0 @@
|
||||
(ns hooks.metabase.lib.schema.mbql-clause
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn define-mbql-clause
|
||||
"Register the schema keyword so LSP can jump to it."
|
||||
[x]
|
||||
(letfn [(update-def-keyword [k]
|
||||
(-> (api/keyword-node (keyword "mbql.clause" (name (api/sexpr k))))
|
||||
(with-meta (meta k))
|
||||
(api/reg-keyword! 'metabase.lib.schema.mbql-clause/define-mbql-clause)))
|
||||
(update-children [[_def k :as children]]
|
||||
(if (api/keyword-node? k)
|
||||
(update (vec children) 1 update-def-keyword)
|
||||
children))
|
||||
(update-node [node]
|
||||
(if (api/list-node? node)
|
||||
(-> (api/list-node (update-children (:children node)))
|
||||
(with-meta (meta node)))
|
||||
node))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns hooks.metabase.lib.test-util.macros
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn with-testing-against-standard-queries [{{[_ & args] :children} :node}]
|
||||
(let [[query-sym & body] args
|
||||
node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[query-sym (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,21 +0,0 @@
|
||||
(ns hooks.metabase.models.interface
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn define-hydration-method
|
||||
"This is used for both [[metabase.models.interface/define-simple-hydration-method]] and
|
||||
for [[metabase.models.interface/define-batched-hydration-method]]."
|
||||
[{{[_define-simple-hydration-method fn-name hydration-key & fn-tail] :children, :as node} :node}]
|
||||
(let [node (-> (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
hydration-key
|
||||
(hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'defn)
|
||||
fn-name
|
||||
fn-tail))))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)]
|
||||
{:node node}))
|
||||
@@ -1,266 +0,0 @@
|
||||
(ns hooks.metabase.models.setting
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(def ^:private ignored-implicit-export?
|
||||
'#{active-users-count
|
||||
admin-email
|
||||
analytics-uuid
|
||||
anon-tracking-enabled
|
||||
api-key
|
||||
attachment-table-row-limit
|
||||
audit-max-retention-days
|
||||
bcc-enabled?
|
||||
breakout-bin-width
|
||||
check-for-updates
|
||||
cloud-gateway-ips
|
||||
config-from-file-sync-databases
|
||||
custom-homepage
|
||||
custom-homepage-dashboard
|
||||
database-enable-actions
|
||||
deprecation-notice-version
|
||||
dismissed-custom-dashboard-toast
|
||||
email-configured?
|
||||
email-from-address
|
||||
email-from-name
|
||||
email-reply-to
|
||||
email-smtp-host
|
||||
email-smtp-password
|
||||
email-smtp-port
|
||||
email-smtp-security
|
||||
email-smtp-username
|
||||
embedding-app-origin
|
||||
embedding-secret-key
|
||||
enable-password-login
|
||||
enable-public-sharing
|
||||
enable-query-caching
|
||||
engines
|
||||
enum-cardinality-threshold
|
||||
follow-up-email-sent
|
||||
google-auth-auto-create-accounts-domain
|
||||
google-auth-client-id
|
||||
google-auth-configured
|
||||
google-auth-enabled
|
||||
has-sample-database?
|
||||
has-user-setup
|
||||
help-link
|
||||
help-link-custom-destination
|
||||
instance-creation
|
||||
is-hosted?
|
||||
is-metabot-enabled
|
||||
jdbc-data-warehouse-max-connection-pool-size
|
||||
jdbc-data-warehouse-unreturned-connection-timeout-seconds
|
||||
jwt-attribute-email
|
||||
jwt-attribute-firstname
|
||||
jwt-attribute-groups
|
||||
jwt-attribute-lastname
|
||||
jwt-configured
|
||||
jwt-enabled
|
||||
jwt-group-mappings
|
||||
jwt-group-sync
|
||||
jwt-identity-provider-uri
|
||||
jwt-user-provisioning-enabled?
|
||||
jwt-shared-secret
|
||||
last-acknowledged-version
|
||||
last-used-native-database-id
|
||||
ldap-attribute-email
|
||||
ldap-attribute-firstname
|
||||
ldap-attribute-lastname
|
||||
ldap-bind-dn
|
||||
ldap-configured?
|
||||
ldap-enabled
|
||||
ldap-group-base
|
||||
ldap-group-mappings
|
||||
ldap-group-membership-filter
|
||||
ldap-group-sync
|
||||
ldap-host
|
||||
ldap-password
|
||||
ldap-port
|
||||
ldap-user-provisioning-enabled?
|
||||
ldap-security
|
||||
ldap-sync-user-attributes
|
||||
ldap-sync-user-attributes-blacklist
|
||||
ldap-user-base
|
||||
ldap-user-filter
|
||||
load-analytics-content
|
||||
map-tile-server-url
|
||||
metabot-default-embedding-model
|
||||
metabot-feedback-url
|
||||
metabot-get-prompt-templates-url
|
||||
metabot-prompt-generator-token-limit
|
||||
multi-setting-read-only
|
||||
notebook-native-preview-shown
|
||||
notebook-native-preview-sidebar-width
|
||||
notification-link-base-url
|
||||
num-metabot-choices
|
||||
openai-api-key
|
||||
openai-available-models
|
||||
openai-model
|
||||
openai-organization
|
||||
other-sso-enabled?
|
||||
password-complexity
|
||||
persist-models-enabled
|
||||
persisted-model-refresh-cron-schedule
|
||||
premium-embedding-token
|
||||
prometheus-server-port
|
||||
query-caching-max-kb
|
||||
query-caching-max-ttl
|
||||
query-caching-min-ttl
|
||||
query-caching-ttl-ratio
|
||||
redirect-all-requests-to-https
|
||||
reset-token-ttl-hours
|
||||
retired-setting
|
||||
retry-initial-interval
|
||||
retry-max-attempts
|
||||
retry-max-interval-millis
|
||||
retry-multiplier
|
||||
retry-randomization-factor
|
||||
saml-application-name
|
||||
saml-attribute-email
|
||||
saml-attribute-firstname
|
||||
saml-attribute-group
|
||||
saml-attribute-lastname
|
||||
saml-configured
|
||||
saml-enabled
|
||||
saml-group-mappings
|
||||
saml-group-sync
|
||||
saml-identity-provider-certificate
|
||||
saml-identity-provider-issuer
|
||||
saml-identity-provider-uri
|
||||
saml-keystore-alias
|
||||
saml-keystore-password
|
||||
saml-keystore-path
|
||||
saml-user-provisioning-enabled?
|
||||
send-email-on-first-login-from-new-device
|
||||
send-new-sso-user-admin-email?
|
||||
session-cookie-samesite
|
||||
session-cookies
|
||||
session-timeout
|
||||
setup-token
|
||||
show-database-syncing-modal
|
||||
show-metabase-links
|
||||
site-url
|
||||
site-uuid
|
||||
site-uuid-for-premium-features-token-checks
|
||||
site-uuid-for-unsubscribing-url
|
||||
site-uuid-for-version-info-fetching
|
||||
slack-app-token
|
||||
slack-cached-channels-and-usernames
|
||||
slack-channels-and-usernames-last-updated
|
||||
slack-files-channel
|
||||
slack-token
|
||||
slack-token-valid?
|
||||
snowplow-available
|
||||
snowplow-enabled
|
||||
snowplow-url
|
||||
sql-jdbc-fetch-size
|
||||
ssh-heartbeat-interval-sec
|
||||
ssl-certificate-public-key
|
||||
startup-time-millis
|
||||
token-features
|
||||
token-status
|
||||
toucan-name
|
||||
trial-banner-dismissal-timestamp
|
||||
uncached-setting
|
||||
user-visibility
|
||||
version
|
||||
version-info
|
||||
version-info-last-checked})
|
||||
|
||||
(defn- defsetting-lint [node setting-name docstring options-list]
|
||||
(let [anon-binding (common/with-macro-meta (hooks/token-node '_) node)
|
||||
;; (defn my-setting [] ...)
|
||||
getter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
setting-name
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node []))
|
||||
hooks/list-node
|
||||
(with-meta (meta node)))
|
||||
;; (defn my-setting! [_x] ...)
|
||||
setter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
(with-meta
|
||||
(hooks/token-node (symbol (str (hooks/sexpr setting-name) \!)))
|
||||
(meta setting-name))
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node [(hooks/token-node '_value-or-nil)]))
|
||||
hooks/list-node
|
||||
(with-meta (meta node))
|
||||
common/add-lsp-ignore-unused-public-var-metadata)]
|
||||
|
||||
(when (nil? (second (drop-while (comp not #{[:k :export?]} first) options-list)))
|
||||
(when-not (contains? ignored-implicit-export? (:value setting-name))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Setting definition must provide an explicit value for :export? indicating whether the setting should be exported or not with serialization."
|
||||
:type :metabase/defsetting-must-specify-export))))
|
||||
|
||||
{:node (-> (list
|
||||
(hooks/token-node 'let)
|
||||
;; include description and the options map so they can get validated as well.
|
||||
(hooks/vector-node
|
||||
[anon-binding docstring
|
||||
anon-binding (hooks/map-node options-list)])
|
||||
getter-node
|
||||
setter-node)
|
||||
hooks/list-node
|
||||
(with-meta (meta node)))}))
|
||||
|
||||
(defn defsetting
|
||||
"Rewrite a [[metabase.models.defsetting]] form like
|
||||
|
||||
(defsetting my-setting \"Description\" :type :boolean)
|
||||
|
||||
as
|
||||
|
||||
(let [_ \"Description\"
|
||||
_ {:type :boolean}]
|
||||
(defn my-setting \"Docstring.\" [])
|
||||
(defn my-setting! \"Docstring.\" [_value-or-nil]))
|
||||
|
||||
for linting purposes."
|
||||
[{:keys [node]}]
|
||||
(let [[setting-name docstring & options] (rest (:children node))]
|
||||
(defsetting-lint node setting-name docstring options)))
|
||||
|
||||
(defn define-multi-setting
|
||||
"Rewrite a [[metabase.models.define-multi-setting]] form like
|
||||
|
||||
(defsetting my-setting \"Description\" :key :type :boolean)
|
||||
|
||||
as
|
||||
|
||||
(let [_ \"Description\"
|
||||
_ {:type :boolean, :multi-thunk :key}]
|
||||
(defn my-setting \"Docstring.\" [])
|
||||
(defn my-setting! \"Docstring.\" [_value-or-nil]))
|
||||
|
||||
for linting purposes."
|
||||
[{:keys [node]}]
|
||||
(let [[setting-name docstring thunk & options] (rest (:children node))]
|
||||
(defsetting-lint node setting-name docstring (concat options [(hooks/token-node :multi-thunk) thunk]))))
|
||||
|
||||
(comment
|
||||
(defn- defsetting* [form]
|
||||
(hooks/sexpr
|
||||
(:node
|
||||
(defsetting
|
||||
{:node
|
||||
(hooks/parse-string
|
||||
(with-out-str
|
||||
#_{:clj-kondo/ignore [:unresolved-namespace]}
|
||||
(clojure.pprint/pprint
|
||||
form)))}))))
|
||||
|
||||
(defn x []
|
||||
(defsetting*
|
||||
'(defsetting active-users-count
|
||||
(deferred-tru "Cached number of active users. Refresh every 5 minutes.")
|
||||
:visibility :admin
|
||||
:type :integer
|
||||
:default 0
|
||||
:getter (fn []
|
||||
(if-not ((requiring-resolve 'metabase.db/db-is-set-up?))
|
||||
0
|
||||
(cached-active-users-count)))))))
|
||||
@@ -1,65 +0,0 @@
|
||||
(ns hooks.metabase.public-settings.premium-features
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn defenterprise [{node :node}]
|
||||
(let [[_defenterprise fn-name & args] (:children node)
|
||||
[docstring & args] (if (hooks/string-node? (first args))
|
||||
args
|
||||
(cons nil args))
|
||||
[_enterprise-namespace & args] (if (hooks/token-node? (first args))
|
||||
args
|
||||
(cons nil args))
|
||||
[options fn-tail] (loop [options (hooks/map-node []), [x y & more :as args] args]
|
||||
(if (hooks/keyword-node? x)
|
||||
(recur (update options :children concat [x y]) more)
|
||||
[options args]))]
|
||||
{:node (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(hooks/token-node '_options) options])
|
||||
(-> (hooks/list-node
|
||||
(concat
|
||||
(filter some?
|
||||
(list (hooks/token-node 'defn)
|
||||
fn-name
|
||||
docstring))
|
||||
fn-tail))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)))}))
|
||||
|
||||
(comment
|
||||
(defn- defenterprise* [form]
|
||||
(hooks/sexpr
|
||||
(:node
|
||||
(defenterprise
|
||||
{:node
|
||||
(hooks/parse-string
|
||||
(with-out-str
|
||||
(clojure.pprint/pprint
|
||||
form)))}))))
|
||||
|
||||
(defn- x []
|
||||
(defenterprise*
|
||||
'(defenterprise score-result
|
||||
"Scoring implementation that adds score for items in official collections."
|
||||
:feature :any
|
||||
:fallback score-result-fallback
|
||||
[result]
|
||||
(conj (scoring/weights-and-scores result)
|
||||
{:weight 2
|
||||
:score (official-collection-score result)
|
||||
:name "official collection score"}
|
||||
{:weight 2
|
||||
:score (verified-score result)
|
||||
:name "verified"}))))
|
||||
|
||||
(defn- y []
|
||||
(defenterprise*
|
||||
'(defenterprise score-result
|
||||
"Score a result, returning a collection of maps with score and weight."
|
||||
metabase-enterprise.search.scoring
|
||||
[result]
|
||||
(weights-and-scores result)))))
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns hooks.metabase.query-processor-test.expressions-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.metabase.test.data :as data]))
|
||||
|
||||
(defn calculate-bird-scarcity
|
||||
[{{[_ formula filter-clause] :children} :node}]
|
||||
(let [formula* (data/replace-$id-special-tokens formula)
|
||||
filter-clause* (or (some-> filter-clause data/replace-$id-special-tokens)
|
||||
(hooks/token-node 'nil))
|
||||
node* (hooks/list-node
|
||||
(list
|
||||
(hooks/token-node 'do)
|
||||
formula*
|
||||
filter-clause*))]
|
||||
{:node node*}))
|
||||
@@ -1,155 +0,0 @@
|
||||
(ns hooks.metabase.test.data
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[clojure.walk :as walk]))
|
||||
|
||||
(defn- global-dataset-symbols
|
||||
"Dataset definitions defined in [[metabase.test.data.dataset-definitions]]. This is only populated if clj-kondo has
|
||||
cached analysis for that namespace -- so it may or may not be populated. If it is populated we can do a bit of extra
|
||||
linting with that information."
|
||||
[]
|
||||
(not-empty (set (keys (:clj (hooks/ns-analysis 'metabase.test.data.dataset-definitions))))))
|
||||
|
||||
(defn- local-def?
|
||||
[ns* sexpr]
|
||||
(= ns* (:ns (hooks/resolve {:name sexpr}))))
|
||||
|
||||
(defn- dataset-type
|
||||
"`dataset` can be one of:
|
||||
|
||||
- a qualified symbol
|
||||
- an unqualified symbol referring to a a var in [[metabase.test.data.dataset-definitions]]
|
||||
- an unqualified symbol (referring to a let-bound value or a var in the current namespace
|
||||
- some sort of non-symbol form like a function call
|
||||
|
||||
We can only determine if an unqualified symbol refers to something in the dataset definitions namespace if there are
|
||||
cached results available from [[global-dataset-symbols]]."
|
||||
[this-ns dataset]
|
||||
(let [sexpr (hooks/sexpr dataset)
|
||||
global-defs (global-dataset-symbols)]
|
||||
(cond
|
||||
(not (symbol? sexpr))
|
||||
:non-symbol
|
||||
|
||||
(namespace sexpr)
|
||||
:qualified
|
||||
|
||||
(contains? global-defs sexpr)
|
||||
:unqualified/from-dataset-defs-namespace
|
||||
|
||||
(local-def? this-ns sexpr)
|
||||
:unqualified/local-def
|
||||
|
||||
:else
|
||||
:unqualified/unknown)))
|
||||
|
||||
(defn dataset
|
||||
[{{[_ dataset & body] :children} :node this-ns :ns}]
|
||||
(let [noop (constantly nil)
|
||||
body (case (dataset-type this-ns dataset)
|
||||
;; non-symbol, qualified symbols, and unqualified symbols from the current namespace/let-bound can all
|
||||
;; get converted from something like
|
||||
;;
|
||||
;; (dataset whatever
|
||||
;; ...)
|
||||
;;
|
||||
;; to
|
||||
;;
|
||||
;; (let [_ whatever]
|
||||
;; ...)
|
||||
(:non-symbol :qualified :unqualified/local-def)
|
||||
(list* (hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node '_) dataset])
|
||||
body)
|
||||
|
||||
;; for ones that came from the dataset defs namespace or ones whose origin is unknown, just ignore them
|
||||
;; and generate a `print` form:
|
||||
;;
|
||||
;; (print ...)
|
||||
;;
|
||||
;; (this used to be a `do` form (which makes a lot more semantic sense), but that resulted in warnings
|
||||
;; about unused values
|
||||
(:unqualified/from-dataset-defs-namespace :unqualified/unknown)
|
||||
(list* (hooks/token-node noop)
|
||||
body))]
|
||||
{:node (with-meta (hooks/list-node (with-meta body
|
||||
(meta dataset)))
|
||||
(meta dataset))}))
|
||||
|
||||
(defn- special-token-node?
|
||||
"Whether this node is one of the special symbols like `$field`."
|
||||
[node]
|
||||
(when (hooks/token-node? node)
|
||||
(let [symb (hooks/sexpr node)]
|
||||
(and (symbol? symb)
|
||||
(some (partial str/starts-with? symb)
|
||||
#{"$" "!" "&" "*" "%"})
|
||||
;; ignore args like `%` or `%1` inside function literals. $id forms have to be more than one character long,
|
||||
;; and the first character won't be a number (hopefully -- maybe some DBs allow this -- but we don't use it)
|
||||
(> (count (str symb)) 1)
|
||||
(not (re-find #"^%\d+" (str symb)))))))
|
||||
|
||||
(defn replace-$id-special-tokens
|
||||
"Impl for [[$ids]] and [[mbql-query]]. Walk `form` and look for special tokens like `$field` and replace them with
|
||||
strings so we don't get unresolved symbol errors. Preserves metadata."
|
||||
[form]
|
||||
;; [[walk/postwalk]] seems to preserve its meta so we don't need to do anything special
|
||||
(walk/postwalk
|
||||
(fn [node]
|
||||
(if (special-token-node? node)
|
||||
(-> (hooks/string-node (str (hooks/sexpr node)))
|
||||
(with-meta (meta node)))
|
||||
node))
|
||||
form))
|
||||
|
||||
(defn $ids
|
||||
[{{[_ & args] :children} :node}]
|
||||
;; `$ids` accepts either
|
||||
;;
|
||||
;; ($ids form)
|
||||
;;
|
||||
;; or
|
||||
;;
|
||||
;; ($ids table & body)
|
||||
;;
|
||||
;; table is only relevant for expanding the special tokens so we can ignore it.
|
||||
(let [body (if (= (count args) 1)
|
||||
(first args)
|
||||
(hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node `do)
|
||||
(rest args))))]
|
||||
{:node (replace-$id-special-tokens body)}))
|
||||
|
||||
(defn mbql-query
|
||||
[{{[_ & args] :children} :node}]
|
||||
;; `mbql-query` accepts either
|
||||
;;
|
||||
;; (mbql-query table)
|
||||
;;
|
||||
;; or
|
||||
;;
|
||||
;; (mbql-query table query)
|
||||
;;
|
||||
;; and table may be `nil`.
|
||||
;;
|
||||
;; table is only relevant for expanding the special tokens so we can ignore it either way.
|
||||
(let [[table query] (if (= (count args) 1)
|
||||
[(first args)
|
||||
(hooks/map-node [])]
|
||||
args)]
|
||||
(when-not ((some-fn symbol? nil?) (hooks/sexpr table))
|
||||
(hooks/reg-finding! (assoc (meta table)
|
||||
:message "First arg to mbql-query should be either a table name symbol or nil."
|
||||
:type :metabase/mbql-query-first-arg)))
|
||||
(let [result (replace-$id-special-tokens query)
|
||||
;; HACK I'm not sure WHY it works but I ran into https://github.com/clj-kondo/clj-kondo/issues/1773 when
|
||||
;; trying to get this working -- for some magical reason wrapping the whole thing in a `do` form seems to fix
|
||||
;; it. Once that bug is resolved we can go ahead and remove this line
|
||||
result (with-meta (hooks/list-node (with-meta (list
|
||||
(hooks/token-node 'do)
|
||||
result)
|
||||
(meta query)))
|
||||
(meta query))]
|
||||
{:node result})))
|
||||
@@ -1,56 +0,0 @@
|
||||
(ns hooks.metabase.test.data.datasets
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn- only-core-drivers?
|
||||
"Whether we're for sure only testing against the 'core' drivers that are also app DB types: `:postgres`, `:mysql`, or
|
||||
`:h2`. Tests like these are ok to mark `:mb/once` or `:mb/driver-tests` since we always run the full test suite
|
||||
against app DB drivers anyway."
|
||||
[node]
|
||||
(let [drivers (hooks/sexpr (second (:children node)))
|
||||
drivers (cond
|
||||
(keyword? drivers) #{drivers}
|
||||
(set? drivers) drivers
|
||||
:else nil)]
|
||||
(when drivers
|
||||
(empty? (set/difference drivers #{:h2 :mysql :postgres})))))
|
||||
|
||||
(defn- validate-mb-once [x]
|
||||
(when (:mb/once (meta (:ns x)))
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta (:ns x))
|
||||
:message (str "You should not use test-driver or test-drivers in a namespace marked ^:mb/once"
|
||||
" [:metabase/validate-mb-once]")
|
||||
:type :metabase/validate-mb-once))))
|
||||
|
||||
(defn- validate-mb-driver-tests [x]
|
||||
(when-not (:mb/driver-tests (meta (:ns x)))
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta (:ns x))
|
||||
:message (str "Namespaces that run driver tests (test-driver or test-drivers) should be marked"
|
||||
" ^:mb/driver-tests [:metabase/validate-mb-driver-tests]")
|
||||
:type :metabase/validate-mb-driver-tests))))
|
||||
|
||||
(defn- validate-ns-tags [x]
|
||||
(when-not (only-core-drivers? (:node x))
|
||||
(validate-mb-once x)
|
||||
(validate-mb-driver-tests x)))
|
||||
|
||||
(defn test-drivers [x]
|
||||
(validate-ns-tags x)
|
||||
(letfn [(update-node [node]
|
||||
(let [[test-drivers drivers-expr & body] (:children node)]
|
||||
(-> (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
test-drivers
|
||||
(-> drivers-expr
|
||||
(vary-meta update :clj-kondo/ignore #(conj (vec %) :unused-value)))
|
||||
body))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
|
||||
(defn test-driver
|
||||
"We can use the same hook for `test-driver` as we use for `test-drivers`."
|
||||
[x]
|
||||
(test-drivers x))
|
||||
@@ -1,60 +0,0 @@
|
||||
(ns hooks.metabase.test.util
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(defn- namespaced-symbol-node? [node]
|
||||
(when (hooks/token-node? node)
|
||||
(let [symb (hooks/sexpr node)]
|
||||
(and (symbol? symb)
|
||||
(namespace symb)))))
|
||||
|
||||
(defn with-temporary-setting-values
|
||||
"Rewrite a form like
|
||||
|
||||
(with-temporary-setting-values [x 1, some.ns/y 2]
|
||||
...)
|
||||
|
||||
as one like
|
||||
|
||||
(let [_ 1, _ some.ns/y, _ 2]
|
||||
...)
|
||||
|
||||
for analysis purposes. We only need to 'capture' namespaced Setting bindings with a `_` so Kondo considers their
|
||||
namespace to be 'used' and to catch undefined var usage."
|
||||
[{:keys [node]}]
|
||||
(let [{[_ bindings & body] :children} node
|
||||
bindings (if (hooks/vector-node? bindings)
|
||||
(hooks/vector-node (into []
|
||||
(mapcat (fn [[setting-name v]]
|
||||
(concat
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) v]
|
||||
;; if the setting name is namespace-qualified add a `_`
|
||||
;; entry for it too.
|
||||
(when (namespaced-symbol-node? setting-name)
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) setting-name]))))
|
||||
(partition 2 (:children bindings))))
|
||||
bindings)]
|
||||
{:node (-> (list*
|
||||
(hooks/token-node 'let)
|
||||
bindings
|
||||
body)
|
||||
(with-meta (meta body))
|
||||
hooks/list-node
|
||||
(with-meta (meta body)))}))
|
||||
|
||||
(defn with-temp-file
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding filename]]
|
||||
[binding (or (when (and filename
|
||||
(not (nil? (hooks/sexpr filename))))
|
||||
filename)
|
||||
(hooks/string-node "filename"))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns hooks.metabase.util
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[hooks.common]))
|
||||
|
||||
(defn format-color [{:keys [node], :as x}]
|
||||
(let [[_format-color _color format-string-node & args] (:children node)]
|
||||
(when (api/string-node? format-string-node)
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string-node))
|
||||
actual-arg-count (count args)]
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "metabase.util/format-color format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :format))))))
|
||||
x)
|
||||
@@ -1,124 +0,0 @@
|
||||
(ns hooks.metabase.util.log
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[clojure.walk :as walk]
|
||||
[hooks.common]))
|
||||
|
||||
;;;; Checks for both log + logf
|
||||
|
||||
(defn- disallow-i18n [node]
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol x)]
|
||||
(when (and (= (namespace qualified-symbol) "metabase.util.i18n")
|
||||
(or (str/includes? (name qualified-symbol) "tru")
|
||||
(str/includes? (name qualified-symbol) "trs")))
|
||||
(api/reg-finding! (assoc (meta x)
|
||||
:message "do not i18n the logs!"
|
||||
:type :metabase/validate-logging)))))
|
||||
node))
|
||||
|
||||
(defn- check-position-of-exception-arg
|
||||
"Tell people to do (info e \"whatever\") instead of (info \"whatever\" e)."
|
||||
[node]
|
||||
(let [[f _first-arg & args] (:children node)]
|
||||
(doseq [arg args]
|
||||
(when (and (api/token-node? arg)
|
||||
(= (api/sexpr arg) 'e))
|
||||
(api/reg-finding! (assoc (meta arg)
|
||||
:message (format "pass exceptions as the first arg to %s" (api/sexpr f))
|
||||
:type :metabase/validate-logging))))))
|
||||
|
||||
(defn- common-checks [node]
|
||||
(check-position-of-exception-arg node)
|
||||
(disallow-i18n node))
|
||||
|
||||
(defn- check-for-i18n-format-specifiers
|
||||
"Make sure people aren't using things like {0} in their log messages."
|
||||
[node string-node]
|
||||
(when (re-find #"\{\d+\}" (api/sexpr string-node))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message "this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
;;; log only
|
||||
|
||||
(defn- check-not-format-string
|
||||
"Make sure we're not using something like log/trace with a format string."
|
||||
[{[f] :children, :as node} string-node]
|
||||
(when (pos? (hooks.common/format-string-specifier-count (api/sexpr string-node)))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "%s used with a format string, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str (api/sexpr f) "f"))
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
(defn- format-node? [node]
|
||||
(when (api/list-node? node)
|
||||
(let [[x] (:children node)]
|
||||
(and (api/token-node? x)
|
||||
(or (= (hooks.common/node->qualified-symbol x) 'clojure.core/format)
|
||||
(= (api/sexpr x) 'format))))))
|
||||
|
||||
(defn- warn-against-log+format-instead-of-logf
|
||||
"Tell people to use things like infof instead of info + format."
|
||||
[{[f] :children, :as node} [x :as args]]
|
||||
(when (and (= (count args) 1)
|
||||
(api/list-node? x))
|
||||
(when (format-node? x)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Use %s instead of %s + format"
|
||||
(str (api/sexpr f) "f")
|
||||
(api/sexpr f))
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
(defn info
|
||||
"Valid log, debug, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e & [x :as args]] (if (and (api/token-node? (first args))
|
||||
(or (api/string-node? (second args))
|
||||
(format-node? (second args))))
|
||||
args
|
||||
(cons nil args))]
|
||||
(when (api/string-node? x)
|
||||
(check-for-i18n-format-specifiers node x)
|
||||
(check-not-format-string node x))
|
||||
(warn-against-log+format-instead-of-logf node args))
|
||||
x)
|
||||
|
||||
;;;; logf only
|
||||
|
||||
(defn- check-format-string-arg-count
|
||||
"Make sure the number of args passed to something like tracef match the number of format string specifiers."
|
||||
[{[f] :children, :as node} format-string args]
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string))
|
||||
actual-arg-count (count args)]
|
||||
(when (zero? expected-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Don't use %s with no format string arguments, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str/replace (str (api/sexpr f)) #"f$" ""))
|
||||
:type :metabase/validate-logging)))
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "log format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
(defn infof
|
||||
"Valid logf, debugf, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e format-string & args] (if (api/string-node? (first args))
|
||||
(cons nil args)
|
||||
args)]
|
||||
;; TODO -- maybe we can error if you did not pass a format string, e.g. (log/infof 1 2) is obviously incorrect.
|
||||
(when (api/string-node? format-string)
|
||||
(check-for-i18n-format-specifiers node format-string)
|
||||
(check-format-string-arg-count node format-string args)))
|
||||
x)
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns hooks.metabase.util.log.capture
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn with-log-messages-for-level [x]
|
||||
(letfn [(update-binding [[lhs-node _rhs-node]]
|
||||
[lhs-node (api/list-node
|
||||
(list
|
||||
(api/token-node 'clojure.core/fn)
|
||||
(api/vector-node [])))])
|
||||
(update-bindings [bindings-node]
|
||||
(-> (api/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat update-binding))
|
||||
(:children bindings-node)))
|
||||
(with-meta (meta bindings-node))))
|
||||
(update-node [node]
|
||||
(let [[_with-log-messages-for-level bindings & body] (:children node)]
|
||||
(-> (api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/let)
|
||||
(update-bindings bindings)
|
||||
body))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,20 +0,0 @@
|
||||
(ns hooks.metabase.util.malli.registry
|
||||
(:refer-clojure :exclude [def])
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn def
|
||||
"Register the schema keyword so LSP can jump to it."
|
||||
[x]
|
||||
(letfn [(update-def-keyword [k]
|
||||
(api/reg-keyword! k 'metabase.util.malli.registry/def))
|
||||
(update-children [[_def k :as children]]
|
||||
(if (api/keyword-node? k)
|
||||
(update (vec children) 1 update-def-keyword)
|
||||
children))
|
||||
(update-node [node]
|
||||
(if (api/list-node? node)
|
||||
(-> (api/list-node (update-children (:children node)))
|
||||
(with-meta (meta node)))
|
||||
node))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,20 +0,0 @@
|
||||
(ns hooks.toucan.util.test
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn- with-temp-inner [body bindings]
|
||||
(let [binding-infos (for [[model {[binding value] :children}] (partition 2 bindings)]
|
||||
{:model model
|
||||
:binding binding
|
||||
:value (or value
|
||||
(api/token-node 'nil))})]
|
||||
(-> (api/vector-node
|
||||
[(api/vector-node (map :model binding-infos))
|
||||
(-> (api/list-node (list* (api/token-node `let)
|
||||
(api/vector-node (mapcat (juxt :binding :value) binding-infos))
|
||||
body))
|
||||
(with-meta (meta body)))])
|
||||
(with-meta (meta body)))))
|
||||
|
||||
(defn with-temp* [{:keys [node]}]
|
||||
(let [[_ bindings & body] (:children node)]
|
||||
{:node (with-temp-inner body (:children bindings))}))
|
||||
10
.clj-kondo/test.clj
Normal file
10
.clj-kondo/test.clj
Normal file
@@ -0,0 +1,10 @@
|
||||
(ns test
|
||||
(:require [metabase.query-processor.streaming :as streaming]
|
||||
[toucan.models :as models]))
|
||||
|
||||
(models/defmodel Card :foo)
|
||||
|
||||
(map->Card {:foo 1})
|
||||
(map->CardInstance {:foo 1})
|
||||
|
||||
(streaming/streaming-response [x 1 (inc x)])
|
||||
@@ -1,55 +0,0 @@
|
||||
(ns hooks.clojure.test-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.clojure.test]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- deftest-warnings
|
||||
[form]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/disallow-hardcoded-driver-names-in-tests {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(hooks.clojure.test/deftest {:node (api/parse-string form)
|
||||
:config {:linters
|
||||
{:metabase/disallow-hardcoded-driver-names-in-tests
|
||||
{:drivers
|
||||
#{:athena}}}}})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*))))
|
||||
|
||||
(deftest ^:parallel disallow-hardcoded-driver-names-in-tests-test
|
||||
(is (= []
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
(do-something))")))
|
||||
(is (= ["Do not hardcode driver name :athena in driver tests! [:metabase/disallow-hardcoded-driver-names-in-tests]"]
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
(when-not (= driver/*driver* :athena)
|
||||
(do-something)))")))
|
||||
(testing "make sure :clj-kondo/ignore is propagated correctly"
|
||||
(is (= []
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
#_{:clj-kondo/ignore [:metabase/disallow-hardcoded-driver-names-in-tests]}
|
||||
(when-not (= driver/*driver* :athena)
|
||||
(do-something)))")))))
|
||||
|
||||
(deftest ^:parallel check-driver-keywords-test
|
||||
(testing "Make sure we keep hooks.clojure.test/driver-keywords up to date"
|
||||
(let [driver-keywords (-> (slurp ".clj-kondo/config.edn")
|
||||
edn/read-string
|
||||
(get-in [:linters :metabase/disallow-hardcoded-driver-names-in-tests :drivers]))
|
||||
driver-modules (->> (slurp "modules/drivers/deps.edn")
|
||||
edn/read-string
|
||||
:deps
|
||||
vals
|
||||
(keep (comp keyword :local/root))
|
||||
(into #{}))]
|
||||
(doseq [driver driver-modules]
|
||||
(is (contains? driver-keywords driver)
|
||||
(format "hooks.clojure.test/driver-keywords should contain %s, please add it" driver))))))
|
||||
@@ -1,19 +0,0 @@
|
||||
(ns ^:mb/once hooks.common-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.common]))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom nil)}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "metabase.util.i18n/tru"))))))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test-2
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom {:clj {:clj '{hooks.common-test {:qualify-ns {i18n metabase.util.i18n}}}}})
|
||||
:lang :clj
|
||||
:base-lang :clj
|
||||
:ns {:name 'hooks.common-test}}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "i18n/tru"))))))
|
||||
@@ -1,35 +0,0 @@
|
||||
(ns hooks.metabase.api.common-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.api.common]))
|
||||
|
||||
(deftest ^:parallel defendpoint-test
|
||||
(is (= '(do
|
||||
compojure.core/POST
|
||||
(clojure.core/defn
|
||||
POST_:id_copy
|
||||
"Copy a `Card`, with the new name 'Copy of _name_'"
|
||||
[id]
|
||||
{id [:maybe ms/PositiveInt]}
|
||||
(let [orig-card (api/read-check Card id)
|
||||
new-name (str (trs "Copy of ") (:name orig-card))
|
||||
new-card (assoc orig-card :name new-name)]
|
||||
(-> (card/create-card! new-card @api/*current-user*)
|
||||
hydrate-card-details
|
||||
(assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*))))))
|
||||
(-> {:node (-> '(api/defendpoint POST "/:id/copy"
|
||||
"Copy a `Card`, with the new name 'Copy of _name_'"
|
||||
[id]
|
||||
{id [:maybe ms/PositiveInt]}
|
||||
(let [orig-card (api/read-check Card id)
|
||||
new-name (str (trs "Copy of ") (:name orig-card))
|
||||
new-card (assoc orig-card :name new-name)]
|
||||
(-> (card/create-card! new-card @api/*current-user*)
|
||||
hydrate-card-details
|
||||
(assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*)))))
|
||||
pr-str
|
||||
api/parse-string)}
|
||||
hooks.metabase.api.common/defendpoint
|
||||
:node
|
||||
api/sexpr))))
|
||||
@@ -1,19 +0,0 @@
|
||||
(ns hooks.metabase.util.log.capture-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.util.log.capture]))
|
||||
|
||||
(deftest ^:parallel with-log-messages-for-level-test
|
||||
(is (= '(clojure.core/let [messages-1 (clojure.core/fn [])
|
||||
messages-2 (clojure.core/fn [])]
|
||||
(concat (messages-1)
|
||||
(messages-2)))
|
||||
(-> (hooks.metabase.util.log.capture/with-log-messages-for-level
|
||||
{:node (-> '(with-log-messages-for-level [messages-1 [my.namespace :debug]
|
||||
messages-2 :info]
|
||||
(concat (messages-1) (messages-2)))
|
||||
pr-str
|
||||
api/parse-string)})
|
||||
:node
|
||||
api/sexpr))))
|
||||
@@ -1,77 +0,0 @@
|
||||
(ns ^:mb/once hooks.metabase.util.log-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.util.log]))
|
||||
|
||||
(defn- warnings
|
||||
[form]
|
||||
(let [f (if (str/ends-with? (name (first form)) "f")
|
||||
hooks.metabase.util.log/infof
|
||||
hooks.metabase.util.log/info)]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-logging {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(f {:node (api/parse-string (pr-str form))})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*)))))
|
||||
|
||||
(deftest ^:parallel warn-on-missing-format-args-test
|
||||
(testing "should fail, missing a format arg"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 0."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries.")
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries."))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-1
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 2."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries." 1 2)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries." 1 2))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-2
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
"Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."
|
||||
"log format string expects 0 arguments instead of 1."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after {0} retries." 1)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after {0} retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-args-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["metabase.util.log/warn used with a format string, use metabase.util.log/warnf instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn "Migration lock was acquired after %d retries." 1)
|
||||
(metabase.util.log/warn e "Migration lock was acquired after %d retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Use metabase.util.log/warnf instead of metabase.util.log/warn + format"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (format "Migration lock was acquired after %d retries." 1))
|
||||
(metabase.util.log/warn e (format "Migration lock was acquired after %d retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-logf-with-no-format-args-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock cleared.")
|
||||
(metabase.util.log/warnf e "Migration lock cleared."))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-1
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warnf e (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-2
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warn e (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1)))))
|
||||
339
.cljfmt.edn
339
.cljfmt.edn
@@ -1,339 +0,0 @@
|
||||
;; Cljfmt config. See https://github.com/weavejester/cljfmt#formatting-options
|
||||
{:sort-ns-references?
|
||||
true
|
||||
|
||||
:function-arguments-indentation
|
||||
:community
|
||||
|
||||
:parallel?
|
||||
true
|
||||
|
||||
:paths
|
||||
["src"
|
||||
"test"
|
||||
"enterprise/backend"
|
||||
"bin/build"
|
||||
"modules/drivers"
|
||||
".clj-kondo/src"
|
||||
".clj-kondo/test"
|
||||
#_"dev"]
|
||||
|
||||
;; See https://github.com/weavejester/cljfmt/blob/master/cljfmt/resources/cljfmt/indents/clojure.clj for the default
|
||||
;; definitions
|
||||
;;
|
||||
;; [[:block 0]]
|
||||
;; ^ number of special args
|
||||
;;
|
||||
;; `:block` indentation means format with all arguments indented 2 spaces except for the 'special' arguments which
|
||||
;; normally are supposed to go on the first line. e.g. `let` gets [[:block 1]] because it has one 'special' arg.
|
||||
;;
|
||||
;; [[:inner 0]]
|
||||
;; ^ depth
|
||||
;; [[:inner 2 0]]
|
||||
;; ^ argument index to restrict this rule to
|
||||
;;
|
||||
;; `:inner` indentation is like `:defn` indentation. `[[:inner 0]]` (default for `defn`) means all arguments at depth
|
||||
;; zero get indented 2 spaces if not on the first line regardless of number of elements on the first line.
|
||||
:extra-indents
|
||||
{;; clojure.core stuff
|
||||
fn* [[:inner 0]]
|
||||
let* [[:block 1]]
|
||||
with-meta [[:default]]
|
||||
;; library stuff. Libraries that specify their own `:style/indent` specs are included in the section after this
|
||||
;; since it is generated automatically.
|
||||
clojure.core.logic/fresh [[:block 1]]
|
||||
clojure.core.logic/matcha [[:block 1]]
|
||||
clojure.core.logic/run [[:block 2]]
|
||||
clojure.core.match/match [[:block 1]]
|
||||
clojure.test.check.properties/for-all [[:block 1]]
|
||||
clojure.test.check.properties/for-all* [[:block 1]]
|
||||
clojure.test.check/quick-check [[:block 1]]
|
||||
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
|
||||
clojurewerkz.quartzite.jobs/build [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-description [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
|
||||
clojurewerkz.quartzite.triggers/build [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-description [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-priority [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
|
||||
def-map-type [[:block 2] [:inner 1]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
defrecord+ [[:block 2] [:inner 1]]
|
||||
deftype+ [[:block 2] [:inner 1]]
|
||||
net.cgrand.macrovich/case [[:block 0]]
|
||||
potemkin.namespaces/import-vars [[:default]]
|
||||
potemkin.types/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin.types/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/import-vars [[:default]]
|
||||
taoensso.nippy/extend-freeze [[:inner 0]]
|
||||
taoensso.nippy/extend-thaw [[:inner 0]]
|
||||
throttle.core/with-throttling [[:block 1]]
|
||||
;; the rest of the stuff in this list is generated automatically based on `:style/indent` specs
|
||||
;; by running [[dev.generate-cljfmt-config/specs]]... use that code to update this list
|
||||
metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.api.card-test/with-temp-native-card! [[:block 1]]
|
||||
metabase.api.card-test/with-temp-native-card-with-params! [[:block 1]]
|
||||
metabase.api.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.api.collection-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.api.collection-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.api.common.internal/auto-coerce [[:block 1]]
|
||||
metabase.api.common.internal/auto-parse [[:block 1]]
|
||||
metabase.api.common.internal-test/no-route-regexes [[:block 0]]
|
||||
metabase.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.api.public-test/with-temp-public-card [[:block 1]]
|
||||
metabase.api.public-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.api.public-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.api.search-test/do-test-users [[:block 1]]
|
||||
metabase.api.session/http-401-on-error [[:block 0]]
|
||||
metabase.async.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.async.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.async.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.async.util/cancelable-thread [[:block 0]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.db/with-application-db [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.models.params.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.models.params.chain-filter-test/with-fk-field-to-field-remapping [[:block 0]]
|
||||
metabase.models.params.chain-filter-test/with-human-readable-values-remapping [[:block 0]]
|
||||
metabase.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.models.setting/defsetting [[:block 1]]
|
||||
metabase.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
|
||||
metabase.query-processor.debug/debug> [[:block 0]]
|
||||
metabase.query-processor.error-type/deferror [[:block 1]]
|
||||
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
|
||||
metabase.query-processor.store/cached [[:block 1]]
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.sample-data-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/native-query [[:block 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/native-query [[:block 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
|
||||
metabase.upload-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
@@ -1,11 +1,11 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:11
|
||||
|
||||
# Set up nodesource, install node, yarn, fontconfig for static viz, rlwrap for dev ergonomics
|
||||
RUN apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com\
|
||||
&& apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends yarn
|
||||
|
||||
RUN ( curl -fsSL https://deb.nodesource.com/setup_18.x | bash ) \
|
||||
&& export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com \
|
||||
&& apt-get update && apt-get -y install --no-install-recommends nodejs yarn rlwrap fontconfig
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash
|
||||
RUN apt-get update && apt-get -y install --no-install-recommends nodejs
|
||||
|
||||
# install Clojure
|
||||
RUN curl https://download.clojure.org/install/linux-install-1.11.1.1262.sh | bash
|
||||
RUN curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh \
|
||||
&& bash ./linux-install-1.10.3.933.sh
|
||||
@@ -13,9 +13,5 @@
|
||||
"forwardPorts": [3000],
|
||||
|
||||
// Comment out the next line to run as root instead.
|
||||
"remoteUser": "vscode",
|
||||
|
||||
"runArgs": ["--network=metabase-dev"],
|
||||
|
||||
"postStartCommand": "git config --global --add safe.directory /workspaces/metabase"
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
||||
104
.dir-locals.el
104
.dir-locals.el
@@ -6,7 +6,6 @@
|
||||
;; prefer keeping source width about ~118, GitHub seems to cut off stuff at either 119 or 120 and it's nicer
|
||||
;; to look at code in GH when you don't have to scroll back and forth
|
||||
(fill-column . 118)
|
||||
(whitespace-line-column . 118)
|
||||
;; tell find-things-fast to always use this directory as project root regardless of presence of other
|
||||
;; deps.edn files
|
||||
(ftf-project-finders . (ftf-get-top-git-dir)))
|
||||
@@ -20,77 +19,66 @@
|
||||
(clojure-mode
|
||||
;; Specify which arg is the docstring for certain macros
|
||||
;; (Add more as needed)
|
||||
(eval . (put 'defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'define-premium-feature 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'api/defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'setting/defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 's/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'p.types/defprotocol+ 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'methodical/defmethod 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'methodical/defmulti 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mi/define-simple-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mi/define-batched-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mr/def 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mu/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'define-premium-feature 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'api/defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'setting/defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 's/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'p.types/defprotocol+ 'clojure-doc-string-elt 2))
|
||||
;; Define custom indentation for functions inside metabase.
|
||||
;; This list isn't complete; add more forms as we come across them.
|
||||
;;
|
||||
;; `put-clojure-indent' is a safe-local-eval-function, so use a bunch of calls to that
|
||||
;; instead of one call to `define-clojure-indent'
|
||||
(eval . (put-clojure-indent 'api/let-404 1))
|
||||
(eval . (put-clojure-indent 'c/step 1))
|
||||
(eval . (put-clojure-indent 'impl/test-migrations 2))
|
||||
(eval . (put-clojure-indent 'let-404 0))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match-one '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace-in '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.util.match/replace '(:defn)))
|
||||
(eval . (put-clojure-indent 'macros/case 0))
|
||||
(eval . (put-clojure-indent 'match 1))
|
||||
(eval . (put-clojure-indent 'mt/dataset 1))
|
||||
(eval . (put-clojure-indent 'mt/format-rows-by '(:form)))
|
||||
(eval . (put-clojure-indent 'mt/query 1))
|
||||
(eval . (put-clojure-indent 'mt/test-driver 1))
|
||||
(eval . (put-clojure-indent 'mt/test-drivers 1))
|
||||
(eval . (put-clojure-indent 'mt/with-anaphora 1))
|
||||
(eval . (put-clojure-indent 'prop/for-all 1))
|
||||
(eval . (put-clojure-indent 'c/step 1))
|
||||
(eval . (put-clojure-indent 'db/insert-many! 1))
|
||||
(eval . (put-clojure-indent 'db/update! 2))
|
||||
(eval . (put-clojure-indent 'impl/test-migrations 2))
|
||||
(eval . (put-clojure-indent 'let-404 0))
|
||||
(eval . (put-clojure-indent 'macros/case 0))
|
||||
(eval . (put-clojure-indent 'match 1))
|
||||
(eval . (put-clojure-indent 'mbql.match/match 1))
|
||||
(eval . (put-clojure-indent 'mbql.match/match-one 1))
|
||||
(eval . (put-clojure-indent 'mbql.match/replace 1))
|
||||
(eval . (put-clojure-indent 'mbql.match/replace-in 2))
|
||||
(eval . (put-clojure-indent 'mt/dataset 1))
|
||||
(eval . (put-clojure-indent 'mt/format-rows-by 1))
|
||||
(eval . (put-clojure-indent 'mt/query 1))
|
||||
(eval . (put-clojure-indent 'mt/test-drivers 1))
|
||||
(eval . (put-clojure-indent 'prop/for-all 1))
|
||||
(eval . (put-clojure-indent 'qp.streaming/streaming-response 1))
|
||||
(eval . (put-clojure-indent 'tc/quick-check 1))
|
||||
(eval . (put-clojure-indent 'u/profile 1))
|
||||
(eval . (put-clojure-indent 'u/prog1 1))
|
||||
(eval . (put-clojure-indent 'u/select-keys-when '(:form)))
|
||||
(eval . (put-clojure-indent 'with-meta '(:form)))
|
||||
(eval . (put-clojure-indent 'u/select-keys-when 1))
|
||||
(eval . (put-clojure-indent 'u/strict-extend 1))
|
||||
;; these ones have to be done with `define-clojure-indent' for now because of upstream bug
|
||||
;; https://github.com/clojure-emacs/clojure-mode/issues/600 once that's resolved we should use `put-clojure-indent'
|
||||
;; instead. Please don't add new entries unless they don't work with `put-clojure-indent'
|
||||
(eval . (define-clojure-indent
|
||||
(l/matcha '(1 (:defn)))
|
||||
(l/matche '(1 (:defn)))
|
||||
(p.types/def-abstract-type '(1 (:defn)))
|
||||
(p.types/defprotocol+ '(1 (:defn)))
|
||||
(p.types/defrecord+ '(2 nil nil (:defn)))
|
||||
(p.types/deftype+ '(2 nil nil (:defn)))
|
||||
(p/def-map-type '(2 nil nil (:defn)))
|
||||
(p/defprotocol+ '(1 (:defn)))
|
||||
(p/defrecord+ '(2 nil nil (:defn)))
|
||||
(p/deftype+ '(2 nil nil (:defn)))
|
||||
(tools.macro/macrolet '(1 ((:defn)) :form))))
|
||||
(l/matcha '(1 (:defn)))
|
||||
(l/matche '(1 (:defn)))
|
||||
(p.types/def-abstract-type '(1 (:defn)))
|
||||
(p.types/defprotocol+ '(1 (:defn)))
|
||||
(p.types/defrecord+ '(2 nil nil (:defn)))
|
||||
(p.types/deftype+ '(2 nil nil (:defn)))
|
||||
(p/def-map-type '(2 nil nil (:defn)))
|
||||
(p/defprotocol+ '(1 (:defn)))
|
||||
(p/defrecord+ '(2 nil nil (:defn)))
|
||||
(p/deftype+ '(2 nil nil (:defn)))
|
||||
(tools.macro/macrolet '(1 ((:defn)) :form))))
|
||||
(cider-clojure-cli-aliases . "dev:drivers:drivers-dev:ee:ee-dev:user")
|
||||
(clojure-indent-style . always-align)
|
||||
(cljr-favor-prefix-notation . nil)
|
||||
(cljr-insert-newline-after-require . t)
|
||||
(cljr-print-right-margin . 118)
|
||||
(clojure-docstring-fill-column . 118)
|
||||
(cider-preferred-build-tool . clojure-cli)
|
||||
(cider-default-cljs-repl . shadow-select)
|
||||
(cider-shadow-default-options . "app"))
|
||||
(cider-preferred-build-tool . clojure-cli))
|
||||
|
||||
("shared"
|
||||
(clojure-mode
|
||||
(cider-default-cljs-repl . shadow-select)
|
||||
(cider-shadow-default-options . "node-repl")
|
||||
(cider-preferred-build-tool . shadow-cljs)))
|
||||
|
||||
("bin"
|
||||
(clojure-mode
|
||||
(cider-clojure-cli-aliases . "dev:drivers:build:build-dev"))))
|
||||
(cider-clojure-cli-aliases . "dev"))))
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
frontend/src/cljs
|
||||
frontend/src/cljs_release
|
||||
e2e/support/cypress_sample_database.js
|
||||
e2e/support/cypress_sample_instance_data.js
|
||||
|
||||
88
.eslintrc
Normal file
88
.eslintrc
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"rules": {
|
||||
"strict": [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
"no-unused-vars": [
|
||||
1,
|
||||
{ "vars": "all", "args": "none", "varsIgnorePattern": "^_" }
|
||||
],
|
||||
"no-empty": [1, { "allowEmptyCatch": true }],
|
||||
"curly": [1, "all"],
|
||||
"eqeqeq": [1, "smart"],
|
||||
"import/no-commonjs": 1,
|
||||
"no-console": 0,
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/display-name": 1,
|
||||
"react/prop-types": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-find-dom-node": 0,
|
||||
"react/no-children-prop": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { "forbid": ["w", "h"] }],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ "additionalHooks": "(useSyncedQueryString|useAsyncFunction)" }
|
||||
],
|
||||
"prefer-const": [1, { "destructuring": "all" }],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"complexity": ["error", { "max": 54 }]
|
||||
},
|
||||
"globals": {
|
||||
"before": true,
|
||||
"cy": true,
|
||||
"Cypress": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"commonjs": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": ["react", "no-only-tests"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": "webpack",
|
||||
"import/ignore": ["\\.css$"],
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"legacyDecorators": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
288
.eslintrc.js
288
.eslintrc.js
@@ -1,288 +0,0 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
// `postcss-modules` lints css modules class names, but it currently crashes
|
||||
// eslint on vscode. If you use webstorm or want to run the lint for the cli, you
|
||||
// can use this flag to enable it. This is set to true in CI
|
||||
const shouldLintCssModules =
|
||||
process.env.LINT_CSS_MODULES === "true" || process.env.CI;
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
strict: [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
vars: "all",
|
||||
args: "none",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
"no-empty": [1, { allowEmptyCatch: true }],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "moment",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
{
|
||||
name: "moment-timezone",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
curly: [1, "all"],
|
||||
eqeqeq: [1, "smart"],
|
||||
"import/no-default-export": 2,
|
||||
"import/no-named-as-default": 0,
|
||||
"import/no-commonjs": 1,
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
orderImportKind: "asc",
|
||||
caseInsensitive: false,
|
||||
},
|
||||
groups: [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index",
|
||||
],
|
||||
warnOnUnassignedImports: false,
|
||||
},
|
||||
],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
// allows this rule to work with import/order
|
||||
ignoreDeclarationSort: true,
|
||||
},
|
||||
],
|
||||
"no-console": [2, { allow: ["warn", "error", "errorBuffer"] }],
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/display-name": 1,
|
||||
"react/prop-types": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-find-dom-node": 0,
|
||||
"react/no-children-prop": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { forbid: ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ additionalHooks: "(useSafeAsyncFunction)" },
|
||||
],
|
||||
"prefer-const": [1, { destructuring: "all" }],
|
||||
"no-restricted-globals": ["error", "close"],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": [
|
||||
"error",
|
||||
{
|
||||
block: [
|
||||
"describe",
|
||||
"it",
|
||||
"context",
|
||||
"test",
|
||||
"tape",
|
||||
"fixture",
|
||||
"serial",
|
||||
"Feature",
|
||||
"Scenario",
|
||||
"Given",
|
||||
"And",
|
||||
"When",
|
||||
"Then",
|
||||
"describeWithSnowplow",
|
||||
"describeEE",
|
||||
],
|
||||
},
|
||||
],
|
||||
complexity: ["error", { max: 54 }],
|
||||
...(shouldLintCssModules
|
||||
? {
|
||||
"postcss-modules/no-undef-class": "error",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
globals: {
|
||||
before: true,
|
||||
cy: true,
|
||||
Cypress: true,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
commonjs: true,
|
||||
jest: true,
|
||||
"jest/globals": true,
|
||||
},
|
||||
parser: "babel-eslint",
|
||||
plugins: ["react", "no-only-tests", "postcss-modules"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:depend/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
settings: {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
"import/resolver": {
|
||||
webpack: {
|
||||
typescript: true,
|
||||
},
|
||||
},
|
||||
"import/ignore": ["\\.css$"],
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
"postcss-modules": {
|
||||
baseDir: "./frontend/src",
|
||||
},
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true,
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
rules: {
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
"depend/ban-dependencies": [
|
||||
"error",
|
||||
{
|
||||
allowed: [
|
||||
"underscore",
|
||||
"moment",
|
||||
"lodash.orderby",
|
||||
"lodash.debounce",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"*.unit.spec.*",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
],
|
||||
rules: {
|
||||
"no-unconditional-metabase-links-render": "off",
|
||||
"no-literal-metabase-strings": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
fixStyle: "inline-type-imports",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
// This was introduced in 6.0.0
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react",
|
||||
"plugin:jest-formatting/recommended",
|
||||
],
|
||||
plugins: ["jest", "jest-dom", "testing-library", "jest-formatting"],
|
||||
files: [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
"*.unit.spec.js",
|
||||
"*.unit.spec.jsx",
|
||||
],
|
||||
rules: {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Enable jest formatting for cypress tests too, the plugin logic just works
|
||||
extends: ["plugin:jest-formatting/recommended"],
|
||||
files: ["*.cy.spec.ts", "*.cy.spec.js"],
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/metabase/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='legacyQuery'] > ObjectExpression > Property[key.name='useStructuredQuery'][value.value=true]",
|
||||
message: "StructuredQuery usage is forbidden. Use MLv2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
@@ -1,12 +0,0 @@
|
||||
# Before changing this file, make sure you have read how we use it:
|
||||
# https://www.notion.so/metabase/Pull-Request-Code-Review-Guide-374f05df889748b69d67af753f9bc9b8?pvs=4#36550c04ceab40b5a8b8bee6264b090c
|
||||
|
||||
enterprise/backend/src/metabase_enterprise/advanced_permissions @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/audit_app @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/sso @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/task/truncate_audit_table.clj @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/internal_user.clj @noahmoss
|
||||
frontend/src/metabase/ui @metabase/core-frontend-admin-webapp
|
||||
src/metabase/**/*permissions* @noahmoss
|
||||
src/metabase/integrations @noahmoss
|
||||
snowplow/* @metabase/data
|
||||
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ".Needs Triage, Type:Bug"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Logs**
|
||||
Please include javascript console and server logs around the time this bug occurred. For information about how to get these, consult our [bug troubleshooting guide](https://metabase.com/docs/latest/troubleshooting-guide/bugs.html)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Information about your Metabase Installation:**
|
||||
|
||||
You can get this information by going to Admin -> Troubleshooting.
|
||||
|
||||
- Your browser and the version: (e.g., Chrome 52.1, Firefox 48.0, Safari 11.1, …)
|
||||
- Your operating system: (e.g., OS X 10.10, Windows 10.1809, Ubuntu 16.04, …)
|
||||
- Your databases: (e.g, MySQL, Postgres, MongoDB, …)
|
||||
- Metabase version: (e.g., 0.19.3)
|
||||
- Metabase hosting environment: (e.g., Elastic Beanstalk, Docker, Heroku, Jar-file on Ubuntu, …)
|
||||
- Metabase internal database: (e.g., H2 (default), Postgres or MySQL)
|
||||
|
||||
**Severity**
|
||||
How severe an issue is this bug to you? Is this annoying, blocking some users, blocking an upgrade or blocking your usage of Metabase entirely?
|
||||
Note: the more honest and specific you are here the more we will take you seriously.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
95
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
95
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,95 +0,0 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: [".Needs Triage", "Type:Bug"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Thanks for taking the time to fill out this bug report!
|
||||
### Please read this before you proceed.
|
||||
The Github issue tracker is intended to collect bug reports and feature requests.
|
||||
Any issues open for help requests will be closed to keep from clogging up the issue tracker.
|
||||
Please use our [discussion forum](https://discourse.metabase.com) instead.
|
||||
|
||||
- We might close bug reports without sufficient information. Either provide repro steps or help us reproduce the issue.
|
||||
- Please check if the [bug already exists](https://github.com/metabase/metabase/labels/Type%3ABug). We will close duplicate reports.
|
||||
- Use as many screenshots or videos as possible to describe what you're seeing.
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: Tell us what happened!
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: repro-steps
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior (if you can reproduce the bug using the Sample Database, we will find the issue faster).
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: How should this work?
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
Please include relevant JavaScript console logs from the "console" tab in the browser dev tools.
|
||||
Additionally, include Metabase server logs (`Settings -> Admin -> Troubleshooting -> Logs`) around the time this bug occurred.
|
||||
For information about how to get these, consult our [bug troubleshooting guide](https://metabase.com/docs/latest/troubleshooting-guide/bugs.html)
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: about-metabase
|
||||
attributes:
|
||||
label: Information about your Metabase installation
|
||||
description: |
|
||||
Copy and paste the JSON from `Admin -> Troubleshooting`.
|
||||
_This will be automatically formatted into code, so no need for backticks._
|
||||
placeholder: |
|
||||
{"browser-info": {
|
||||
"language": "en-US",
|
||||
"platform": "MacIntel",
|
||||
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"vendor": "Google Inc."},
|
||||
"metabase-info": {
|
||||
"databases": [ "postgres",...
|
||||
render: JSON
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: |
|
||||
How severe an issue is this bug to you? Is this annoying, blocking some users, blocking an upgrade or blocking your usage of Metabase entirely?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here. This is a good place to include any additional screenshots and/or videos.
|
||||
validations:
|
||||
required: false
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Questions and help
|
||||
url: https://discourse.metabase.com
|
||||
about: For help with installation and setup, information on how specific features work, or general questions about Metabase, please use our discussion forum.
|
||||
23
.github/ISSUE_TEMPLATE/epic.md
vendored
23
.github/ISSUE_TEMPLATE/epic.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Epic (Internal Use)
|
||||
about: This issue is used to track a feature/project that may span several days
|
||||
to weeks to implement. This is used by the core Metabase team to project plan
|
||||
and manage work.
|
||||
title: "[Epic] Title"
|
||||
labels: ".Epic"
|
||||
---
|
||||
|
||||
### Links
|
||||
|
||||
- [product doc](LINK_TO_PRODUCT_DOC)
|
||||
- [technical design doc](LINK_TO_TECHNICAL_DESIGN_DOC)
|
||||
- [testing plan](LINK_TO_TESTING_PLAN)
|
||||
- feature branch: `BRANCH_NAME`
|
||||
- issue links: _related issues, if any_
|
||||
|
||||
### Implementation Plan
|
||||
|
||||
```[tasklist]
|
||||
#### Milestone 1
|
||||
- [ ] _Add tasks here_
|
||||
```
|
||||
38
.github/ISSUE_TEMPLATE/feature-or-project-implementation.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/feature-or-project-implementation.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Feature or Project Implementation (Internal Use)
|
||||
about: This issue is used to track a feature implementation for a solution to a user
|
||||
problem that may span days to weeks to implement. This is used by the core Metabase
|
||||
team to project plan and manage work.
|
||||
title: X - Internal Use - Feature/Project X Implementation
|
||||
labels: ".Epic"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<Insert feature / project description here>: Notes - This demonstrates a workflow and implementation plan around feature X that we can use in features that go out in a release. There should be a paragraph description of the problem we're trying to solve and a high level what the solution will be.
|
||||
|
||||
**Links**
|
||||
- product doc: _link to product doc_
|
||||
- eng doc: _link to technical design doc_
|
||||
- feature branch: `branch-name` _this should be the feature branch where this work will be done in. PRs will be delivered against this branch_
|
||||
- Issue links: _list of issues this should close when this is delivered_
|
||||
|
||||
**Implementation Plan**
|
||||
|
||||
|
||||
***Milestone 1*** )_(note, each milestone should correspond to a point at which product/design can jump in to apply polish, evaluate changes, etc)_
|
||||
- [x] step 1: #somePRnumber
|
||||
- [ ] step 2
|
||||
- [ ] step 3
|
||||
|
||||
***Milestone 2***
|
||||
- [ ] etc
|
||||
|
||||
***Mileston N*** _(we can put stuff like translations, docs, testing, etc)_
|
||||
- [ ] String translations
|
||||
- [ ] Write rough pass documents for handoff to customer success
|
||||
|
||||
|
||||
**How to Test This Feature Out**
|
||||
|
||||
_(include steps on how to test this project feature out, for someone unfamiliar with this work. please try to use the sample dataset when possible. this will serve as the basis for automated e2e tests, as well as the acceptance test to showcase the essence of this feature for bug bashes, etc)_
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -17,6 +17,7 @@ A clear and concise description of what you want to happen.
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**How important is this feature to you?**
|
||||
Note: the more honest and specific you are here the more we will take you seriously.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/questions-and-help.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/questions-and-help.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Questions and help
|
||||
about: If you think you need help with something related to Metabase
|
||||
title: ''
|
||||
labels: ".Needs Triage, Type:Question"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
For help with installation and setup, information on how specific features work, or general questions about Metabase, please use our discussion forum:
|
||||
|
||||
https://discourse.metabase.com
|
||||
|
||||
The Github issue tracker is intended to collect bug reports and feature requests.
|
||||
Any issues open for help requests will be closed to keep from clogging up the issue tracker.
|
||||
14
.github/ISSUE_TEMPLATE/task.md
vendored
14
.github/ISSUE_TEMPLATE/task.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Task (Internal Use)
|
||||
about: This issue is used to track a smaller feature/task that does not require
|
||||
being broken down to milestones. This is used by the core Metabase team to
|
||||
project plan and manage work.
|
||||
labels: ".Task"
|
||||
|
||||
---
|
||||
|
||||
**Context**
|
||||
|
||||
|
||||
- issue links: _related issues if any_
|
||||
|
||||
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,27 +1,10 @@
|
||||
> [!IMPORTANT]
|
||||
> For those employed by Metabase: if you are merging into master, please add either a `backport` or a `no-backport` label to this PR. You will not be able to merge until you do this step. Refer to the section [Do I need to backport this PR?](https://www.notion.so/metabase/Metabase-Branching-Strategy-6eb577d5f61142aa960a626d6bbdfeb3?pvs=4#89f80d6f17714a0198aeb66c0efd1b71) in the Metabase Branching Strategy document for more details. If you're not employed by Metabase, this section does not apply to you, and the label will be taken care of by your reviewer.
|
||||
###### Before submitting the PR, please make sure you do the following
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> If that is your first contribution to Metabase, please sign the [Contributor License Agreement](https://docs.google.com/a/metabase.com/forms/d/1oV38o7b9ONFSwuzwmERRMi9SYrhYeOrkbmNaq9pOJ_E/viewform) (unless it's a tiny documentation change). Also, if you're attempting to fix a translation issue, please submit your changes to our [POEditor project](https://poeditor.com/join/project/ynjQmwSsGh) instead of opening a PR.
|
||||
- [ ] If you're attempting to fix a translation issue, please submit your changes to our [POEditor project](https://poeditor.com/join/project/ynjQmwSsGh) instead of opening a PR.
|
||||
|
||||
Closes https://github.com/metabase/metabase/issues/[issue_number]
|
||||
### Tests
|
||||
|
||||
### Description
|
||||
|
||||
Describe the overall approach and the problem being solved.
|
||||
|
||||
### How to verify
|
||||
|
||||
Describe the steps to verify that the changes are working as expected.
|
||||
|
||||
1. New question -> Sample Dataset -> ...
|
||||
2. ...
|
||||
|
||||
### Demo
|
||||
|
||||
_Upload a demo video or before/after screenshots if sensible or remove the section_
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Tests have been added/updated to cover changes in this PR
|
||||
- [ ] Run the frontend and Cypress end-to-end tests with `yarn lint && yarn test`)
|
||||
- [ ] If there are changes to the backend codebase, run the backend tests with `clojure -X:dev:test`
|
||||
- [ ] Sign the [Contributor License Agreement](https://docs.google.com/a/metabase.com/forms/d/1oV38o7b9ONFSwuzwmERRMi9SYrhYeOrkbmNaq9pOJ_E/viewform)
|
||||
(unless it's a tiny documentation change).
|
||||
|
||||
15
.github/actions/await-port/action.yml
vendored
15
.github/actions/await-port/action.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Wait for localhost port to be ready
|
||||
|
||||
inputs:
|
||||
port:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Wait for port ${{ inputs.port }} to be ready
|
||||
shell: bash
|
||||
run: |
|
||||
while ! nc -z localhost ${{ inputs.port }}; do
|
||||
sleep 0.1;
|
||||
done
|
||||
71
.github/actions/build-e2e-matrix/action.yml
vendored
71
.github/actions/build-e2e-matrix/action.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: Build E2E Matrix
|
||||
description: Build and output a custom matrix for E2E tests.
|
||||
|
||||
outputs:
|
||||
matrix:
|
||||
description: Parameterized matrix in JSON format
|
||||
value: ${{ steps.matrix.outputs.result }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
id: matrix
|
||||
with:
|
||||
script: | # js
|
||||
const java = 21;
|
||||
|
||||
const defaultRunner = "ubuntu-22.04";
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
runner: defaultRunner,
|
||||
edition: "ee",
|
||||
context: "folder"
|
||||
};
|
||||
|
||||
const testSets = [
|
||||
["actions", {} ],
|
||||
["admin", {} ],
|
||||
["admin-2", {} ],
|
||||
["binning", {} ],
|
||||
["collections", {} ],
|
||||
["custom-column", {} ],
|
||||
["dashboard", {} ],
|
||||
["dashboard-cards", {} ],
|
||||
["dashboard-filters", {} ],
|
||||
["dashboard-filters-2", {} ],
|
||||
["embedding", {} ],
|
||||
["embedding-sdk", { context: "embedding-sdk" } ],
|
||||
["filters", {} ],
|
||||
["filters-reproductions", {}],
|
||||
["joins", {} ],
|
||||
["metrics", {} ],
|
||||
["models", {} ],
|
||||
["native", {} ],
|
||||
["native-filters", {} ],
|
||||
["onboarding", {} ],
|
||||
["organization", {} ],
|
||||
["permissions", {} ],
|
||||
["question", {} ],
|
||||
["question-reproductions", {} ],
|
||||
["search", {}],
|
||||
["sharing", {} ],
|
||||
["visualizations-charts", {} ],
|
||||
["visualizations-tabular", {} ],
|
||||
["oss-subset", { edition: 'oss', context: "special" } ],
|
||||
["slow", { context: "special" } ],
|
||||
["flaky", { context: "special" } ],
|
||||
["mongo", { context: "qa-database" } ],
|
||||
];
|
||||
|
||||
const config = testSets.map(([name, specialOptions]) => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
...specialOptions,
|
||||
name,
|
||||
}
|
||||
});
|
||||
|
||||
const matrix = { include: config };
|
||||
return matrix;
|
||||
132
.github/actions/build-ee-extra/action.yml
vendored
132
.github/actions/build-ee-extra/action.yml
vendored
@@ -1,132 +0,0 @@
|
||||
name: Build ee-extra
|
||||
description: Build Metabase Enterprise Edition with extra features
|
||||
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
# build with Java 11 since that's still officially what we target as a minimum and we don't want Java >11 bytecode
|
||||
# sneaking in.
|
||||
default: 11
|
||||
iam-role:
|
||||
description: "The IAM role to assume"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'temurin'
|
||||
- name: Reveal its original version.properties
|
||||
run: jar xf ./bin/docker/metabase.jar version.properties && cat version.properties
|
||||
shell: bash
|
||||
- name: get major version
|
||||
id: major_version
|
||||
run: echo "major_version=$(cat version.properties | grep 'tag=' | cut -d'.' -f2)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: get drivers registry
|
||||
run: |
|
||||
curl -OL https://raw.githubusercontent.com/metabase/metabase-registry/main/registry.yaml
|
||||
shell: bash
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/Hydrogen # 18.x.x
|
||||
- run: rm package.json && npm install yaml
|
||||
shell: bash
|
||||
- name: parse drivers registry
|
||||
uses: actions/github-script@v7
|
||||
id: drivers_registry
|
||||
with:
|
||||
script: | # js
|
||||
const { parse } = require('yaml');
|
||||
const fs = require('fs');
|
||||
const registry = parse(fs.readFileSync('registry.yaml', 'utf8'));
|
||||
|
||||
const drivers = registry.drivers;
|
||||
const majorVersion = '${{ steps.major_version.outputs.major_version }}';
|
||||
|
||||
const getDriverUrl = (driverName) => {
|
||||
const driver = drivers.find(driver => driver.name === driverName);
|
||||
|
||||
if (!driver) {
|
||||
throw new Error(`Driver ${driverName} not found in the registry`);
|
||||
}
|
||||
|
||||
const driverUrl = driver.versions[majorVersion] ?? driver.versions.default;
|
||||
|
||||
if (!driverUrl) {
|
||||
throw new Error(`Driver ${driverName} version ${majorVersion} not found in the registry`);
|
||||
}
|
||||
|
||||
return driverUrl;
|
||||
};
|
||||
|
||||
const extraDrivers = {
|
||||
firebolt: getDriverUrl('firebolt'),
|
||||
starburst: getDriverUrl('starburst'),
|
||||
exasol: getDriverUrl('exasol'),
|
||||
exasol_jdbc: drivers.find(d => d.name === 'exasol').deps[0],
|
||||
clickhouse: getDriverUrl('clickhouse'),
|
||||
ocient: getDriverUrl('ocient'),
|
||||
materialize: getDriverUrl('materialize'),
|
||||
};
|
||||
|
||||
console.log(extraDrivers);
|
||||
|
||||
return extraDrivers;
|
||||
- name: Download Firebolt driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).firebolt }}
|
||||
working-directory: modules
|
||||
shell: bash
|
||||
- name: Download Starburst driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).starburst }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Exasol driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).exasol }}
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).exasol_jdbc }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Clickhouse driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).clickhouse }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Ocient driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).ocient }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Materialize driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).materialize }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Bundle additional drivers into the Uberjar
|
||||
run: jar uf bin/docker/metabase.jar modules/*.jar
|
||||
shell: bash
|
||||
- name: Launch uberjar
|
||||
run: >-
|
||||
java --add-opens java.base/java.nio=ALL-UNNAMED -jar ./bin/docker/metabase.jar &
|
||||
shell: bash
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
shell: bash
|
||||
- name: Kill the java process
|
||||
run: pkill -f metabase.jar
|
||||
shell: bash
|
||||
- name: configure aws credentials
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ inputs.iam-role }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Login to Amazon ECR
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Cross-branch migration test
|
||||
|
||||
inputs:
|
||||
base-ref:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref }}
|
||||
|
||||
- name: Prepare backend environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: cross-branch-migrations
|
||||
|
||||
- name: Run backend on base branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Stop Metabase on base branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
- name: Check out pull request branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Run backend on pull request branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited. Check that your migration changes work on an instance that has been previously initialized on your PR's target branch."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Stop Metabase on pull request branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
77
.github/actions/e2e-download-uberjar/action.yml
vendored
77
.github/actions/e2e-download-uberjar/action.yml
vendored
@@ -1,77 +0,0 @@
|
||||
name: Find and download uberjar for E2E tests
|
||||
description: Download previously built artifact from one of the previous commits.
|
||||
inputs:
|
||||
edition:
|
||||
description: Metabase edition.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Download previously stored uberjar
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
const fs = require('fs');
|
||||
const execSync = require("child_process").execSync;
|
||||
|
||||
const getCommit = (ref = "HEAD") => {
|
||||
const [commit] = execSync(`git rev-parse ${ref}`, { encoding: "utf8" }).split(
|
||||
"\n",
|
||||
);
|
||||
return commit;
|
||||
};
|
||||
|
||||
const baseConfig = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
};
|
||||
|
||||
async function getArtifact(commit, depth = 0) {
|
||||
if (depth > 20) {
|
||||
throw new Error("Couldn't find the artifact!");
|
||||
}
|
||||
|
||||
const artifactName = `metabase-${{ inputs.edition }}-${commit}-uberjar`;
|
||||
|
||||
const {
|
||||
data: { artifacts },
|
||||
} = await github.rest.actions.listArtifactsForRepo({
|
||||
...baseConfig,
|
||||
name: artifactName,
|
||||
per_page: 1,
|
||||
});
|
||||
|
||||
const [artifact] = artifacts;
|
||||
|
||||
if (!artifact) {
|
||||
const parentCommit = getCommit(`${commit}^`);
|
||||
console.log(`Didn't find ${artifactName}.\n`);
|
||||
console.log(`Switching to the parent commit: ${parentCommit}`);
|
||||
return getArtifact(parentCommit, depth + 1);
|
||||
} else {
|
||||
return artifact;
|
||||
}
|
||||
}
|
||||
|
||||
const currentCommit = "${{ github.event.pull_request.head.sha || github.sha }}";
|
||||
const { id } = await getArtifact(currentCommit);
|
||||
|
||||
const download = await github.rest.actions.downloadArtifact({
|
||||
...baseConfig,
|
||||
artifact_id: id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/mb.zip`, Buffer.from(download.data));
|
||||
|
||||
- name: Unzip Metabase artifact containing an uberjar
|
||||
run: unzip mb.zip
|
||||
shell: bash
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
cat version.properties
|
||||
mv version.properties resources/
|
||||
shell: bash
|
||||
@@ -1,97 +0,0 @@
|
||||
name: Prepare Docker containers
|
||||
description: Prepare Docker containers for E2E tests.
|
||||
inputs:
|
||||
username:
|
||||
description: DockerHub username
|
||||
required: true
|
||||
password:
|
||||
description: DockerHub password/token
|
||||
required: true
|
||||
maildev:
|
||||
description: Maildev container
|
||||
required: true
|
||||
default: false
|
||||
openldap:
|
||||
description: OpenLDAP container
|
||||
required: true
|
||||
default: false
|
||||
postgres:
|
||||
description: Postgres container
|
||||
required: true
|
||||
default: false
|
||||
mysql:
|
||||
description: MySQL container
|
||||
required: true
|
||||
default: false
|
||||
mongo:
|
||||
description: Mongo container
|
||||
required: true
|
||||
default: false
|
||||
webhook:
|
||||
description: Webhook tester container
|
||||
required: false
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Authenticate to prevent API rate limit
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ inputs.username }}
|
||||
password: ${{ inputs.password }}
|
||||
|
||||
- name: Start Containers
|
||||
run: |
|
||||
Y='\033[33m'
|
||||
G='\033[1;32m'
|
||||
|
||||
if ${{ inputs.maildev }}; then
|
||||
echo -e "${Y}Starting maildev container..." &&
|
||||
docker run -d -p 1080:1080 -p 1025:1025 maildev/maildev:2.0.5 &&
|
||||
while ! nc -z localhost 1080; do sleep 1; done &&
|
||||
while ! nc -z localhost 1025; do sleep 1; done &&
|
||||
echo -e "${G}Maildev is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.openldap }}; then
|
||||
echo -e "${Y}Starting openldap container..." &&
|
||||
docker run -d -p 389:389 \
|
||||
--env LDAP_ADMIN_PASSWORD=adminpass \
|
||||
--env LDAP_USERS=user01@example.org,user02@example.org \
|
||||
--env LDAP_PASSWORDS=123456,123465 \
|
||||
--env LDAP_ROOT=dc=example,dc=org \
|
||||
--env LDAP_PORT_NUMBER=389 \
|
||||
bitnami/openldap:2.6.4 &&
|
||||
while ! nc -z localhost 389; do sleep 1; done &&
|
||||
echo -e "${G}Openldap is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.postgres }}; then
|
||||
echo -e "${Y}Starting postgres container..." &&
|
||||
docker run -d -p 5404:5432 -e TZ='US/Pacific' metabase/qa-databases:postgres-sample-12 &&
|
||||
while ! nc -z localhost 5404; do sleep 1; done &&
|
||||
echo -e "${G}Postgres is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.mysql }}; then
|
||||
echo -e "${Y}Starting mysql container..." &&
|
||||
docker run -d -p 3304:3306 -e TZ='US/Pacific' metabase/qa-databases:mysql-sample-8 &&
|
||||
while ! nc -z localhost 3304; do sleep 1; done &&
|
||||
echo -e "${G}MySQL is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.mongo }}; then
|
||||
echo -e "${Y}Starting mongo container..." &&
|
||||
docker run -d -p 27004:27017 metabase/qa-databases:mongo-sample-4.4 &&
|
||||
while ! nc -z localhost 27004; do sleep 1; done &&
|
||||
echo -e "${G}Mongo is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.webhook }}; then
|
||||
echo -e "${Y}Starting webhook test container..." &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
while ! nc -z localhost 9080; do sleep 1; done &&
|
||||
echo -e "${G}Webhook tester is up and running!"
|
||||
fi
|
||||
shell: bash
|
||||
@@ -22,7 +22,7 @@ runs:
|
||||
COMMIT=$(env -i git log $BASE_REF --grep="(#$PULL_REQUEST_NUMBER)" --format="%H")
|
||||
echo "commit SHA $COMMIT"
|
||||
|
||||
echo "commit=$COMMIT" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=commit::$COMMIT"
|
||||
id: find-squashed-commit
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -7,19 +7,19 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v4
|
||||
id: get-latest-release-branch
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const releaseBranches = await github.rest.git.listMatchingRefs({
|
||||
const releaseBranches = await github.git.listMatchingRefs({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: "heads/release-x.",
|
||||
});
|
||||
|
||||
const getVersionFromBranch = branch => {
|
||||
const match = branch.match(/release-x\.(.*?)\.x$/);
|
||||
const match = branch.match(/release-x\.(.*?)\.x/);
|
||||
return match && parseInt(match[1]);
|
||||
};
|
||||
const latestReleaseBranch = releaseBranches.data
|
||||
|
||||
@@ -9,7 +9,7 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v4
|
||||
id: get-latest-release-branch
|
||||
with:
|
||||
result-encoding: string
|
||||
@@ -26,8 +26,8 @@ runs:
|
||||
body += ` [[Logs]](${runUrl})`;
|
||||
}
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.payload.issue.number,
|
||||
github.issues.createComment({
|
||||
issue_number: context.payload.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body,
|
||||
|
||||
13
.github/actions/prepare-backend/action.yml
vendored
13
.github/actions/prepare-backend/action.yml
vendored
@@ -2,10 +2,10 @@ name: Prepare back-end environment
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
default: '21'
|
||||
default: '11'
|
||||
clojure-version:
|
||||
required: true
|
||||
default: '1.11.1.1262'
|
||||
default: '1.10.3.933'
|
||||
m2-cache-key:
|
||||
description: 'Key to cache M2 packages from Maven Central'
|
||||
required: true
|
||||
@@ -15,7 +15,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare JDK ${{ inputs.java-version }}
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'temurin'
|
||||
@@ -24,13 +24,8 @@ runs:
|
||||
run: |
|
||||
curl -O https://download.clojure.org/install/linux-install-${{ inputs.clojure-version }}.sh &&
|
||||
sudo bash ./linux-install-${{ inputs.clojure-version }}.sh
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
shell: bash
|
||||
- name: Get M2 cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.m2
|
||||
|
||||
21
.github/actions/prepare-cypress/action.yml
vendored
21
.github/actions/prepare-cypress/action.yml
vendored
@@ -1,28 +1,9 @@
|
||||
name: Prepare cypress environment
|
||||
description: Cypress preparation steps
|
||||
|
||||
outputs:
|
||||
chrome-path:
|
||||
description: Path to the custom Chrome executable
|
||||
value: ${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Chrome v111
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
with:
|
||||
# https://chromium.cypress.io/linux/stable/111.0.5563.146
|
||||
chrome-version: 1097615
|
||||
id: setup-chrome
|
||||
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
shell: bash
|
||||
- name: Get Cypress cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-Cypress-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
41
.github/actions/prepare-frontend/action.yml
vendored
41
.github/actions/prepare-frontend/action.yml
vendored
@@ -1,50 +1,21 @@
|
||||
name: Prepare front-end environment
|
||||
inputs:
|
||||
node-version:
|
||||
description: "The version of Node.js to use, overriding .nvmrc"
|
||||
required: false
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }} # this overrides, if present
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: |
|
||||
echo "Commit message includes [ci nocache]; dependencies will NOT be cached"
|
||||
yarn cache clean
|
||||
shell: bash
|
||||
node-version: 14.x
|
||||
cache: 'yarn'
|
||||
- name: Get M2 cache
|
||||
uses: actions/cache@v4
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-cljs-${{ hashFiles('**/shadow-cljs.edn') }}
|
||||
|
||||
- name: Get node_modules cache
|
||||
uses: actions/cache@v4
|
||||
id: node-modules-cache
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock', '**/patches/*.patch') }}
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v4
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-
|
||||
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
name: Prepare Uberjar Artifact
|
||||
description: Prepare and upload Metabase JAR as a GitHub artifact.
|
||||
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
description: Artifact name
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -16,9 +9,9 @@ runs:
|
||||
run: sha256sum ./target/uberjar/metabase.jar > SHA256.sum
|
||||
shell: bash
|
||||
- name: Upload JARs as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ inputs.name }}
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
path: |
|
||||
./target/uberjar/metabase.jar
|
||||
./COMMIT-ID
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Run Embedding SDK Storybook
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare frontend environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
|
||||
- name: Compile CLJS
|
||||
run: yarn build-pure:cljs
|
||||
shell: bash
|
||||
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook-embedding-sdk
|
||||
shell: bash
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Storybook to become available
|
||||
run: |
|
||||
echo "Waiting for Storybook to be served..."
|
||||
while ! curl --silent --fail http://localhost:6006; do
|
||||
sleep 5
|
||||
echo "Retrying..."
|
||||
done
|
||||
echo "Storybook is being served!"
|
||||
shell: bash
|
||||
33
.github/actions/test-driver/action.yml
vendored
33
.github/actions/test-driver/action.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Test database driver
|
||||
inputs:
|
||||
junit-name:
|
||||
required: true
|
||||
default: "driver"
|
||||
test-args:
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: driver
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
- name: Test database driver
|
||||
run: clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
|
||||
shell: bash
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
if: failure()
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
name: JUnit Test Report ${{ inputs.junit-name }}
|
||||
reporter: java-junit
|
||||
list-suites: failed
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
54
.github/actions/upload-test-results/action.yml
vendored
54
.github/actions/upload-test-results/action.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Upload Test Results to s3
|
||||
description: Uploads test results files to s3
|
||||
|
||||
inputs:
|
||||
input-path:
|
||||
required: true
|
||||
output-name:
|
||||
required: true
|
||||
default: test
|
||||
aws-access-key-id:
|
||||
required: true
|
||||
aws-secret-access-key:
|
||||
required: true
|
||||
aws-region:
|
||||
required: true
|
||||
default: us-east-1
|
||||
bucket:
|
||||
required: true
|
||||
trunk-api-token:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ inputs.aws-access-key-id }}
|
||||
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
|
||||
aws-region: ${{ inputs.aws-region }}
|
||||
- name: zip test results
|
||||
env:
|
||||
INPUT_DIR: ${{ inputs.input-path }}
|
||||
OUTPUT_FILE: ${{ inputs.output-name }}
|
||||
shell: bash
|
||||
run: | # sh
|
||||
zip ${OUTPUT_FILE}.zip $INPUT_DIR/*.xml
|
||||
- name: Upload test results to S3
|
||||
env:
|
||||
BUCKET: ${{ inputs.bucket }}
|
||||
OUTPUT_FILE: ${{ inputs.output-name }}
|
||||
shell: bash
|
||||
run: | # sh
|
||||
DATE=$(date '+%Y-%m-%d')
|
||||
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
|
||||
|
||||
- name: Upload results to Trunk
|
||||
if: ${{ always() }}
|
||||
uses: trunk-io/analytics-uploader@main
|
||||
with:
|
||||
junit-paths: ${{ inputs.input-path }}
|
||||
org-slug: metabase
|
||||
token: ${{ inputs.trunk-api-token }}
|
||||
continue-on-error: true
|
||||
22
.github/actions/verify-membership/action.yml
vendored
22
.github/actions/verify-membership/action.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: 'Verify Organization Membership'
|
||||
description: 'Check if a user is a member of a GitHub organization'
|
||||
inputs:
|
||||
github_token:
|
||||
description: 'Token with access to the organization'
|
||||
required: true
|
||||
organization_name:
|
||||
description: 'Organization name'
|
||||
required: true
|
||||
username:
|
||||
description: 'Username'
|
||||
required: true
|
||||
outputs:
|
||||
is_member:
|
||||
value: ${{ steps.verify-membership.outputs.is_member }}
|
||||
description: "Is the user a member of the organization"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: bash ${{ github.action_path }}/verify_membership.sh ${{ inputs.github_token }} ${{ inputs.organization_name }} ${{ inputs.username }}
|
||||
id: verify-membership
|
||||
shell: bash
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
TOKEN=$1
|
||||
ORG_NAME=$2
|
||||
USERNAME=$3
|
||||
|
||||
response=$(curl -L -o /dev/null -w "%{http_code}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/orgs/$ORG_NAME/members/$USERNAME")
|
||||
|
||||
# Github returns an empty response with 204 status if user is a member and 404 status otherwise
|
||||
# See: https://docs.github.com/en/rest/orgs/members?apiVersion=2022-11-28#check-organization-membership-for-a-user
|
||||
if [ "$response" == "204" ]; then
|
||||
echo "The user $USERNAME is a member of $ORG_NAME"
|
||||
echo "is_member=true" >> $GITHUB_OUTPUT
|
||||
elif [ "$response" == "404" ]; then
|
||||
echo "The user $USERNAME is not a member of $ORG_NAME"
|
||||
echo "is_member=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Failed to determine the membership status for $USERNAME in $ORG_NAME"
|
||||
echo "is_member=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
17
.github/auto_request_review.yml
vendored
17
.github/auto_request_review.yml
vendored
@@ -1,17 +0,0 @@
|
||||
# The reviewers configuration for the auto_request_review workflow
|
||||
# See https://github.com/necojackarc/auto-request-review/tree/master?tab=readme-ov-file#reviewers-configuration
|
||||
# for the spec
|
||||
|
||||
files:
|
||||
# migrations related files
|
||||
'resources/migrations/**':
|
||||
- team:core-workflows
|
||||
'src/metabase/db/custom_migrations.clj':
|
||||
- team:core-workflows
|
||||
|
||||
options:
|
||||
ignore_draft: true
|
||||
ignored_keywords:
|
||||
- DO NOT REVIEW
|
||||
- WIP
|
||||
enable_group_assignment: false
|
||||
7
.github/codeql/codeql-config.yml
vendored
7
.github/codeql/codeql-config.yml
vendored
@@ -1,7 +0,0 @@
|
||||
name: "Metabase CodeQL config"
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "frontend/test/**"
|
||||
- "frontend/**/*.unit.*"
|
||||
- "e2e/**"
|
||||
155
.github/file-paths.yaml
vendored
155
.github/file-paths.yaml
vendored
@@ -1,155 +0,0 @@
|
||||
default: &default
|
||||
- "locales/**"
|
||||
- "bin/**/!(*.md)"
|
||||
|
||||
shared_sources: &shared_sources
|
||||
- "src/**/*.cljc"
|
||||
- "src/**/*.cljs"
|
||||
|
||||
shared_specs: &shared_specs
|
||||
- "test/**/*.cljc"
|
||||
- "test/**/*.cljs"
|
||||
|
||||
frontend_ci: &frontend_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/frontend.yml"
|
||||
|
||||
frontend_sources: &frontend_sources
|
||||
- *shared_sources
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "yarn.lock"
|
||||
- "**/tsconfig*.json"
|
||||
- "package.json"
|
||||
- "babel.config.json"
|
||||
- ".eslintrc.js"
|
||||
- "postcss.config.js"
|
||||
- "webpack.config.js"
|
||||
- "webpack.embedding-sdk.config.js"
|
||||
- "webpack.static-viz.config.js"
|
||||
- ".nvmrc"
|
||||
|
||||
frontend_specs: &frontend_specs
|
||||
- *shared_specs
|
||||
- "frontend/test/**"
|
||||
- "frontend/**/tests/**"
|
||||
- "frontend/**/*.unit.*"
|
||||
- "jest.config.js"
|
||||
- "jest.tz.unit.conf.json"
|
||||
|
||||
release_source: &release_sources
|
||||
- "release/**"
|
||||
|
||||
frontend_all: &frontend_all
|
||||
- *frontend_ci
|
||||
- *frontend_sources
|
||||
- *frontend_specs
|
||||
|
||||
frontend_loki_ci: &frontend_loki_ci
|
||||
- ".github/workflows/loki.yml"
|
||||
- ".loki/**"
|
||||
- ".storybook/**"
|
||||
|
||||
backend_ci: &backend_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/actions/test-driver/**"
|
||||
- ".github/workflows/backend.yml"
|
||||
- ".github/workflows/drivers.yml"
|
||||
|
||||
backend_presto_kerberos:
|
||||
- "**/presto_jdbc/**"
|
||||
- "**/presto_jdbc.clj"
|
||||
- ".github/workflows/presto-kerberos-integration-test.yml"
|
||||
|
||||
backend_sources: &backend_sources
|
||||
- *shared_sources
|
||||
- "**deps.edn"
|
||||
- "enterprise/backend/src/**"
|
||||
- "src/**"
|
||||
- "modules/drivers/{*,*/{*,!(test)/**}}"
|
||||
- "resources/**"
|
||||
- ".clj-kondo/**"
|
||||
|
||||
backend_specs: &backend_specs
|
||||
- *shared_specs
|
||||
- "test/**"
|
||||
- "enterprise/backend/test/**"
|
||||
- "test_modules/**"
|
||||
- "modules/drivers/*/test/**"
|
||||
- "test_config/**"
|
||||
- "dev/**"
|
||||
- "test_resources/**"
|
||||
|
||||
backend_all: &backend_all
|
||||
- *default
|
||||
- *backend_ci
|
||||
- *backend_sources
|
||||
- *backend_specs
|
||||
|
||||
sources: &sources
|
||||
- *shared_sources
|
||||
- *frontend_sources
|
||||
- *backend_sources
|
||||
|
||||
e2e_ci: &e2e_ci
|
||||
- ".github/actions/build-e2e-matrix/**"
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/actions/prepare-uberjar-artifact/**"
|
||||
- ".github/actions/e2e-prepare-containers/**"
|
||||
- ".github/actions/prepare-cypress/**"
|
||||
- ".github/actions/run-snowplow-micro/**"
|
||||
- ".github/workflows/e2e-tests.yml"
|
||||
|
||||
e2e_specs: &e2e_specs
|
||||
- "**/*.cy.*.js"
|
||||
- "**/*.cy.*.ts"
|
||||
- "e2e/runner/**"
|
||||
- "e2e/support/**"
|
||||
- "e2e/snapshot*/**"
|
||||
|
||||
e2e_cross_version: &e2e_cross_version
|
||||
- ".github/workflows/e2e-cross-version.yml"
|
||||
- "e2e/test/scenarios/cross-version/**"
|
||||
|
||||
e2e_all:
|
||||
- *default
|
||||
- *e2e_ci
|
||||
- *sources
|
||||
- *e2e_specs
|
||||
|
||||
snowplow:
|
||||
- ".github/workflows/snowplow.yml"
|
||||
- "snowplow/**"
|
||||
|
||||
documentation:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
|
||||
yaml:
|
||||
- "**/*.yml"
|
||||
- "**/*.yaml"
|
||||
|
||||
codeql:
|
||||
- "frontend/src/**"
|
||||
- "enterprise/frontend/src/**"
|
||||
|
||||
i18n:
|
||||
- *default
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/i18n.yml"
|
||||
- *sources
|
||||
|
||||
visualizations:
|
||||
- "frontend/src/metabase/visualizations/**"
|
||||
- "frontend/src/metabase/static-viz/**"
|
||||
- "src/metabase/pulse/**"
|
||||
|
||||
cljs:
|
||||
- *shared_sources
|
||||
- *shared_specs
|
||||
- ".github/workflows/cljs.yml"
|
||||
- "shadow-cljs.edn"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user