Compare commits
2 Commits
python-run
...
poc-reacti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83645a4ceb | ||
|
|
904f74a21a |
@@ -1 +0,0 @@
|
||||
Fix flakey test $ARGUMENTS
|
||||
@@ -1 +0,0 @@
|
||||
Fix issue $ARGUMENTS
|
||||
@@ -1 +0,0 @@
|
||||
Fetch unresolved comments for this branch's PR, then fix them
|
||||
@@ -1 +0,0 @@
|
||||
Repro issue $ARGUMENTS in a failing test
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,7 @@
|
||||
toucan2.core/insert-returning-pk!
|
||||
toucan2.core/insert-returning-pks!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/<!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/>!
|
||||
clojure.core.async/alts!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/poll!
|
||||
@@ -52,43 +49,36 @@
|
||||
metabase.actions.models/insert!
|
||||
metabase.analytics.snowplow-test/fake-track-event-impl!
|
||||
metabase.analytics.snowplow/track-event-impl!
|
||||
metabase.app-db.core/finish-db-setup!
|
||||
metabase.app-db.core/migrate!
|
||||
metabase.app-db.core/setup-db!
|
||||
metabase.app-db.schema-migrations-test.impl/run-migrations-in-range!
|
||||
metabase.app-db.setup/migrate!
|
||||
metabase.app-db.setup/setup-db!
|
||||
metabase.channel.email-test/reset-inbox!
|
||||
metabase.channel.email/send-email!
|
||||
metabase.cmd.dump-to-h2/dump-to-h2!
|
||||
metabase.cmd.load-from-h2/load-from-h2!
|
||||
metabase.content-verification.models.moderation-review/create-review!
|
||||
metabase.core.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.indexed-entities.models.model-index/add-values!
|
||||
metabase.driver.sql-jdbc.connection.ssh-tunnel-test/start-mock-servers!
|
||||
metabase.driver.sql-jdbc.connection.ssh-tunnel-test/stop-mock-servers!
|
||||
metabase.indexed-entities.task.index-values/job-init!
|
||||
metabase.model-persistence.models.persisted-info/ready-database!
|
||||
metabase.model-persistence.task.persist-refresh/job-init!
|
||||
metabase.model-persistence.task.persist-refresh/refresh-tables!
|
||||
metabase.model-persistence.task.persist-refresh/schedule-persistence-for-database!
|
||||
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.moderation-review/create-review!
|
||||
metabase.models.on-demand-test/add-dashcard-with-parameter-mapping!
|
||||
metabase.permissions.core/grant-application-permissions!
|
||||
metabase.permissions.core/grant-collection-read-permissions!
|
||||
metabase.permissions.core/grant-collection-readwrite-permissions!
|
||||
metabase.permissions.core/grant-full-data-permissions!
|
||||
metabase.permissions.core/grant-native-readwrite-permissions!
|
||||
metabase.permissions.core/grant-permissions!
|
||||
metabase.permissions.core/revoke-application-permissions!
|
||||
metabase.permissions.core/revoke-data-perms!
|
||||
metabase.permissions.core/update-data-perms-graph!
|
||||
metabase.permissions.core/update-group-permissions!
|
||||
metabase.permissions.models.collection.graph-test/clear-graph-revisions!
|
||||
metabase.permissions.models.collection.graph-test/do-with-n-temp-users-with-personal-collections!
|
||||
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.models.permissions/grant-application-permissions!
|
||||
metabase.permissions.models.permissions/grant-collection-read-permissions!
|
||||
metabase.permissions.models.permissions/grant-collection-readwrite-permissions!
|
||||
@@ -106,12 +96,7 @@
|
||||
metabase.query-processor.streaming.interface/begin!
|
||||
metabase.query-processor.streaming.interface/finish!
|
||||
metabase.query-processor.streaming.interface/write-row!
|
||||
metabase.sample-data.impl/try-to-extract-sample-database!
|
||||
metabase.settings.models.setting-test/test-user-local-allowed-setting!
|
||||
metabase.settings.models.setting-test/test-user-local-only-setting!
|
||||
metabase.settings.models.setting.cache/restore-cache!
|
||||
metabase.settings.models.setting/set!
|
||||
metabase.settings.models.setting/validate-settings-formatting!
|
||||
metabase.sample-data/try-to-extract-sample-database!
|
||||
metabase.setup.core/create-token!
|
||||
metabase.sso.ldap-test-util/start-ldap-server!
|
||||
metabase.sync.core/sync-database!
|
||||
@@ -119,8 +104,7 @@
|
||||
metabase.sync.sync-metadata/sync-db-metadata!
|
||||
metabase.sync.util-test/sync-database!
|
||||
metabase.sync.util/store-sync-summary!
|
||||
metabase.task.core/delete-task!
|
||||
metabase.task.impl/delete-task!
|
||||
metabase.task/delete-task!
|
||||
metabase.test.data.bigquery-cloud-sdk/execute!
|
||||
metabase.test.data.impl.get-or-create/set-test-db-permissions!
|
||||
metabase.test.data.impl/copy-db-tables-and-fields!
|
||||
@@ -142,6 +126,7 @@
|
||||
metabase.test/initialize-if-needed!
|
||||
metabase.test/test-helpers-set-global-values!
|
||||
metabase.test/with-temp-env-var-value!
|
||||
metabase.upload.impl-test/set-local-infile!
|
||||
metabase.upload-test/set-local-infile!
|
||||
metabase.util.files/create-dir-if-not-exists!
|
||||
metabase.warehouse-schema.models.field-values/create-or-update-full-field-values!}}}}
|
||||
metabase.util.ssh-test/start-mock-servers!
|
||||
metabase.util.ssh-test/stop-mock-servers!}}}}
|
||||
|
||||
13
.clj-kondo/macros/metabase/api/card_test.clj
Normal file
13
.clj-kondo/macros/metabase/api/card_test.clj
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns macros.metabase.api.card-test)
|
||||
|
||||
(defmacro with-ordered-items
|
||||
[collection model-and-name-syms & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[model binding]]
|
||||
[binding model])))
|
||||
model-and-name-syms)
|
||||
~collection
|
||||
~@(->> (partition-all 2 model-and-name-syms)
|
||||
(map second))
|
||||
~@body))
|
||||
24
.clj-kondo/macros/metabase/api/collection_test.clj
Normal file
24
.clj-kondo/macros/metabase/api/collection_test.clj
Normal file
@@ -0,0 +1,24 @@
|
||||
(ns macros.metabase.api.collection-test)
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
`(let [~'a nil
|
||||
~'b nil
|
||||
~'c nil
|
||||
~'d nil
|
||||
~'e nil
|
||||
~'f nil
|
||||
~'g nil]
|
||||
~'a
|
||||
~'b
|
||||
~'c
|
||||
~'d
|
||||
~'e
|
||||
~'f
|
||||
~'g
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
`(let [~'&ids ~collection-or-id-or-nil]
|
||||
~'&ids
|
||||
~@body))
|
||||
13
.clj-kondo/macros/metabase/api/embed_test.clj
Normal file
13
.clj-kondo/macros/metabase/api/embed_test.clj
Normal file
@@ -0,0 +1,13 @@
|
||||
(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,24 +0,0 @@
|
||||
(ns macros.metabase.collections.api-test)
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
`(let [~'a nil
|
||||
~'b nil
|
||||
~'c nil
|
||||
~'d nil
|
||||
~'e nil
|
||||
~'f nil
|
||||
~'g nil]
|
||||
~'a
|
||||
~'b
|
||||
~'c
|
||||
~'d
|
||||
~'e
|
||||
~'f
|
||||
~'g
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
`(let [~'&ids ~collection-or-id-or-nil]
|
||||
~'&ids
|
||||
~@body))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.embedding.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))
|
||||
@@ -0,0 +1,4 @@
|
||||
(ns macros.metabase.models.params.chain-filter-test)
|
||||
|
||||
(defmacro chain-filter [& _]
|
||||
`(metabase.models.params.chain-filter/chain-filter nil nil))
|
||||
12
.clj-kondo/macros/metabase/models/query_analysis_test.clj
Normal file
12
.clj-kondo/macros/metabase/models/query_analysis_test.clj
Normal file
@@ -0,0 +1,12 @@
|
||||
(ns macros.metabase.models.query-analysis-test)
|
||||
|
||||
(defmacro with-test-setup [& body]
|
||||
`(let [~'card-id 1
|
||||
~'table-id 2
|
||||
~'tax-id 3
|
||||
~'total-id 4]
|
||||
~'card-id
|
||||
~'table-id
|
||||
~'tax-id
|
||||
~'total-id
|
||||
~@body))
|
||||
9
.clj-kondo/macros/metabase/models/user_test.clj
Normal file
9
.clj-kondo/macros/metabase/models/user_test.clj
Normal file
@@ -0,0 +1,9 @@
|
||||
(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,4 +0,0 @@
|
||||
(ns macros.metabase.parameters.chain-filter-test)
|
||||
|
||||
(defmacro chain-filter [& _]
|
||||
`(metabase.parameters.chain-filter/chain-filter nil nil))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.queries.api.card-test)
|
||||
|
||||
(defmacro with-ordered-items
|
||||
[collection model-and-name-syms & body]
|
||||
`(let ~(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[model binding]]
|
||||
[binding model])))
|
||||
model-and-name-syms)
|
||||
~collection
|
||||
~@(->> (partition-all 2 model-and-name-syms)
|
||||
(map second))
|
||||
~@body))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns macros.metabase.query-analysis.models.query-analysis-test)
|
||||
|
||||
(defmacro with-test-setup [& body]
|
||||
`(let [~'card-id 1
|
||||
~'table-id 2
|
||||
~'tax-id 3
|
||||
~'total-id 4]
|
||||
~'card-id
|
||||
~'table-id
|
||||
~'tax-id
|
||||
~'total-id
|
||||
~@body))
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns macros.metabase.server.statistics-handler-test)
|
||||
|
||||
(defmacro with-server
|
||||
[_ _ [connector chan-start-handle chan-finish-handle chan-done-request] & body]
|
||||
`(let [~connector nil
|
||||
~chan-start-handle nil
|
||||
~chan-finish-handle nil
|
||||
~chan-done-request nil]
|
||||
~@body))
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns macros.metabase.users.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))
|
||||
@@ -0,0 +1,6 @@
|
||||
(ns macros.metabase-enterprise.advanced-permissions.api.util-test)
|
||||
|
||||
(defmacro with-impersonations! [impersonations-and-attributes-map & body]
|
||||
`(let [~'&group ~impersonations-and-attributes-map]
|
||||
~'&group
|
||||
~@body))
|
||||
@@ -1,5 +0,0 @@
|
||||
(ns macros.metabase-enterprise.database-routing.e2e-test)
|
||||
|
||||
(defmacro with-temp-dbs! [bindings & body]
|
||||
`(let [~@(interleave bindings (repeat nil))]
|
||||
~@body))
|
||||
@@ -1,6 +0,0 @@
|
||||
(ns macros.metabase-enterprise.impersonation.util-test)
|
||||
|
||||
(defmacro with-impersonations! [impersonations-and-attributes-map & body]
|
||||
`(let [~'&group ~impersonations-and-attributes-map]
|
||||
~'&group
|
||||
~@body))
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns hooks.clojure.core.ns
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]
|
||||
[hooks.common.modules :as modules]))
|
||||
|
||||
@@ -60,15 +59,12 @@
|
||||
(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-node [ns-form-node]
|
||||
(some (fn [node]
|
||||
(when (and (hooks/token-node? node)
|
||||
(not= (hooks/sexpr node) 'ns))
|
||||
node))
|
||||
(:children ns-form-node)))
|
||||
|
||||
(defn- ns-form-node->ns-symb [ns-form-node]
|
||||
(some-> (ns-form-node->ns-symb-node 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- lint-modules [ns-form-node config]
|
||||
@@ -87,33 +83,9 @@
|
||||
:message error
|
||||
:type :metabase/modules))))))))
|
||||
|
||||
(defn- lint-namespace-name [ns-form-node]
|
||||
(when-let [ns-symb-node (ns-form-node->ns-symb-node ns-form-node)]
|
||||
(when-not (contains? (hooks.common/ignored-linters ns-symb-node) :metabase/namespace-name)
|
||||
(let [parts (str/split (str (hooks/sexpr ns-symb-node)) #"\.")]
|
||||
(when (and (<= (count parts) 2)
|
||||
(#{"metabase" "metabase-enterprise"} (first parts))
|
||||
;; exclude test namespaces for now
|
||||
(not (str/ends-with? (last parts) "test")))
|
||||
(hooks/reg-finding! (assoc (meta ns-symb-node)
|
||||
:message "Metabase namespaces should have the form metabase[-enterprise].<module>.* [:metabase/namespace-name]"
|
||||
:type :metabase/namespace-name)))))))
|
||||
|
||||
(defn lint-ns [x]
|
||||
(doto (:node x)
|
||||
lint-require-shapes
|
||||
lint-requires-on-new-lines
|
||||
(lint-modules (modules/config x))
|
||||
lint-namespace-name)
|
||||
(lint-modules (modules/config x)))
|
||||
x)
|
||||
|
||||
(comment
|
||||
(lint-ns
|
||||
{:node (hooks/parse-string
|
||||
(pr-str
|
||||
'(ns hooks.clojure.core.ns
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]
|
||||
[hooks.common.modules :as modules]))))}))
|
||||
|
||||
@@ -52,13 +52,7 @@
|
||||
|
||||
(defn- lint-require* [node current-ns config]
|
||||
(let [[_require & args] (:children node)
|
||||
required-namespaces (->> (keep unwrap-require args)
|
||||
;; `require` runs through `cc/load-libs` which allows
|
||||
;; #{:as :reload :reload-all :require :use :verbose :refer :as-alias}
|
||||
;; as args. If you use a REPL tool that applies clj-kondo linting (e.g., Clojure MCP)
|
||||
;; you may wish to `:reload` or `:reload-all` a namespace. So we shouldn't assume all
|
||||
;; args are keywords:
|
||||
(remove keyword?))]
|
||||
required-namespaces (keep unwrap-require args)]
|
||||
(lint* node current-ns config required-namespaces)))
|
||||
|
||||
(defn- lint-requiring-resolve* [node current-ns config]
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
(ns hooks.metabase.app-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*}))
|
||||
@@ -0,0 +1,14 @@
|
||||
(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.mongo-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn with-describe-table-for-sample
|
||||
"Forward bindings for results of functions used in mongo's describe-table implementation."
|
||||
[{{[_ _ & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(hooks/token-node 'dbfields) (hooks/token-node '(delay nil))
|
||||
(hooks/token-node 'ftree) (hooks/token-node '(delay nil))
|
||||
(hooks/token-node 'nested-fields) (hooks/token-node '(delay nil))])
|
||||
body))]
|
||||
;; Avoid warnings when not all from the dbfields, ftree and nested-fields are used.
|
||||
{:node (hooks.common/update-ignored-linters node* conj :unused-binding)}))
|
||||
294
.clj-kondo/src/hooks/metabase/models/setting.clj
Normal file
294
.clj-kondo/src/hooks/metabase/models/setting.clj
Normal file
@@ -0,0 +1,294 @@
|
||||
(ns hooks.metabase.models.setting
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
;;; TODO -- move this into
|
||||
(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
|
||||
dismissed-onboarding-sidebar-link
|
||||
email-configured?
|
||||
email-from-address
|
||||
email-from-name
|
||||
email-reply-to
|
||||
email-smtp-host
|
||||
email-smtp-password
|
||||
email-smtp-port
|
||||
email-smtp-security
|
||||
email-smtp-username
|
||||
embedding-app-origin
|
||||
embedding-secret-key
|
||||
enable-password-login
|
||||
enable-public-sharing
|
||||
enable-query-caching
|
||||
engines
|
||||
enum-cardinality-threshold
|
||||
follow-up-email-sent
|
||||
google-auth-auto-create-accounts-domain
|
||||
google-auth-client-id
|
||||
google-auth-configured
|
||||
google-auth-enabled
|
||||
has-sample-database?
|
||||
has-user-setup
|
||||
help-link
|
||||
help-link-custom-destination
|
||||
instance-creation
|
||||
is-hosted?
|
||||
is-metabot-enabled
|
||||
jdbc-data-warehouse-max-connection-pool-size
|
||||
jdbc-data-warehouse-unreturned-connection-timeout-seconds
|
||||
jwt-attribute-email
|
||||
jwt-attribute-firstname
|
||||
jwt-attribute-groups
|
||||
jwt-attribute-lastname
|
||||
jwt-configured
|
||||
jwt-enabled
|
||||
jwt-group-mappings
|
||||
jwt-group-sync
|
||||
jwt-identity-provider-uri
|
||||
jwt-user-provisioning-enabled?
|
||||
jwt-shared-secret
|
||||
last-acknowledged-version
|
||||
last-used-native-database-id
|
||||
ldap-attribute-email
|
||||
ldap-attribute-firstname
|
||||
ldap-attribute-lastname
|
||||
ldap-bind-dn
|
||||
ldap-configured?
|
||||
ldap-enabled
|
||||
ldap-group-base
|
||||
ldap-group-mappings
|
||||
ldap-group-membership-filter
|
||||
ldap-group-sync
|
||||
ldap-host
|
||||
ldap-password
|
||||
ldap-port
|
||||
ldap-user-provisioning-enabled?
|
||||
ldap-security
|
||||
ldap-sync-user-attributes
|
||||
ldap-sync-user-attributes-blacklist
|
||||
ldap-user-base
|
||||
ldap-user-filter
|
||||
load-analytics-content
|
||||
map-tile-server-url
|
||||
metabot-default-embedding-model
|
||||
metabot-feedback-url
|
||||
metabot-get-prompt-templates-url
|
||||
metabot-prompt-generator-token-limit
|
||||
multi-setting-read-only
|
||||
notebook-native-preview-shown
|
||||
notebook-native-preview-sidebar-width
|
||||
notification-link-base-url
|
||||
num-metabot-choices
|
||||
openai-api-key
|
||||
openai-available-models
|
||||
openai-model
|
||||
openai-organization
|
||||
other-sso-enabled?
|
||||
password-complexity
|
||||
persist-models-enabled
|
||||
persisted-model-refresh-cron-schedule
|
||||
premium-embedding-token
|
||||
prometheus-server-port
|
||||
query-caching-max-kb
|
||||
query-caching-max-ttl
|
||||
query-caching-min-ttl
|
||||
query-caching-ttl-ratio
|
||||
redirect-all-requests-to-https
|
||||
reset-token-ttl-hours
|
||||
retired-setting
|
||||
retry-initial-interval
|
||||
retry-max-attempts
|
||||
retry-max-interval-millis
|
||||
retry-multiplier
|
||||
retry-randomization-factor
|
||||
saml-application-name
|
||||
saml-attribute-email
|
||||
saml-attribute-firstname
|
||||
saml-attribute-group
|
||||
saml-attribute-lastname
|
||||
saml-configured
|
||||
saml-enabled
|
||||
saml-group-mappings
|
||||
saml-group-sync
|
||||
saml-identity-provider-certificate
|
||||
saml-identity-provider-issuer
|
||||
saml-identity-provider-uri
|
||||
saml-keystore-alias
|
||||
saml-keystore-password
|
||||
saml-keystore-path
|
||||
saml-user-provisioning-enabled?
|
||||
send-email-on-first-login-from-new-device
|
||||
send-new-sso-user-admin-email?
|
||||
session-cookie-samesite
|
||||
session-cookies
|
||||
session-timeout
|
||||
setup-token
|
||||
show-database-syncing-modal
|
||||
show-metabase-links
|
||||
site-url
|
||||
site-uuid
|
||||
site-uuid-for-premium-features-token-checks
|
||||
site-uuid-for-unsubscribing-url
|
||||
site-uuid-for-version-info-fetching
|
||||
slack-app-token
|
||||
slack-cached-channels-and-usernames
|
||||
slack-channels-and-usernames-last-updated
|
||||
slack-files-channel
|
||||
slack-token
|
||||
slack-token-valid?
|
||||
snowplow-available
|
||||
snowplow-enabled
|
||||
snowplow-url
|
||||
sql-jdbc-fetch-size
|
||||
ssh-heartbeat-interval-sec
|
||||
ssl-certificate-public-key
|
||||
startup-time-millis
|
||||
token-features
|
||||
token-status
|
||||
toucan-name
|
||||
trial-banner-dismissal-timestamp
|
||||
uncached-setting
|
||||
user-visibility
|
||||
version
|
||||
version-info
|
||||
version-info-last-checked})
|
||||
|
||||
(defn- defsetting-lint [node setting-name docstring options-list]
|
||||
(let [anon-binding (common/with-macro-meta (hooks/token-node '_) node)
|
||||
;; (defn my-setting [] ...)
|
||||
getter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
setting-name
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node []))
|
||||
hooks/list-node
|
||||
(with-meta (meta node)))
|
||||
;; (defn my-setting! [_x] ...)
|
||||
setter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
(with-meta
|
||||
(hooks/token-node (symbol (str (hooks/sexpr setting-name) \!)))
|
||||
(meta setting-name))
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node [(hooks/token-node '_value-or-nil)]))
|
||||
hooks/list-node
|
||||
(with-meta (meta node))
|
||||
common/add-lsp-ignore-unused-public-var-metadata)]
|
||||
|
||||
(when (nil? (second (drop-while (comp not #{[:k :export?]} first) options-list)))
|
||||
(when-not (contains? ignored-implicit-export? (:value setting-name))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Setting definition must provide an explicit value for :export? indicating whether the setting should be exported or not with serialization."
|
||||
:type :metabase/defsetting-must-specify-export))))
|
||||
|
||||
{:node (-> (hooks/list-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)])
|
||||
(hooks/reg-keyword! (-> (hooks/keyword-node (keyword (hooks/sexpr setting-name)))
|
||||
(with-meta (meta setting-name)))
|
||||
'metabase.models.setting/defsetting)
|
||||
getter-node
|
||||
setter-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], :as context}]
|
||||
(let [[setting-name docstring & options] (rest (:children node))]
|
||||
(merge
|
||||
context
|
||||
(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], :as context}]
|
||||
(let [[setting-name docstring thunk & options] (rest (:children node))]
|
||||
(merge
|
||||
context
|
||||
(defsetting-lint node setting-name docstring (concat options [(hooks/token-node :multi-thunk) thunk])))))
|
||||
|
||||
(defn define-multi-setting-impl
|
||||
[context]
|
||||
(letfn [(update-node [node]
|
||||
(let [[_define-multi-setting-impl setting-name & more] (:children node)]
|
||||
(-> (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
;; For a qualified setting name like `oss-namespace/setting`, then leave the symbol as is, so the
|
||||
;; usage of that namespace/var gets recorded. Otherwise for an unqualified name convert it to a
|
||||
;; keyword so we don't get unresolved var errors.
|
||||
(if (and (hooks/token-node? setting-name)
|
||||
(qualified-symbol? (hooks/sexpr setting-name)))
|
||||
setting-name
|
||||
(-> (hooks/keyword-node (keyword (hooks/sexpr setting-name)))
|
||||
(with-meta (meta setting-name))))
|
||||
more))
|
||||
(with-meta (meta node)))))]
|
||||
(update context :node update-node)))
|
||||
|
||||
(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,310 +0,0 @@
|
||||
(ns hooks.metabase.settings.models.setting
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(defn lint-defsetting-namespace [node context]
|
||||
(when-not (re-matches #"^metabase(?:-enterprise)?\.[^\.]+\.settings$" (name (:ns context)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "All defsettings should live in metabase[-enterprise].<module>.settings namespaces"
|
||||
:type :metabase/defsetting-namespace))))
|
||||
|
||||
;;; TODO -- move this into a Kondo config file in `.clj-kondo/config/`
|
||||
(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?
|
||||
sdk-iframe-embed-setup-settings
|
||||
send-email-on-first-login-from-new-device
|
||||
send-new-sso-user-admin-email?
|
||||
session-cookie-samesite
|
||||
session-cookies
|
||||
session-timeout
|
||||
setup-token
|
||||
show-database-syncing-modal
|
||||
show-metabase-links
|
||||
site-url
|
||||
site-uuid
|
||||
site-uuid-for-premium-features-token-checks
|
||||
site-uuid-for-unsubscribing-url
|
||||
site-uuid-for-version-info-fetching
|
||||
slack-app-token
|
||||
slack-cached-channels-and-usernames
|
||||
slack-channels-and-usernames-last-updated
|
||||
slack-files-channel
|
||||
slack-token
|
||||
slack-token-valid?
|
||||
snowplow-available
|
||||
snowplow-enabled
|
||||
snowplow-url
|
||||
sql-jdbc-fetch-size
|
||||
ssh-heartbeat-interval-sec
|
||||
ssl-certificate-public-key
|
||||
startup-time-millis
|
||||
token-features
|
||||
token-status
|
||||
toucan-name
|
||||
trial-banner-dismissal-timestamp
|
||||
uncached-setting
|
||||
user-visibility
|
||||
version
|
||||
version-info
|
||||
version-info-last-checked})
|
||||
|
||||
(defn- lint-defsetting-export [node]
|
||||
(let [[_defsetting setting-name _docstring & options] (:children node)]
|
||||
(when (nil? (second (drop-while (comp not #{[:k :export?]} first) options)))
|
||||
(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))))))
|
||||
|
||||
(defn- defsetting-replacement-node [node]
|
||||
(let [[_defsetting setting-name docstring & options-list] (:children node)
|
||||
anon-binding (common/with-macro-meta (hooks/token-node '_) node)
|
||||
;; (defn my-setting [] ...)
|
||||
getter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
setting-name
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node []))
|
||||
hooks/list-node
|
||||
(with-meta (meta node)))
|
||||
;; (defn my-setting! [_x] ...)
|
||||
setter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
(with-meta
|
||||
(hooks/token-node (symbol (str (hooks/sexpr setting-name) \!)))
|
||||
(meta setting-name))
|
||||
(hooks/string-node "Docstring.")
|
||||
(hooks/vector-node [(hooks/token-node '_value-or-nil)]))
|
||||
hooks/list-node
|
||||
(with-meta (meta node))
|
||||
common/add-lsp-ignore-unused-public-var-metadata)]
|
||||
(-> (hooks/list-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)])
|
||||
(hooks/reg-keyword! (-> (hooks/keyword-node (keyword (hooks/sexpr setting-name)))
|
||||
(with-meta (meta setting-name)))
|
||||
'metabase.settings.models.setting/defsetting)
|
||||
getter-node
|
||||
setter-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], :as context}]
|
||||
(lint-defsetting-namespace node context)
|
||||
(lint-defsetting-export node)
|
||||
(update context :node defsetting-replacement-node))
|
||||
|
||||
(defn define-multi-setting-replacement-node [node]
|
||||
(let [[defsetting setting-name docstring thunk & options] (:children node)]
|
||||
(-> (hooks/list-node
|
||||
(list*
|
||||
defsetting
|
||||
setting-name
|
||||
docstring
|
||||
(hooks/token-node :multi-thunk) thunk
|
||||
options))
|
||||
(with-meta (meta node))
|
||||
defsetting-replacement-node)))
|
||||
|
||||
(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], :as context}]
|
||||
(update context :node define-multi-setting-replacement-node))
|
||||
|
||||
(defn define-multi-setting-impl
|
||||
[context]
|
||||
(letfn [(update-node [node]
|
||||
(let [[_define-multi-setting-impl setting-name & more] (:children node)]
|
||||
(-> (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
;; For a qualified setting name like `oss-namespace/setting`, then leave the symbol as is, so the
|
||||
;; usage of that namespace/var gets recorded. Otherwise for an unqualified name convert it to a
|
||||
;; keyword so we don't get unresolved var errors.
|
||||
(if (and (hooks/token-node? setting-name)
|
||||
(qualified-symbol? (hooks/sexpr setting-name)))
|
||||
setting-name
|
||||
(-> (hooks/keyword-node (keyword (hooks/sexpr setting-name)))
|
||||
(with-meta (meta setting-name))))
|
||||
more))
|
||||
(with-meta (meta node)))))]
|
||||
(update context :node update-node)))
|
||||
|
||||
(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.app-db.core/db-is-set-up?))
|
||||
0
|
||||
(cached-active-users-count)))))))
|
||||
@@ -2,8 +2,8 @@
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn iterate*
|
||||
"(iterate {:as limit-spec} [s1 val1 & more] body-form body-form...)"
|
||||
(defn with-gentest
|
||||
"(with-gentest {:as limit-spec} [s1 val1 & more] body-form body-form...)"
|
||||
[{{:keys [children] :as node} :node :as orig}]
|
||||
(let [[token limit-spec bindings & body-nodes] children
|
||||
node* (-> (hooks/list-node
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
(ns hooks.metabase.test.util.generators.jvm
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn with-random-cards
|
||||
"(with-random-cards <metadata-provider> <integer-count-value> & body-forms)"
|
||||
[{{:keys [children] :as node} :node :as orig}]
|
||||
(let [[token num-cards & body-nodes] children
|
||||
node* (-> (hooks/list-node
|
||||
(list token num-cards
|
||||
(hooks/list-node (list* (hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node '&cards) (hooks/list-node ())])
|
||||
body-nodes))))
|
||||
(with-meta (meta node)))]
|
||||
(assoc orig :node node*)))
|
||||
@@ -1,74 +0,0 @@
|
||||
(ns hooks.metabase.util.i18n
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- valid-apostrophes?
|
||||
"Returns true if `s` contains only doubled quotes (''') or complete placeholder-escaping quotes ('{...}'), false otherwise."
|
||||
[s]
|
||||
(loop [chars (seq s)]
|
||||
(if (empty? chars)
|
||||
true
|
||||
(let [c (first chars)]
|
||||
(if (= c \')
|
||||
(cond
|
||||
;; Doubled quotes: skip both
|
||||
(and (next chars) (= (second chars) \'))
|
||||
(recur (nthrest chars 2))
|
||||
|
||||
;; Placeholder escape. We allow for intentional escaping, like either '{0}` or '{{`, but not 'arbitrary
|
||||
;; strings' So basically, just require the *next* character to be a `{`, find the next single-quote, and
|
||||
;; continue on from there.
|
||||
;;
|
||||
;; Technically a string like "foo bar's happy but i'm not" is totally fine, but we'll say it's invalid
|
||||
;; because... that inner string literal is probably not what we actually want. So since we've never done
|
||||
;; this intentionally as far as I can tell, let's just remove the possibility to avoid ambiguity.
|
||||
(= (second chars) \{)
|
||||
(let [at-closing (drop-while #(not= \' %) (next chars))]
|
||||
(if (seq at-closing)
|
||||
(recur (next at-closing))
|
||||
false))
|
||||
;; Unpaired single quote: invalid
|
||||
:else
|
||||
false)
|
||||
(recur (rest chars)))))))
|
||||
|
||||
(defn format-str-arg-node->str
|
||||
[node]
|
||||
(cond
|
||||
;; it's just a string literal
|
||||
(api/string-node? node) (api/sexpr node)
|
||||
;; it's `(str "some" "strings")`
|
||||
(and (api/list-node? node)
|
||||
(= 'str (api/sexpr (first (:children node))))
|
||||
(every? api/string-node? (rest (:children node))))
|
||||
(apply str (map api/sexpr (rest (:children node))))
|
||||
|
||||
;; it's something else, no good
|
||||
:else
|
||||
(do
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "Must be a string literal or a call to `str` with string literal arguments."
|
||||
:type :metabase/validate-string-or-str-args-to-i18n))
|
||||
nil)))
|
||||
|
||||
(defn strict-apostrophes
|
||||
"Checks the node to make sure it's not a i18n call that will result in dropped apostrophes."
|
||||
[{:keys [node]}]
|
||||
(let [format-str-arg (second (:children node))
|
||||
;; `format-str-arg` must be either a `"string"` or `(str "a call " "to" "str")` (to allow for splitting long
|
||||
;; strings into shorter lines)
|
||||
format-str (format-str-arg-node->str format-str-arg)]
|
||||
(when-not (valid-apostrophes? format-str)
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "Format string contains invalid single quote usage. Use '' for literals or '{...}' for escaping."
|
||||
:type :metabase/validate-escaped-single-quotes-in-i18n))))
|
||||
{:node node})
|
||||
|
||||
(def tru strict-apostrophes)
|
||||
(def trs strict-apostrophes)
|
||||
(def deferred-tru strict-apostrophes)
|
||||
(def deferred-trs strict-apostrophes)
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns hooks.metabase.util.timing
|
||||
"Custom CLJ Kondo hooks for timing-related linting rules."
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn discourage-millis-duration
|
||||
"Discourage slow and unreliable duration calculations using System/currentTimeMillis "
|
||||
[{:keys [node]}]
|
||||
(let [sexpr (api/sexpr node)]
|
||||
(when (and (list? sexpr) (= (first sexpr) '-) (>= (count sexpr) 2))
|
||||
(let [first-arg (second sexpr)]
|
||||
(when (= first-arg (list 'System/currentTimeMillis))
|
||||
(api/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message "Avoid System/currentTimeMillis for calculating durations. Use u/start-timer and u/since-ms for more reliability."
|
||||
:type :metabase/discourage-millis-duration
|
||||
:level :warning)))))))
|
||||
@@ -20,17 +20,15 @@
|
||||
(lint-ns form (assoc-in config [:linters :metabase/modules :level] :warning)))
|
||||
|
||||
(deftest ^:parallel module-checker-allowed-modules-test
|
||||
(are [task-namespace] (=? [{:message "Module task should not be used in the api module. [:metabase/modules api :uses]"
|
||||
:type :metabase/modules}]
|
||||
(lint-modules
|
||||
'(ns metabase.api.search
|
||||
(:require
|
||||
[metabase.request.core :as request]
|
||||
[task-namespace :as task]))
|
||||
'{:metabase/modules {api {:uses #{request}}
|
||||
request {:api #{metabase.request.core}}}}))
|
||||
metabase.task
|
||||
metabase.task.core))
|
||||
(is (=? [{:message "Module task should not be used in the api module. [:metabase/modules api :uses]"
|
||||
:type :metabase/modules}]
|
||||
(lint-modules
|
||||
'(ns metabase.api.search
|
||||
(:require
|
||||
[metabase.request.core :as request]
|
||||
[metabase.task :as task]))
|
||||
'{:metabase/modules {api {:uses #{request}}
|
||||
request {:api #{metabase.request.core}}}}))))
|
||||
|
||||
(deftest ^:parallel module-checker-api-namespaces-test
|
||||
(is (=? [{:message "Namespace metabase.search.config is not an allowed external API namespace for the search module. [:metabase/modules search :api]"
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
(ns hooks.metabase.settings.models.setting-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.settings.models.setting]))
|
||||
|
||||
#_{:clj-kondo/ignore [:metabase/validate-deftest]}
|
||||
(deftest ^:parallel defsetting-test
|
||||
(let [node (-> '(defsetting active-users-count
|
||||
(deferred-tru "Cached number of active users. Refreshed every 5 minutes.")
|
||||
:visibility :admin
|
||||
:type :integer
|
||||
:default 0
|
||||
:getter (fn []
|
||||
(if-not ((requiring-resolve 'metabase.app-db.core/db-is-set-up?))
|
||||
0
|
||||
(cached-active-users-count))))
|
||||
pr-str
|
||||
hooks/parse-string)]
|
||||
(is (= '(let [_ (deferred-tru "Cached number of active users. Refreshed every 5 minutes.")
|
||||
_ {:default 0
|
||||
:type :integer
|
||||
:getter (fn []
|
||||
(if-not ((requiring-resolve 'metabase.app-db.core/db-is-set-up?))
|
||||
0
|
||||
(cached-active-users-count)))
|
||||
:visibility :admin}]
|
||||
:active-users-count
|
||||
(defn active-users-count "Docstring." [])
|
||||
(defn active-users-count! "Docstring." [_value-or-nil]))
|
||||
(-> {:node node, :ns 'hooks.metabase.settings.models.setting-test}
|
||||
hooks.metabase.settings.models.setting/defsetting
|
||||
:node
|
||||
hooks/sexpr)))))
|
||||
@@ -1,33 +0,0 @@
|
||||
(ns hooks.metabase.util.i18n-test
|
||||
(:require [clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer [deftest testing is are]]
|
||||
[hooks.metabase.util.i18n]))
|
||||
|
||||
(defn- warnings
|
||||
[form]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-string-or-str-args-to-i18n {:level :warning}
|
||||
:metabase/validate-escaped-single-quotes-in-i18n {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(hooks.metabase.util.i18n/strict-apostrophes {:node (api/parse-string (pr-str form))})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*))))
|
||||
|
||||
(deftest ^:parallel strict-apostrophes-warns
|
||||
(are [form] (= ["Format string contains invalid single quote usage. Use '' for literals or '{...}' for escaping."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.i18n/tru "this isn't good bob")
|
||||
(metabase.util.i18n/tru "'this is not good either'")
|
||||
(metabase.util.i18n/tru "''this isn't''")
|
||||
(metabase.util.i18n/tru "nope you can not escape 'like {{0}} so {{1}}'")
|
||||
(metabase.util.i18n/tru (str "it works" "when strings" "are concatenated'"))))
|
||||
|
||||
(deftest ^:parallel strict-apostrophes-doesnt-warn-on-good-ones
|
||||
(are [form] (= []
|
||||
(warnings (quote form)))
|
||||
(metabase.util.i18n/tru "this isn''t bad")
|
||||
(metabase.util.i18n/tru (str "not sure why you'" "'d do this" "... but ok"))
|
||||
(metabase.util.i18n/tru "you can '{{0}}' do things like that")
|
||||
(metabase.util.i18n/tru "or '{{ this or }}}}}}}}}}' that")))
|
||||
540
.cljfmt.edn
540
.cljfmt.edn
@@ -81,278 +81,268 @@
|
||||
throttle.core/with-throttling [[:block 1]]
|
||||
;; the rest of the stuff in this list is generated automatically based on `:style/indent` specs
|
||||
;; by running [[dev.generate-cljfmt-config/specs]]... use that code to update this list
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.impersonation.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.app-db.core/with-application-db [[:inner 0]]
|
||||
metabase.app-db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.app-db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.collections.api-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections.api-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.collections.api-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.driver-api.core/match [[:inner 0]]
|
||||
metabase.driver-api.core/match-one [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-in [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.embedding.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.embedding.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.embedding.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.embedding.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.embedding.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.embedding.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-5-query [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.parameters.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.parameters.chain-filter-test/with-fk-field-to-field-remapping [[:block 0]]
|
||||
metabase.parameters.chain-filter-test/with-human-readable-values-remapping [[:block 0]]
|
||||
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
|
||||
metabase.permissions.core/disable-perms-cache [[:block 0]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-temp-public-card [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.pulse.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.queries.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.queries.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.queries.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.queries.api.card-test/with-temp-native-card! [[:block 1]]
|
||||
metabase.queries.api.card-test/with-temp-native-card-with-params! [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
|
||||
metabase.query-processor.debug/debug> [[:block 0]]
|
||||
metabase.query-processor.error-type/deferror [[:block 1]]
|
||||
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
|
||||
metabase.query-processor.store/cached [[:block 1]]
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.request.core/as-admin [[:block 0]]
|
||||
metabase.request.core/with-current-request [[:inner 0]]
|
||||
metabase.request.core/with-current-user [[:inner 0]]
|
||||
metabase.request.core/with-limit-and-offset [[:inner 0]]
|
||||
metabase.request.session/as-admin [[:block 0]]
|
||||
metabase.request.session/with-current-user [[:inner 0]]
|
||||
metabase.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.search.api-test/do-test-users [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.server.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.session.api/http-401-on-error [[:block 0]]
|
||||
metabase.settings.core/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.core/defsetting [[:block 1]]
|
||||
metabase.settings.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.settings.models.setting/defsetting [[:block 1]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.task-history.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.task.core/rerun-on-error [[:block 1]]
|
||||
metabase.task.impl/rerun-on-error [[:block 1]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db! [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
|
||||
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/for-map [[:block 1]]
|
||||
metabase.util.async/cancelable-thread [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabase.util.queue-test/await-test-while [[:block 1]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.api.card-test/with-temp-native-card! [[:block 1]]
|
||||
metabase.api.card-test/with-temp-native-card-with-params! [[:block 1]]
|
||||
metabase.api.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.api.collection-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.api.collection-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.db/with-application-db [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.models.params.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.models.params.chain-filter-test/with-fk-field-to-field-remapping [[:block 0]]
|
||||
metabase.models.params.chain-filter-test/with-human-readable-values-remapping [[:block 0]]
|
||||
metabase.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.models.setting/defsetting [[:block 1]]
|
||||
metabase.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.permissions.core/allow-changing-all-users-group-members [[:block 0]]
|
||||
metabase.permissions.core/disable-perms-cache [[:block 0]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.public-sharing.api-test/with-temp-public-card [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.public-sharing.api-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.pulse.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.pulse.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.pulse.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
|
||||
metabase.query-processor.debug/debug> [[:block 0]]
|
||||
metabase.query-processor.error-type/deferror [[:block 1]]
|
||||
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
|
||||
metabase.query-processor.store/cached [[:block 1]]
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.request.core/as-admin [[:block 0]]
|
||||
metabase.request.core/with-current-request [[:inner 0]]
|
||||
metabase.request.core/with-current-user [[:inner 0]]
|
||||
metabase.request.core/with-limit-and-offset [[:inner 0]]
|
||||
metabase.request.session/as-admin [[:block 0]]
|
||||
metabase.request.session/with-current-user [[:inner 0]]
|
||||
metabase.sample-data-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.search.api-test/do-test-users [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.server.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.server.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.server.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.session.api/http-401-on-error [[:block 0]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.task/rerun-on-error [[:block 1]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/native-query [[:block 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/native-query [[:block 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
|
||||
metabase.upload-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabase.util.async/cancelable-thread [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:21
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:11
|
||||
|
||||
# Set up nodesource, install node, yarn, fontconfig for static viz, rlwrap for dev ergonomics
|
||||
|
||||
RUN ( curl -fsSL https://deb.nodesource.com/setup_22.x | bash ) \
|
||||
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
|
||||
|
||||
# install Clojure
|
||||
RUN curl https://download.clojure.org/install/linux-install-1.12.0.1488.sh | bash
|
||||
RUN curl https://download.clojure.org/install/linux-install-1.11.1.1262.sh | bash
|
||||
|
||||
75
.eslintrc.js
75
.eslintrc.js
@@ -1,20 +1,13 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const path = require("path");
|
||||
|
||||
// `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;
|
||||
const plugins = ["react", "no-only-tests", "ttag", "i18next"];
|
||||
if (shouldLintCssModules) {
|
||||
plugins.push("postcss-modules");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ignorePatterns: ["!.storybook"],
|
||||
rules: {
|
||||
strict: [2, "never"],
|
||||
"no-undef": 2,
|
||||
@@ -49,7 +42,6 @@ module.exports = {
|
||||
],
|
||||
curly: [1, "all"],
|
||||
eqeqeq: [1, "smart"],
|
||||
"import/no-duplicates": ["warn", { considerQueryString: true }],
|
||||
"import/no-default-export": 2,
|
||||
"import/no-named-as-default": 0,
|
||||
"import/no-commonjs": 1,
|
||||
@@ -122,7 +114,7 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
complexity: ["error", { max: 55 }],
|
||||
complexity: ["error", { max: 54 }],
|
||||
...(shouldLintCssModules
|
||||
? {
|
||||
"postcss-modules/no-undef-class": "error",
|
||||
@@ -142,7 +134,7 @@ module.exports = {
|
||||
"jest/globals": true,
|
||||
},
|
||||
parser: "babel-eslint",
|
||||
plugins,
|
||||
plugins: ["react", "no-only-tests", "postcss-modules"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
@@ -153,13 +145,11 @@ module.exports = {
|
||||
"plugin:import/typescript",
|
||||
"plugin:depend/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
"plugin:i18next/recommended",
|
||||
],
|
||||
settings: {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
"import/resolver": {
|
||||
webpack: {
|
||||
config: path.resolve(__dirname, "./rspack.main.config.js"),
|
||||
typescript: true,
|
||||
},
|
||||
},
|
||||
@@ -180,9 +170,7 @@ module.exports = {
|
||||
{
|
||||
files: ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
rules: {
|
||||
"jtag-missing-key": "error",
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-color-literals": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
"depend/ban-dependencies": [
|
||||
"error",
|
||||
@@ -202,40 +190,17 @@ module.exports = {
|
||||
"*.unit.spec.*",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/whitelabel/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"**/.storybook/*",
|
||||
"stories-data.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
"rspack.config.js",
|
||||
"rspack.main.config.js",
|
||||
"rspack.embedding-sdk-package.config.js",
|
||||
"rspack.embedding-sdk-bundle.config.js",
|
||||
],
|
||||
rules: {
|
||||
"no-color-literals": "off",
|
||||
"no-unconditional-metabase-links-render": "off",
|
||||
"no-literal-metabase-strings": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"*.unit.spec.*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"stories-data.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
"rspack.main.config.js",
|
||||
],
|
||||
rules: {
|
||||
"i18next/no-literal-string": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
@@ -283,13 +248,6 @@ module.exports = {
|
||||
],
|
||||
rules: {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
assertFunctionNames: ["expect*", "assert*"],
|
||||
additionalTestBlockFunctions: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -320,32 +278,13 @@ module.exports = {
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='legacyQuery'] > ObjectExpression > Property[key.name='useStructuredQuery'][value.value=true]",
|
||||
message: "StructuredQuery usage is forbidden. Use MLv2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/metabase/query_builder/**/*"],
|
||||
rules: {
|
||||
"import/no-cycle": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["docs/**/snippets/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"import/no-commonjs": "off",
|
||||
"import/no-default-export": "off",
|
||||
"import/order": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-color-literals": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/build/**/*.js"],
|
||||
rules: {
|
||||
"import/no-commonjs": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
340
.github/CODEOWNERS
vendored
340
.github/CODEOWNERS
vendored
@@ -1,266 +1,258 @@
|
||||
# Before changing this file, make sure you have read how we use it:
|
||||
# https://www.notion.so/metabase/Pull-Request-Code-Review-Guide-374f05df889748b69d67af753f9bc9b8?pvs=4#36550c04ceab40b5a8b8bee6264b090c
|
||||
|
||||
enterprise/frontend/src/embedding-sdk-package @metabase/embedding
|
||||
enterprise/frontend/src/embedding-sdk-bundle @metabase/embedding
|
||||
enterprise/frontend/src/embedding-sdk-shared @metabase/embedding
|
||||
enterprise/frontend/src/embedding-sdk @metabase/embedding
|
||||
frontend/src/metabase/embedding-sdk @metabase/embedding
|
||||
frontend/src/metabase/public/containers/PublicOrEmbeddedQuestion @metabase/embedding
|
||||
frontend/src/metabase/public/containers/PublicOrEmbeddedDashboard @metabase/embedding
|
||||
frontend/src/metabase/ui @metabase/core-frontend-ux-west
|
||||
# snowplow/* @metabase/data
|
||||
src/metabase/analytics/prometheus.clj @metabase/cloud-ops
|
||||
frontend/src/metabase/ui @metabase/core-frontend-admin-webapp
|
||||
snowplow/* @metabase/data
|
||||
e2e @filiphric
|
||||
|
||||
.github @metabase/devexp
|
||||
.clj-kondo @metabase/devexp
|
||||
deps.edn @metabase/backend-developers
|
||||
|
||||
docs @metabase/tech-writers
|
||||
docs/developers-guide # exclude from tech-writer ownership
|
||||
|
||||
#
|
||||
# Backend modules <=> teams that own them
|
||||
#
|
||||
|
||||
# modules/drivers @metabase/drivers
|
||||
src/metabase/driver/sql_jdbc/actions.clj @metabase/core-ux-east
|
||||
test/metabase/driver/sql_jdbc/actions_test.clj @metabase/core-ux-east
|
||||
modules/drivers @metabase/drivers
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/advanced_config @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/advanced_config @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/advanced_config @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/advanced_config @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/advanced_permissions @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/advanced_permissions @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/advanced_permissions @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/advanced_permissions @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/airgap @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/airgap @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/airgap @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/airgap @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/api @metabase/devexp
|
||||
# enterprise/backend/test/metabase_enterprise/api @metabase/devexp
|
||||
enterprise/backend/src/metabase_enterprise/api @metabase/devexp
|
||||
enterprise/backend/test/metabase_enterprise/api @metabase/devexp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/audit_app @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/audit_app @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/audit_app @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/audit_app @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/auth_provider @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/auth_provider @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/auth_provider @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/auth_provider @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/billing @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/billing @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/billing @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/billing @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/cache @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/cache @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/cache @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/cache @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/content_verification @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/content_verification @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/content_verification @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/content_verification @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/core @metabase/devexp
|
||||
# enterprise/backend/test/metabase_enterprise/core @metabase/devexp
|
||||
enterprise/backend/src/metabase_enterprise/core @metabase/devexp
|
||||
enterprise/backend/test/metabase_enterprise/core @metabase/devexp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/dashboard_subscription_filters @metabase/core-ux-east
|
||||
# enterprise/backend/test/metabase_enterprise/dashboard_subscription_filters @metabase/core-ux-east
|
||||
enterprise/backend/src/metabase_enterprise/dashboard_subscription_filters @metabase/core-workflows
|
||||
enterprise/backend/test/metabase_enterprise/dashboard_subscription_filters @metabase/core-workflows
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/enhancements @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/enhancements @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/enhancements @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/enhancements @metabase/core-backend-admin-webapp
|
||||
|
||||
# # enterprise/backend/src/metabase_enterprise/llm
|
||||
# # enterprise/backend/test/metabase_enterprise/llm
|
||||
# enterprise/backend/src/metabase_enterprise/llm
|
||||
# enterprise/backend/test/metabase_enterprise/llm
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/query_reference_validation @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/query_reference_validation @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/query_reference_validation @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/query_reference_validation @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/sandbox @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/sandbox @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/sandbox @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/sandbox @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/scim @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/scim @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/scim @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/scim @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/search ...
|
||||
# enterprise/backend/test/metabase_enterprise/search ...
|
||||
enterprise/backend/src/metabase_enterprise/search @metabase/core-workflows
|
||||
enterprise/backend/test/metabase_enterprise/search @metabase/core-workflows
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/serialization @metabase/core-ux-east
|
||||
# enterprise/backend/test/metabase_enterprise/serialization @metabase/core-ux-east
|
||||
enterprise/backend/src/metabase_enterprise/serialization @metabase/core-workflows
|
||||
enterprise/backend/test/metabase_enterprise/serialization @metabase/core-workflows
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/snippet_collections @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/snippet_collections @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/snippet_collections @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/snippet_collections @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/sso @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/sso @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/sso @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/sso @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/stale @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/stale @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/stale @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/stale @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/stats @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/stats @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/stats @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/stats @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/task @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/task @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/task @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/task @metabase/core-backend-admin-webapp
|
||||
|
||||
# enterprise/backend/src/metabase_enterprise/upload_management @metabase/core-backend-admin-webapp
|
||||
# enterprise/backend/test/metabase_enterprise/upload_management @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/src/metabase_enterprise/upload_management @metabase/core-backend-admin-webapp
|
||||
enterprise/backend/test/metabase_enterprise/upload_management @metabase/core-backend-admin-webapp
|
||||
|
||||
src/metabase/actions @metabase/core-ux-east
|
||||
test/metabase/actions @metabase/core-ux-east
|
||||
src/metabase/actions @metabase/core-backend-admin-webapp
|
||||
test/metabase/actions @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/activity_feed @metabase/core-backend-admin-webapp
|
||||
# test/metabase/activity_feed @metabase/core-backend-admin-webapp
|
||||
src/metabase/activity_feed @metabase/core-backend-admin-webapp
|
||||
test/metabase/activity_feed @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/analytics @metabase/core-backend-admin-webapp
|
||||
# test/metabase/analytics @metabase/core-backend-admin-webapp
|
||||
src/metabase/analytics @metabase/core-backend-admin-webapp
|
||||
test/metabase/analytics @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/analyze @metabase/drivers
|
||||
# test/metabase/analyze @metabase/drivers
|
||||
src/metabase/analyze @metabase/drivers
|
||||
test/metabase/analyze @metabase/drivers
|
||||
|
||||
# src/metabase/api @metabase/devexp
|
||||
# test/metabase/api @metabase/devexp
|
||||
src/metabase/api @metabase/devexp
|
||||
test/metabase/api @metabase/devexp
|
||||
|
||||
# src/metabase/audit @metabase/core-backend-admin-webapp
|
||||
# test/metabase/audit @metabase/core-backend-admin-webapp
|
||||
src/metabase/audit @metabase/core-backend-admin-webapp
|
||||
test/metabase/audit @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/auth_provider @metabase/core-backend-admin-webapp
|
||||
# test/metabase/auth_provider @metabase/core-backend-admin-webapp
|
||||
src/metabase/auth_provider @metabase/core-backend-admin-webapp
|
||||
test/metabase/auth_provider @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/bookmarks @metabase/core-backend-admin-webapp
|
||||
# test/metabase/bookmarks @metabase/core-backend-admin-webapp
|
||||
src/metabase/bookmarks @metabase/core-backend-admin-webapp
|
||||
test/metabase/bookmarks @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/cmd @metabase/devexp
|
||||
# test/metabase/cmd @metabase/devexp
|
||||
src/metabase/cmd @metabase/devexp
|
||||
test/metabase/cmd @metabase/devexp
|
||||
|
||||
# src/metabase/channel @metabase/core-ux-east
|
||||
# test/metabase/channel @metabase/core-ux-east
|
||||
src/metabase/channel @metabase/core-workflows
|
||||
test/metabase/channel @metabase/core-workflows
|
||||
|
||||
# src/metabase/cloud_migration ...
|
||||
# test/metabase/cloud_migration ...
|
||||
src/metabase/cloud_migration @metabase/core-workflows
|
||||
test/metabase/cloud_migration @metabase/core-workflows
|
||||
|
||||
# src/metabase/cmd @metabase/devexp
|
||||
# test/metabase/cmd @metabase/devexp
|
||||
src/metabase/cmd @metabase/devexp
|
||||
test/metabase/cmd @metabase/devexp
|
||||
|
||||
# src/metabase/config @metabase/devexp
|
||||
# test/metabase/config @metabase/devexp
|
||||
src/metabase/config @metabase/devexp
|
||||
test/metabase/config @metabase/devexp
|
||||
|
||||
# src/metabase/core @metabase/devexp
|
||||
# test/metabase/core @metabase/devexp
|
||||
src/metabase/core @metabase/devexp
|
||||
test/metabase/core @metabase/devexp
|
||||
|
||||
# src/metabase/db @metabase/core-backend-admin-webapp
|
||||
# test/metabase/db @metabase/core-backend-admin-webapp
|
||||
src/metabase/db @metabase/core-backend-admin-webapp
|
||||
test/metabase/db @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/driver @metabase/drivers
|
||||
# test/metabase/driver @metabase/drivers
|
||||
src/metabase/driver @metabase/drivers
|
||||
test/metabase/driver @metabase/drivers
|
||||
|
||||
# src/metabase/eid_translation @metabase/core-backend-admin-webapp
|
||||
# test/metabase/eid_translation @metabase/core-backend-admin-webapp
|
||||
src/metabase/eid_translation @metabase/core-backend-admin-webapp
|
||||
test/metabase/eid_translation @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/embed @metabase/embedding
|
||||
# test/metabase/embed @metabase/embedding
|
||||
src/metabase/embed @metabase/embedding
|
||||
test/metabase/embed @metabase/embedding
|
||||
|
||||
# src/metabase/events @metabase/devexp
|
||||
# test/metabase/events @metabase/devexp
|
||||
src/metabase/events @metabase/devexp
|
||||
test/metabase/events @metabase/devexp
|
||||
|
||||
# src/metabase/formatter @metabase/core-backend-querying
|
||||
# test/metabase/formatter @metabase/core-backend-querying
|
||||
src/metabase/formatter @metabase/core-backend-querying
|
||||
test/metabase/formatter @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/indexed_entities @metabase/core-backend-querying
|
||||
# test/metabase/indexed_entities @metabase/core-backend-querying
|
||||
src/metabase/indexed_entities @metabase/core-backend-querying
|
||||
test/metabase/indexed_entities @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/integrations ...
|
||||
# test/metabase/integrations ...
|
||||
src/metabase/integrations @metabase/core-workflows
|
||||
test/metabase/integrations @metabase/core-workflows
|
||||
|
||||
# src/metabase/legacy_mbql @metabase/core-backend-querying
|
||||
# test/metabase/legacy_mbql @metabase/core-backend-querying
|
||||
src/metabase/legacy_mbql @metabase/core-backend-querying
|
||||
test/metabase/legacy_mbql @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/lib @metabase/core-backend-querying
|
||||
# test/metabase/lib @metabase/core-backend-querying
|
||||
src/metabase/lib @metabase/core-backend-querying
|
||||
test/metabase/lib @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/logger @metabase/devexp
|
||||
# test/metabase/logger @metabase/devexp
|
||||
src/metabase/logger @metabase/devexp
|
||||
test/metabase/logger @metabase/devexp
|
||||
|
||||
# src/metabase/login_history @metabase/core-backend-admin-webapp
|
||||
# test/metabase/login_history @metabase/core-backend-admin-webapp
|
||||
src/metabase/login_history @metabase/core-backend-admin-webapp
|
||||
test/metabase/login_history @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/model_persistence @metabase/core-backend-querying
|
||||
# test/metabase/model_persistence @metabase/core-backend-querying
|
||||
src/metabase/model_persistence @metabase/core-backend-querying
|
||||
test/metabase/model_persistence @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/models @metabase/devexp
|
||||
# test/metabase/models @metabase/devexp
|
||||
src/metabase/models @metabase/devexp
|
||||
test/metabase/models @metabase/devexp
|
||||
|
||||
# src/metabase/moderation @metabase/core-backend-admin-webapp
|
||||
# test/metabase/moderation @metabase/core-backend-admin-webapp
|
||||
src/metabase/moderation @metabase/core-backend-admin-webapp
|
||||
test/metabase/moderation @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/notification @metabase/core-ux-east
|
||||
# test/metabase/notification @metabase/core-ux-east
|
||||
src/metabase/notification @metabase/core-workflows
|
||||
test/metabase/notification @metabase/core-workflows
|
||||
|
||||
# src/metabase/permissions @metabase/core-backend-admin-webapp
|
||||
# test/metabase/permissions @metabase/core-backend-admin-webapp
|
||||
src/metabase/permissions @metabase/core-backend-admin-webapp
|
||||
test/metabase/permissions @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/plugins @metabase/devexp
|
||||
# test/metabase/plugins @metabase/devexp
|
||||
src/metabase/plugins @metabase/devexp
|
||||
test/metabase/plugins @metabase/devexp
|
||||
|
||||
# src/metabase/premium_features @metabase/core-backend-admin-webapp
|
||||
# test/metabase/premium_features @metabase/core-backend-admin-webapp
|
||||
src/metabase/premium_features @metabase/core-backend-admin-webapp
|
||||
test/metabase/premium_features @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/public_settings @metabase/core-backend-admin-webapp
|
||||
# test/metabase/public_settings @metabase/core-backend-admin-webapp
|
||||
src/metabase/public_settings @metabase/core-backend-admin-webapp
|
||||
test/metabase/public_settings @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/public_sharing @metabase/core-backend-admin-webapp
|
||||
# test/metabase/public_sharing @metabase/core-backend-admin-webapp
|
||||
src/metabase/public_sharing @metabase/core-backend-admin-webapp
|
||||
test/metabase/public_sharing @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/pulse @metabase/core-ux-east
|
||||
# test/metabase/pulse @metabase/core-ux-east
|
||||
src/metabase/pulse @metabase/core-workflows
|
||||
test/metabase/pulse @metabase/core-workflows
|
||||
|
||||
# src/metabase/query_analysis @metabase/core-backend-querying
|
||||
# test/metabase/query_analysis @metabase/core-backend-querying
|
||||
src/metabase/query_analysis @metabase/core-backend-querying
|
||||
test/metabase/query_analysis @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/query_processor @metabase/core-backend-querying
|
||||
# test/metabase/query_processor @metabase/core-backend-querying
|
||||
src/metabase/query_processor @metabase/core-backend-querying
|
||||
test/metabase/query_processor @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/request @metabase/devexp
|
||||
# test/metabase/request @metabase/devexp
|
||||
src/metabase/request @metabase/devexp
|
||||
test/metabase/request @metabase/devexp
|
||||
|
||||
# src/metabase/revisions @metabase/core-backend-admin-webapp
|
||||
# test/metabase/revisions @metabase/core-backend-admin-webapp
|
||||
src/metabase/revisions @metabase/core-backend-admin-webapp
|
||||
test/metabase/revisions @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/sample_data @metabase/core-backend-admin-webapp
|
||||
# test/metabase/sample_data @metabase/core-backend-admin-webapp
|
||||
src/metabase/sample_data @metabase/core-backend-admin-webapp
|
||||
test/metabase/sample_data @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/search ...
|
||||
# test/metabase/search ...
|
||||
src/metabase/search @metabase/core-workflows
|
||||
test/metabase/search @metabase/core-workflows
|
||||
|
||||
# src/metabase/segments @metabase/core-backend-querying
|
||||
# test/metabase/segments @metabase/core-backend-querying
|
||||
src/metabase/segments @metabase/core-backend-querying
|
||||
test/metabase/segments @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/server @metabase/devexp
|
||||
# test/metabase/server @metabase/devexp
|
||||
src/metabase/server @metabase/devexp
|
||||
test/metabase/server @metabase/devexp
|
||||
|
||||
# src/metabase/session @metabase/core-backend-admin-webapp
|
||||
# test/metabase/session @metabase/core-backend-admin-webapp
|
||||
src/metabase/session @metabase/core-backend-admin-webapp
|
||||
test/metabase/session @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/setup @metabase/core-backend-admin-webapp
|
||||
# test/metabase/setup @metabase/core-backend-admin-webapp
|
||||
src/metabase/setup @metabase/core-backend-admin-webapp
|
||||
test/metabase/setup @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/sso @metabase/core-backend-admin-webapp
|
||||
# test/metabase/sso @metabase/core-backend-admin-webapp
|
||||
src/metabase/sso @metabase/core-backend-admin-webapp
|
||||
test/metabase/sso @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/sync @metabase/drivers
|
||||
# test/metabase/sync @metabase/drivers
|
||||
src/metabase/sync @metabase/drivers
|
||||
test/metabase/sync @metabase/drivers
|
||||
|
||||
# src/metabase/task @metabase/devexp
|
||||
# test/metabase/task @metabase/devexp
|
||||
src/metabase/task @metabase/devexp
|
||||
test/metabase/task @metabase/devexp
|
||||
|
||||
# src/metabase/tiles @metabase/core-backend-dashviz
|
||||
# test/metabase/tiles @metabase/core-backend-dashviz
|
||||
src/metabase/tiles @metabase/core-backend-dashviz
|
||||
test/metabase/tiles @metabase/core-backend-dashviz
|
||||
|
||||
# src/metabase/timeline @metabase/core-backend-dashviz
|
||||
# test/metabase/timeline @metabase/core-backend-dashviz
|
||||
src/metabase/timeline @metabase/core-backend-dashviz
|
||||
test/metabase/timeline @metabase/core-backend-dashviz
|
||||
|
||||
# src/metabase/types @metabase/core-backend-querying
|
||||
# test/metabase/types @metabase/core-backend-querying
|
||||
src/metabase/types @metabase/core-backend-querying
|
||||
test/metabase/types @metabase/core-backend-querying
|
||||
|
||||
# src/metabase/upload @metabase/core-backend-admin-webapp
|
||||
# test/metabase/upload @metabase/core-backend-admin-webapp
|
||||
src/metabase/upload @metabase/core-backend-admin-webapp
|
||||
test/metabase/upload @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/user_key_value @metabase/core-backend-admin-webapp
|
||||
# test/metabase/user_key_value @metabase/core-backend-admin-webapp
|
||||
src/metabase/user_key_value @metabase/core-backend-admin-webapp
|
||||
test/metabase/user_key_value @metabase/core-backend-admin-webapp
|
||||
|
||||
# src/metabase/util @metabase/devexp
|
||||
# test/metabase/util @metabase/devexp
|
||||
src/metabase/util @metabase/devexp
|
||||
test/metabase/util @metabase/devexp
|
||||
|
||||
# src/metabase/xrays @metabase/core-backend-querying
|
||||
# test/metabase/xrays @metabase/core-backend-querying
|
||||
src/metabase/xrays @metabase/core-backend-querying
|
||||
test/metabase/xrays @metabase/core-backend-querying
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -63,8 +63,7 @@ body:
|
||||
attributes:
|
||||
label: Information about your Metabase installation
|
||||
description: |
|
||||
For versions >= 56, copy and paste the Diagnostic info from `Admin -> Tools -> Help`.
|
||||
For versions < 56, copy and paste the Diagnostic info from `Admin -> Troubleshooting`.
|
||||
Copy and paste the JSON from `Admin -> Troubleshooting`.
|
||||
_This will be automatically formatted into code, so no need for backticks._
|
||||
placeholder: |
|
||||
{"browser-info": {
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@
|
||||
|
||||
> **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). Also, if you're attempting to fix a translation issue, please submit your changes to our [Crowdin project](https://crowdin.com/project/metabase-i18n) instead of opening a PR.
|
||||
> 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.
|
||||
|
||||
Closes https://github.com/metabase/metabase/issues/[issue_number]
|
||||
|
||||
|
||||
3
.github/actions/await-port/action.yml
vendored
3
.github/actions/await-port/action.yml
vendored
@@ -1,5 +1,4 @@
|
||||
name: await-port
|
||||
description: Wait for localhost port to be ready
|
||||
name: Wait for localhost port to be ready
|
||||
|
||||
inputs:
|
||||
port:
|
||||
|
||||
116
.github/actions/build-ee-extra/action.yml
vendored
Normal file
116
.github/actions/build-ee-extra/action.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
name: Build ee-extra
|
||||
description: Build Metabase Enterprise Edition with extra features
|
||||
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
default: 21
|
||||
iam-role:
|
||||
description: "The IAM role to assume"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'temurin'
|
||||
- name: Reveal its original version.properties
|
||||
run: jar xf ./bin/docker/metabase.jar version.properties && cat version.properties
|
||||
shell: bash
|
||||
- name: get major version
|
||||
id: major_version
|
||||
run: echo "major_version=$(cat version.properties | grep 'tag=' | cut -d'.' -f2)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: get drivers registry
|
||||
run: |
|
||||
curl -OL https://raw.githubusercontent.com/metabase/metabase-registry/main/registry.yaml
|
||||
shell: bash
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/Jod # 22.x.x
|
||||
- run: rm package.json && npm install yaml
|
||||
shell: bash
|
||||
- name: parse drivers registry
|
||||
uses: actions/github-script@v7
|
||||
id: drivers_registry
|
||||
with:
|
||||
script: | # js
|
||||
const { parse } = require('yaml');
|
||||
const fs = require('fs');
|
||||
const registry = parse(fs.readFileSync('registry.yaml', 'utf8'));
|
||||
|
||||
const drivers = registry.drivers;
|
||||
const majorVersion = '${{ steps.major_version.outputs.major_version }}';
|
||||
|
||||
const getDriverUrl = (driverName) => {
|
||||
const driver = drivers.find(driver => driver.name === driverName);
|
||||
|
||||
if (!driver) {
|
||||
throw new Error(`Driver ${driverName} not found in the registry`);
|
||||
}
|
||||
|
||||
const driverUrl = driver.versions[majorVersion] ?? driver.versions.default;
|
||||
|
||||
if (!driverUrl) {
|
||||
throw new Error(`Driver ${driverName} version ${majorVersion} not found in the registry`);
|
||||
}
|
||||
|
||||
return driverUrl;
|
||||
};
|
||||
|
||||
const extraDrivers = {
|
||||
firebolt: getDriverUrl('firebolt'),
|
||||
starburst: getDriverUrl('starburst'),
|
||||
clickhouse: getDriverUrl('clickhouse'),
|
||||
materialize: getDriverUrl('materialize'),
|
||||
};
|
||||
|
||||
console.log(extraDrivers);
|
||||
|
||||
return extraDrivers;
|
||||
- name: Download Firebolt driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).firebolt }}
|
||||
working-directory: modules
|
||||
shell: bash
|
||||
- name: Download Starburst driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).starburst }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Clickhouse driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).clickhouse }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Materialize driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).materialize }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Bundle additional drivers into the Uberjar
|
||||
run: jar uf bin/docker/metabase.jar modules/*.jar
|
||||
shell: bash
|
||||
- name: Launch uberjar
|
||||
run: >-
|
||||
java --add-opens java.base/java.nio=ALL-UNNAMED -jar ./bin/docker/metabase.jar &
|
||||
shell: bash
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
shell: bash
|
||||
- name: Kill the java process
|
||||
run: pkill -f metabase.jar
|
||||
shell: bash
|
||||
- name: configure aws credentials
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ inputs.iam-role }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Login to Amazon ECR
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
36
.github/actions/cleanup-python-runner/action.yml
vendored
36
.github/actions/cleanup-python-runner/action.yml
vendored
@@ -1,36 +0,0 @@
|
||||
name: Cleanup Python Runner
|
||||
description: Cleanup python-runner services and upload logs as artifacts
|
||||
|
||||
inputs:
|
||||
upload-logs:
|
||||
description: 'Whether to upload logs (typically on failure)'
|
||||
required: false
|
||||
default: 'false'
|
||||
logs-artifact-suffix:
|
||||
description: 'The unique name of the .zip artifact containing the logs'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cleanup python-runner services
|
||||
if: always()
|
||||
run: |
|
||||
echo "Stopping python-runner services..."
|
||||
if [ -d "python-runner" ]; then
|
||||
cd python-runner
|
||||
make stop || echo "WARNING: Failed to stop python runner"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Upload python-runner logs
|
||||
if: ${{inputs.upload-logs == 'true' && inputs.logs-artifact-suffix != ''}}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-runner-logs-${{inputs.logs-artifact-suffix}}
|
||||
path: |
|
||||
python-runner.out.log
|
||||
python-runner.err.log
|
||||
retention-days: 1
|
||||
if-no-files-found: warn
|
||||
17
.github/actions/create-backport/action.yml
vendored
17
.github/actions/create-backport/action.yml
vendored
@@ -23,7 +23,7 @@ runs:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
COMMIT: ${{ inputs.backport-commit }}
|
||||
shell: bash
|
||||
run: | # bash
|
||||
run: |
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
|
||||
@@ -35,8 +35,6 @@ runs:
|
||||
git checkout -b ${BACKPORT_BRANCH}
|
||||
git cherry-pick ${COMMIT} || true
|
||||
|
||||
CONFLICT_LIST=$(git ls-files -u)
|
||||
|
||||
CONFLICTS=$(git ls-files -u | wc -l)
|
||||
if [ "$CONFLICTS" -gt 0 ]; then
|
||||
git cherry-pick --abort
|
||||
@@ -48,7 +46,7 @@ runs:
|
||||
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.\n\nTo backport translations run: bin/i18n/merge-translations <release-branch>'" >> ./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"
|
||||
@@ -57,14 +55,7 @@ runs:
|
||||
#${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. Check the box below when done.
|
||||
|
||||
Conflicts:
|
||||
\`\`\`shell
|
||||
${CONFLICT_LIST}
|
||||
\`\`\`
|
||||
|
||||
- [ ] Conflicts resolved
|
||||
Checkout the branch and run \`./backport.sh\` script. Force push your changes after cherry-picking.
|
||||
END
|
||||
)
|
||||
else
|
||||
@@ -74,7 +65,7 @@ runs:
|
||||
|
||||
git push -u origin ${BACKPORT_BRANCH}
|
||||
|
||||
BACKPORT_PR_URL=$(gh pr create --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" --label "was-backported" --assignee "${{ github.event.pull_request.user.login }}" --title "🤖 backported \"${ORIGINAL_TITLE}\"" --body "${PR_BODY}")
|
||||
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##*/}
|
||||
|
||||
echo "backport_pr_number=$BACKPORT_PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
name: Cross-branch migration test
|
||||
|
||||
inputs:
|
||||
base-ref: ## Base branch PR came from
|
||||
required: true
|
||||
head-ref: ## Source branch of the pull request
|
||||
base-ref:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
## First we deploy the base branch
|
||||
- name: Check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -42,11 +39,10 @@ runs:
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
## Then we deploy the PR branch to ensure it migrates up successfully
|
||||
- name: Check out pull request branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.head-ref }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Run backend on pull request branch
|
||||
run: |
|
||||
@@ -69,38 +65,3 @@ runs:
|
||||
- name: Stop Metabase on pull request branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
## Testing that originally deploying this PR allows you to roll back to a previous release
|
||||
## Reset the checksums so when we go back to the base branch, it's as if the PR branch was what was originally deployed
|
||||
- name: Reset the checksums as if the PR branch was what was originally deployed
|
||||
run: |
|
||||
clojure -M:dev:ee:migrate reset-checksums
|
||||
shell: bash
|
||||
|
||||
- name: Rollback the last deployment
|
||||
run: |
|
||||
clojure -M:dev:ee:migrate rollback last-deployment
|
||||
shell: bash
|
||||
|
||||
- name: Re-check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref }}
|
||||
|
||||
- name: Re-run backend on base branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
@@ -8,7 +8,7 @@ inputs:
|
||||
description: DockerHub password/token
|
||||
required: true
|
||||
maildev:
|
||||
description: Maildev containers
|
||||
description: Maildev container
|
||||
required: true
|
||||
default: false
|
||||
openldap:
|
||||
@@ -31,10 +31,6 @@ inputs:
|
||||
description: Webhook tester container
|
||||
required: false
|
||||
default: false
|
||||
snowplow:
|
||||
description: Snowplow micro container
|
||||
required: false
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -46,68 +42,56 @@ runs:
|
||||
password: ${{ inputs.password }}
|
||||
|
||||
- name: Start Containers
|
||||
shell: bash
|
||||
run: | # bash
|
||||
run: |
|
||||
Y='\033[33m'
|
||||
G='\033[1;32m'
|
||||
|
||||
cd e2e/test/scenarios/
|
||||
|
||||
docker compose up \
|
||||
$([ ${{ inputs.postgres }} = true ] && echo "postgres-sample") \
|
||||
$([ ${{ inputs.mysql }} = true ] && echo "mysql-sample") \
|
||||
$([ ${{ inputs.mongo }} = true ] && echo "mongo-sample") \
|
||||
$([ ${{ inputs.maildev }} = true ] && echo "maildev") \
|
||||
$([ ${{ inputs.maildev }} = true ] && echo "maildev-ssl") \
|
||||
$([ ${{ inputs.openldap }} = true ] && echo "ldap") \
|
||||
$([ ${{ inputs.webhook }} = true ] && echo "webhook-tester") \
|
||||
$([ ${{ inputs.snowplow }} = true ] && echo "snowplow-micro") \
|
||||
-d
|
||||
|
||||
if ${{ inputs.maildev }}; then
|
||||
echo -e "${Y}checking maildev container..." &&
|
||||
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!"
|
||||
|
||||
echo -e "${Y}checking maildev-ssl container..." &&
|
||||
while ! nc -z localhost 1081; do sleep 1; done &&
|
||||
while ! nc -z localhost 465; do sleep 1; done &&
|
||||
echo -e "${G}Maildev-ssl is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.openldap }}; then
|
||||
echo -e "${Y}checking openldap container..." &&
|
||||
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}checking postgres container..." &&
|
||||
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}checking mysql container..." &&
|
||||
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}checking mongo container..." &&
|
||||
echo -e "${Y}Starting mongo container..." &&
|
||||
docker run -d -p 27004:27017 metabase/qa-databases:mongo-sample-4.4 &&
|
||||
while ! nc -z localhost 27004; do sleep 1; done &&
|
||||
echo -e "${G}Mongo is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.webhook }}; then
|
||||
echo -e "${Y}checking webhook test container..." &&
|
||||
echo -e "${Y}Starting webhook test container..." &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester:1.1.0 serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
while ! nc -z localhost 9080; do sleep 1; done &&
|
||||
echo -e "${G}Webhook tester is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.snowplow }}; then
|
||||
echo -e "${Y}checking snowplow micro container..." &&
|
||||
while ! nc -z localhost 9090; do sleep 1; done &&
|
||||
echo -e "${G}Snowplow micro is up and running!"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
105
.github/actions/fetch-artifact/action.yml
vendored
105
.github/actions/fetch-artifact/action.yml
vendored
@@ -1,105 +0,0 @@
|
||||
name: Fetch artifact
|
||||
description: Fetches a specific artifact and downloads it to the current directory
|
||||
|
||||
inputs:
|
||||
name:
|
||||
description: The name of the artifact to fetch
|
||||
required: true
|
||||
output-name:
|
||||
description: Optional custom name for the output jar file (defaults to metabase.jar)
|
||||
required: false
|
||||
default: "metabase.jar"
|
||||
extract-path:
|
||||
description: Directory path where the jar should be extracted (defaults to current directory)
|
||||
required: false
|
||||
default: "."
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: find_release_artifact
|
||||
id: find_release_artifact
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
result-encoding: string
|
||||
script: | # js
|
||||
const fs = require('fs');
|
||||
|
||||
const artifacts = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: "${{ inputs.name }}",
|
||||
per_page: 1,
|
||||
});
|
||||
|
||||
if (!artifacts.data?.artifacts?.[0]?.id) {
|
||||
throw new Error(`No artifacts found for ${{ inputs.name }}`);
|
||||
}
|
||||
|
||||
const artifact_id = artifacts.data.artifacts[0].id;
|
||||
|
||||
const download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact_id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/mb.zip`, Buffer.from(download.data));
|
||||
|
||||
- name: Extract and rename JAR
|
||||
shell: bash
|
||||
run: | # bash
|
||||
OUTPUT_NAME="${{ inputs.output-name }}"
|
||||
EXTRACT_PATH="${{ inputs.extract-path }}"
|
||||
|
||||
# Create a temporary directory for extraction to avoid conflicts
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
|
||||
# Extract to temporary directory
|
||||
unzip mb.zip -d "$TEMP_DIR"
|
||||
|
||||
# Create target directory
|
||||
mkdir -p "$EXTRACT_PATH"
|
||||
|
||||
# Find the jar and move all files from its directory
|
||||
if [ -e "$TEMP_DIR/metabase.jar" ]; then
|
||||
# Move all files to extract path
|
||||
mv "$TEMP_DIR"/* "$EXTRACT_PATH/" 2>/dev/null || true
|
||||
# Rename the jar to custom name if different
|
||||
if [ "$OUTPUT_NAME" != "metabase.jar" ]; then
|
||||
mv "$EXTRACT_PATH/metabase.jar" "$EXTRACT_PATH/$OUTPUT_NAME"
|
||||
fi
|
||||
elif [ -e "$TEMP_DIR/target/uberjar/metabase.jar" ]; then
|
||||
# Move all files from uberjar directory to extract path
|
||||
mv "$TEMP_DIR/target/uberjar"/* "$EXTRACT_PATH/" 2>/dev/null || true
|
||||
# Rename the jar to custom name if different
|
||||
if [ "$OUTPUT_NAME" != "metabase.jar" ]; then
|
||||
mv "$EXTRACT_PATH/metabase.jar" "$EXTRACT_PATH/$OUTPUT_NAME"
|
||||
fi
|
||||
else
|
||||
echo "Could not find metabase.jar in extracted files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up temporary directory
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Set the JAR_PATH environment variable
|
||||
if [ "$EXTRACT_PATH" == "." ]; then
|
||||
echo "JAR_PATH=$OUTPUT_NAME" >> $GITHUB_ENV
|
||||
else
|
||||
echo "JAR_PATH=$EXTRACT_PATH/$OUTPUT_NAME" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Verify that this is a valid JAR file
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Verifying JAR at: $JAR_PATH"
|
||||
file --mime-type "$JAR_PATH"
|
||||
|
||||
- name: Reveal its version.properties
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Extracting version.properties from: $JAR_PATH"
|
||||
jar xf "$JAR_PATH" version.properties && cat version.properties
|
||||
74
.github/actions/get-docker-info/action.yml
vendored
74
.github/actions/get-docker-info/action.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: Get Docker Info
|
||||
description: Determines Docker repository and tag based on branch and edition
|
||||
|
||||
inputs:
|
||||
branch:
|
||||
description: Branch name (defaults to current branch)
|
||||
required: false
|
||||
default: ${{ github.head_ref || github.ref_name }}
|
||||
edition:
|
||||
description: Edition (ee or oss)
|
||||
required: false
|
||||
default: ee
|
||||
owner:
|
||||
description: Repository owner (defaults to current repo owner)
|
||||
required: false
|
||||
default: ${{ github.repository_owner }}
|
||||
|
||||
outputs:
|
||||
repo:
|
||||
description: Docker repository name
|
||||
value: ${{ steps.docker-info.outputs.repo }}
|
||||
tag:
|
||||
description: Docker tag
|
||||
value: ${{ steps.docker-info.outputs.tag }}
|
||||
full_image:
|
||||
description: Full Docker image name (repo:tag)
|
||||
value: ${{ steps.docker-info.outputs.full_image }}
|
||||
editions:
|
||||
description: JSON array of editions to build
|
||||
value: ${{ steps.docker-info.outputs.editions }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Determine Docker info
|
||||
id: docker-info
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
const branch = '${{ inputs.branch }}';
|
||||
const edition = '${{ inputs.edition }}';
|
||||
const owner = '${{ inputs.owner }}';
|
||||
|
||||
console.log(`Determining Docker info for branch: ${branch}, edition: ${edition}`);
|
||||
|
||||
let repo, tag, editions;
|
||||
|
||||
if (branch === 'master') {
|
||||
// Master builds both editions with specific repos
|
||||
editions = ['ee', 'oss'];
|
||||
tag = 'latest';
|
||||
|
||||
if (edition === 'ee') {
|
||||
repo = `${owner}/metabase-enterprise-head`;
|
||||
} else {
|
||||
repo = `${owner}/metabase-head`;
|
||||
}
|
||||
} else {
|
||||
// All other branches only build EE to metabase-dev
|
||||
editions = ['ee'];
|
||||
tag = branch.replaceAll('/', '-'); // slashes make docker sad
|
||||
repo = `${owner}/metabase-dev`;
|
||||
}
|
||||
|
||||
const fullImage = `${repo}:${tag}`;
|
||||
|
||||
// Set outputs
|
||||
core.setOutput('repo', repo);
|
||||
core.setOutput('tag', tag);
|
||||
core.setOutput('full_image', fullImage);
|
||||
core.setOutput('editions', JSON.stringify(editions));
|
||||
|
||||
console.log(`Docker image: ${fullImage}`);
|
||||
console.log(`Editions: ${JSON.stringify(editions)}`);
|
||||
49
.github/actions/notify-pull-request/action.yml
vendored
49
.github/actions/notify-pull-request/action.yml
vendored
@@ -1,13 +1,5 @@
|
||||
name: Notify Pull Request
|
||||
inputs:
|
||||
remove-previous-comment:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
add-new-comment:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
message:
|
||||
required: true
|
||||
include-log:
|
||||
@@ -17,44 +9,7 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Remove previous pull-request comment
|
||||
uses: actions/github-script@v7
|
||||
if: ${{ inputs.remove-previous-comment == 'true' && always() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request ?? context.payload.issue;
|
||||
|
||||
if (!pr) {
|
||||
console.error("This action can only be run on pull requests");
|
||||
return;
|
||||
}
|
||||
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
}).catch(console.error);
|
||||
|
||||
if (!comments) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = "${{ inputs.message }}";
|
||||
const comment = comments.find((comment) => comment.body.includes(message));
|
||||
|
||||
if (comment) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id,
|
||||
});
|
||||
}
|
||||
|
||||
- name: Notify pull-request
|
||||
uses: actions/github-script@v7
|
||||
if: ${{ inputs.add-new-comment == 'true' && always() && github.event_name == 'pull_request' }}
|
||||
- uses: actions/github-script@v7
|
||||
id: notify-pull-request
|
||||
with:
|
||||
result-encoding: string
|
||||
@@ -66,7 +21,7 @@ runs:
|
||||
return;
|
||||
}
|
||||
|
||||
const message = "${{ inputs.message }}";
|
||||
const message = "${{ inputs.message }}"
|
||||
const shouldIncludeLog = ${{ inputs.include-log }}.toString() === "true";
|
||||
const author = context.payload.sender.login;
|
||||
|
||||
|
||||
8
.github/actions/prepare-backend/action.yml
vendored
8
.github/actions/prepare-backend/action.yml
vendored
@@ -1,7 +1,4 @@
|
||||
name: Prepare back-end environment
|
||||
description: |
|
||||
Prepare the backend. Installs jdk, Clojure, and fetches the cache for m2. The CI config fetchs `ci-test-config.json`
|
||||
from master and lets us ignore tests at the var level if they are flaking.
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
@@ -24,14 +21,9 @@ runs:
|
||||
distribution: 'temurin'
|
||||
- name: Install Clojure CLI
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
run: |
|
||||
curl -O https://download.clojure.org/install/linux-install-${{ inputs.clojure-version }}.sh &&
|
||||
sudo bash ./linux-install-${{ inputs.clojure-version }}.sh
|
||||
- name: Fetch CI config
|
||||
shell: bash
|
||||
run: |
|
||||
curl -o ci-test-config.json https://raw.githubusercontent.com/metabase/metabase/refs/heads/master/ci-test-config.json
|
||||
- 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"
|
||||
|
||||
7
.github/actions/prepare-cypress/action.yml
vendored
7
.github/actions/prepare-cypress/action.yml
vendored
@@ -1,12 +1,6 @@
|
||||
name: Prepare cypress environment
|
||||
description: Cypress preparation steps
|
||||
|
||||
inputs:
|
||||
working-directory:
|
||||
description: "Optional working directory for yarn commands"
|
||||
required: false
|
||||
default: "."
|
||||
|
||||
outputs:
|
||||
chrome-path:
|
||||
description: Path to the custom Chrome executable
|
||||
@@ -39,4 +33,3 @@ runs:
|
||||
yarn cypress cache list
|
||||
yarn cypress verify
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
13
.github/actions/run-snowplow-micro/action.yml
vendored
Normal file
13
.github/actions/run-snowplow-micro/action.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Run Snowplow micro
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Download Snowplow micro JAR
|
||||
run: curl -L -o snowplow-micro.jar https://github.com/snowplow-incubator/snowplow-micro/releases/download/micro-1.2.1/snowplow-micro-1.2.1.jar
|
||||
shell: bash
|
||||
- name: Launch Snowplow micro
|
||||
run: java -cp snowplow-micro.jar:snowplow com.snowplowanalytics.snowplow.micro.Main --collector-config snowplow/micro.conf --iglu snowplow/iglu.json &
|
||||
shell: bash
|
||||
- name: Wait until Snowplow micro is ready
|
||||
run: while ! curl -s 'http://localhost:9090' | grep '404'; do sleep 1; done
|
||||
shell: bash
|
||||
27
.github/actions/setup-python-runner/action.yml
vendored
27
.github/actions/setup-python-runner/action.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Setup Python Runner
|
||||
description: Setup python-runner for tests
|
||||
inputs:
|
||||
metabase-automation-token:
|
||||
description: Token for accessing the python-runner repository
|
||||
required: true
|
||||
python-runner-version:
|
||||
description: Git tag or commit of the python-runner repository to checkout
|
||||
required: false
|
||||
default: 'v0.0.1'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Clone and setup python-runner
|
||||
run: |
|
||||
echo "Cloning python-runner repository..."
|
||||
git clone --depth 1 --branch ${{ inputs.python-runner-version }} \
|
||||
https://${{ inputs.metabase-automation-token }}@github.com/metabase/python-runner-container.git python-runner
|
||||
|
||||
echo "Starting python-runner..."
|
||||
cd python-runner
|
||||
make run-ci 1> ../python-runner.out.log 2> ../python-runner.err.log
|
||||
make logs 1>> ../python-runner.out.log 2>> ../python-runner.err.log &
|
||||
cd ..
|
||||
shell: bash
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ inputs.metabase-automation-token }}
|
||||
10
.github/actions/test-driver/action.yml
vendored
10
.github/actions/test-driver/action.yml
vendored
@@ -1,5 +1,4 @@
|
||||
name: Test database driver
|
||||
description: Test database driver
|
||||
inputs:
|
||||
junit-name:
|
||||
required: true
|
||||
@@ -24,8 +23,6 @@ runs:
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Test database driver
|
||||
run: clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
|
||||
shell: bash
|
||||
@@ -39,10 +36,3 @@ runs:
|
||||
list-suites: failed
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
- name: Upload Logs on Failure
|
||||
if: always() && !success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-logs-${{ github.job }}
|
||||
path: logs/test-log.json
|
||||
retention-days: 1
|
||||
|
||||
@@ -11,7 +11,4 @@ runs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "github-automation@metabase.com"
|
||||
git config --global user.name "Metabase Automation"
|
||||
bash ${{ github.action_path }}/upgrade-deps.sh
|
||||
run: bash ${{ github.action_path }}/upgrade-deps.sh
|
||||
|
||||
@@ -59,11 +59,6 @@ for upgrade in $UPGRADES; do
|
||||
|
||||
# Commit the dependency update, and link to the diff
|
||||
git add deps.edn modules
|
||||
|
||||
# Use an email and name that will pass the CLA check:
|
||||
git config --global user.email "github-automation@metabase.com"
|
||||
git config --global user.name "Metabase Automation"
|
||||
|
||||
git commit -m ${TITLE} -m ${BODY} --no-verify
|
||||
git push -u origin $BRANCH_NAME
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@ inputs:
|
||||
required: true
|
||||
trunk-api-token:
|
||||
required: true
|
||||
variant:
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -48,11 +45,10 @@ runs:
|
||||
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
|
||||
|
||||
- name: Upload results to Trunk
|
||||
if: ${{ always() && !(github.event_name == 'pull_request' && github.event.pull_request.draft == true) }}
|
||||
if: ${{ always() }}
|
||||
uses: trunk-io/analytics-uploader@main
|
||||
with:
|
||||
junit-paths: ${{ inputs.input-path }}
|
||||
variant: ${{ inputs.variant || inputs.output-name }}
|
||||
org-slug: metabase
|
||||
token: ${{ inputs.trunk-api-token }}
|
||||
continue-on-error: true
|
||||
|
||||
17
.github/auto_request_review.yml
vendored
Normal file
17
.github/auto_request_review.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# The reviewers configuration for the auto_request_review workflow
|
||||
# See https://github.com/necojackarc/auto-request-review/tree/master?tab=readme-ov-file#reviewers-configuration
|
||||
# for the spec
|
||||
|
||||
files:
|
||||
# migrations related files
|
||||
'resources/migrations/**':
|
||||
- team:core-workflows
|
||||
'src/metabase/db/custom_migrations.clj':
|
||||
- team:core-workflows
|
||||
|
||||
options:
|
||||
ignore_draft: true
|
||||
ignored_keywords:
|
||||
- DO NOT REVIEW
|
||||
- WIP
|
||||
enable_group_assignment: false
|
||||
52
.github/copilot-instructions.md
vendored
52
.github/copilot-instructions.md
vendored
@@ -1,52 +0,0 @@
|
||||
# Metabase Developer Assistant Instructions
|
||||
|
||||
## Frontend Development
|
||||
|
||||
### Repository Structure
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── metabase/ # Main application components and pages
|
||||
├── metabase-lib/ # Query building and data modeling utilities
|
||||
├── metabase-types/ # TypeScript type definitions
|
||||
└── metabase/ui/ # Design system components
|
||||
|
||||
e2e/ # Cypress end-to-end tests
|
||||
frontend/test/ # Jest unit tests
|
||||
```
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Framework**: React 18 with TypeScript
|
||||
- **State**: Redux Toolkit
|
||||
- **Styling**: CSS Modules (preferred) > Emotion styled-components
|
||||
- **UI**: `metabase/ui` components built with Mantine v8
|
||||
- **Build**: Rspack (primary), Webpack (legacy)
|
||||
- **Testing**: Jest + React Testing Library, Cypress
|
||||
|
||||
### Coding Standards
|
||||
|
||||
#### Component Preferences
|
||||
|
||||
- Prefer `metabase/ui`components over `metabase/common/components`
|
||||
- Use `.tsx` for components, `.ts` for utilities
|
||||
|
||||
#### Styling
|
||||
|
||||
ALWAYS prefer Mantine style props,then CSS modules. DO NOT suggest styled components, they are deprecated.
|
||||
|
||||
#### TypeScript Migration
|
||||
|
||||
When heavily editing `.js`/`.jsx` files, create a separate PR to convert to TypeScript first, then implement changes.
|
||||
|
||||
#### Enterprise Features
|
||||
|
||||
Enterprise functionality MUST use the plugin system. It is very important to not expose enterprise code in the OSS version.
|
||||
|
||||
#### Testing Requirements
|
||||
|
||||
All PRs should include tests. Prefer Unit tests over E2E tests.
|
||||
|
||||
#### Localization
|
||||
|
||||
All user-facing strings MUST be localized using the ttag library. Localized strings should be complete phrases, do not concatenate a few separately localized strings. You should add context to strings where the meaning of the string might not be obvious in isolation: e.g. "Home" might have different words in some languages depending on whether you're talking about a dwelling or the landing page for a website.
|
||||
72
.github/file-paths.yaml
vendored
72
.github/file-paths.yaml
vendored
@@ -16,49 +16,26 @@ frontend_ci: &frontend_ci
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/frontend.yml"
|
||||
|
||||
frontend_embedding_sdk_ci: &frontend_embedding_sdk_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/*embedding-sdk*.yml"
|
||||
|
||||
frontend_configs: &frontend_configs
|
||||
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"
|
||||
- "rspack.config.js"
|
||||
- "rspack.main.config.js"
|
||||
- "rspack.embedding-sdk-package.config.js"
|
||||
- "rspack.embedding-sdk-bundle.config.js"
|
||||
- "rspack.static-viz.config.js"
|
||||
- "webpack.config.js"
|
||||
- "webpack.embedding-sdk.config.js"
|
||||
- "webpack.static-viz.config.js"
|
||||
- ".nvmrc"
|
||||
|
||||
frontend_sources: &frontend_sources
|
||||
- *shared_sources
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sources:
|
||||
- "enterprise/frontend/src/{embedding,embedding-*}/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "enterprise/frontend/src/metabase-enterprise/{embedding,embedding-*}**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_sources:
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_package_sources:
|
||||
- "enterprise/frontend/src/embedding-sdk-package/**/!(*.spec).{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_specs: &frontend_specs
|
||||
- *shared_specs
|
||||
- "frontend/test/**"
|
||||
- "frontend/**/tests/**"
|
||||
- "frontend/**/*.unit.*"
|
||||
- "enterprise/frontend/**/*.unit.*"
|
||||
- ".github/**/*.unit.spec.*"
|
||||
- "jest.config.js"
|
||||
- "jest.tz.unit.conf.json"
|
||||
|
||||
@@ -122,49 +99,34 @@ e2e_ci: &e2e_ci
|
||||
- ".github/actions/prepare-uberjar-artifact/**"
|
||||
- ".github/actions/e2e-prepare-containers/**"
|
||||
- ".github/actions/prepare-cypress/**"
|
||||
- ".github/actions/run-snowplow-micro/**"
|
||||
- ".github/workflows/e2e-*"
|
||||
|
||||
e2e_specs: &e2e_specs
|
||||
- e2e/!(test-host-app)**/*.cy.*.{js,ts}
|
||||
- "**/*.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_embedding_sdk:
|
||||
- *frontend_embedding_sdk_ci
|
||||
- *sources
|
||||
- "e2e/component/**/*.cy.*.{js,ts}"
|
||||
|
||||
e2e_embedding_sdk_compatibility:
|
||||
- *default
|
||||
- *frontend_embedding_sdk_ci
|
||||
- *frontend_sources
|
||||
- *backend_sources
|
||||
- "e2e/embedding-sdk-host-apps/**"
|
||||
- "e2e/runner/embedding-sdk/**"
|
||||
- "e2e/test-host-app/**/*.cy.*.{js,ts}"
|
||||
|
||||
snowplow: &snowplow
|
||||
- ".github/workflows/snowplow.yml"
|
||||
- "snowplow/**"
|
||||
|
||||
e2e_all:
|
||||
- *default
|
||||
- *e2e_ci
|
||||
- *sources
|
||||
- *e2e_specs
|
||||
- "e2e/**"
|
||||
- *snowplow
|
||||
|
||||
snowplow:
|
||||
- ".github/workflows/snowplow.yml"
|
||||
- "snowplow/**"
|
||||
|
||||
documentation:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
|
||||
embedding_documentation:
|
||||
- ".typedoc/**"
|
||||
- "docs/embedding/**"
|
||||
|
||||
yaml:
|
||||
- "**/*.yml"
|
||||
- "**/*.yaml"
|
||||
|
||||
172
.github/scripts/average-e2e-timings.js
vendored
172
.github/scripts/average-e2e-timings.js
vendored
@@ -1,172 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const collectAndAverageTimings = async ({
|
||||
github,
|
||||
context,
|
||||
daysBack = 7,
|
||||
existingTimingsPath = "e2e/support/timings.json",
|
||||
}) => {
|
||||
try {
|
||||
const artifacts = await findMergedTimingArtifacts({
|
||||
github,
|
||||
context,
|
||||
daysBack,
|
||||
});
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
throw new Error("No timing artifacts found in the specified time range");
|
||||
}
|
||||
|
||||
const timingData = await downloadAndExtractArtifacts(
|
||||
artifacts,
|
||||
token,
|
||||
github,
|
||||
context,
|
||||
);
|
||||
|
||||
if (timingData.length === 0) {
|
||||
throw new Error("No valid timing data extracted from artifacts");
|
||||
}
|
||||
|
||||
const averagedTimings = averageTimings(timingData);
|
||||
averagedTimings.durations.sort((a, b) => a.spec.localeCompare(b.spec));
|
||||
|
||||
fs.writeFileSync(
|
||||
existingTimingsPath,
|
||||
JSON.stringify(averagedTimings, null, 2),
|
||||
);
|
||||
console.log(`✅ Updated ${existingTimingsPath} with new timing data`);
|
||||
|
||||
return { artifactCount: timingData.length };
|
||||
} catch (error) {
|
||||
console.error("❌ Error in collectAndAverageTimings:", error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const findMergedTimingArtifacts = async ({ github, context, daysBack }) => {
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - daysBack);
|
||||
|
||||
console.log(
|
||||
`Searching for merged timing artifacts created after ${cutoffDate.toISOString()}`,
|
||||
);
|
||||
|
||||
const artifacts = [];
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { data } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: "merged-e2e-timings",
|
||||
per_page: 100,
|
||||
page,
|
||||
});
|
||||
|
||||
const validArtifacts = data.artifacts
|
||||
.filter((a) => new Date(a.created_at) > cutoffDate && !a.expired)
|
||||
.map(({ name, created_at, id }) => ({ name, created_at, id }));
|
||||
artifacts.push(...validArtifacts);
|
||||
|
||||
if (
|
||||
data.artifacts.some((a) => new Date(a.created_at) <= cutoffDate) ||
|
||||
data.artifacts.length < 100
|
||||
) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Error fetching artifacts page ${page - 1}: ${error.message}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${artifacts.length} merged timing artifacts total`);
|
||||
return artifacts;
|
||||
};
|
||||
|
||||
const downloadAndExtractArtifacts = async (artifacts, github, context) => {
|
||||
const tempDir = "timing-artifacts-temp";
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
const timingData = [];
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
try {
|
||||
console.log(`Downloading artifact: ${artifact.name}`);
|
||||
|
||||
const zipPath = path.join(tempDir, `${artifact.name}.zip`);
|
||||
const extractPath = path.join(tempDir, artifact.name);
|
||||
|
||||
const { data } = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: "zip",
|
||||
});
|
||||
|
||||
fs.writeFileSync(zipPath, Buffer.from(data));
|
||||
fs.mkdirSync(extractPath, { recursive: true });
|
||||
execSync(`unzip -q "${zipPath}" -d "${extractPath}"`);
|
||||
|
||||
const timingFilePath = path.join(extractPath, "runTimings.json");
|
||||
|
||||
if (fs.existsSync(timingFilePath)) {
|
||||
const content = JSON.parse(fs.readFileSync(timingFilePath, "utf8"));
|
||||
|
||||
if (content.durations?.length) {
|
||||
timingData.push(content);
|
||||
console.log(
|
||||
`✅ Extracted timing data from ${artifact.name} (${content.durations.length} specs)`,
|
||||
);
|
||||
} else {
|
||||
console.log(`⚠ Invalid timing data structure in ${artifact.name}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠ No timing data found in ${artifact.name}`);
|
||||
}
|
||||
|
||||
fs.rmSync(extractPath, { recursive: true });
|
||||
} catch (error) {
|
||||
console.log(`❌ Error processing ${artifact.name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
fs.rmSync(tempDir, { recursive: true });
|
||||
|
||||
console.log(
|
||||
`Successfully extracted timing data from ${timingData.length}/${artifacts.length} artifacts`,
|
||||
);
|
||||
return timingData;
|
||||
};
|
||||
|
||||
const averageTimings = (timingDataArray) => {
|
||||
if (!timingDataArray.length) {
|
||||
throw new Error("No timing data to process");
|
||||
}
|
||||
|
||||
const specTimings = timingDataArray
|
||||
.flatMap((data) => data.durations)
|
||||
.filter((item) => item.spec && typeof item.duration === "number")
|
||||
.reduce((acc, { spec, duration }) => {
|
||||
(acc[spec] = acc[spec] || []).push(duration);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const durations = Object.entries(specTimings).map(([spec, durations]) => ({
|
||||
spec,
|
||||
duration: Math.round(
|
||||
durations.reduce((sum, d) => sum + d, 0) / durations.length,
|
||||
),
|
||||
}));
|
||||
|
||||
return { durations };
|
||||
};
|
||||
|
||||
module.exports = { collectAndAverageTimings, averageTimings };
|
||||
132
.github/scripts/average-e2e-timings.unit.spec.js
vendored
132
.github/scripts/average-e2e-timings.unit.spec.js
vendored
@@ -1,132 +0,0 @@
|
||||
const { averageTimings } = require("./average-e2e-timings");
|
||||
|
||||
describe("averageTimings", () => {
|
||||
it("should throw error when given empty array", () => {
|
||||
expect(() => averageTimings([])).toThrow("No timing data to process");
|
||||
});
|
||||
|
||||
it("should work for n=1", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should calculate average for multiple specs", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 2000 },
|
||||
{ spec: "test2.cy.js", duration: 4000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1500,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should round averages to nearest integer", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [{ spec: "test1.cy.js", duration: 1001 }],
|
||||
},
|
||||
{
|
||||
durations: [{ spec: "test1.cy.js", duration: 1002 }],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations[0].duration).toBe(1002);
|
||||
});
|
||||
|
||||
it("should ignore invalid entries", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: "invalid" },
|
||||
{ duration: 2000 }, // missing spec
|
||||
{ spec: "test3.cy.js", duration: 3000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle merging different specs", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 3000 },
|
||||
{ spec: "test3.cy.js", duration: 1000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(3);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
});
|
||||
});
|
||||
84
.github/scripts/build-e2e-matrix.js
vendored
84
.github/scripts/build-e2e-matrix.js
vendored
@@ -1,84 +0,0 @@
|
||||
// This script is used in .github/workflows/e2e-matrix-builder.yml
|
||||
// its aim is to split the e2e test matrix into multiple jobs
|
||||
// grouping some specific tests together, other tests are split into chunks
|
||||
|
||||
const DEFAULT_SPEC_PATTERN = "./e2e/test/scenarios/**/*.cy.spec.*";
|
||||
const EMBEDDING_SDK_SPEC_PATTERN =
|
||||
"./e2e/test/scenarios/embedding-sdk/**.cy.spec.*";
|
||||
|
||||
const specialTestConfigs = [
|
||||
{
|
||||
name: "embedding-sdk",
|
||||
specs: EMBEDDING_SDK_SPEC_PATTERN,
|
||||
},
|
||||
{
|
||||
name: "oss-subset",
|
||||
edition: "oss",
|
||||
tags: "@OSS @smoke+-@EE",
|
||||
specs: DEFAULT_SPEC_PATTERN,
|
||||
},
|
||||
{ name: "mongo", tags: "@mongo", specs: DEFAULT_SPEC_PATTERN },
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} inputSpecs - specs, which were changed in the PR, separated by comma
|
||||
* .e.g e2e/test/scenarios/onboarding/command-palette.cy.spec.js,e2e/test/scenarios/question/document-title.cy.spec.js
|
||||
* or a pattern like DEFAULT_SPEC_PATTERN
|
||||
*/
|
||||
function buildMatrix(options, inputSpecs, inputChunks) {
|
||||
const { java, defaultRunner } = options;
|
||||
|
||||
// number of specs per chunk when running specific specs
|
||||
const SPECS_PER_CHUNK = 5;
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
runner: defaultRunner,
|
||||
edition: "ee",
|
||||
};
|
||||
|
||||
const isDefaultSpecPattern =
|
||||
inputSpecs === "" || inputSpecs === DEFAULT_SPEC_PATTERN;
|
||||
|
||||
console.log("Processed specs value:", inputSpecs);
|
||||
console.log("Is default pattern:", isDefaultSpecPattern);
|
||||
|
||||
let regularChunks;
|
||||
if (isDefaultSpecPattern) {
|
||||
regularChunks = inputChunks - specialTestConfigs.length;
|
||||
} else {
|
||||
// when pattern is not default, it means we passed some custom list of the changed specs
|
||||
// so we need to calculate how many chunks we need to run
|
||||
const matchingSpecsCount = inputSpecs.split(",").length;
|
||||
regularChunks = Math.max(
|
||||
1,
|
||||
Math.ceil(matchingSpecsCount / SPECS_PER_CHUNK),
|
||||
);
|
||||
}
|
||||
|
||||
const regularTests = new Array(regularChunks).fill(1).map((files, index) => ({
|
||||
name: `e2e-group-${index + 1}`,
|
||||
// works when specs less than 5, otherwise seems all chunks will contain
|
||||
// same specs
|
||||
...(!isDefaultSpecPattern && {
|
||||
specs: inputSpecs
|
||||
.split(",")
|
||||
.slice(SPECS_PER_CHUNK * index, SPECS_PER_CHUNK * (index + 1))
|
||||
.join(","),
|
||||
}),
|
||||
}));
|
||||
|
||||
const testSets = isDefaultSpecPattern
|
||||
? regularTests.concat(specialTestConfigs)
|
||||
: regularTests;
|
||||
|
||||
const config = testSets.map((options) => ({
|
||||
...defaultOptions,
|
||||
...options,
|
||||
}));
|
||||
|
||||
return { config, regularChunks, isDefaultSpecPattern };
|
||||
}
|
||||
|
||||
module.exports = { buildMatrix };
|
||||
97
.github/scripts/build-e2e-matrix.unit.spec.js
vendored
97
.github/scripts/build-e2e-matrix.unit.spec.js
vendored
@@ -1,97 +0,0 @@
|
||||
const { buildMatrix } = require("./build-e2e-matrix");
|
||||
|
||||
describe("buildMatrix", () => {
|
||||
const options = {
|
||||
java: "11",
|
||||
defaultRunner: "ubuntu-22.04",
|
||||
};
|
||||
const testBuildMatrix = (entry, chunks) =>
|
||||
buildMatrix(options, entry, chunks);
|
||||
|
||||
it("should calculate correct regular chunks with default parameters", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 30);
|
||||
|
||||
expect(result.regularChunks).toBe(27);
|
||||
});
|
||||
|
||||
it("should create correct number of configuration objects", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 10);
|
||||
|
||||
expect(result.config.length).toBe(10);
|
||||
});
|
||||
|
||||
it("should apply default specs path when empty entry is provided", () => {
|
||||
const result = testBuildMatrix("", 5);
|
||||
|
||||
expect(result.regularChunks).toBe(2);
|
||||
expect(result.config.length).toBe(5);
|
||||
});
|
||||
|
||||
it("should name regular test groups sequentially", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 10);
|
||||
const regularTests = result.config.filter((cfg) =>
|
||||
cfg.name.startsWith("e2e-group-"),
|
||||
);
|
||||
|
||||
regularTests.forEach((test, index) => {
|
||||
expect(test.name).toBe(`e2e-group-${index + 1}`);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle case when input chunks equals special tests count", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 3);
|
||||
|
||||
expect(result.regularChunks).toBe(0);
|
||||
expect(result.config.length).toBe(3);
|
||||
expect(result.config[0].name).toBe("embedding-sdk");
|
||||
expect(result.config[1].name).toBe("oss-subset");
|
||||
expect(result.config[2].name).toBe("mongo");
|
||||
});
|
||||
|
||||
it("should correctly calculate regularChunks for non-default spec pattern", () => {
|
||||
const entry = "file.cy.spec.js,".repeat(12).slice(0, -1);
|
||||
const result = testBuildMatrix(entry, 10);
|
||||
|
||||
// For 12 matching specs with SPECS_PER_CHUNK=5, we expect ceil(12/5) = 3 chunks
|
||||
expect(result.regularChunks).toBe(3);
|
||||
|
||||
const firstGroupSpecs = result.config[0].specs.split(",").length;
|
||||
const secondGroupSpecs = result.config[1].specs.split(",").length;
|
||||
const thirdGroupSpecs = result.config[2].specs.split(",").length;
|
||||
|
||||
expect(firstGroupSpecs).toBe(5);
|
||||
expect(secondGroupSpecs).toBe(5);
|
||||
expect(thirdGroupSpecs).toBe(2);
|
||||
});
|
||||
|
||||
it("should return isDefaultSpecPattern as true when empty or default value is provided", () => {
|
||||
let result = testBuildMatrix("", 5);
|
||||
|
||||
expect(result.isDefaultSpecPattern).toBe(true);
|
||||
|
||||
result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 5);
|
||||
|
||||
expect(result.isDefaultSpecPattern).toBe(true);
|
||||
});
|
||||
|
||||
it("should return isDefaultSpecPattern as false when non-default spec pattern is provided", () => {
|
||||
const result = testBuildMatrix("test1.cy.spec.js,test2.cy.spec.js", 1);
|
||||
|
||||
expect(result.isDefaultSpecPattern).toBe(false);
|
||||
});
|
||||
|
||||
it("should return special test configs when default spec pattern is provided", () => {
|
||||
const result = testBuildMatrix("./e2e/test/scenarios/**/*.cy.spec.*", 50);
|
||||
|
||||
expect(result.config[47].name).toBe("embedding-sdk");
|
||||
expect(result.config[48].name).toBe("oss-subset");
|
||||
expect(result.config[49].name).toBe("mongo");
|
||||
});
|
||||
|
||||
it("should not return special test configs when non-default spec pattern is provided", () => {
|
||||
const result = testBuildMatrix("test1.cy.spec.js,test2.cy.spec.js", 50);
|
||||
|
||||
expect(result.config.length).toBe(1);
|
||||
expect(result.config[0].name).toBe("e2e-group-1");
|
||||
});
|
||||
});
|
||||
@@ -1,66 +1,24 @@
|
||||
const TYPES = new Set([
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"docs",
|
||||
"style",
|
||||
"refactor",
|
||||
"test",
|
||||
"build",
|
||||
"ci",
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param title {string}
|
||||
*/
|
||||
function isConventionalTitle(title) {
|
||||
const match = /^(?<type>\w+)\((?<rawScopes>[^)]+)\):/.exec(title);
|
||||
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, type, rawScopes] = match;
|
||||
const scopes = rawScopes
|
||||
.split(",")
|
||||
.map((scope) => scope.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return TYPES.has(type) && scopes.length > 0;
|
||||
}
|
||||
exports.updateComment = updateComment;
|
||||
|
||||
/** @param {import('@types/github-script').AsyncFunctionArguments} AsyncFunctionArguments */
|
||||
async function updateCommentForEmbedding({ github, context }) {
|
||||
async function updateComment({ github, context }) {
|
||||
const githubUsername = context.payload.sender.login;
|
||||
|
||||
const comment = `${CONVENTIONAL_COMMENT_IDENTIFIER}
|
||||
@${githubUsername} You have modified embedding code. Please make sure the PR title follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style.
|
||||
Here are all the supported types:
|
||||
const comment = `${CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER}
|
||||
@${githubUsername} You have modified embedding SDK code. Please make sure the PR title follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style.
|
||||
Here are all the supported types that will be shown in the changelog:
|
||||
- \`feat\`
|
||||
- \`fix\`
|
||||
- \`perf\`
|
||||
- \`docs\`
|
||||
- \`style\`
|
||||
- \`refactor\`
|
||||
- \`test\`
|
||||
- \`build\`
|
||||
- \`ci\`
|
||||
|
||||
Please also make sure to include a scope.
|
||||
These types \`docs\`, \`style\`, \`refactor\`, \`test\`, \`build\`, and \`ci\` are also encouraged for non-changelog related tasks.
|
||||
|
||||
Please also make sure to include \`sdk\` scope, otherwise, the changelog will not include this task.
|
||||
|
||||
For example, these are valid PR titles:
|
||||
- \`feat(sdk-bundle): Add interactive dashboard component\`
|
||||
- \`fix(sdk-package): Fixed MetabaseProviderPropsStore logic\`
|
||||
- \`fix(sdk-package, embed-js): Fix API\``;
|
||||
- \`feat(sdk): Add interactive dashboard component\`
|
||||
- \`feat(sdk): Support theming pie chart\`
|
||||
- \`fix(sdk): Fix static dashboard crash\``;
|
||||
|
||||
return updateComment(comment, { github, context });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param comment {string}
|
||||
* @param {import('@types/github-script').AsyncFunctionArguments} AsyncFunctionArguments
|
||||
*/
|
||||
async function updateComment(comment, { github, context }) {
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pullRequestNumber = context.issue.number;
|
||||
@@ -91,19 +49,20 @@ async function updateComment(comment, { github, context }) {
|
||||
}
|
||||
}
|
||||
|
||||
const CONVENTIONAL_COMMENT_IDENTIFIER =
|
||||
"<!---conventional comment identifier-->";
|
||||
|
||||
const CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER =
|
||||
"<!---conventional commit reminder comment ID-->";
|
||||
/**
|
||||
* @param comments {{body: string}[]}
|
||||
* @typedef Comment
|
||||
* @property {string} body
|
||||
*
|
||||
* @param {Comment[]} comments
|
||||
*/
|
||||
function getExistingConventionalCommitReminderComment(comments) {
|
||||
for (const comment of comments) {
|
||||
if (comment.body.startsWith(CONVENTIONAL_COMMENT_IDENTIFIER)) {
|
||||
if (
|
||||
comment.body.startsWith(CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER)
|
||||
) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.isConventionalTitle = isConventionalTitle;
|
||||
exports.updateCommentForEmbedding = updateCommentForEmbedding;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
const { isConventionalTitle } = require("./utils");
|
||||
|
||||
describe("isConventionalTitle", () => {
|
||||
it("returns true for a simple conventional commit", () => {
|
||||
expect(isConventionalTitle("feat: add new feature")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for a commit with a scope", () => {
|
||||
expect(isConventionalTitle("fix(api): correct API bug")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for a commit with multiple scopes, comma separated", () => {
|
||||
expect(isConventionalTitle("feat(build,deps): update dependencies")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns true for a commit with multiple scopes, comma separated with space", () => {
|
||||
expect(isConventionalTitle("feat(build, deps): update dependencies")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for a commit with round brackets in a message", () => {
|
||||
expect(isConventionalTitle("feat: (something) update dependencies")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for missing colon", () => {
|
||||
expect(isConventionalTitle("feat add feature")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for invalid type", () => {
|
||||
expect(isConventionalTitle("invalidType: some message")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty message", () => {
|
||||
expect(isConventionalTitle("feat:")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty input", () => {
|
||||
expect(isConventionalTitle("")).toBe(false);
|
||||
});
|
||||
});
|
||||
39
.github/scripts/extract-changed-timings.js
vendored
39
.github/scripts/extract-changed-timings.js
vendored
@@ -1,39 +0,0 @@
|
||||
const extractChangedTimings = ({ oldTimings, newTimings }) => {
|
||||
const oldTimingMap =
|
||||
oldTimings.durations?.reduce((map, item) => {
|
||||
if (item.spec) {
|
||||
map[item.spec] = item.duration;
|
||||
}
|
||||
return map;
|
||||
}, {}) || {};
|
||||
|
||||
const changedDurations = newTimings.durations
|
||||
.filter((item) => {
|
||||
if (!item || !item.spec || typeof item.duration !== "number") {
|
||||
return false;
|
||||
}
|
||||
const newSpec = convertPathFormat(item.spec);
|
||||
const oldDuration = oldTimingMap[newSpec] || oldTimingMap[item.spec];
|
||||
// Include if it's a new spec or the duration has changed
|
||||
return oldDuration === undefined || oldDuration !== item.duration;
|
||||
})
|
||||
.map((item) => ({
|
||||
spec: convertPathFormat(item.spec),
|
||||
duration: item.duration,
|
||||
}));
|
||||
|
||||
return {
|
||||
changedTimings: { durations: changedDurations },
|
||||
hasChanges: changedDurations.length > 0,
|
||||
};
|
||||
};
|
||||
|
||||
// Convert cypress-split path format to timings.json path format
|
||||
// from "e2e/test/scenarios/..." to "../test/scenarios/..."
|
||||
function convertPathFormat(specPath) {
|
||||
return specPath.startsWith("e2e/test/")
|
||||
? specPath.replace("e2e/test/", "../test/")
|
||||
: specPath;
|
||||
}
|
||||
|
||||
module.exports = { extractChangedTimings };
|
||||
195
.github/scripts/extract-changed-timings.unit.spec.js
vendored
195
.github/scripts/extract-changed-timings.unit.spec.js
vendored
@@ -1,195 +0,0 @@
|
||||
const { extractChangedTimings } = require("./extract-changed-timings");
|
||||
|
||||
describe("extractChangedTimings", () => {
|
||||
it("should return empty durations when no changes", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(0);
|
||||
expect(result.hasChanges).toBe(false);
|
||||
});
|
||||
|
||||
it("should detect new specs", () => {
|
||||
const oldTimings = {
|
||||
durations: [{ spec: "test1.cy.js", duration: 1000 }],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect duration changes", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2200 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2200,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle missing old timings", () => {
|
||||
const oldTimings = { durations: [] };
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
// All timings should be considered "changed" since old timings is empty
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle invalid timing entries", () => {
|
||||
const oldTimings = {
|
||||
durations: [{ spec: "test1.cy.js", duration: 1000 }],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: "invalid" },
|
||||
{ duration: 2000 }, // missing spec
|
||||
{ spec: "test3.cy.js", duration: 3000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
// Only test3.cy.js should be included (new spec with valid data)
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle timing data with different specs", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 3000 },
|
||||
{ spec: "test3.cy.js", duration: 1000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should convert cypress-split paths to relative paths", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{
|
||||
spec: "../test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1000,
|
||||
},
|
||||
{
|
||||
spec: "../test/scenarios/admin/databases.cy.spec.js",
|
||||
duration: 2000,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{
|
||||
spec: "e2e/test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1200,
|
||||
},
|
||||
{
|
||||
spec: "e2e/test/scenarios/admin/databases.cy.spec.js",
|
||||
duration: 2000,
|
||||
},
|
||||
{
|
||||
spec: "e2e/test/scenarios/collections/collections.cy.spec.js",
|
||||
duration: 1500,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "../test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1200,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "../test/scenarios/collections/collections.cy.spec.js",
|
||||
duration: 1500,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
});
|
||||
84
.github/scripts/get-issue-data.js
vendored
84
.github/scripts/get-issue-data.js
vendored
@@ -1,84 +0,0 @@
|
||||
const githubSlackMap = require("../../release/github-slack-map.json");
|
||||
|
||||
async function getIssueData({ context, github, manualIssueNumber }) {
|
||||
let issueNumber;
|
||||
let issueData;
|
||||
let prReviews = [];
|
||||
const triggerType = context.eventName;
|
||||
|
||||
if (context.eventName === "issues") {
|
||||
issueNumber = context.payload.issue.number;
|
||||
issueData = context.payload.issue;
|
||||
} else if (context.eventName === "workflow_dispatch") {
|
||||
issueNumber = parseInt(manualIssueNumber);
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
issueData = data;
|
||||
} else if (context.eventName === "pull_request_review") {
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const { data: prData } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
// Extract issue number from branch name (e.g., "claude-fix/issue-12345-some-title")
|
||||
const branchMatch = prData.head.ref.match(/claude-fix\/issue-(\d+)/);
|
||||
if (!branchMatch) {
|
||||
throw new Error(
|
||||
`Could not extract issue number from branch name: ${prData.head.ref}`,
|
||||
);
|
||||
}
|
||||
|
||||
issueNumber = parseInt(branchMatch[1]);
|
||||
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
issueData = data;
|
||||
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
prReviews = reviews.filter(
|
||||
(review) =>
|
||||
review.state === "CHANGES_REQUESTED" &&
|
||||
isMetabaseMember(review.user.login),
|
||||
);
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
|
||||
const employeeComments = comments.filter((comment) =>
|
||||
isMetabaseMember(comment.user.login),
|
||||
);
|
||||
|
||||
return {
|
||||
issueNumber,
|
||||
issueData,
|
||||
employeeComments,
|
||||
prReviews,
|
||||
triggerType,
|
||||
isIssueAuthorEmployee: isMetabaseMember(issueData.user.login),
|
||||
};
|
||||
}
|
||||
|
||||
function isMetabaseMember(username) {
|
||||
return username in githubSlackMap;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getIssueData,
|
||||
};
|
||||
30
.github/scripts/setup-python-runner.sh
vendored
30
.github/scripts/setup-python-runner.sh
vendored
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Parse command line arguments
|
||||
SKIP_START=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--skip-start)
|
||||
SKIP_START=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 [--skip-start]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Cloning python-runner repository..."
|
||||
git clone --depth 1 https://${METABASE_AUTOMATION_USER_TOKEN}@github.com/metabase/python-runner-container.git python-runner
|
||||
|
||||
if [ "$SKIP_START" = false ]; then
|
||||
echo "Starting python-runner..."
|
||||
cd python-runner
|
||||
make run-ci 1> ../python-runner.out.log 2> ../python-runner.err.log
|
||||
make logs 1>> ../python-runner.out.log 2>> ../python-runner.err.log &
|
||||
cd ..
|
||||
fi
|
||||
241
.github/scripts/update-pr-with-issues.js
vendored
241
.github/scripts/update-pr-with-issues.js
vendored
@@ -1,241 +0,0 @@
|
||||
const https = require('https');
|
||||
|
||||
function getLinkedIssues(body) {
|
||||
const matches = body.match(
|
||||
/(close(s|d)?|fixe?(s|d)?|resolve(s|d)?)(:?) (#|https?:\/\/github\.com\/.+metabase\/issues\/)(\d+)/gi,
|
||||
);
|
||||
|
||||
if (matches) {
|
||||
console.log(`Matches: ${matches}`);
|
||||
return matches.map(m => {
|
||||
const numberMatch = m.match(/\d+/);
|
||||
return numberMatch ? numberMatch[0] : null;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Function to make HTTPS requests
|
||||
function httpsRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(options, (res) => {
|
||||
let responseData = '';
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(responseData));
|
||||
} catch (e) {
|
||||
resolve(responseData);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Request failed with status code ${res.statusCode}: ${responseData}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Makes an HTTPS request with exponential backoff retry logic
|
||||
async function httpsRequestWithBackoff(options, data = null, backoffOptions = {}) {
|
||||
// Set default backoff options:
|
||||
const {
|
||||
maxRetries = 8,
|
||||
totalDurationSeconds = 60
|
||||
} = backoffOptions;
|
||||
|
||||
let retries = 0;
|
||||
let delay = 500; // Start with a .5 second delay
|
||||
|
||||
// Calculate a reasonable initial delay based on total duration and max retries
|
||||
// This helps distribute the retry attempts across the total allowed duration
|
||||
const totalDurationMs = totalDurationSeconds * 1000;
|
||||
|
||||
// Track the start time
|
||||
const startTime = Date.now();
|
||||
|
||||
// Ensure data is stringified if it's an object
|
||||
const requestData = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const response = await httpsRequest(options, requestData);
|
||||
return response; // Success: Return the data
|
||||
|
||||
} catch (error) {
|
||||
retries++;
|
||||
|
||||
const elapsedMs = Date.now() - startTime;
|
||||
const timeRemaining = totalDurationMs - elapsedMs;
|
||||
|
||||
// Stop retrying if we've hit max retries or exceeded the total duration
|
||||
if (retries >= maxRetries || timeRemaining <= 0) {
|
||||
console.error(`Failed after ${retries} retries (${Math.round(elapsedMs/1000)}s elapsed):`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Calculate the next delay with exponential backoff and jitter
|
||||
delay = Math.min(delay * 2 * (0.8 + Math.random() * 0.4), timeRemaining);
|
||||
|
||||
console.log(`Request failed (attempt ${retries}/${maxRetries}). Retrying in ${Math.round(delay)}ms... (${Math.round(timeRemaining/1000)}s remaining)`);
|
||||
|
||||
// Wait for the calculated delay before trying again
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function link_issues(github) {
|
||||
|
||||
try {
|
||||
// Get environment variables
|
||||
const githubToken = process.env.GITHUB_TOKEN;
|
||||
const linearApiKey = process.env.LINEAR_API_KEY;
|
||||
const prNumber = process.env.PR_NUMBER;
|
||||
const repoOwner = process.env.REPO_OWNER;
|
||||
const repoName = process.env.REPO_NAME;
|
||||
|
||||
if (!githubToken || !linearApiKey || !prNumber || !repoOwner || !repoName) {
|
||||
console.error('Missing required environment variables');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get PR details from GitHub API
|
||||
const prInfo = await github.rest.issues.get({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
issue_number: prNumber
|
||||
}).catch((err) => {
|
||||
console.log("error getting prInfo:\n" + err);
|
||||
});
|
||||
|
||||
console.log('PR Info:\n' + JSON.stringify(prInfo, null, 2) + "\n");
|
||||
|
||||
if (prInfo &&
|
||||
prInfo.data &&
|
||||
prInfo.data.labels &&
|
||||
prInfo.data.labels.some(label => label.name === "no-auto-issue-links")) {
|
||||
console.log('PR has "no-auto-issue-links" label, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
const prUrl = prInfo.data.html_url;
|
||||
console.log(`Processing PR: ${prUrl}`);
|
||||
|
||||
// Query Linear API to find tasks linked to this PR
|
||||
const linearQuery = JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
attachmentsForURL(url: "${prUrl}") {
|
||||
nodes {
|
||||
issue {
|
||||
id
|
||||
identifier
|
||||
attachments {
|
||||
nodes {url}}}}}}`});
|
||||
|
||||
console.log('--- Linear API query ---');
|
||||
console.log(linearQuery);
|
||||
console.log('----- End of query -----');
|
||||
|
||||
const linearData = await httpsRequestWithBackoff({
|
||||
hostname: 'api.linear.app',
|
||||
path: '/graphql',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': linearApiKey
|
||||
}
|
||||
},
|
||||
linearQuery,
|
||||
{ maxRetries: 8, totalDurationSeconds: 60 });
|
||||
|
||||
console.log('Linear API response:');
|
||||
console.log(JSON.stringify(linearData) + "\n");
|
||||
|
||||
if (!linearData.data || !linearData.data.attachmentsForURL || linearData.data.attachmentsForURL.nodes.length === 0) {
|
||||
console.log('No Linear tasks linked to this PR');
|
||||
return;
|
||||
} else {
|
||||
console.log(`Found ${linearData.data.attachmentsForURL.nodes.length} linked Linear tasks`);
|
||||
}
|
||||
|
||||
|
||||
// Extract GitHub issue numbers from all attachments in the linked Linear task
|
||||
const issueNumbers = [];
|
||||
for (const node of linearData.data.attachmentsForURL.nodes) {
|
||||
console.log(`checking node task: ${JSON.stringify(node)}`)
|
||||
const linearIssue = node.issue;
|
||||
|
||||
for (const attachment of linearIssue.attachments.nodes) {
|
||||
const url = attachment.url;
|
||||
console.log(`--> checking url: ${url}`)
|
||||
const match = url.match(/github\.com\/metabase\/metabase\/issues\/(\d+)/);
|
||||
if (match) {
|
||||
console.log(`--> found match: ${match[1]}`)
|
||||
issueNumbers.push(match[1]);
|
||||
console.log("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found linked GitHub issues: ${JSON.stringify(issueNumbers)}`);
|
||||
|
||||
if (issueNumbers.length === 0) {
|
||||
console.log(`Found linked GitHub issues: ${JSON.stringify(issueNumbers)}`);
|
||||
console.log('No GitHub issues linked to the Linear task');
|
||||
return;
|
||||
}
|
||||
|
||||
let body = prInfo.data.body || '';
|
||||
|
||||
let linked_issues = getLinkedIssues(body);
|
||||
console.log(`Linked issues: ${linked_issues}`);
|
||||
|
||||
let newBody = body || '';
|
||||
|
||||
let issueNumbersToAdd = issueNumbers.filter(num => !linked_issues.includes(num));
|
||||
|
||||
console.log(`Issue numbers to add: ${issueNumbersToAdd}`);
|
||||
|
||||
if (issueNumbersToAdd.length > 0) {
|
||||
|
||||
// Generate closing references
|
||||
const closingRefs = issueNumbersToAdd.map(num => `closes #${num}`).join(' ');
|
||||
|
||||
const messagePreamble = "<!-- Added by 'Add Issue References to PR' GitHub Action. To disable linking, add 'no-auto-issue-links' label to your PR. -->"
|
||||
|
||||
newBody = `${messagePreamble} ${closingRefs}\n` + newBody;
|
||||
|
||||
// Update PR body with new closing references
|
||||
await github.rest.issues.update({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
issue_number: prNumber,
|
||||
body: newBody
|
||||
});
|
||||
|
||||
console.log(`Updated PR #${prNumber} with issue references:\n ${closingRefs}`);
|
||||
} else {
|
||||
console.log('PR already contains all closing references');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {link_issues};
|
||||
95
.github/team.json
vendored
95
.github/team.json
vendored
@@ -3,19 +3,24 @@
|
||||
{
|
||||
"name": "Querying",
|
||||
"label": ".Team/Querying",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/97/views/1",
|
||||
"members": [
|
||||
"ranquild",
|
||||
"kamilmielnik",
|
||||
"romeovs",
|
||||
"bshepherdson",
|
||||
"metamben",
|
||||
"lbrdnk",
|
||||
"appleby",
|
||||
"uladzimirdev",
|
||||
"wzimrin",
|
||||
"ericnormand",
|
||||
"galdre"
|
||||
"ericnormand"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Drivers",
|
||||
"label": ".Team/Drivers",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/98/views/1",
|
||||
"members": [
|
||||
"snoe",
|
||||
"rileythomp"
|
||||
@@ -24,33 +29,52 @@
|
||||
{
|
||||
"name": "Embedding",
|
||||
"label": ".Team/Embedding",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/88/views/1",
|
||||
"members": [
|
||||
"WiNloSt",
|
||||
"oisincoveney",
|
||||
"npretto",
|
||||
"heypoom",
|
||||
"sanex3339",
|
||||
"lorem--ipsum"
|
||||
"sanex3339"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Workflows",
|
||||
"label": ".Team/Workflows",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/50/views/1",
|
||||
"members": [
|
||||
"qnkhuat",
|
||||
"piranha",
|
||||
"crisptrutski",
|
||||
"deniskaber",
|
||||
"wotbrew",
|
||||
"spalt08",
|
||||
"stasgavrylov",
|
||||
"uladzimirdev",
|
||||
"kulyk",
|
||||
"kamilmielnik"
|
||||
"stasgavrylov"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Admin Webapp",
|
||||
"label": ".Team/AdminWebapp",
|
||||
"members": [
|
||||
"eliotmetabase"
|
||||
"npfitz",
|
||||
"johnswanson",
|
||||
"rafpaf",
|
||||
"sloansparger",
|
||||
"iethree",
|
||||
"nvoxland",
|
||||
"fraserdrops",
|
||||
"edpaget"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DashViz",
|
||||
"label": ".Team/DashViz",
|
||||
"members": [
|
||||
"alxnddr",
|
||||
"kulyk",
|
||||
"tsplude",
|
||||
"lorem--ipsum",
|
||||
"noahmoss"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -60,59 +84,6 @@
|
||||
"camsaul",
|
||||
"escherize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Metabot",
|
||||
"label": ".Team/Metabot",
|
||||
"members": [
|
||||
"appleby",
|
||||
"noahmoss",
|
||||
"retro",
|
||||
"sloansparger",
|
||||
"somtom",
|
||||
"wotbrew",
|
||||
"tsplude",
|
||||
"lbrdnk",
|
||||
"bronsa"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Product Growth",
|
||||
"label": ".Team/ProductGrowth",
|
||||
"members": [
|
||||
"nemanjaglumac",
|
||||
"chodorowicz",
|
||||
"andsalves"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Cloud",
|
||||
"label": "cloud",
|
||||
"members": [
|
||||
"filipesilva"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DashViz",
|
||||
"label": ".Team/DashViz",
|
||||
"members": [
|
||||
"eliotmetabase"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UX West",
|
||||
"label": ".Team/UXWest",
|
||||
"members": [
|
||||
"eliotmetabase",
|
||||
"npfitz",
|
||||
"johnswanson",
|
||||
"iethree",
|
||||
"nvoxland",
|
||||
"edpaget",
|
||||
"deniskaber",
|
||||
"alxnddr",
|
||||
"bpander"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
407
.github/workflows/app-db.yml
vendored
407
.github/workflows/app-db.yml
vendored
@@ -1,407 +0,0 @@
|
||||
name: App DB Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
skip:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-app-db
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
be-tests-mariadb:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: MariaDB 10.2
|
||||
junit-name: be-tests-mariadb-10-2-ee
|
||||
image: circleci/mariadb:10.2.23
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MariaDB Latest
|
||||
junit-name: be-tests-mariadb-latest-ee
|
||||
image: circleci/mariadb:latest
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ matrix.version.image }}
|
||||
ports:
|
||||
- "3306:3306"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
#
|
||||
# SSL config: only for mysql latest tests.
|
||||
#
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
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 }}
|
||||
|
||||
be-tests-mysql:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: MySQL 8.0
|
||||
junit-name: be-tests-mysql-8-0-ee
|
||||
image: cimg/mysql:8.0
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL Latest
|
||||
junit-name: be-tests-mysql-latest-ee
|
||||
image: mysql:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ matrix.version.image }}
|
||||
ports:
|
||||
- "3306:3306"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
#
|
||||
# SSL config: only for mysql latest tests.
|
||||
#
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
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 }}
|
||||
|
||||
be-tests-postgres:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: Postgres 12.x
|
||||
junit-name: postgres-ee
|
||||
docker-image: postgres:12-alpine
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: Postgres Latest
|
||||
junit-name: postgres-latest-ee
|
||||
docker-image: postgres:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
exclude-tag: ':mb/transforms-python-test'
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: postgres
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_PORT: 5432
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_DBNAME: mb_test
|
||||
MB_DB_USER: mb_test
|
||||
MB_POSTGRESQL_TEST_USER: mb_test
|
||||
# SSL tests are only enabled for the postgres-latest job.
|
||||
MB_POSTGRES_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
|
||||
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
|
||||
services:
|
||||
postgres:
|
||||
image: ${{ matrix.version.docker-image }}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
POSTGRES_USER: mb_test
|
||||
POSTGRES_DB: mb_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
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 }}
|
||||
|
||||
app-db-tests-result:
|
||||
needs:
|
||||
- be-tests-mariadb
|
||||
- be-tests-mysql
|
||||
- be-tests-postgres
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
name: app-db-tests-result
|
||||
if: always() && !cancelled()
|
||||
steps:
|
||||
- name: Check job status
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
needs: ${{ toJson(needs) }}
|
||||
with:
|
||||
script: | # js
|
||||
const needs = JSON.parse(process.env.needs);
|
||||
|
||||
const jobs = Object.entries(needs).map(
|
||||
([jobName, jobValues]) => ({
|
||||
name: jobName,
|
||||
result: jobValues.result
|
||||
}));
|
||||
|
||||
// are all jobs skipped or successful?
|
||||
if (jobs.every(job => (job.result === 'skipped' || job.result === 'success'))) {
|
||||
console.log("");
|
||||
console.log(" _------. ");
|
||||
console.log(" / , \_ ");
|
||||
console.log(" / / /{}\ |o\_ ");
|
||||
console.log(" / \ `--' /-' \ ");
|
||||
console.log(" | \ \ | ");
|
||||
console.log(" | |`-, | ");
|
||||
console.log(" / /__/)/ ");
|
||||
console.log(" | | ");
|
||||
console.log("");
|
||||
console.log("All app db tests have passed (or have been skipped). Cam is very proud of you.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// otherwise, something failed
|
||||
console.log("");
|
||||
console.log(" .::::::::::. .::::::::::. ");
|
||||
console.log(" .::::''''''::::. .::::''''''::::. ");
|
||||
console.log(" .:::' `::::.... ....::::' `:::. ");
|
||||
console.log(" .::' `:::::::| |:::::::' `::. ");
|
||||
console.log(" .::| |::::::|_ ___ __|::::::| |::. ");
|
||||
console.log(" `--' |::::::|_()__()_|::::::| `--' ");
|
||||
console.log(" ::: |::-o::| |::o-::| ::: ");
|
||||
console.log(" `::. .|::::::| |::::::|. .::' ");
|
||||
console.log(" `:::. .::\-----' `-----/::. .:::' ");
|
||||
console.log(" `::::......::::' `::::......::::' ");
|
||||
console.log(" `::::::::::' `::::::::::' ");
|
||||
console.log("");
|
||||
console.log("App db tests have failed. You have been sentenced to MetaJail.");
|
||||
|
||||
jobs.forEach((job) => {
|
||||
if (job.result !== 'success') {
|
||||
console.log(`${job.name} - ${job.result}`);
|
||||
}
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
38
.github/workflows/athena.yml
vendored
38
.github/workflows/athena.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Publish Athena JDBC Driver
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Athena JDBC driver version to publish'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Publish Athena JDBC Driver
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Babashka
|
||||
uses: turtlequeue/setup-babashka@v1.7.0
|
||||
with:
|
||||
babashka-version: 1.12.197
|
||||
|
||||
- name: Set up AWS CLI
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_S3_MAVEN_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_S3_MAVEN_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
|
||||
- name: Deploy Athena JDBC driver
|
||||
run: ./bin/athena.bb ${{ github.event.inputs.version }} --deploy
|
||||
|
||||
- name: Verify deployment
|
||||
run: |
|
||||
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc/${{ github.event.inputs.version }}/
|
||||
echo "Athena JDBC driver version ${{ github.event.inputs.version }} has been deployed"
|
||||
26
.github/workflows/auto-backport.yml
vendored
26
.github/workflows/auto-backport.yml
vendored
@@ -1,24 +1,18 @@
|
||||
name: Auto-backport
|
||||
run-name: Auto Backport ${{ github.event.pull_request.title }}
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed, labeled]
|
||||
|
||||
jobs:
|
||||
create-backport:
|
||||
if:
|
||||
| # the PR must have a backport label when it's merged, or be labeled with a backport label after merge
|
||||
github.event.pull_request.merged == true && (
|
||||
github.event.action == 'closed' && (
|
||||
contains(github.event.pull_request.labels.*.name, 'backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'double-backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'triple-backport')
|
||||
) ||
|
||||
github.event.action == 'labeled' && (
|
||||
github.event.label.name == 'backport' ||
|
||||
github.event.label.name == 'double-backport' ||
|
||||
github.event.label.name == 'triple-backport'
|
||||
)
|
||||
if: | # the PR must be closed and have a backport label
|
||||
(github.event.pull_request.merged == true || github.event.action == 'closed') && (
|
||||
contains(github.event.pull_request.labels.*.name, 'backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'double-backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'triple-backport') ||
|
||||
github.event.label.name == 'backport' ||
|
||||
github.event.label.name == 'double-backport' ||
|
||||
github.event.label.name == 'triple-backport'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -82,7 +76,7 @@ jobs:
|
||||
github-token-2: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Double Backport
|
||||
if: ${{ always() && fromJson(steps.how-many-backports.outputs.result) >= 2 }}
|
||||
if: ${{ fromJson(steps.how-many-backports.outputs.result) >= 2 }}
|
||||
uses: ./.github/actions/create-backport
|
||||
with:
|
||||
target-version: ${{ fromJson(steps.get-latest-release-version.outputs.result).secondLatest }}
|
||||
@@ -91,7 +85,7 @@ jobs:
|
||||
github-token-2: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Triple Backport
|
||||
if: ${{ always() && fromJson(steps.how-many-backports.outputs.result) >= 3 }}
|
||||
if: ${{ fromJson(steps.how-many-backports.outputs.result) >= 3 }}
|
||||
uses: ./.github/actions/create-backport
|
||||
with:
|
||||
target-version: ${{ fromJson(steps.get-latest-release-version.outputs.result).thirdLatest }}
|
||||
|
||||
21
.github/workflows/auto-request-review.yml
vendored
Normal file
21
.github/workflows/auto-request-review.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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 }}'
|
||||
2
.github/workflows/backend-cloverage.yml
vendored
2
.github/workflows/backend-cloverage.yml
vendored
@@ -19,8 +19,6 @@ jobs:
|
||||
m2-cache-key: "cloverage"
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
- name: Collect the test coverage
|
||||
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
|
||||
- name: Upload coverage to codecov.io
|
||||
|
||||
14
.github/workflows/backend.yml
vendored
14
.github/workflows/backend.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
m2-cache-key: 'kondo'
|
||||
- name: Run clj-kondo
|
||||
run: ./bin/mage kondo
|
||||
run: ./bin/kondo.sh
|
||||
|
||||
be-linter-eastwood:
|
||||
if: ${{ !inputs.skip }}
|
||||
@@ -114,8 +114,6 @@ jobs:
|
||||
- name: Build static-viz frontend
|
||||
if: ${{ matrix.job.build-static-viz == 'true' && !inputs.skip }}
|
||||
run: yarn build-static-viz
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.job.edition }}
|
||||
- name: Download Static Viz Bundle Artifact
|
||||
if: ${{ matrix.job.build-static-viz == 'true' && !inputs.skip }}
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -153,14 +151,6 @@ jobs:
|
||||
list-tests: failed
|
||||
fail-on-error: false
|
||||
|
||||
- name: Upload Logs on Failure
|
||||
if: always() && !success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-logs-${{ matrix.java-version }}-${{ matrix.job.edition }}-${{ github.job }}
|
||||
path: logs/test-log.json
|
||||
retention-days: 1
|
||||
|
||||
# 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:
|
||||
@@ -193,7 +183,7 @@ jobs:
|
||||
with:
|
||||
m2-cache-key: 'cljfmt'
|
||||
- name: Run cljfmt
|
||||
run: ./bin/mage cljfmt-all --force-check
|
||||
run: clojure -T:cljfmt check
|
||||
|
||||
be-tests-result:
|
||||
needs:
|
||||
|
||||
1
.github/workflows/backport-reminder.yml
vendored
1
.github/workflows/backport-reminder.yml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
types: [auto_merge_enabled, review_requested, labeled, unlabeled, synchronize]
|
||||
branches:
|
||||
- "master"
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
backport-reminder:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
name: Block Master Branch PRs
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
check-source-branch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Block master branch PRs
|
||||
if: github.head_ref == 'master'
|
||||
run: |
|
||||
echo "PRs from `master` branches are not allowed, please use a new branch name for your PR."
|
||||
exit 1
|
||||
1
.github/workflows/check-backport-script.yml
vendored
1
.github/workflows/check-backport-script.yml
vendored
@@ -3,7 +3,6 @@ name: Prevent backport.sh from being merged
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
check-backport-script:
|
||||
|
||||
1
.github/workflows/chromatic.yml
vendored
1
.github/workflows/chromatic.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_all: ${{ steps.changes.outputs.frontend_all }}
|
||||
e2e_specs: ${{ steps.changes.outputs.e2e_specs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
|
||||
327
.github/workflows/claude-code-bug-fix.yml
vendored
327
.github/workflows/claude-code-bug-fix.yml
vendored
@@ -1,327 +0,0 @@
|
||||
name: "Claude Code Bug Fix"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: "Issue number to fix"
|
||||
required: true
|
||||
type: string
|
||||
override_employee_check:
|
||||
description: "Override employee check for issue creator"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
auto-fix-bug:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
if: |
|
||||
(github.event_name == 'issues' && github.event.label.name == 'Claude Bug Fix' && github.event.issue.state == 'open') ||
|
||||
(github.event_name == 'workflow_dispatch') ||
|
||||
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'claude-fix/'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get issue data and check employee status
|
||||
id: get_issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { getIssueData } = require('${{ github.workspace }}/.github/scripts/get-issue-data.js');
|
||||
|
||||
try {
|
||||
const result = await getIssueData({
|
||||
context,
|
||||
github,
|
||||
manualIssueNumber: '${{ github.event.inputs.issue_number }}'
|
||||
});
|
||||
|
||||
const { issueNumber, issueData, employeeComments, prReviews, triggerType, isIssueAuthorEmployee } = result;
|
||||
|
||||
// Check employee status and fail if not authorized
|
||||
if (!result.isIssueAuthorEmployee && '${{ github.event.inputs.override_employee_check }}' !== 'true') {
|
||||
core.setFailed(`User ${result.issueData.user.login} is not a Metabase employee. Skipping bug fix automation.`);
|
||||
}
|
||||
|
||||
core.setOutput('issue_number', issueNumber);
|
||||
core.setOutput('issue_data', JSON.stringify(issueData));
|
||||
core.setOutput('issue_comments', JSON.stringify(employeeComments));
|
||||
core.setOutput('pr_reviews', JSON.stringify(prReviews));
|
||||
core.setOutput('trigger_type', triggerType);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
|
||||
- name: Set up git configuration
|
||||
run: |
|
||||
git config --global user.name "Claude Code[bot]"
|
||||
git config --global user.email "noreply@anthropic.com"
|
||||
|
||||
- name: Prepare issue context and bug fix prompt
|
||||
id: prepare_prompt
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
|
||||
ISSUE_COMMENTS: ${{ steps.get_issue.outputs.issue_comments }}
|
||||
PR_REVIEWS: ${{ steps.get_issue.outputs.pr_reviews }}
|
||||
TRIGGER_TYPE: ${{ steps.get_issue.outputs.trigger_type }}
|
||||
with:
|
||||
script: |
|
||||
const issueData = JSON.parse(process.env.ISSUE_DATA);
|
||||
const issueComments = JSON.parse(process.env.ISSUE_COMMENTS);
|
||||
const prReviews = JSON.parse(process.env.PR_REVIEWS);
|
||||
const triggerType = process.env.TRIGGER_TYPE;
|
||||
const issueNumber = '${{ steps.get_issue.outputs.issue_number }}';
|
||||
|
||||
// Convert issue title to kebab case and create branch name
|
||||
const issueTitle = issueData.title || 'untitled';
|
||||
const kebabTitle = issueTitle
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.substring(0, 50);
|
||||
const branchName = `claude-fix/issue-${issueNumber}-${kebabTitle}`;
|
||||
|
||||
// Format data for bash environment
|
||||
const formattedComments = issueComments.length > 0
|
||||
? issueComments.map(comment =>
|
||||
`**Comment by @${comment.user.login} on ${comment.created_at}:**\n${comment.body}`
|
||||
).join('\n\n---\n\n')
|
||||
: '';
|
||||
const formattedReviews = prReviews.length > 0
|
||||
? prReviews.map(review =>
|
||||
`**Review by @${review.user.login} on ${review.submitted_at} (Changes Requested):**\n${review.body || 'No review comment provided'}`
|
||||
).join('\n\n---\n\n')
|
||||
: '';
|
||||
|
||||
core.exportVariable('BRANCH_NAME', branchName);
|
||||
core.exportVariable('ISSUE_NUMBER', issueNumber);
|
||||
core.exportVariable('ISSUE_TITLE', issueData.title);
|
||||
core.exportVariable('ISSUE_AUTHOR', issueData.user.login);
|
||||
core.exportVariable('ISSUE_CREATED', issueData.created_at);
|
||||
core.exportVariable('ISSUE_BODY', issueData.body || 'No description provided');
|
||||
core.exportVariable('ISSUE_COMMENTS_FORMATTED', formattedComments);
|
||||
core.exportVariable('PR_REVIEWS_FORMATTED', formattedReviews);
|
||||
core.exportVariable('TRIGGER_TYPE', triggerType);
|
||||
|
||||
- name: Generate bug fix prompt
|
||||
run: |
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
BUG_FIX_PROMPT<<PROMPT_END
|
||||
You are helping to fix a bug reported in GitHub issue #${{ env.ISSUE_NUMBER }}.
|
||||
|
||||
## Bug Report Details
|
||||
**Title:** ${{ env.ISSUE_TITLE }}
|
||||
**Reporter:** ${{ env.ISSUE_AUTHOR }}
|
||||
**Created:** ${{ env.ISSUE_CREATED }}
|
||||
|
||||
**Description:**
|
||||
${{ env.ISSUE_BODY }}
|
||||
|
||||
**Comments:**
|
||||
${{ env.ISSUE_COMMENTS_FORMATTED }}
|
||||
EOF
|
||||
|
||||
# Add review feedback section only if this is a review trigger
|
||||
if [ "${{ env.TRIGGER_TYPE }}" = "pull_request_review" ]; then
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
|
||||
## Previous Fix Attempt - Review Feedback (MUST ADDRESS)
|
||||
Your previous automated fix attempt received the following feedback that needs to be addressed:
|
||||
|
||||
${{ env.PR_REVIEWS_FORMATTED }}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Add the rest of the prompt (always the same)
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
|
||||
## Instructions
|
||||
1. **Analyze the issue description and comments** - Understand what the issue is describing
|
||||
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
|
||||
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
|
||||
3. **Implement a fix** - Follow existing code patterns and conventions
|
||||
4. **Test the fix** - Run appropriate tests using the commands in CLAUDE.md:
|
||||
- JavaScript/TypeScript: `yarn test-unit path/to/file.unit.spec.js`
|
||||
- Clojure: `clojure -X:dev:test :only namespace/test-name`
|
||||
- Lint: `yarn lint-eslint-pure` for JS/TS, `./bin/mage kondo <file>` for Clojure
|
||||
5. **Verify the fix** - Ensure tests pass and code follows conventions
|
||||
|
||||
## Important Guidelines
|
||||
- Follow the development patterns in CLAUDE.md
|
||||
- Write failing tests first, then fix them
|
||||
- Work in small, testable increments
|
||||
- Don't commit changes - leave that for the workflow
|
||||
- IMPORTANT: Focus only on fixing the reported bug, don't make unrelated changes
|
||||
|
||||
## Scratchpad Directory
|
||||
A `.claude-scratchpad/` directory has been created for your temporary files:
|
||||
- Use this directory for ALL temporary verification files. However, prefer using production test files rather than adhoc ones.
|
||||
- Only modify actual production code in the main repository directories
|
||||
- The scratchpad will be cleaned up automatically - nothing from it will be committed
|
||||
|
||||
Start by understanding the bug and then systematically work toward a solution.
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
- name: Determine branch strategy
|
||||
run: |
|
||||
BRANCH_NAME="${{ env.BRANCH_NAME }}"
|
||||
if git branch -r --list "origin/${BRANCH_NAME}" | grep -q .; then
|
||||
echo "Branch already exists: $BRANCH_NAME"
|
||||
echo "Will push to existing branch and skip PR creation"
|
||||
echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Creating new branch: $BRANCH_NAME"
|
||||
echo "Will create new PR"
|
||||
echo "BRANCH_EXISTS=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Export the branch name
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Create scratchpad directory for Claude
|
||||
run: |
|
||||
# Create a scratchpad directory for Claude to use for temporary files
|
||||
mkdir -p .claude-scratchpad
|
||||
echo "Scratchpad directory created for Claude's temporary verification files"
|
||||
|
||||
- name: Generate GitHub App Token for Claude
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: claude-app-token
|
||||
with:
|
||||
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Generate Bug Fix with Claude Code
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
mode: agent
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ steps.claude-app-token.outputs.token }}
|
||||
direct_prompt: ${{ env.BUG_FIX_PROMPT }}
|
||||
timeout_minutes: "25"
|
||||
allowed_tools: "Bash(yarn test-unit*),Bash(yarn lint-eslint-pure),Bash(./bin/mage*),Bash(clojure*),Bash(find*),Bash(ls*),Bash(grep*),Write,Read,Glob,Grep,Edit,MultiEdit"
|
||||
disallowed_tools: "Bash(git commit*),Bash(git push*),Bash(gh*)"
|
||||
|
||||
- name: Clean up scratchpad directory
|
||||
run: |
|
||||
# Remove the scratchpad directory so its contents don't get committed
|
||||
rm -rf .claude-scratchpad/
|
||||
# Remove output file generated by Claude Code action
|
||||
rm -f output.txt
|
||||
echo "Scratchpad directory and action output files cleaned up"
|
||||
|
||||
- name: Check for changes and analysis
|
||||
id: check_changes
|
||||
run: |
|
||||
# Now check for actual code changes (excluding analysis file)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "Changes detected:"
|
||||
git status --short
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No changes made by Claude."
|
||||
fi
|
||||
|
||||
- name: Get issue team labels and prepare reviewers
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
id: prepare_reviewers
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
|
||||
with:
|
||||
script: |
|
||||
const issueData = JSON.parse(process.env.ISSUE_DATA);
|
||||
const issueLabels = issueData.labels.map(label => label.name);
|
||||
const teamLabels = issueLabels.filter(label => label.startsWith('.Team/'));
|
||||
const issueCreator = issueData.user.login;
|
||||
core.setOutput('team_labels', teamLabels.join(','));
|
||||
core.setOutput('reviewers', issueCreator);
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ env.BRANCH_NAME }}
|
||||
base: master
|
||||
commit-message: |
|
||||
Fix: ${{ env.ISSUE_TITLE }}
|
||||
|
||||
Fixes #${{ env.ISSUE_NUMBER }}
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
committer: "Claude Code[bot] <noreply@anthropic.com>"
|
||||
author: "Claude Code[bot] <noreply@anthropic.com>"
|
||||
title: "Fix: ${{ env.ISSUE_TITLE }}"
|
||||
body: |
|
||||
This PR automatically fixes the bug reported in #${{ env.ISSUE_NUMBER }}.
|
||||
|
||||
## Bug Report
|
||||
**Original Issue:** #${{ env.ISSUE_NUMBER }}
|
||||
**Reporter:** @${{ env.ISSUE_AUTHOR }}
|
||||
**Issue Title:** ${{ env.ISSUE_TITLE }}
|
||||
|
||||
## Automated Fix
|
||||
🤖 This fix was generated automatically by Claude Code based on the issue description.
|
||||
|
||||
**Please review carefully:**
|
||||
- [ ] The fix addresses the reported issue
|
||||
- [ ] Tests pass and code follows conventions
|
||||
- [ ] No unrelated changes were introduced
|
||||
- [ ] The solution is appropriate and maintainable
|
||||
|
||||
## Next Steps
|
||||
- Review the changes and test thoroughly
|
||||
- If the fix looks good, approve and merge
|
||||
- If issues are found, either fix them or close this PR
|
||||
|
||||
---
|
||||
Closes #${{ env.ISSUE_NUMBER }}
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
labels: |
|
||||
${{ steps.prepare_reviewers.outputs.team_labels }}
|
||||
automated-fix
|
||||
ci skip
|
||||
reviewers: ${{ steps.prepare_reviewers.outputs.reviewers }}
|
||||
|
||||
- name: Log existing branch update
|
||||
if: steps.check_changes.outputs.has_changes == 'true' && env.BRANCH_EXISTS == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
console.log('🔄 Updated existing branch: ${{ env.BRANCH_NAME }}');
|
||||
console.log('The changes have been pushed to the existing PR for this issue.');
|
||||
|
||||
- name: Log analysis for no changes
|
||||
if: steps.check_changes.outputs.has_changes == 'false'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
console.log('🤖 No code changes were made by Claude.');
|
||||
2
.github/workflows/cljs.yml
vendored
2
.github/workflows/cljs.yml
vendored
@@ -40,5 +40,5 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'cljs'
|
||||
- name: Run Cljs tests
|
||||
- name: Run Cljs tests for shared/ code
|
||||
run: yarn run test-cljs
|
||||
|
||||
141
.github/workflows/containerize-jar.yml
vendored
141
.github/workflows/containerize-jar.yml
vendored
@@ -1,141 +0,0 @@
|
||||
name: Containerize Uberjar
|
||||
run-name: Containerize ${{ inputs.tag }} Uberjar
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
artifact-name:
|
||||
type: string
|
||||
repo:
|
||||
description: owner and repo like "metabase/metabase"
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
commit:
|
||||
type: string
|
||||
release:
|
||||
type: string
|
||||
default: 'true'
|
||||
build-args:
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
artifact-name:
|
||||
description: usually something like "metabase-release-{{ edition }}-{{ hash }}-uberjar"
|
||||
type: string
|
||||
repo:
|
||||
description: docker owner and repo like "metabase/metabase"
|
||||
type: string
|
||||
tag:
|
||||
description: usually a version like v1.87.2
|
||||
type: string
|
||||
commit:
|
||||
description: the full commit hash
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
containerize:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# we want the dockerfile from the commit we're building
|
||||
- run: git checkout ${{ inputs.commit }} -- bin/docker
|
||||
- name: Retrieve test build uberjar artifact
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
name: ${{ inputs.artifact-name }}
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get platforms
|
||||
id: platforms
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
result-encoding: string
|
||||
script: | # js
|
||||
const { getBuildRequirements, isValidVersionString} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const version = "${{ inputs.tag }}".split('-')[0];
|
||||
|
||||
if (!isValidVersionString(version)) {
|
||||
return "linux/amd64,linux/arm64";
|
||||
}
|
||||
// versions before 53 need to be handled differently
|
||||
const buildInfo = getBuildRequirements(version);
|
||||
return buildInfo.platforms;
|
||||
- name: Move the Uberjar to the context dir
|
||||
run: mv ./metabase.jar bin/docker/.
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
if: contains(steps.platforms.outputs.result, 'arm')
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
if: contains(steps.platforms.outputs.result, 'arm')
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build multi-arch container
|
||||
if: contains(steps.platforms.outputs.result, 'arm')
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: bin/docker/.
|
||||
platforms: ${{ steps.platforms.outputs.result }}
|
||||
network: host
|
||||
tags: ${{ inputs.repo }}:${{ inputs.tag }}
|
||||
no-cache: true
|
||||
push: true
|
||||
build-args: ${{ inputs.build-args }}
|
||||
|
||||
# we need to do this to get the nice fallback behavior of the legacy manifest file
|
||||
- name: Build container (x86 only)
|
||||
if: contains(steps.platforms.outputs.result, 'arm') == false
|
||||
run: | # bash
|
||||
docker build \
|
||||
-t ${{ inputs.repo }}:${{ inputs.tag }} \
|
||||
--network=host \
|
||||
--push \
|
||||
bin/docker/.
|
||||
|
||||
verify-docker-pull:
|
||||
runs-on: ubuntu-latest
|
||||
needs: containerize
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Pull the container image
|
||||
run: docker pull ${{ inputs.repo }}:${{ inputs.tag }}
|
||||
- name: Launch container
|
||||
run: docker run --rm -dp 3000:3000 ${{ inputs.repo }}:${{ inputs.tag }}
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 5
|
||||
|
||||
verify-docker-pull-arm:
|
||||
runs-on: arm-ubuntu-24-2core
|
||||
needs: containerize
|
||||
if: contains(needs.containerize.outputs.platforms, 'linux/arm64')
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Pull the container image
|
||||
run: docker pull ${{ inputs.repo }}:${{ inputs.tag }}
|
||||
- name: Launch container
|
||||
run: docker run --rm -dp 3000:3000 ${{ inputs.repo }}:${{ inputs.tag }}
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 5
|
||||
126
.github/workflows/containerize-uberjar.yml
vendored
126
.github/workflows/containerize-uberjar.yml
vendored
@@ -1,126 +0,0 @@
|
||||
name: Containerize Uberjar
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
get-container-info:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
name: Get container info
|
||||
outputs:
|
||||
editions: ${{ steps.ee-info.outputs.editions }}
|
||||
ee: ${{ toJson(fromJson(steps.build-matrix.outputs.result).ee) }}
|
||||
oss: ${{ toJson(fromJson(steps.build-matrix.outputs.result).oss) }}
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
- name: Get EE Docker info
|
||||
id: ee-info
|
||||
uses: ./.github/actions/get-docker-info
|
||||
with:
|
||||
edition: ee
|
||||
- name: Get OSS Docker info
|
||||
id: oss-info
|
||||
uses: ./.github/actions/get-docker-info
|
||||
with:
|
||||
edition: oss
|
||||
- name: Build matrix info
|
||||
id: build-matrix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
const editions = JSON.parse('${{ steps.ee-info.outputs.editions }}');
|
||||
const result = {
|
||||
editions: editions,
|
||||
ee: {
|
||||
repo: '${{ steps.ee-info.outputs.repo }}',
|
||||
tag: '${{ steps.ee-info.outputs.tag }}'
|
||||
},
|
||||
oss: {
|
||||
repo: '${{ steps.oss-info.outputs.repo }}',
|
||||
tag: '${{ steps.oss-info.outputs.tag }}'
|
||||
}
|
||||
};
|
||||
console.log('Build matrix:', result);
|
||||
return result;
|
||||
|
||||
containerize:
|
||||
needs: [get-container-info]
|
||||
if: needs.get-container-info.result == 'success'
|
||||
strategy:
|
||||
matrix:
|
||||
edition: ${{ fromJson(needs.get-container-info.outputs.editions) }}
|
||||
uses: ./.github/workflows/containerize-jar.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
artifact-name: metabase-${{ matrix.edition }}-${{ github.event.inputs.commit || github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
commit: ${{ github.event.inputs.commit || github.sha }}
|
||||
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
|
||||
tag: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).tag }}
|
||||
release: "false"
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.event.inputs.commit || github.sha }}
|
||||
|
||||
run-trivy:
|
||||
needs: [get-container-info, containerize]
|
||||
strategy:
|
||||
matrix:
|
||||
edition: ${{ fromJson(needs.get-container-info.outputs.editions) }}
|
||||
runs-on: ubuntu-22.04
|
||||
name: Run Trivy vulnerability scanner
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit || github.sha }}
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@0.29.0
|
||||
env:
|
||||
TRIVY_OFFLINE_SCAN: true
|
||||
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
|
||||
tag: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).tag }}
|
||||
with:
|
||||
image-ref: docker.io/${{ env.repo }}:${{ env.tag }}
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
version: "v0.57.1"
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: "trivy-results.sarif"
|
||||
|
||||
post-comment:
|
||||
needs: [get-container-info, containerize]
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-22.04
|
||||
name: Post PR comment
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
- name: Get Docker info for comment
|
||||
id: docker-info
|
||||
uses: ./.github/actions/get-docker-info
|
||||
with:
|
||||
branch: ${{ github.head_ref || github.ref_name }}
|
||||
edition: ee
|
||||
|
||||
- name: Post PR comment
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: docker-build-status
|
||||
message: |
|
||||
✅ Docker build completed for commit ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
The uberjar has been built and containerized successfully.
|
||||
|
||||
**Branch:** `${{ github.head_ref || github.ref_name }}`
|
||||
**Docker Image:** `${{ steps.docker-info.outputs.full_image }}`
|
||||
**Docker Hub:** [View on Docker Hub](https://hub.docker.com/r/${{ steps.docker-info.outputs.repo }}/tags?name=${{ steps.docker-info.outputs.tag }})
|
||||
**Triggered by:** @${{ github.actor }}
|
||||
|
||||
Pull the image:
|
||||
```bash
|
||||
docker pull ${{ steps.docker-info.outputs.full_image }}
|
||||
```
|
||||
@@ -4,53 +4,39 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- enterprise/frontend/src/embedding-sdk/**
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_embedding_sources: ${{ steps.changes.outputs.frontend_embedding_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
|
||||
|
||||
pr-title-reminder:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pull-requests: write
|
||||
needs: [ files-changed ]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.files-changed.outputs.frontend_embedding_sources == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Check if the PR title is already following Conventional Commits style
|
||||
uses: actions/github-script@v7
|
||||
id: is-conventional-commit
|
||||
with:
|
||||
result-encoding: string
|
||||
script: | # js
|
||||
const { isConventionalTitle } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
const pullRequestTitle = context.payload.pull_request.title
|
||||
const conventionalCommitRegex = /^(?<type>\w+)\((?<scope>\w+)\):/
|
||||
const match = pullRequestTitle.match(conventionalCommitRegex)
|
||||
if (match) {
|
||||
const { type, scope } = match.groups
|
||||
return ['feat', 'fix', 'perf', 'docs', 'style', 'refactor', 'test', 'build', 'ci'].includes(type) && scope === 'sdk'
|
||||
}
|
||||
|
||||
return isConventionalTitle(pullRequestTitle)
|
||||
- name: Checkout repository so `gh` CLI works
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Remind PR authors to use conventional commit in the title
|
||||
if: ${{ steps.is-conventional-commit.outputs.result != 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const { updateCommentForEmbedding } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
|
||||
await updateCommentForEmbedding({github, context})
|
||||
script: |
|
||||
const { updateComment } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
await updateComment({github, context})
|
||||
|
||||
26
.github/workflows/coverage.yml
vendored
26
.github/workflows/coverage.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Test coverage
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '40 1 * * *' # every day at 1:40
|
||||
|
||||
jobs:
|
||||
fe-test-coverage:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
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: "cljs"
|
||||
- name: Run frontend unit tests
|
||||
run: yarn run test-unit --silent --coverage
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
flags: front-end
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
49
.github/workflows/cross-branch-migrations.yml
vendored
49
.github/workflows/cross-branch-migrations.yml
vendored
@@ -5,19 +5,6 @@ on:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "resources/migrations/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base-ref:
|
||||
description: 'Base reference for the migration test'
|
||||
required: true
|
||||
type: string
|
||||
head-ref:
|
||||
description: 'Head reference for the migration test'
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
BASE_REF: ${{ inputs.base-ref || github.base_ref }}
|
||||
HEAD_REF: ${{ inputs.head-ref || github.head_ref }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
@@ -26,21 +13,19 @@ concurrency:
|
||||
jobs:
|
||||
migrations-h2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: h2
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-postgres-12-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
@@ -61,12 +46,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-postgres-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
@@ -87,12 +71,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mysql-8-0-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -109,12 +92,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mysql-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -135,12 +117,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mariadb-10-2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -157,12 +138,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mariadb-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -179,5 +159,4 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
182
.github/workflows/docs-generate-base.yml
vendored
182
.github/workflows/docs-generate-base.yml
vendored
@@ -1,182 +0,0 @@
|
||||
# .github/workflows/reusable-doc-update.yml
|
||||
name: Reusable - Update Documentation
|
||||
|
||||
# This workflow is designed to be called by other workflows.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
target_branch:
|
||||
description: "The branch to check out and create a PR against"
|
||||
required: true
|
||||
type: string
|
||||
m2_cache_key_suffix:
|
||||
description: "A unique suffix for the Maven cache key"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
automation_token:
|
||||
description: "A token with permissions to create branches and PRs"
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
name: Update documentation for ${{ inputs.target_branch }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.automation_token }}
|
||||
ref: ${{ inputs.target_branch }}
|
||||
|
||||
- name: Prepare backend environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "auto-update-docs-${{ inputs.m2_cache_key_suffix }}"
|
||||
|
||||
- name: Generate environment variables documentation
|
||||
id: generate_env_docs
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
# Ensure we're on the target branch for documentation generation
|
||||
git checkout ${{ inputs.target_branch }}
|
||||
echo "Generating environment variables documentation from branch: ${{ inputs.target_branch }}"
|
||||
clojure -M:ee:doc environment-variables-documentation
|
||||
continue-on-error: false
|
||||
|
||||
- name: Generate config template documentation
|
||||
id: generate_config_docs
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
echo "Generating config template documentation..."
|
||||
clojure -M:ee:doc config-template
|
||||
continue-on-error: false
|
||||
|
||||
- name: Generate API documentation
|
||||
id: generate_api_docs
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
echo "Generating API documentation..."
|
||||
clojure -M:ee:doc api-documentation
|
||||
continue-on-error: false
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if git diff --quiet docs/; then
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
echo "No changes detected in docs directory"
|
||||
else
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
echo "Changes detected in docs directory"
|
||||
fi
|
||||
|
||||
- name: Create branch and commit changes
|
||||
id: commit_and_push
|
||||
if: steps.check_changes.outputs.changes == 'true'
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
|
||||
BRANCH_NAME="docs/auto-update-${{ inputs.target_branch }}-$(date +%Y-%m-%d-%H%M%S)"
|
||||
echo "Creating branch: $BRANCH_NAME"
|
||||
|
||||
# Create and switch to new branch from current target branch
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
|
||||
# Stage and commit changes
|
||||
git add docs/
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git commit -m "Update generated docs (${{ inputs.target_branch }})" --no-verify
|
||||
git push -u origin "$BRANCH_NAME"
|
||||
|
||||
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
echo "Successfully created and pushed branch"
|
||||
|
||||
- name: Create pull request
|
||||
id: create_pr
|
||||
if: steps.check_changes.outputs.changes == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.automation_token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
CHANGES_SUMMARY=$(git diff --name-only HEAD~1 docs/ 2>/dev/null | head -10 | sed 's/^/- /' || echo "- No specific files detected")
|
||||
TOTAL_CHANGES=$(git diff --name-only HEAD~1 docs/ 2>/dev/null | wc -l || echo "0")
|
||||
BRANCH_NAME="${{ steps.commit_and_push.outputs.branch_name }}"
|
||||
|
||||
echo "Creating PR with branch: $BRANCH_NAME"
|
||||
echo "Changes detected: $TOTAL_CHANGES files"
|
||||
|
||||
for i in {1..3}; do
|
||||
echo "Attempt $i of 3..."
|
||||
if gh pr create \
|
||||
--base "${{ inputs.target_branch }}" \
|
||||
--head "$BRANCH_NAME" \
|
||||
--title "Auto-update documentation for ${{ inputs.target_branch }}" \
|
||||
--body "This PR contains automatically generated documentation updates.
|
||||
|
||||
## Changes detected
|
||||
The following documentation files were updated:
|
||||
$CHANGES_SUMMARY
|
||||
$([ "$TOTAL_CHANGES" -gt 10 ] && echo "- ... and $((TOTAL_CHANGES - 10)) more files")
|
||||
|
||||
## Generation commands
|
||||
Documentation was generated using:
|
||||
- \`clojure -M:ee:doc environment-variables-documentation\`
|
||||
- \`clojure -M:ee:doc config-template\`
|
||||
- \`clojure -M:ee:doc api-documentation\`
|
||||
|
||||
Please review the changes and merge if they look correct." \
|
||||
--label "Type:Documentation" \
|
||||
--label "no-backport" \
|
||||
--reviewer "metabase/tech-writers"; then
|
||||
echo "PR created successfully on attempt $i"
|
||||
break
|
||||
else
|
||||
echo "PR creation failed on attempt $i"
|
||||
if [ $i -eq 3 ]; then
|
||||
echo "All attempts failed. Exiting with error."
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting 10 seconds before retry..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Skip PR creation
|
||||
if: steps.check_changes.outputs.changes == 'false'
|
||||
run: |
|
||||
echo "No documentation changes detected, skipping PR creation"
|
||||
|
||||
- name: Workflow summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.check_changes.outputs.changes }}" == "true" ]; then
|
||||
if [ "${{ steps.commit_and_push.outcome }}" == "success" ]; then
|
||||
if [ "${{ steps.create_pr.outcome }}" == "success" ]; then
|
||||
echo "## ✅ Documentation Updated Successfully" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Branch: \`${{ steps.commit_and_push.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- PR created targeting **${{ inputs.target_branch }}**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ⚠️ Branch Created but PR Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Branch: \`${{ steps.commit_and_push.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Manual PR creation required" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "## ❌ Failed to Create Branch" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Check workflow logs for details" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "## ℹ️ No Changes Detected" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Documentation is up to date on **${{ inputs.target_branch }}**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
36
.github/workflows/docs-links.yml
vendored
36
.github/workflows/docs-links.yml
vendored
@@ -1,36 +0,0 @@
|
||||
name: Check doc links
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
link-checker:
|
||||
name: Check documentation links
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
docs
|
||||
.lychee
|
||||
|
||||
- name: Cache lychee
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/lychee
|
||||
key: lychee-${{ runner.os }}-${{ hashFiles('.lychee/config.toml') }}
|
||||
|
||||
- name: Run link checker
|
||||
id: link-check
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
fail: true
|
||||
args: docs --config ./.lychee/config.toml --offline
|
||||
format: markdown
|
||||
16
.github/workflows/docs-master-generate.yml
vendored
16
.github/workflows/docs-master-generate.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Auto-update documentation (master)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 1 * * 1-5" # Every Monday through Friday at 1 AM UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-master-docs:
|
||||
name: Call documentation update for master
|
||||
uses: ./.github/workflows/docs-generate-base.yml
|
||||
with:
|
||||
target_branch: "master"
|
||||
m2_cache_key_suffix: "master"
|
||||
secrets:
|
||||
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
16
.github/workflows/docs-release-generate.yml
vendored
16
.github/workflows/docs-release-generate.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Auto-update documentation (release)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * 1-5" # Every Monday through Friday at 2 AM UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-release-docs:
|
||||
name: Call documentation update for release branch
|
||||
uses: ./.github/workflows/docs-generate-base.yml
|
||||
with:
|
||||
target_branch: "release-x.56.x"
|
||||
m2_cache_key_suffix: "release"
|
||||
secrets:
|
||||
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
44
.github/workflows/docs_bump_detected.yml
vendored
44
.github/workflows/docs_bump_detected.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Docs Bump Detected
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-x.[0-9]+.x'
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
push_workflow_to_docs_metabase_github_io_job:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes in the docs directory was detected."
|
||||
echo "Sending workflow trigger to create/update the corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | #js
|
||||
github.rest.repos.createDispatchEvent({
|
||||
owner: "${{ github.repository_owner }}",
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_update",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_update",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
43
.github/workflows/docs_close_detected.yml
vendored
43
.github/workflows/docs_close_detected.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Docs Close Detected
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-x.[0-9]+.x'
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
push_and_merge_workflow_to_docs_metabase_github_io_job:
|
||||
if: github.event.pull_request.merged == false
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes to the docs directory was closed."
|
||||
echo "Sending workflow trigger to close corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | #js
|
||||
github.rest.repos.createDispatchEvent({
|
||||
owner: "${{ github.repository_owner }}",
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_close",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_close",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
43
.github/workflows/docs_merge_detected.yml
vendored
43
.github/workflows/docs_merge_detected.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Docs Merge Detected
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-x.[0-9]+.x'
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
push_and_merge_workflow_to_docs_metabase_github_io_job:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes to the docs directory was merged."
|
||||
echo "Sending workflow trigger to merge corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | #js
|
||||
github.rest.repos.createDispatchEvent({
|
||||
owner: "${{ github.repository_owner }}",
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_merge",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_merge",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
61
.github/workflows/drivers-stress-test.yml
vendored
61
.github/workflows/drivers-stress-test.yml
vendored
@@ -42,7 +42,6 @@ on:
|
||||
- SQLite
|
||||
- SQL Server 2017
|
||||
- SQL Server 2022
|
||||
- Starburst
|
||||
- Vertica
|
||||
|
||||
jobs:
|
||||
@@ -664,76 +663,18 @@ jobs:
|
||||
junit-name: 'be-tests-sqlserver-2022-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
be-tests-starburst-ee:
|
||||
if: inputs.driver == 'Starburst'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: starburst
|
||||
services:
|
||||
starburst:
|
||||
image: trinodb/trino:latest
|
||||
env:
|
||||
CATALOG_MANAGEMENT: dynamic
|
||||
ports:
|
||||
- "8080:8080"
|
||||
name: Starburst
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make plugins directory
|
||||
run: mkdir plugins
|
||||
- name: Test Starburst driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-starburst-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
login-to-ecr:
|
||||
if: inputs.driver == 'Vertica'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Get ECR password
|
||||
id: login-ecr
|
||||
run: |
|
||||
PASSWORD=$(aws ecr get-login-password --region us-east-1)
|
||||
|
||||
REGISTRY="$(aws sts get-caller-identity --query Account --output text).dkr.ecr.us-east-1.amazonaws.com"
|
||||
|
||||
echo "docker_username=AWS" >> $GITHUB_OUTPUT
|
||||
echo "docker_password=$PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "registry=$REGISTRY" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
registry: ${{ steps.login-ecr.outputs.registry }}
|
||||
docker_username: ${{ steps.login-ecr.outputs.docker_username }}
|
||||
docker_password: ${{ steps.login-ecr.outputs.docker_password }}
|
||||
|
||||
be-tests-vertica-ee:
|
||||
if: inputs.driver == 'Vertica'
|
||||
runs-on: ubuntu-22.04
|
||||
needs: login-to-ecr
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: vertica
|
||||
services:
|
||||
vertica:
|
||||
image: ${{ needs.login-to-ecr.outputs.registry }}/vertica-ce:25.3.0-0
|
||||
image: vertica/vertica-ce:12.0.2-0
|
||||
ports:
|
||||
- "5433:5433"
|
||||
credentials:
|
||||
username: ${{ needs.login-to-ecr.outputs.docker_username }}
|
||||
password: ${{ needs.login-to-ecr.outputs.docker_password }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make plugins directory
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user