Compare commits
2 Commits
webpack-ch
...
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-field-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,8 +0,0 @@
|
||||
(ns macros.metabase.shared.util.namespaces)
|
||||
|
||||
(defmacro import-fn
|
||||
"Kondo macro replacement for [[metabase.shared.util.namespaces/import-fn]]."
|
||||
([sym]
|
||||
`(def ~(-> sym name symbol) "docstring" ~sym))
|
||||
([sym name]
|
||||
`(def ~name "docstring" ~sym)))
|
||||
@@ -1,12 +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 'arch) 5]
|
||||
~@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,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,13 +0,0 @@
|
||||
(ns macros.metabase-enterprise.query-field-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 'qf-1) 10
|
||||
~(macros.common/ignore-unused 'qf-1b) 15
|
||||
~(macros.common/ignore-unused 'qf-2) 20
|
||||
~(macros.common/ignore-unused 'qf-3) 30]
|
||||
~@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.set :as set]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- node->qualified-symbol [node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(let [resolved (hooks/resolve {:name sexpr})]
|
||||
(when-not (= :clj-kondo/unknown-namespace (:ns resolved))
|
||||
(symbol (name (:ns resolved)) (name (:name resolved))))))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(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.revision/revert!
|
||||
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-notifications!
|
||||
metabase.pulse/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- non-thread-safe-form-should-end-with-exclamation*
|
||||
[{[defn-or-defmacro form-name] :children, :as node}]
|
||||
(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)))]
|
||||
(walk (fn [form]
|
||||
(when-let [qualified-symbol (node->qualified-symbol form)]
|
||||
(when (and (not (contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol))
|
||||
(end-with-exclamation? 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`."
|
||||
(:string-value defn-or-defmacro) qualified-symbol)
|
||||
:type :metabase/test-helpers-use-non-thread-safe-functions)))))
|
||||
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]}]
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(non-thread-safe-form-should-end-with-exclamation* node))
|
||||
{: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,241 +0,0 @@
|
||||
(ns hooks.clojure.test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(def ^:private disallowed-parallel-forms
|
||||
"Things you should not be allowed to use inside parallel tests. Besides these, anything ending in `!` not whitelisted
|
||||
in [[allowed-parallel-forms]] is not allowed."
|
||||
'#{clojure.core/alter-var-root
|
||||
clojure.core/with-redefs
|
||||
clojure.core/with-redefs-fn
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user!
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes
|
||||
metabase-enterprise.test/with-gtaps!
|
||||
metabase-enterprise.test/with-gtaps-for-user!
|
||||
metabase-enterprise.test/with-user-attributes
|
||||
metabase.actions.test-util/with-actions
|
||||
metabase.actions.test-util/with-actions-disabled
|
||||
metabase.actions.test-util/with-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data-tables
|
||||
metabase.analytics.snowplow-test/with-fake-snowplow-collector
|
||||
metabase.email-test/with-expected-messages
|
||||
metabase.email-test/with-fake-inbox
|
||||
metabase.test.data.users/with-group
|
||||
metabase.test.data.users/with-group-for-user
|
||||
metabase.test.persistence/with-persistence-enabled
|
||||
metabase.test.util.log/with-log-level
|
||||
metabase.test.util.log/with-log-messages-for-level
|
||||
metabase.test.util.misc/with-single-admin-user
|
||||
metabase.test.util/with-all-users-permission
|
||||
metabase.test.util/with-discarded-collections-perms-changes
|
||||
metabase.test.util/with-env-keys-renamed-by
|
||||
metabase.test.util/with-locale
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test.util/with-temp-env-var-value
|
||||
metabase.test.util/with-temp-vals-in-db
|
||||
metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test.util/with-temporary-setting-values
|
||||
metabase.test.util/with-user-in-groups
|
||||
metabase.test/with-actions
|
||||
metabase.test/with-actions-disabled
|
||||
metabase.test/with-actions-enabled
|
||||
metabase.test/with-actions-test-data
|
||||
metabase.test/with-actions-test-data-and-actions-enabled
|
||||
metabase.test/with-actions-test-data-tables
|
||||
metabase.test/with-all-users-permission
|
||||
metabase.test/with-discarded-collections-perms-changes
|
||||
metabase.test/with-env-keys-renamed-by
|
||||
metabase.test/with-expected-messages
|
||||
metabase.test/with-fake-inbox
|
||||
metabase.test/with-group
|
||||
metabase.test/with-group-for-user
|
||||
metabase.test/with-locale
|
||||
metabase.test/with-log-level
|
||||
metabase.test/with-log-messages-for-level
|
||||
metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test/with-persistence-enabled
|
||||
metabase.test/with-single-admin-user
|
||||
metabase.test/with-temp-env-var-value
|
||||
metabase.test/with-temp-vals-in-db
|
||||
metabase.test/with-temporary-raw-setting-values
|
||||
metabase.test/with-temporary-setting-values
|
||||
metabase.test/with-user-in-groups})
|
||||
|
||||
;;; TODO -- we should disallow `metabase.test/user-http-request` with any method other than `:get`
|
||||
|
||||
(def ^:private allowed-parallel-forms
|
||||
"These fns are destructive, but are probably fine inside ^:parallel tests because it usually means you're doing
|
||||
something to an atom or something like that."
|
||||
'#{clojure.core/assoc!
|
||||
clojure.core/compare-and-set!
|
||||
clojure.core/conj!
|
||||
clojure.core/disj!
|
||||
clojure.core/dissoc!
|
||||
clojure.core/persistent!
|
||||
clojure.core/pop!
|
||||
clojure.core/reset!
|
||||
clojure.core/reset-vals!
|
||||
clojure.core/run!
|
||||
clojure.core/swap!
|
||||
clojure.core/swap-vals!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.core.async/<!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alt!
|
||||
clojure.core.async/alt!!
|
||||
clojure.core.async/alts!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/ioc-alts!
|
||||
clojure.core.async/offer!
|
||||
clojure.core.async/onto-chan!
|
||||
clojure.core.async/onto-chan!!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.async/put!
|
||||
clojure.core.async/take!
|
||||
clojure.core.async/to-chan!
|
||||
clojure.core.async/to-chan!!
|
||||
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
|
||||
metabase.pulse/send-pulse!
|
||||
metabase.query-processor.store/store-database!
|
||||
next.jdbc/execute!})
|
||||
|
||||
(defn- warn-about-disallowed-parallel-forms [form]
|
||||
(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
|
||||
(disallowed-parallel-forms qualified-symbol)
|
||||
(error! form (format "%s is not allowed inside a ^:parallel test or test fixture" qualified-symbol))
|
||||
|
||||
(and (not (allowed-parallel-forms 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 .clj-kondo/hooks/clojure/test.clj"
|
||||
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}]
|
||||
(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` 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)))))
|
||||
|
||||
(def ^:private number-of-lines-for-a-test-to-be-considered-horrifically-long
|
||||
200)
|
||||
|
||||
(defn- deftest-check-not-horrifically-long
|
||||
[node]
|
||||
(let [{:keys [row end-row]} (meta node)]
|
||||
(when (and row end-row)
|
||||
(let [num-lines (- end-row row)]
|
||||
(when (>= num-lines number-of-lines-for-a-test-to-be-considered-horrifically-long)
|
||||
(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 deftest [{:keys [node cljc lang]}]
|
||||
;; 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))
|
||||
(deftest-check-not-horrifically-long node)
|
||||
{: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- warn-about-schema= [{[_is assertion-node] :children, :as _is-node}]
|
||||
(let [{[assertion-symb-node] :children} assertion-node]
|
||||
(when (and (hooks/token-node? assertion-symb-node)
|
||||
(= (hooks/sexpr assertion-symb-node) 'schema=))
|
||||
(hooks/reg-finding! (assoc (meta assertion-symb-node)
|
||||
:message "Use =? or malli= instead of schema="
|
||||
:type :metabase/warn-about-schema=)))))
|
||||
|
||||
(defn is [{:keys [node lang]}]
|
||||
(when (= lang :cljs)
|
||||
(warn-about-missing-test-expr-requires-in-cljs node))
|
||||
(warn-about-schema= node)
|
||||
{:node node})
|
||||
|
||||
(defn use-fixtures [{:keys [node]}]
|
||||
(warn-about-disallowed-parallel-forms node)
|
||||
{:node node})
|
||||
@@ -1,409 +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)
|
||||
|
||||
(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 node->qualified-symbol [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"))
|
||||
@@ -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-testing-model)
|
||||
query-info
|
||||
(hooks/list-node
|
||||
(list* (hooks/token-node 'fn)
|
||||
(hooks/vector-node [binding])
|
||||
body))])]
|
||||
#_(println (hooks/sexpr node*))
|
||||
{:node node*}))
|
||||
@@ -1,45 +0,0 @@
|
||||
(ns hooks.metabase.api.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(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))))
|
||||
(-> (api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/defn)
|
||||
(api/token-node (route-fn-name (api/sexpr method) (api/sexpr route)))
|
||||
body))
|
||||
(with-meta (meta node)))))))]
|
||||
(update arg :node update-defendpoint)))
|
||||
|
||||
(comment
|
||||
(-> {: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)}
|
||||
defendpoint
|
||||
:node
|
||||
api/sexpr))
|
||||
@@ -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,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,18 +0,0 @@
|
||||
(ns hooks.metabase.models.interface
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(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 (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))]
|
||||
{:node node}))
|
||||
@@ -1,264 +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
|
||||
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 (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))]
|
||||
|
||||
(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,63 +0,0 @@
|
||||
(ns hooks.metabase.public-settings.premium-features
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(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 (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))))}))
|
||||
|
||||
(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,152 +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- 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]]."
|
||||
[dataset]
|
||||
(let [sexpr (hooks/sexpr dataset)
|
||||
global-defs (global-dataset-symbols)]
|
||||
(cond
|
||||
(not (symbol? sexpr))
|
||||
:non-symbol
|
||||
|
||||
(namespace sexpr)
|
||||
:qualified
|
||||
|
||||
(empty? global-defs)
|
||||
:unqualified/unknown
|
||||
|
||||
(contains? global-defs sexpr)
|
||||
:unqualified/from-dataset-defs-namespace
|
||||
|
||||
;; either something defined in the current namespace or let-bound in the current scope.
|
||||
:else
|
||||
:unqualified/local-def)))
|
||||
|
||||
(defn dataset
|
||||
[{{[_ dataset & body] :children} :node}]
|
||||
(let [noop (constantly nil)
|
||||
body (case (dataset-type 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,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,126 +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,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,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,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)))))
|
||||
31
.cljfmt.edn
31
.cljfmt.edn
@@ -1,31 +0,0 @@
|
||||
;; https://github.com/weavejester/cljfmt/blob/master/cljfmt/resources/cljfmt/indents/clojure.clj
|
||||
{:extra-indents
|
||||
{auto-parse [[:inner 0]]
|
||||
cond-> [[:inner 0]]
|
||||
check [[:inner 0]]
|
||||
deferred-tru [[:inner 0]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
let-404 [[:block 1]]
|
||||
metabase.legacy-mbql.util/match-one [[:inner 0]]
|
||||
merge-with [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/formatted-rows [[:inner 0]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/with-temporary-setting-values [[:block 1]]
|
||||
;; If we just treat these as functions we can ignore this, despite some files not being consistent
|
||||
;metabase.models.permissions/set-has-full-permissions-for-set? [[:inner 0]]
|
||||
;metabase.models.permissions/set-has-full-permissions? [[:inner 0]]
|
||||
;metabase.models.permissions/set-has-partial-permissions? [[:inner 0]]
|
||||
metabase.models.permissions/set-has-partial-permissions-for-set? [[:inner 0]]
|
||||
publish-event! [[:block 1]]
|
||||
metabase.query-processor.streaming/streaming-response [[:inner 0]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/select-keys-when [[:inner 0]]
|
||||
;; This supports a lot of existing formatting, but these are functions so the
|
||||
;; existing formatting is probably wrong.
|
||||
;toucan2.core/select [[:inner 0]]
|
||||
;toucan2.core/update! [[:inner 0]]
|
||||
}
|
||||
:sort-ns-references? true}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,78 +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))
|
||||
;; 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 '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 'lib.schema.match/match 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match-one 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace-in 2))
|
||||
(eval . (put-clojure-indent 'lib.util.match/replace 1))
|
||||
(eval . (put-clojure-indent 'macros/case 0))
|
||||
(eval . (put-clojure-indent 'match 1))
|
||||
(eval . (put-clojure-indent 'mi/define-methods '(:form)))
|
||||
(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-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 '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 1))
|
||||
(eval . (put-clojure-indent 'with-meta '(:form)))
|
||||
(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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
230
.eslintrc.js
230
.eslintrc.js
@@ -1,230 +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,
|
||||
},
|
||||
],
|
||||
"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-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",
|
||||
],
|
||||
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",
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
],
|
||||
plugins: ["jest", "jest-dom", "testing-library"],
|
||||
files: [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
"*.unit.spec.js",
|
||||
"*.unit.spec.jsx",
|
||||
],
|
||||
rules: {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
14
.github/CODEOWNERS
vendored
14
.github/CODEOWNERS
vendored
@@ -1,14 +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
|
||||
# TL;DR: if @camsaul hasn't been involved you're probably doing it wrong
|
||||
|
||||
src/metabase @camsaul
|
||||
modules @camsaul
|
||||
enterprise/backend @camsaul
|
||||
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
|
||||
src/metabase/**/*permissions* @noahmoss
|
||||
src/metabase/integrations @noahmoss
|
||||
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.
|
||||
94
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
94
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,94 +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: |
|
||||
- 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)
|
||||
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.
|
||||
24
.github/ISSUE_TEMPLATE/epic.md
vendored
24
.github/ISSUE_TEMPLATE/epic.md
vendored
@@ -1,24 +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_
|
||||
- eng doc: _link to technical design doc, if any_
|
||||
- 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: _related issues if any_
|
||||
|
||||
**Implementation Plan**
|
||||
|
||||
|
||||
***Milestone 1***
|
||||
_insert tasklist here_
|
||||
|
||||
***Milestone 2***
|
||||
|
||||
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).
|
||||
|
||||
69
.github/actions/build-e2e-matrix/action.yml
vendored
69
.github/actions/build-e2e-matrix/action.yml
vendored
@@ -1,69 +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 = 11;
|
||||
|
||||
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", {} ],
|
||||
["embedding", {} ],
|
||||
["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;
|
||||
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,87 +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'
|
||||
|
||||
|
||||
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
|
||||
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,
|
||||
|
||||
11
.github/actions/prepare-backend/action.yml
vendored
11
.github/actions/prepare-backend/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
||||
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') }}
|
||||
|
||||
24
.github/actions/prepare-frontend/action.yml
vendored
24
.github/actions/prepare-frontend/action.yml
vendored
@@ -1,33 +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
|
||||
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') }}
|
||||
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
|
||||
|
||||
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-${{ inputs.junit-name }}
|
||||
- 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-backend-components
|
||||
'src/metabase/db/custom_migrations.clj':
|
||||
- team:core-backend-components
|
||||
|
||||
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/**"
|
||||
153
.github/file-paths.yaml
vendored
153
.github/file-paths.yaml
vendored
@@ -1,153 +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.json"
|
||||
- "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"
|
||||
|
||||
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/**"
|
||||
|
||||
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"
|
||||
@@ -3,16 +3,15 @@
|
||||
set -eo pipefail
|
||||
|
||||
# Need Java commands on $PATH, which apparently is not yet the case
|
||||
export JAVA_HOME="/usr/lib/jvm/msopenjdk-current"
|
||||
export PATH="$PATH:$JAVA_HOME/bin"
|
||||
|
||||
# ensure java commmand is available
|
||||
which java
|
||||
|
||||
# install clojure version needed for Metabase
|
||||
curl -O https://download.clojure.org/install/linux-install-1.11.0.1100.sh
|
||||
chmod +x linux-install-1.11.0.1100.sh
|
||||
./linux-install-1.11.0.1100.sh
|
||||
curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh
|
||||
chmod +x linux-install-1.10.3.933.sh
|
||||
./linux-install-1.10.3.933.sh
|
||||
|
||||
RESOURCES_DIR=/app/source/resources
|
||||
|
||||
@@ -46,20 +45,20 @@ openssl x509 -outform der -in $RESOURCES_DIR/presto-ssl-root-ca.pem -out $RESOU
|
||||
keytool -noprompt -import -alias presto-kerberos -keystore $RESOURCES_DIR/cacerts-with-presto-ca.jks \
|
||||
-storepass changeit -file $RESOURCES_DIR/presto-ssl-root-ca.der -trustcacerts
|
||||
|
||||
ADDITIONAL_OPTS="SSLKeyStorePath=$RESOURCES_DIR/ssl_keystore.jks&SSLKeyStorePassword=presto\
|
||||
&SSLTrustStorePath=$RESOURCES_DIR/cacerts-with-presto-ca.jks&SSLTrustStorePassword=changeit"
|
||||
|
||||
# Set up the environment variables pointing to all of this, and run some tests
|
||||
DRIVERS=presto-jdbc \
|
||||
MB_ENABLE_PRESTO_JDBC_DRIVER=true \
|
||||
MB_PRESTO_JDBC_TEST_HOST=presto-kerberos \
|
||||
MB_PRESTO_JDBC_TEST_PORT=7778 \
|
||||
MB_PRESTO_JDBC_TEST_SSL=true \
|
||||
MB_PRESTO_JDBC_TEST_SSL_KEYSTORE_PATH=$RESOURCES_DIR/ssl_keystore.jks \
|
||||
MB_PRESTO_JDBC_TEST_SSL_KEYSTORE_PASSWORD=presto \
|
||||
MB_PRESTO_JDBC_TEST_SSL_TRUSTSTORE_PATH=$RESOURCES_DIR/cacerts-with-presto-ca.jks \
|
||||
MB_PRESTO_JDBC_TEST_SSL_TRUSTSTORE_PASSWORD=changeit \
|
||||
MB_PRESTO_JDBC_TEST_KERBEROS=true \
|
||||
MB_PRESTO_JDBC_TEST_USER=bob@EXAMPLE.COM \
|
||||
MB_PRESTO_JDBC_TEST_KERBEROS_PRINCIPAL=bob@EXAMPLE.COM \
|
||||
MB_PRESTO_JDBC_TEST_KERBEROS_REMOTE_SERVICE_NAME=HTTP \
|
||||
MB_PRESTO_JDBC_TEST_KERBEROS_KEYTAB_PATH=$RESOURCES_DIR/client.keytab \
|
||||
MB_PRESTO_JDBC_TEST_KERBEROS_CONFIG_PATH=$RESOURCES_DIR/krb5.conf \
|
||||
MB_PRESTO_JDBC_TEST_ADDITIONAL_OPTIONS=$ADDITIONAL_OPTS \
|
||||
clojure -X:dev:test:drivers:drivers-dev :only metabase.driver.presto-jdbc-test
|
||||
|
||||
15
.github/scripts/write-poms.sh
vendored
15
.github/scripts/write-poms.sh
vendored
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function pom() {
|
||||
path=$1
|
||||
echo "Writing ${path}/pom.xml"
|
||||
(cd "$path" && clojure -X:deps mvn-pom)
|
||||
}
|
||||
|
||||
pom .
|
||||
|
||||
for k in $(ls modules/drivers/); do
|
||||
test -d "modules/drivers/$k" && pom "modules/drivers/$k"
|
||||
done
|
||||
22
.github/stale.yml
vendored
Normal file
22
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# For now we want to only check for stale pull requests
|
||||
only: pulls
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Override the default Label of "wontfix"
|
||||
staleLabel: ""
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue was automatically marked as stale due to a lack of recent activity.
|
||||
|
||||
This isn't a criticism of the PR, just an attempt to keep the PR queue in a happier state.
|
||||
If you'd like to bring this PR back to the attention of the folks on the Metabase team, just comment
|
||||
to prevent it from being closed.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
69
.github/team.json
vendored
69
.github/team.json
vendored
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"teams": [
|
||||
{
|
||||
"name": "Querying Components",
|
||||
"label": ".Team/QueryingComponents",
|
||||
"members": [
|
||||
"ranquild",
|
||||
"kamilmielnik",
|
||||
"nemanjaglumac",
|
||||
"romeovs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Embedding",
|
||||
"label": ".Team/Embedding",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/88/views/1",
|
||||
"members": ["WiNloSt", "deniskaber", "oisincoveney", "npretto", "heypoom"]
|
||||
},
|
||||
{
|
||||
"name": "BEC",
|
||||
"label": ".Team/BackendComponents",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/50/views/1",
|
||||
"members": [
|
||||
"tsmacdonald",
|
||||
"qnkhuat",
|
||||
"calherries",
|
||||
"piranha",
|
||||
"crisptrutski"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Query Processor",
|
||||
"label": ".Team/QueryProcessor",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/83/views/8",
|
||||
"members": ["camsaul", "snoe", "bshepherdson", "metamben", "lbrdnk"]
|
||||
},
|
||||
{
|
||||
"name": "Admin Webapp",
|
||||
"label": ".Team/AdminWebapp",
|
||||
"members": [
|
||||
"noahmoss",
|
||||
"escherize",
|
||||
"npfitz",
|
||||
"johnswanson",
|
||||
"rafpaf",
|
||||
"sloansparger",
|
||||
"iethree"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DashViz",
|
||||
"label": ".Team/DashViz",
|
||||
"members": [
|
||||
"alxnddr",
|
||||
"EmmadUsmani",
|
||||
"JesseSDevaney",
|
||||
"kulyk",
|
||||
"adam-james-v"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DevEx",
|
||||
"label": ".Team/DevEx",
|
||||
"members": [
|
||||
"uladzimirdev"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
74
.github/workflows/auto-assign.yml
vendored
74
.github/workflows/auto-assign.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: Auto assign pull requests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
update_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Assign author and their team. Returns true if team exists.
|
||||
id: auto-assign
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const teamConfig = JSON.parse(fs.readFileSync('.github/team.json', 'utf-8'));
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
if (prAuthor.endsWith('[bot]')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign PR author
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
assignees: [prAuthor]
|
||||
});
|
||||
|
||||
const team = teamConfig.teams.find(t => t.members.includes(prAuthor));
|
||||
if (!team) {
|
||||
console.log('You are not assigned to any team. If you need one, update .github/team.json');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const label = await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: team.label
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`The label ${team.label} does not exist, create it first`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add team label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: [team.label]
|
||||
});
|
||||
|
||||
return team.projectUrl || null;
|
||||
- uses: actions/add-to-project@v0.5.0
|
||||
if: ${{ steps.auto-assign.outputs.result != 'null' }}
|
||||
with:
|
||||
project-url: ${{ steps.auto-assign.outputs.result }}
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
71
.github/workflows/auto-backport.yml
vendored
71
.github/workflows/auto-backport.yml
vendored
@@ -5,13 +5,13 @@ on:
|
||||
|
||||
jobs:
|
||||
create-backport:
|
||||
if: github.event.pull_request.merged == true && (contains(github.event.pull_request.labels.*.name, 'backport') && github.event.action == 'closed' || github.event.label.name == 'backport')
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.merged == true && (contains(github.event.pull_request.labels.*.name, 'backport') || github.event.label.name == 'backport')
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/find-squashed-commit
|
||||
name: Find commit
|
||||
id: find_commit
|
||||
@@ -24,14 +24,12 @@ jobs:
|
||||
id: get_latest_release_branch
|
||||
|
||||
- id: create_backport_pull_request
|
||||
name: Create backport pull request
|
||||
run: |
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
git config --global user.email "metabase-github-automation@metabase.com"
|
||||
git config --global user.name "$GITHUB_ACTOR"
|
||||
|
||||
BACKPORT_BRANCH="backport-$COMMIT"
|
||||
|
||||
# Checkout the target branch and create a new backport branch
|
||||
git checkout ${TARGET_BRANCH}
|
||||
git fetch --all
|
||||
git checkout -b ${BACKPORT_BRANCH}
|
||||
@@ -39,71 +37,34 @@ jobs:
|
||||
|
||||
CONFLICTS=$(git ls-files -u | wc -l)
|
||||
if [ "$CONFLICTS" -gt 0 ]; then
|
||||
git cherry-pick --abort
|
||||
|
||||
echo "Could not cherry pick because of a conflict"
|
||||
echo "has-conflicts=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Add a shell script for resolving conflicts
|
||||
echo "git reset HEAD~1" > ./backport.sh
|
||||
echo "rm ./backport.sh" >> ./backport.sh
|
||||
echo "git cherry-pick ${COMMIT}" >> ./backport.sh
|
||||
echo "echo 'Resolve conflicts and force push this branch'" >> ./backport.sh
|
||||
chmod +x ./backport.sh
|
||||
git add ./backport.sh
|
||||
git commit -m "Add backport resolution script"
|
||||
|
||||
PR_BODY=$(cat <<-END
|
||||
#${ORIGINAL_PULL_REQUEST_NUMBER}
|
||||
> [!IMPORTANT]
|
||||
> Manual conflict resolution is required.
|
||||
Checkout the branch and run \`./backport.sh\` script. Force push your changes after cherry-picking.
|
||||
END
|
||||
)
|
||||
else
|
||||
echo "has-conflicts=false" >> $GITHUB_OUTPUT
|
||||
PR_BODY="#${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
echo "::set-output name=has-conflicts::true"
|
||||
git cherry-pick --abort
|
||||
git checkout master
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git checkout master
|
||||
git push -u origin ${BACKPORT_BRANCH}
|
||||
|
||||
BACKPORT_PR_URL=$(gh pr create --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" --label "was-backported" --assignee "${GITHUB_ACTOR}" --title "🤖 backported \"${ORIGINAL_TITLE}\"" --body "${PR_BODY}")
|
||||
BACKPORT_PR_NUMBER=${BACKPORT_PR_URL##*/}
|
||||
hub pull-request -b "${TARGET_BRANCH}" -h "${BACKPORT_BRANCH}" -l "auto-backported" -a "${GITHUB_ACTOR}" -F- <<<"🤖 backported \"${ORIGINAL_TITLE}\"
|
||||
|
||||
echo "backport_pr_number=$BACKPORT_PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "New PR has been created"
|
||||
|
||||
git checkout master
|
||||
#${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
env:
|
||||
TARGET_BRANCH: ${{ steps.get_latest_release_branch.outputs.branch-name }}
|
||||
ORIGINAL_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
|
||||
ORIGINAL_TITLE: ${{ github.event.pull_request.title }}
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT: ${{ steps.find_commit.outputs.commit }}
|
||||
|
||||
- name: Auto approve backport PR
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
uses: juliangruber/approve-pull-request-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
|
||||
- name: Enable Pull Request Automerge
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
uses: peter-evans/enable-pull-request-automerge@v2
|
||||
with:
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
pull-request-number: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
merge-method: squash
|
||||
|
||||
- uses: ./.github/actions/notify-pull-request
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'true' }}
|
||||
with:
|
||||
include-log: false
|
||||
message: Manual conflict resolution is required
|
||||
message: could not create a backport due to conflicts
|
||||
|
||||
- uses: ./.github/actions/notify-pull-request
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
include-log: true
|
||||
message: Something went wrong while creating a backport
|
||||
message: something went wrong while creating a backport
|
||||
|
||||
21
.github/workflows/auto-request-review.yml
vendored
21
.github/workflows/auto-request-review.yml
vendored
@@ -1,21 +0,0 @@
|
||||
# Config is in .github/auto_request_review.yml
|
||||
name: Auto Request Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
auto-request-review:
|
||||
# Don't run on forked repos
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
name: Auto Request Review
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request review based on files changes and/or groups the author belongs to
|
||||
uses: necojackarc/auto-request-review@v0.13.0
|
||||
with:
|
||||
token: '${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}'
|
||||
27
.github/workflows/backend-cloverage.yml
vendored
27
.github/workflows/backend-cloverage.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Backend-Cloverage
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1-5' # every weekday at 5am US eastern time
|
||||
|
||||
jobs:
|
||||
be-linter-cloverage:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cloverage"
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
- name: Collect the test coverage
|
||||
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
274
.github/workflows/backend.yml
vendored
274
.github/workflows/backend.yml
vendored
@@ -3,268 +3,40 @@ name: Backend
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release-**"
|
||||
- 'master'
|
||||
- 'release-**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review, labeled]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CLJ_KONDO_VERSION: "2023.09.07"
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
backend_all: ${{ steps.changes.outputs.backend_all }}
|
||||
frontend_sources: ${{ steps.changes.outputs.frontend_sources }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
static-viz-files-changed:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.backend_all != 'true' && needs.files-changed.outputs.frontend_sources == 'true'
|
||||
name: Check whether static-viz files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
static_viz: ${{ steps.static_viz.outputs.static_viz }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "files-changed"
|
||||
- name: Build static-viz frontend
|
||||
run: yarn build-static-viz
|
||||
- name: Upload Static Viz Bundle Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-viz-bundle-${{ github.sha }}
|
||||
path: resources/frontend_client/app/dist
|
||||
|
||||
- name: Check for static viz changes
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: static_viz
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/static-viz-sources.yaml
|
||||
|
||||
be-linter-clj-kondo:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
DOWNLOAD_URL: https://github.com/clj-kondo/clj-kondo/releases/download
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'kondo'
|
||||
- name: Install clj-kondo
|
||||
run: |
|
||||
curl -OL ${DOWNLOAD_URL}/v${CLJ_KONDO_VERSION}/clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip
|
||||
curl -OL ${DOWNLOAD_URL}/v${CLJ_KONDO_VERSION}/clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip.sha256
|
||||
cat clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip.sha256 >> SHA256sum.txt
|
||||
echo " clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip" >> SHA256sum.txt
|
||||
sha256sum -c SHA256sum.txt
|
||||
unzip -d /usr/local/bin clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip
|
||||
- run: clj-kondo --version
|
||||
- name: Run clj-kondo
|
||||
run: ./bin/kondo.sh
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run clj-kondo
|
||||
run: docker run -v $PWD:/work --rm cljkondo/clj-kondo clj-kondo --config /work/lint-config.edn --lint /work/src /work/enterprise/backend/src /work/backend/mbql/src /work/shared/src
|
||||
|
||||
be-linter-eastwood:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "eastwood"
|
||||
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:eastwood
|
||||
name: Run Eastwood linter
|
||||
- uses: actions/checkout@v3
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'eastwood'
|
||||
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:eastwood
|
||||
name: Run Eastwood linter
|
||||
|
||||
# Because it's not possible to conditionally run only `java-11-ee` test in the draft mode,
|
||||
# we have to extract that job manually here. Backend developers have requested that this
|
||||
# test runs at all times to give them an early warning sign is something is broken.
|
||||
be-tests-java-11-ee-pre-check:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == true &&
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-tests-java-11-ee-pre-check
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: java-11-ee-pre-check
|
||||
|
||||
- run: yarn install --frozen-lockfile --prefer-offline
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
|
||||
- name: Run tests
|
||||
run: clojure -X:dev:ci:test:ee:ee-dev
|
||||
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
if: failure()
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
name: JUnit Test Report be-tests-java-11-ee-pre-check
|
||||
reporter: java-junit
|
||||
list-suites: failed
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
|
||||
be-tests:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-tests-java-${{ matrix.java-version }}-${{ matrix.edition }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
edition: [oss, ee]
|
||||
java-version: [11, 17, 21]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: be-tests
|
||||
# Depending on which files changed, we might either have to build static-viz
|
||||
# from scratch or download the previously built artifact
|
||||
- name: Build static-viz frontend
|
||||
if: needs.files-changed.outputs.backend_all == 'true'
|
||||
run: yarn build-static-viz
|
||||
- name: Download Static Viz Bundle Artifact
|
||||
if: needs.static-viz-files-changed.outputs.static_viz == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: static-viz-bundle-${{ github.sha }}
|
||||
path: resources/frontend_client/app/dist
|
||||
|
||||
- name: Run tests
|
||||
id: run-java-tests
|
||||
if: matrix.java-version != 21
|
||||
run: clojure -X:dev:ci:test:${{ matrix.edition }}:${{ matrix.edition }}-dev
|
||||
|
||||
- name: If using Java 21, only run tests on `master`
|
||||
id: run-java-tests-21
|
||||
if: matrix.java-version == 21 && github.ref_name == 'master'
|
||||
run: clojure -X:dev:ci:test:${{ matrix.edition }}:${{ matrix.edition }}-dev
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always() && (steps.run-java-tests.conclusion != 'skipped' || steps.run-java-tests-21.conclusion != 'skipped')
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: be-tests-java-${{ matrix.java-version }}-${{ matrix.edition }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
- name: Publish Test Report (JUnit)
|
||||
uses: dorny/test-reporter@v1
|
||||
if: failure()
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
name: JUnit Test Report be-tests-java-${{ matrix.java-version }}-${{ matrix.edition }}
|
||||
reporter: java-junit
|
||||
list-suites: failed
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
|
||||
be-tests-stub:
|
||||
needs: [be-tests]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false && needs.be-tests.result == 'skipped'
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-tests-java-${{ matrix.java-version }}-${{ matrix.edition }}
|
||||
timeout-minutes: 5
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
edition: [oss, ee]
|
||||
java-version: [11, 17, 21]
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
|
||||
# checks that all the namespaces we actually ship can be compiled, without any dependencies that we don't ship (such
|
||||
# as `:dev` dependencies). See #27009 for more context.
|
||||
be-check:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-check-java-${{ matrix.java-version }}
|
||||
be-linter-namespace-decls:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [11, 17, 21]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare backend
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: be-check
|
||||
- name: Check namespaces
|
||||
run: clojure -M:ee:drivers:check
|
||||
|
||||
be-check-stub:
|
||||
needs: [files-changed, static-viz-files-changed, be-check]
|
||||
if: |
|
||||
always() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
needs.be-check.result == 'skipped'
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-check-java-${{ matrix.java-version }}
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [11, 17, 21]
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'namespace-decls'
|
||||
- run: clojure -X:dev:ee:ee-dev:drivers:drivers-dev:test:namespace-checker
|
||||
name: Check ns forms
|
||||
|
||||
18
.github/workflows/backport-reminder.yml
vendored
18
.github/workflows/backport-reminder.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Backport Reminder
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [auto_merge_enabled, review_requested, labeled, unlabeled, synchronize]
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
backport-reminder:
|
||||
name: Decide whether to backport or not
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
valid-labels: 'backport, no-backport'
|
||||
disable-reviews: true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user