Compare commits
1 Commits
remove-use
...
scatter_pl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4452fb699 |
@@ -1,5 +1,4 @@
|
||||
;; -*- comment-column: 100; -*-
|
||||
{:config-paths ["macros" "src"]
|
||||
{:config-paths ["macros"]
|
||||
:linters
|
||||
{:aliased-namespace-symbol {:level :warning}
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match]} ; TODO (cam): can we fix these?
|
||||
@@ -15,157 +14,18 @@
|
||||
:redundant-fn-wrapper {:level :warning}
|
||||
:refer-all {:level :warning, :exclude [clojure.test]}
|
||||
:single-key-in {:level :warning}
|
||||
:unsorted-required-namespaces {:level :warning}
|
||||
:unused-referred-var {:exclude {compojure.core [GET DELETE POST PUT]}}
|
||||
:use {:level :warning}
|
||||
:warn-on-reflection {:level :warning}
|
||||
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/mbql-query-first-arg {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
:metabase/require-shape-checker {:level :warning}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning}
|
||||
:metabase/validate-logging {:level :warning}
|
||||
:metabase/validate-mb-driver-tests {:level :warning}
|
||||
:metabase/validate-mb-once {:level :warning}
|
||||
|
||||
:metabase/validate-deftest
|
||||
{:level :warning
|
||||
|
||||
;; calling these functions will result in an error in tests marked `^:parallel`
|
||||
;;
|
||||
;; TODO -- these should all be made thread safe if possible or renamed so they end in `!`. Then we can remove them
|
||||
;; from this list because by default anything ending in `!` is considered to be thread-unsafe
|
||||
:parallel/unsafe
|
||||
#{clojure.core/alter-var-root
|
||||
clojure.core/with-redefs
|
||||
clojure.core/with-redefs-fn
|
||||
metabase.actions.test-util/with-actions
|
||||
metabase.actions.test-util/with-actions-disabled
|
||||
metabase.actions.test-util/with-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data-tables
|
||||
metabase.analytics.snowplow-test/with-fake-snowplow-collector
|
||||
metabase.email-test/with-expected-messages
|
||||
metabase.email-test/with-fake-inbox
|
||||
metabase.test.data.users/with-group
|
||||
metabase.test.data.users/with-group-for-user
|
||||
metabase.test.data/with-empty-h2-app-db
|
||||
metabase.test.util.log/with-log-level
|
||||
metabase.test.util.misc/with-single-admin-user
|
||||
metabase.test.util/with-all-users-permission
|
||||
metabase.test.util/with-discarded-collections-perms-changes
|
||||
metabase.test.util/with-env-keys-renamed-by
|
||||
metabase.test.util/with-locale
|
||||
metabase.test.util/with-model-cleanup
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test.util/with-temp-env-var-value
|
||||
metabase.test.util/with-temp-vals-in-db
|
||||
metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test.util/with-temporary-setting-values
|
||||
metabase.test.util/with-user-in-groups
|
||||
metabase.test/with-actions
|
||||
metabase.test/with-actions-disabled
|
||||
metabase.test/with-actions-enabled
|
||||
metabase.test/with-actions-test-data
|
||||
metabase.test/with-actions-test-data-and-actions-enabled
|
||||
metabase.test/with-actions-test-data-tables
|
||||
metabase.test/with-all-users-permission
|
||||
metabase.test/with-discarded-collections-perms-changes
|
||||
metabase.test/with-empty-h2-app-db
|
||||
metabase.test/with-env-keys-renamed-by
|
||||
metabase.test/with-expected-messages
|
||||
metabase.test/with-fake-inbox
|
||||
metabase.test/with-group
|
||||
metabase.test/with-group-for-user
|
||||
metabase.test/with-locale
|
||||
metabase.test/with-log-level
|
||||
metabase.test/with-model-cleanup
|
||||
metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test/with-single-admin-user
|
||||
metabase.test/with-temp-env-var-value
|
||||
metabase.test/with-temp-vals-in-db
|
||||
metabase.test/with-temporary-raw-setting-values
|
||||
metabase.test/with-temporary-setting-values
|
||||
metabase.test/with-user-in-groups}
|
||||
|
||||
;; these functions are allowed in `^:parallel` tests even tho they end in `!`
|
||||
:parallel/safe
|
||||
#{clojure.core/assoc!
|
||||
clojure.core/compare-and-set!
|
||||
clojure.core/conj!
|
||||
clojure.core/disj!
|
||||
clojure.core/dissoc!
|
||||
clojure.core/persistent!
|
||||
clojure.core/pop!
|
||||
clojure.core/reset!
|
||||
clojure.core/reset-vals!
|
||||
clojure.core/run!
|
||||
clojure.core/swap!
|
||||
clojure.core/swap-vals!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.core.async/<!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alt!
|
||||
clojure.core.async/alt!!
|
||||
clojure.core.async/alts!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/ioc-alts!
|
||||
clojure.core.async/offer!
|
||||
clojure.core.async/onto-chan!
|
||||
clojure.core.async/onto-chan!!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.async/put!
|
||||
clojure.core.async/take!
|
||||
clojure.core.async/to-chan!
|
||||
clojure.core.async/to-chan!!
|
||||
metabase.channel.core/send!
|
||||
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
|
||||
metabase.models.notification/create-notification!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase.query-processor.store/store-database!
|
||||
;; Safe in tests, where we typically use a private, temporary index per thread.
|
||||
metabase.search.core/reindex!
|
||||
metabase.util/run-count!
|
||||
next.jdbc/execute!}}
|
||||
|
||||
:metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests
|
||||
{:level :warning
|
||||
:max-length 180}
|
||||
|
||||
;; this is not enabled here, it is enabled below only for specific namespaces.
|
||||
:metabase/disallow-hardcoded-driver-names-in-tests
|
||||
;; don't include `:h2` since that will probably lead to a mountain of tests where we are specifically testing
|
||||
;; middleware or compilation behavior with just H2 since it's the default.
|
||||
{:drivers #{ ;; core drivers
|
||||
#_:h2
|
||||
:postgres
|
||||
:mysql
|
||||
;; module drivers
|
||||
:athena
|
||||
:bigquery-cloud-sdk
|
||||
:databricks
|
||||
:druid
|
||||
:druid-jdbc
|
||||
:googleanalytics
|
||||
:hive-like
|
||||
:mongo
|
||||
:oracle
|
||||
:presto-jdbc
|
||||
:redshift
|
||||
:snowflake
|
||||
:sparksql
|
||||
:sqlite
|
||||
:sqlserver
|
||||
:vertica}}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests {:level :warning}
|
||||
:metabase/mbql-query-first-arg {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning}
|
||||
:metabase/validate-deftest {:level :warning}
|
||||
:metabase/validate-logging {:level :warning}
|
||||
:metabase/warn-about-schema= {:level :warning}
|
||||
|
||||
;;
|
||||
;; disabled linters
|
||||
@@ -190,7 +50,7 @@
|
||||
(clojure.core.logic/fresh)
|
||||
(clojure.core.logic/matcha)
|
||||
(clojure.core.logic/run)
|
||||
(clojure.test/is [query= =? malli= qv=])
|
||||
(clojure.test/is [query= =? malli=])
|
||||
(clojure.tools.macro/macrolet)
|
||||
instaparse.core/transform
|
||||
(metabase.async.streaming-response/streaming-response)
|
||||
@@ -244,16 +104,13 @@
|
||||
malli.core/explainer {:message "Use metabase.util.malli.registry/explainer instead of malli.core/explainer"}
|
||||
me.flowthing.pp/pprint {:message "Use metabase.util.log instead of me.flowthing.pp/pprint"}
|
||||
medley.core/random-uuid {:message "Use clojure.core/random-uuid instead of medley.core/random-uuid"}
|
||||
metabase.driver/database-supports? {:message "Use metabase.driver.util/supports? instead of metabase.driver/database-supports?"}
|
||||
metabase.lib.equality/find-column-indexes-for-refs {:message "Use lib.equality/closest-matches-in-metadata or lib.equality/find-closest-matches-for-refs instead of lib.equality/find-column-indexes-for-refs"}
|
||||
metabase.test/with-temp* {:message "Use mt/with-temp instead of mt/with-temp*"}
|
||||
toucan2.tools.with-temp {:message "Use mt/with-temp instead of t2.with-temp/with-temp"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{camel-snake-kebab.core {:message "CSK is not Turkish-safe, use the versions in metabase.util instead."}
|
||||
cheshire.core {:message "Use metabase.util.json instead of cheshire.core directly."}
|
||||
clojure.tools.logging {:message "Use metabase.util.log instead of clojure.tools.logging directly"}
|
||||
grouper.core {:message "Use metabase.util.grouper instead of grouper.core"}
|
||||
honeysql.core {:message "Use Honey SQL 2. Honey SQL 1 is removed as a dependency in Metabase 49+."}
|
||||
honeysql.format {:message "Use Honey SQL 2. Honey SQL 1 is removed as a dependency in Metabase 49+."}
|
||||
honeysql.helpers {:message "Use Honey SQL 2. Honey SQL 1 is removed as a dependency in Metabase 49+."}
|
||||
@@ -265,197 +122,7 @@
|
||||
{:exclude
|
||||
[colorize.core]}
|
||||
|
||||
;; A "module" is any `metabase.x` namespace in `src`.
|
||||
:metabase/ns-module-checker
|
||||
{:level :warning
|
||||
|
||||
;; Map of module name => the "core" external public-facing API namespace(s). You have three options here:
|
||||
;;
|
||||
;; 1. Special sentinel value `:any` means means this module does not (yet) have external public-facing API
|
||||
;; namespace(s). This is mostly a temporary placeholder until we go in and create module namespaces, which means
|
||||
;; you should go create one.
|
||||
;;
|
||||
;; 2. A set of namespace symbols. All namespaces in other modules will only be allowed to use namespaces from
|
||||
;; this set. Ideally this set should only have one namespace, but restricting it to a set of several is still
|
||||
;; better than `:any`.
|
||||
;;
|
||||
;; 3. `nil` or not listed here -- we default to assuming there is one API namespace that matches the name of the
|
||||
;; module itself, e.g. the module namespace for `metabase.setup` would be `metabase.setup`.
|
||||
;;
|
||||
;; `nil` or an empty set Otherwise this should be a set of namespace symbols.
|
||||
;;
|
||||
;; PRO TIP: Check out the `dev.deps-graph` namespace for helpful tools to see where a module is used externally.
|
||||
:api-namespaces
|
||||
{metabase.actions #{metabase.actions.core}
|
||||
metabase.analytics #{metabase.analytics.prometheus
|
||||
metabase.analytics.snowplow
|
||||
metabase.analytics.stats
|
||||
metabase.analytics.sdk} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.analyze #{metabase.analyze}
|
||||
metabase.api #{metabase.api.common
|
||||
metabase.api.dataset
|
||||
metabase.api.permission-graph
|
||||
metabase.api.routes} ; TODO -- consolidate these into a real API namespace. I think the `*current-user*` type stuff might need to be moved into a separate module.
|
||||
metabase.async #{metabase.async.streaming-response
|
||||
metabase.async.streaming-response.thread-pool
|
||||
metabase.async.util} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.audit #{metabase.audit}
|
||||
metabase.bootstrap #{metabase.bootstrap}
|
||||
metabase.cmd #{} ; there are no namespaces here since you shouldn't be using this in any other module.
|
||||
metabase.channel #{metabase.channel.core
|
||||
metabase.channel.render.core
|
||||
metabase.channel.template.core}
|
||||
metabase.config #{metabase.config}
|
||||
metabase.core #{metabase.core.initialization-status} ; TODO -- only namespace used outside of EE, this probably belongs in `metabase.server` anyway since that's the only place it's used.
|
||||
metabase.db #{metabase.db
|
||||
metabase.db.metadata-queries ; TODO this should probably be separate from metabase.db
|
||||
metabase.db.query ; TODO this is mostly util stuff like `metabase.db.query/query` that we don't even need anymore.
|
||||
metabase.db.setup} ; TODO these are only calling `metabase.db.setup/setup-db!` and there's a slightly different version in `metabase.db`
|
||||
metabase.driver :any ; TODO -- 19 namespaces!!!! CRY
|
||||
metabase.email #{metabase.email
|
||||
metabase.email.messages
|
||||
metabase.email.result-attachment} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.embed #{metabase.embed.settings}
|
||||
metabase.events #{metabase.events}
|
||||
metabase.formatter #{metabase.formatter}
|
||||
metabase.integrations #{metabase.integrations.common
|
||||
metabase.integrations.google
|
||||
metabase.integrations.ldap
|
||||
metabase.integrations.slack} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.legacy-mbql #{metabase.legacy-mbql.normalize
|
||||
metabase.legacy-mbql.predicates
|
||||
metabase.legacy-mbql.schema
|
||||
metabase.legacy-mbql.schema.helpers
|
||||
metabase.legacy-mbql.util} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.lib :any ; TODO -- :cry: 34 externally referenced namespaces, but maybe half of them are schema namespaces which technically don't need to be required.
|
||||
metabase.logger #{metabase.logger}
|
||||
metabase.metabot #{metabase.metabot}
|
||||
metabase.models :any ; TODO -- scream, 62 namespaces used elsewhere, but to be fair a lot of these don't *need* to be required.
|
||||
metabase.moderation #{metabase.moderation}
|
||||
metabase.notification #{metabase.notification.core
|
||||
metabase.notification.payload.core}
|
||||
metabase.permissions #{metabase.permissions.util} ; TODO -- this is currently the only namespace in this module. Give it a real API namespace?
|
||||
metabase.plugins #{metabase.plugins
|
||||
metabase.plugins.classloader} ; TODO -- not 100% sure the classloader belongs here
|
||||
metabase.public-settings #{metabase.public-settings
|
||||
metabase.public-settings.premium-features}
|
||||
metabase.pulse #{metabase.pulse.core}
|
||||
metabase.query-processor :any ; TODO omg scream, 29 namespaces used outside of the module. WHAT THE HECC
|
||||
metabase.query-analysis #{metabase.query-analysis
|
||||
metabase.query-analysis.failure-map}
|
||||
metabase.related #{metabase.related}
|
||||
metabase.sample-data #{metabase.sample-data}
|
||||
metabase.search #{metabase.search
|
||||
;; deprecating naked metabase.search namespace in favor of this.
|
||||
metabase.search.core
|
||||
metabase.search.spec}
|
||||
metabase.server #{metabase.server
|
||||
metabase.server.handler
|
||||
metabase.server.middleware.auth
|
||||
metabase.server.middleware.exceptions
|
||||
metabase.server.middleware.json
|
||||
metabase.server.middleware.misc
|
||||
metabase.server.middleware.offset-paging
|
||||
metabase.server.middleware.session
|
||||
metabase.server.protocols
|
||||
metabase.server.request.util} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.setup #{metabase.setup}
|
||||
metabase.sync #{metabase.sync
|
||||
metabase.sync.analyze
|
||||
metabase.sync.concurrent
|
||||
metabase.sync.field-values
|
||||
metabase.sync.schedules
|
||||
metabase.sync.sync-metadata
|
||||
metabase.sync.sync-metadata.fields
|
||||
metabase.sync.sync-metadata.tables
|
||||
metabase.sync.util} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.task #{metabase.task
|
||||
metabase.task.index-values
|
||||
metabase.task.persist-refresh
|
||||
;; We need to expose this to the metabase.search module.
|
||||
;; Ideally, it would just live inside that module.
|
||||
metabase.task.search-index} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.troubleshooting #{metabase.troubleshooting}
|
||||
metabase.types #{metabase.types}
|
||||
metabase.upload #{metabase.upload}
|
||||
metabase.util :any ; I think util being `:any` is actually something I am ok with. But this has 32 namespaces.
|
||||
metabase.xrays #{metabase.xrays}}
|
||||
|
||||
;; Map of module => other modules you're allowed to use there. You have two options here:
|
||||
;;
|
||||
;; 1. `:any` means namespaces in this module are allowed to use any other module -- allowed modules are not
|
||||
;; enforced for this module. Module API namespaces for modules that have them defined are still enforced. For
|
||||
;; ones that are `nil`, please go in and add a list of allowed modules. `:any` is mostly meant a temporary
|
||||
;; placeholder until we can fill these all out, so feel free to fix these.
|
||||
;;
|
||||
;; 2. A set of module symbols. This is the list of modules that are allowed to be referenced. An empty set means no
|
||||
;; other modules are allowed to be referenced; this is the default for any modules that aren't listed here.
|
||||
:allowed-modules
|
||||
{metabase.actions :any
|
||||
metabase.analytics :any
|
||||
metabase.analyze :any
|
||||
metabase.api :any
|
||||
metabase.async :any
|
||||
metabase.audit :any
|
||||
metabase.auth-provider #{metabase.util metabase.public-settings}
|
||||
metabase.bootstrap #{}
|
||||
metabase.cmd :any
|
||||
metabase.compatibility :any
|
||||
metabase.channel :any
|
||||
metabase.config #{metabase.plugins}
|
||||
metabase.core :any
|
||||
metabase.db :any
|
||||
metabase.driver :any
|
||||
metabase.email :any
|
||||
metabase.embed :any
|
||||
metabase.events :any
|
||||
metabase.formatter :any
|
||||
metabase.integrations :any
|
||||
metabase.legacy-mbql :any
|
||||
metabase.lib :any
|
||||
metabase.logger #{metabase.config
|
||||
metabase.plugins}
|
||||
metabase.metabot :any
|
||||
metabase.models :any
|
||||
metabase.moderation :any
|
||||
metabase.notification :any
|
||||
metabase.permissions :any
|
||||
metabase.plugins :any
|
||||
metabase.public-settings :any
|
||||
metabase.pulse :any
|
||||
metabase.query-analysis #{metabase.config
|
||||
metabase.driver
|
||||
metabase.legacy-mbql
|
||||
metabase.lib
|
||||
metabase.models
|
||||
metabase.public-settings
|
||||
metabase.query-processor
|
||||
metabase.util}
|
||||
metabase.query-processor :any
|
||||
metabase.related :any
|
||||
metabase.sample-data :any
|
||||
metabase.search :any
|
||||
metabase.server :any
|
||||
metabase.setup :any
|
||||
metabase.sync :any
|
||||
metabase.task :any
|
||||
metabase.troubleshooting :any
|
||||
metabase.types #{metabase.util}
|
||||
metabase.upload :any
|
||||
metabase.util :any
|
||||
metabase.xrays :any}
|
||||
|
||||
;; namespaces matching these patterns (with `re-find`) are excluded from the module linter. Since regex literals
|
||||
;; aren't allowed in EDN just used the `(str <regex>)` version i.e. two slashes instead of one.
|
||||
;;
|
||||
;; this is mostly intended for excluding test namespaces or those rare 'glue' namespaces that glue multiple modules
|
||||
;; together, e.g. `metabase.lib.metadata.jvm`.
|
||||
:ignored-namespace-patterns
|
||||
#{"-test$" ; anything ending in `-test`
|
||||
"test[-.]util" ; anything that contains `test.util` or `test-util`
|
||||
"test\\.impl" ; anything that contains `test.impl`
|
||||
"^metabase\\.test" ; anything starting with `metabase.test`
|
||||
"^metabase\\.http-client$"}} ; `metabase.http-client` which is a test-only namespace despite its name.
|
||||
:unsorted-required-namespaces {:level :warning}
|
||||
|
||||
:consistent-alias
|
||||
{:aliases
|
||||
@@ -480,11 +147,16 @@
|
||||
clojure.tools.namespace.parse ns.parse
|
||||
colorize.core colorize
|
||||
environ.core env
|
||||
hf.depstar.api depstar
|
||||
hiccup.core hiccup
|
||||
hiccup.util hiccup.util
|
||||
honey.sql sql
|
||||
honey.sql.helpers sql.helpers
|
||||
java-time.api t
|
||||
honeysql.core hsql
|
||||
honeysql.format hformat
|
||||
honeysql.helpers hh
|
||||
honeysql.types htypes
|
||||
java-time t
|
||||
macaw.core macaw
|
||||
malli.core mc
|
||||
malli.error me
|
||||
@@ -497,10 +169,7 @@
|
||||
metabase-enterprise.audit-app.pages.common common
|
||||
metabase-enterprise.sandbox.api.table table
|
||||
metabase-enterprise.test met
|
||||
metabase.analytics.sdk sdk
|
||||
metabase.analytics.stats stats
|
||||
metabase.analysis.native-query-analyzer nqa
|
||||
metabase.analysis.native-query-analyzer.parameter-substitution nqa.sub
|
||||
metabase.analyze.query-results qr
|
||||
metabase.analyze.classifiers.category classifiers.category
|
||||
metabase.analyze.classifiers.core classifiers
|
||||
@@ -551,11 +220,13 @@
|
||||
metabase.api.tiles api.tiles
|
||||
metabase.api.timeline api.timeline
|
||||
metabase.api.timeline-event api.timeline-event
|
||||
metabase.api.transform api.transform
|
||||
metabase.api.user api.user
|
||||
metabase.api.util api.util
|
||||
metabase.async.streaming-response.thread-pool thread-pool
|
||||
metabase.channel.core channel
|
||||
metabase.channel.render.core channel.render
|
||||
metabase.automagic-dashboards.core magic
|
||||
metabase.automagic-dashboards.dashboard-templates dashboard-templates
|
||||
metabase.automagic-dashboards.populate populate
|
||||
metabase.cmd.copy.h2 copy.h2
|
||||
metabase.config config
|
||||
metabase.config.file config.file
|
||||
@@ -565,6 +236,7 @@
|
||||
metabase.db.metadata-queries metadata-queries
|
||||
metabase.db.spec mdb.spec
|
||||
metabase.db.query mdb.query
|
||||
metabase.domain-entities.specs de.specs
|
||||
metabase.driver.common.parameters params
|
||||
metabase.driver.common.parameters.dates params.dates
|
||||
metabase.driver.common.parameters.operators params.ops
|
||||
@@ -575,6 +247,10 @@
|
||||
metabase.driver.druid.js druid.js
|
||||
metabase.driver.druid.query-processor druid.qp
|
||||
metabase.driver.druid.sync druid.sync
|
||||
metabase.driver.googleanalytics.client ga.client
|
||||
metabase.driver.googleanalytics.execute ga.execute
|
||||
metabase.driver.googleanalytics.metadata ga.metadata
|
||||
metabase.driver.googleanalytics.query-processor ga.qp
|
||||
metabase.driver.impl driver.impl
|
||||
metabase.driver.mongo.execute mongo.execute
|
||||
metabase.driver.mongo.parameters mongo.params
|
||||
@@ -595,7 +271,6 @@
|
||||
metabase.driver.sql-jdbc.test-util sql-jdbc.tu
|
||||
metabase.driver.sql.parameters.substitute sql.params.substitute
|
||||
metabase.driver.sql.parameters.substitution sql.params.substitution
|
||||
metabase.eid-translation eid-translation
|
||||
metabase.email-test et
|
||||
metabase.email.messages messages
|
||||
metabase.formatter formatter
|
||||
@@ -611,7 +286,6 @@
|
||||
metabase.models.collection collection
|
||||
metabase.models.collection.graph graph
|
||||
metabase.models.collection-permission-graph-revision c-perm-revision
|
||||
metabase.models.channel models.channel
|
||||
metabase.models.dashboard-card dashboard-card
|
||||
metabase.models.database database
|
||||
metabase.models.dependency dependency
|
||||
@@ -619,26 +293,26 @@
|
||||
metabase.models.interface mi
|
||||
metabase.models.moderation-review moderation-review
|
||||
metabase.models.native-query-snippet native-query-snippet
|
||||
metabase.models.notification models.notification
|
||||
metabase.models.params params
|
||||
metabase.models.permissions perms
|
||||
metabase.models.permissions-group perms-group
|
||||
metabase.models.permissions-group-membership perms-group-membership
|
||||
metabase.models.permissions-revision perms-revision
|
||||
metabase.models.permissions.parse perms-parse
|
||||
metabase.models.pulse models.pulse
|
||||
metabase.models.pulse pulse
|
||||
metabase.models.pulse-channel pulse-channel
|
||||
metabase.models.pulse-channel-recipient pulse-channel-recipient
|
||||
metabase.models.query.permissions query-perms
|
||||
metabase.models.setting.cache setting.cache
|
||||
metabase.models.timeline timeline
|
||||
metabase.models.timeline-event timeline-event
|
||||
metabase.notification.core notification
|
||||
metabase.notification.test-util notification.tu
|
||||
metabase.native-query-analyzer query-analyzer
|
||||
metabase.native-query-analyzer.parameter-substitution nqa.sub
|
||||
metabase.plugins.initialize plugins.init
|
||||
metabase.public-settings public-settings
|
||||
metabase.public-settings.premium-features premium-features
|
||||
metabase.pulse.core pulse
|
||||
metabase.pulse pulse ; NB some conflicts with metabase.models.pulse
|
||||
metabase.pulse.markdown markdown
|
||||
metabase.pulse.render render
|
||||
metabase.pulse.render.body body
|
||||
metabase.pulse.render.style style
|
||||
@@ -671,9 +345,8 @@
|
||||
metabase.related related
|
||||
metabase.search.config search.config
|
||||
metabase.search.filter search.filter
|
||||
metabase.search.in-place.filter search.in-place.filter
|
||||
metabase.search.in-place.util search.util
|
||||
metabase.search.in-place.scoring scoring
|
||||
metabase.search.util search.util
|
||||
metabase.search.scoring scoring
|
||||
metabase.server.middleware.auth mw.auth
|
||||
metabase.server.middleware.browser-cookie mw.browser-cookie
|
||||
metabase.server.middleware.exceptions mw.exceptions
|
||||
@@ -686,6 +359,8 @@
|
||||
metabase.server.middleware.ssl mw.ssl
|
||||
metabase.server.request.util req.util
|
||||
metabase.server.protocols server.protocols
|
||||
metabase.shared.util shared.u
|
||||
metabase.shared.util.currency currency
|
||||
metabase.sync.analyze.fingerprint sync.fingerprint
|
||||
metabase.sync.field-values field-values
|
||||
metabase.sync.interface i
|
||||
@@ -706,8 +381,10 @@
|
||||
metabase.test.util.js test.js
|
||||
metabase.test.util.timezone test.tz
|
||||
metabase.timeseries-query-processor-test.util tqpt
|
||||
metabase.transforms.core tf
|
||||
metabase.transforms.materialize tf.materialize
|
||||
metabase.transforms.specs tf.specs
|
||||
metabase.util.cron u.cron
|
||||
metabase.util.currency currency
|
||||
metabase.util.date-2 u.date
|
||||
metabase.util.date-2.common u.date.common
|
||||
metabase.util.date-2.parse u.date.parse
|
||||
@@ -715,7 +392,6 @@
|
||||
metabase.util.encryption encryption
|
||||
metabase.util.encryption-test encryption-test
|
||||
metabase.util.files u.files
|
||||
metabase.util.grouper grouper
|
||||
metabase.util.honey-sql-2 h2x
|
||||
metabase.util.i18n i18n
|
||||
metabase.util.i18n.impl i18n.impl
|
||||
@@ -724,12 +400,10 @@
|
||||
metabase.util.macros u.macros
|
||||
metabase.util.malli mu
|
||||
metabase.util.malli.schema ms
|
||||
metabase.util.markdown markdown
|
||||
metabase.util.password u.password
|
||||
metabase.util.schema su
|
||||
metabase.util.ui-logic ui-logic
|
||||
metabase.util.urls urls
|
||||
metabase.xrays xrays
|
||||
metabuild-common.core u
|
||||
metabuild-common.output out
|
||||
metabuild-common.shell shell
|
||||
@@ -743,117 +417,106 @@
|
||||
toucan.models models}}}
|
||||
|
||||
:lint-as
|
||||
{cljs.cache/defcache clojure.core/deftype
|
||||
clojure.core.cache/defcache clojure.core/deftype
|
||||
clojure.core.logic/defne clj-kondo.lint-as/def-catch-all
|
||||
clojure.test.check.clojure-test/defspec clojure.test/deftest
|
||||
clojurewerkz.quartzite.jobs/defjob clojure.core/defn
|
||||
metabase-enterprise.serialization.test-util/with-dbs clojure.core/fn
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir clojure.core/let
|
||||
metabase.actions.test-util/with-actions clojure.core/let
|
||||
metabase.api.common/defroutes clojure.core/def
|
||||
metabase.api.common/let-404 clojure.core/let
|
||||
metabase.api.search-test/do-test-users clojure.core/let
|
||||
metabase.async.api-response-test/with-response clojure.core/let
|
||||
metabase.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let
|
||||
metabase.channel.impl.http-test/with-captured-http-requests clojure.core/fn
|
||||
metabase.channel.impl.http-test/with-server clojure.core/let
|
||||
metabase.db.custom-migrations/define-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.db.custom-migrations/define-reversible-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.db.data-migrations/defmigration clojure.core/def
|
||||
metabase.db.liquibase/with-liquibase clojure.core/let
|
||||
metabase.db.schema-migrations-test.impl/with-temp-empty-app-db clojure.core/let
|
||||
metabase.driver.mongo.connection/with-mongo-client clojure.core/let
|
||||
metabase.driver.mongo.connection/with-mongo-database clojure.core/let
|
||||
metabase.driver.mongo.query-processor/mongo-let clojure.core/let
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction clojure.core/let
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection clojure.core/let
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info clojure.core/fn
|
||||
metabase.integrations.ldap/with-ldap-connection clojure.core/fn
|
||||
metabase.models.collection-test/with-collection-in-location clojure.core/let
|
||||
metabase.models.json-migration/def-json-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.models.setting.multi-setting/define-multi-setting clojure.core/def
|
||||
metabase.models.setting/defsetting clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/defenterprise-schema clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/define-premium-feature clojure.core/def
|
||||
metabase.pulse.send-test/with-pulse-for-card clojure.core/let
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/->
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/->
|
||||
metabase.query-processor.error-type/deferror clojure.core/def
|
||||
metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution! clojure.core/let
|
||||
metabase.query-processor.setup/with-qp-setup clojure.core/let
|
||||
metabase.sync.util/sum-for clojure.core/for
|
||||
metabase.sync.util/with-emoji-progress-bar clojure.core/let
|
||||
metabase.test.data.interface/defdataset clojure.core/def
|
||||
metabase.test.data.interface/defdataset-edn clojure.core/def
|
||||
metabase.test/defdataset clojure.core/def
|
||||
metabase.test/let-url clojure.core/let
|
||||
metabase.test/with-actions clojure.core/let
|
||||
metabase.test/with-grouper-batches! clojure.core/fn
|
||||
metabase.test/with-open-channels clojure.core/let
|
||||
metabase.test/with-single-admin-user clojure.core/fn
|
||||
metabase.test/with-temp-dir clojure.core/let
|
||||
metabase.test/with-temp-empty-app-db clojure.core/let
|
||||
metabase.test/with-temp-file clojure.core/let
|
||||
metabase.test/with-user-in-groups clojure.core/let
|
||||
metabase.upload-test/with-upload-table! clojure.core/let
|
||||
metabase.util.files/with-open-path-to-resource clojure.core/let
|
||||
metabase.util.malli.defn/defn schema.core/defn
|
||||
metabase.util.malli.defn/defn- schema.core/defn
|
||||
metabase.util.malli.fn/fn schema.core/fn
|
||||
metabase.util.malli/defmethod schema.core/defmethod
|
||||
metabase.util.malli/defn schema.core/defn
|
||||
metabase.util.malli/defn- schema.core/defn
|
||||
metabase.util.malli/fn schema.core/fn
|
||||
metabase.util.namespaces/import-fns potemkin/import-vars
|
||||
metabase.util.ssh/with-ssh-tunnel clojure.core/let
|
||||
metabase.xrays.domain-entities.malli/defn schema.core/defn
|
||||
metabuild-common.core/with-open-jar-file-system clojure.core/let
|
||||
metabuild-common.files/with-open-jar-file-system clojure.core/let
|
||||
monger.operators/defoperator clojure.core/def
|
||||
potemkin.types/defprotocol+ clojure.core/defprotocol
|
||||
potemkin.types/defrecord+ clojure.core/defrecord
|
||||
potemkin.types/deftype+ clojure.core/deftype
|
||||
potemkin/defprotocol+ clojure.core/defprotocol
|
||||
potemkin/defrecord+ clojure.core/defrecord
|
||||
potemkin/deftype+ clojure.core/deftype
|
||||
toucan.db/with-call-counting clojure.core/fn
|
||||
toucan2.core/with-call-count clojure.core/fn}
|
||||
{clojure.core.logic/defne clj-kondo.lint-as/def-catch-all
|
||||
clojure.test.check.clojure-test/defspec clojure.test/deftest
|
||||
clojurewerkz.quartzite.jobs/defjob clojure.core/defn
|
||||
honeysql.helpers/defhelper clj-kondo.lint-as/def-catch-all
|
||||
honeysql.util/defalias clojure.core/def
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir clojure.core/let
|
||||
metabase-enterprise.serialization.test-util/with-dbs clojure.core/fn
|
||||
metabase.actions.test-util/with-actions clojure.core/let
|
||||
metabase.api.common/let-404 clojure.core/let
|
||||
metabase.api.common/defroutes clojure.core/def
|
||||
metabase.api.search-test/do-test-users clojure.core/let
|
||||
metabase.async.api-response-test/with-response clojure.core/let
|
||||
metabase.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let
|
||||
metabase.db.custom-migrations/define-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.db.custom-migrations/define-reversible-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.db.data-migrations/defmigration clojure.core/def
|
||||
metabase.db.liquibase/with-liquibase clojure.core/let
|
||||
metabase.db.schema-migrations-test.impl/with-temp-empty-app-db clojure.core/let
|
||||
metabase.domain-entities.malli/defn schema.core/defn
|
||||
metabase.driver.mongo.query-processor/mongo-let clojure.core/let
|
||||
metabase.driver.mongo.connection/with-mongo-client clojure.core/let
|
||||
metabase.driver.mongo.connection/with-mongo-database clojure.core/let
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction clojure.core/let
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection clojure.core/let
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info clojure.core/fn
|
||||
metabase.integrations.ldap/with-ldap-connection clojure.core/fn
|
||||
metabase.models.collection-test/with-collection-in-location clojure.core/let
|
||||
metabase.models.json-migration/def-json-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.models.setting.multi-setting/define-multi-setting clojure.core/def
|
||||
metabase.models.setting/defsetting clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/defenterprise-schema clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/define-premium-feature clojure.core/def
|
||||
metabase.pulse-test/with-pulse-for-card clojure.core/let
|
||||
metabase.query-processor.error-type/deferror clojure.core/def
|
||||
metabase.query-processor.setup/with-qp-setup clojure.core/let
|
||||
metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution clojure.core/let
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/->
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/->
|
||||
metabase.shared.util.namespaces/import-fns potemkin/import-vars
|
||||
metabase.sync.util/sum-for clojure.core/for
|
||||
metabase.sync.util/with-emoji-progress-bar clojure.core/let
|
||||
metabase.test.data.interface/defdataset clojure.core/def
|
||||
metabase.test.data.interface/defdataset-edn clojure.core/def
|
||||
metabase.test/defdataset clojure.core/def
|
||||
metabase.test/let-url clojure.core/let
|
||||
metabase.test/with-actions clojure.core/let
|
||||
metabase.test/with-open-channels clojure.core/let
|
||||
metabase.test/with-single-admin-user clojure.core/fn
|
||||
|
||||
metabase.test/with-temp-dir clojure.core/let
|
||||
metabase.test/with-temp-empty-app-db clojure.core/let
|
||||
metabase.test/with-temp-file clojure.core/let
|
||||
metabase.test/with-user-in-groups clojure.core/let
|
||||
metabase.upload-test/with-upload-table! clojure.core/let
|
||||
metabase.util.files/with-open-path-to-resource clojure.core/let
|
||||
metabase.util.malli.defn/defn schema.core/defn
|
||||
metabase.util.malli.fn/fn schema.core/fn
|
||||
metabase.util.malli/defmethod schema.core/defmethod
|
||||
metabase.util.malli/defn schema.core/defn
|
||||
metabase.util.malli/fn schema.core/fn
|
||||
metabase.util.ssh/with-ssh-tunnel clojure.core/let
|
||||
monger.operators/defoperator clojure.core/def
|
||||
potemkin.types/defprotocol+ clojure.core/defprotocol
|
||||
potemkin.types/defrecord+ clojure.core/defrecord
|
||||
potemkin.types/deftype+ clojure.core/deftype
|
||||
potemkin/defprotocol+ clojure.core/defprotocol
|
||||
potemkin/defrecord+ clojure.core/defrecord
|
||||
potemkin/deftype+ clojure.core/deftype
|
||||
toucan.db/with-call-counting clojure.core/fn
|
||||
toucan2.core/with-call-count clojure.core/fn}
|
||||
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{cljs.test/deftest hooks.clojure.test/deftest
|
||||
cljs.test/is hooks.clojure.test/is
|
||||
cljs.test/use-fixtures hooks.clojure.test/use-fixtures
|
||||
clojure.core/ns hooks.clojure.core/lint-ns
|
||||
clojure.test/deftest hooks.clojure.test/deftest
|
||||
clojure.test/is hooks.clojure.test/is
|
||||
clojure.test/use-fixtures hooks.clojure.test/use-fixtures
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
|
||||
metabase-enterprise.advanced-permissions.models.permissions.application-permissions-test/with-new-group-and-current-graph hooks.common/with-two-top-level-bindings
|
||||
metabase-enterprise.audit-app.pages-test/with-temp-objects hooks.common/with-one-binding
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
|
||||
metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.analytics.prometheus-test/with-prometheus-system! hooks.common/with-two-bindings
|
||||
metabase.api.alert-test/with-alert-in-collection! hooks.common/with-four-bindings
|
||||
metabase.api.automagic-dashboards-test/with-indexed-model! hooks.metabase.api.automagic-dashboards-test/with-indexed-model!
|
||||
metabase.api.card-test/with-card-param-values-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.api.card-test/with-persistence-setup! hooks.common/with-one-top-level-binding
|
||||
metabase.analytics.prometheus-test/with-prometheus-system hooks.common/with-two-bindings
|
||||
metabase.api.automagic-dashboards-test/with-indexed-model hooks.metabase.api.automagic-dashboards-test/with-indexed-model
|
||||
metabase.api.alert-test/with-alert-in-collection hooks.common/with-four-bindings
|
||||
metabase.api.card-test/with-persistence-setup hooks.common/with-one-top-level-binding
|
||||
metabase.api.card-test/with-temp-native-card! hooks.common/with-two-bindings
|
||||
metabase.api.card-test/with-temp-native-card-with-params! hooks.common/with-two-bindings
|
||||
metabase.api.collection-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings
|
||||
metabase.api.common/defendpoint hooks.metabase.api.common/defendpoint
|
||||
metabase.api.common/defendpoint-async hooks.metabase.api.common/defendpoint
|
||||
metabase.api.common/defendpoint-async-schema hooks.metabase.api.common/defendpoint
|
||||
metabase.api.common.internal-test/with-jetty-server hooks.common/with-one-binding
|
||||
metabase.api.collection-test/with-french-user-and-personal-collection hooks.common/with-two-top-level-bindings
|
||||
metabase.api.dashboard-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.api.dashboard-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
|
||||
metabase.api.card-test/with-card-param-values-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/do-response-formats hooks.common/with-two-bindings
|
||||
metabase.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
|
||||
metabase.api.persist-test/with-setup! hooks.common/with-one-top-level-binding
|
||||
metabase.api.public-test/with-required-param-card! hooks.common/with-one-binding
|
||||
metabase.api.persist-test/with-setup hooks.common/with-one-top-level-binding
|
||||
metabase.api.public-test/with-required-param-card hooks.common/with-one-binding
|
||||
metabase.api.public-test/with-temp-public-card hooks.common/let-one-with-optional-value
|
||||
metabase.api.public-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value
|
||||
metabase.api.public-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings
|
||||
@@ -861,39 +524,33 @@
|
||||
metabase.api.search-test/with-search-items-in-root-collection hooks.common/do*
|
||||
metabase.api.user-test/with-temp-user-email hooks.common/with-one-binding
|
||||
metabase.async.streaming-response-test/with-start-execution-chan hooks.common/with-one-binding
|
||||
metabase.channel.template.handlebars-test/with-temp-template! hooks.common/with-vec-first-binding
|
||||
metabase.channel.template.handlebars-helper/defhelper hooks.metabase.channel.template.handlebars-helper/defhelper
|
||||
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard hooks.common/let-second
|
||||
metabase.db.schema-migrations-test.impl/test-migrations hooks.metabase.db.schema-migrations-test.impl/test-migrations
|
||||
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard hooks.common/let-second
|
||||
metabase.driver.bigquery-cloud-sdk-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
|
||||
metabase.driver.oracle-test/with-temp-user hooks.common/let-one-with-optional-value
|
||||
metabase.driver/register! hooks.metabase.driver/register!
|
||||
metabase.legacy-mbql.schema.macros/defclause hooks.metabase.legacy-mbql.schemas.macros/defclause
|
||||
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.schema.mbql-clause/define-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.schema.mbql-clause/define-tuple-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
|
||||
metabase.lib.test-util.macros/$ids hooks.metabase.test.data/$ids
|
||||
metabase.lib.test-util.macros/mbql-query hooks.metabase.test.data/mbql-query
|
||||
metabase.lib.test-util.macros/with-testing-against-standard-queries hooks.metabase.lib.test-util.macros/with-testing-against-standard-queries
|
||||
metabase.models.collection-test/with-collection-hierarchy! hooks.common/let-one-with-optional-value
|
||||
metabase.models.collection-test/with-collection-hierarchy hooks.common/let-one-with-optional-value
|
||||
metabase.models.collection-test/with-personal-and-impersonal-collections hooks.common/with-two-bindings
|
||||
metabase.models.dashboard-test/with-dash-in-collection hooks.common/with-three-bindings
|
||||
metabase.models.dashboard-tab-test/with-dashtab-in-personal-collection hooks.common/with-one-top-level-binding
|
||||
metabase.models.dashboard-test/with-dash-in-collection! hooks.common/with-three-bindings
|
||||
metabase.models.interface/define-batched-hydration-method hooks.metabase.models.interface/define-hydration-method
|
||||
metabase.models.interface/define-simple-hydration-method hooks.metabase.models.interface/define-hydration-method
|
||||
metabase.models.pulse-test/with-dashboard-subscription-in-collection! hooks.common/with-four-bindings
|
||||
metabase.models.pulse-test/with-pulse-in-collection! hooks.common/with-four-bindings
|
||||
metabase.models.interface/define-batched-hydration-method hooks.metabase.models.interface/define-hydration-method
|
||||
metabase.models.pulse-test/with-dashboard-subscription-in-collection hooks.common/with-four-bindings
|
||||
metabase.models.pulse-test/with-pulse-in-collection hooks.common/with-four-bindings
|
||||
metabase.models.setting.multi-setting/define-multi-setting hooks.metabase.models.setting/define-multi-setting
|
||||
metabase.models.setting/defsetting hooks.metabase.models.setting/defsetting
|
||||
metabase.public-settings.premium-features/defenterprise hooks.metabase.public-settings.premium-features/defenterprise
|
||||
metabase.pulse.test-util/checkins-query-card hooks.metabase.test.data/$ids
|
||||
metabase.query-processor-test.expressions-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
|
||||
metabase.query-processor-test.filter-test/count-with-filter-clause hooks.metabase.test.data/$ids
|
||||
metabase.query-processor.middleware.cache-test/with-mock-cache! hooks.common/with-two-bindings
|
||||
metabase.query-processor.middleware.cache-test/with-mock-cache hooks.common/with-two-bindings
|
||||
metabase.sample-data-test/with-temp-sample-database-db hooks.common/with-one-binding
|
||||
metabase.stale-test/with-stale-items hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.test.data.datasets/test-driver hooks.metabase.test.data.datasets/test-driver
|
||||
metabase.test.data.datasets/test-drivers hooks.metabase.test.data.datasets/test-drivers
|
||||
metabase.test.data.users/with-group hooks.common/let-one-with-optional-value
|
||||
metabase.test.data/$ids hooks.metabase.test.data/$ids
|
||||
metabase.test.data/dataset hooks.metabase.test.data/dataset
|
||||
@@ -901,6 +558,7 @@
|
||||
metabase.test.data/run-mbql-query hooks.metabase.test.data/mbql-query
|
||||
metabase.test.util.async/with-open-channels hooks.common/let-with-optional-value-for-last-binding
|
||||
metabase.test.util.log/with-log-level hooks.common/with-ignored-first-arg
|
||||
metabase.test.util.log/with-log-messages-for-level hooks.common/with-ignored-first-arg
|
||||
metabase.test.util/discard-setting-changes hooks.common/with-ignored-first-arg
|
||||
metabase.test.util/with-column-remappings hooks.common/with-ignored-first-arg
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-perms hooks.common/do*
|
||||
@@ -911,71 +569,68 @@
|
||||
metabase.test/discard-setting-changes hooks.common/with-ignored-first-arg
|
||||
metabase.test/mbql-query hooks.metabase.test.data/mbql-query
|
||||
metabase.test/query hooks.metabase.test.data/mbql-query
|
||||
metabase.test/test-drivers hooks.common/do*
|
||||
metabase.test/run-mbql-query hooks.metabase.test.data/mbql-query
|
||||
metabase.test/test-driver hooks.metabase.test.data.datasets/test-driver
|
||||
metabase.test/test-drivers hooks.metabase.test.data.datasets/test-drivers
|
||||
metabase.test/with-column-remappings hooks.common/with-ignored-first-arg
|
||||
metabase.test/with-group hooks.common/let-one-with-optional-value
|
||||
metabase.test/with-log-level hooks.common/with-ignored-first-arg
|
||||
metabase.test/with-log-messages-for-level hooks.metabase.util.log.capture/with-log-messages-for-level
|
||||
metabase.test/with-log-messages-for-level hooks.common/with-ignored-first-arg
|
||||
metabase.test/with-non-admin-groups-no-root-collection-perms hooks.common/do*
|
||||
metabase.test/with-temp hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.test/with-temp-file hooks.metabase.test.util/with-temp-file
|
||||
metabase.test/with-temporary-setting-values hooks.metabase.test.util/with-temporary-setting-values
|
||||
metabase.util.log.capture/with-log-messages-for-level hooks.metabase.util.log.capture/with-log-messages-for-level
|
||||
metabase.util/format-color hooks.metabase.util/format-color
|
||||
metabase.util.log/trace hooks.metabase.util.log/info
|
||||
metabase.util.log/tracef hooks.metabase.util.log/infof
|
||||
metabase.util.log/debug hooks.metabase.util.log/info
|
||||
metabase.util.log/debugf hooks.metabase.util.log/infof
|
||||
metabase.util.log/info hooks.metabase.util.log/info
|
||||
metabase.util.log/infof hooks.metabase.util.log/infof
|
||||
metabase.util.log/warn hooks.metabase.util.log/info
|
||||
metabase.util.log/warnf hooks.metabase.util.log/infof
|
||||
metabase.util.log/error hooks.metabase.util.log/info
|
||||
metabase.util.log/errorf hooks.metabase.util.log/infof
|
||||
metabase.util.log/fatal hooks.metabase.util.log/info
|
||||
metabase.util.log/fatalf hooks.metabase.util.log/infof
|
||||
metabase.util.log/info hooks.metabase.util.log/info
|
||||
metabase.util.log/infof hooks.metabase.util.log/infof
|
||||
metabase.util.log/trace hooks.metabase.util.log/info
|
||||
metabase.util.log/tracef hooks.metabase.util.log/infof
|
||||
metabase.util.log/warn hooks.metabase.util.log/info
|
||||
metabase.util.log/warnf hooks.metabase.util.log/infof
|
||||
metabase.util.malli.registry/def hooks.metabase.util.malli.registry/def
|
||||
metabase.util/format-color hooks.metabase.util/format-color}
|
||||
metabase.util.malli.registry/def hooks.metabase.util.malli.registry/def}
|
||||
|
||||
:macroexpand
|
||||
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
|
||||
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
|
||||
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
|
||||
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! macros.metabase-enterprise.advanced-permissions.api.util-test/with-impersonations!
|
||||
metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup!
|
||||
metabase.api.card-test/with-ordered-items macros.metabase.api.card-test/with-ordered-items
|
||||
metabase.api.collection-test/with-collection-hierarchy! macros.metabase.api.collection-test/with-collection-hierarchy!
|
||||
metabase.api.collection-test/with-some-children-of-collection! macros.metabase.api.collection-test/with-some-children-of-collection!
|
||||
metabase.api.common/define-routes macros.metabase.api.common/define-routes
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing!
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing!
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing!
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
|
||||
metabase.lib.common/defop macros.metabase.lib.common/defop
|
||||
metabase.models.params.chain-filter-test/chain-filter macros.metabase.models.params.chain-filter-test/chain-filter
|
||||
metabase.models.params.chain-filter-test/chain-filter-search macros.metabase.models.params.chain-filter-test/chain-filter
|
||||
metabase.models.query-analysis-test/with-test-setup macros.metabase.models.query-analysis-test/with-test-setup
|
||||
metabase.models.user-test/with-groups macros.metabase.models.user-test/with-groups
|
||||
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
|
||||
metabase.related-test/with-world macros.metabase.related-test/with-world
|
||||
metabase.task.setup.query-analysis-setup/with-test-setup! macros.metabase.task.setup.query-analysis-setup/with-test-setup!
|
||||
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test/with-persistence-enabled! macros.metabase.test.persistence/with-persistence-enabled!
|
||||
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
|
||||
metabase.xrays.domain-entities.malli/define-getters-and-setters macros.metabase.xrays.domain-entities.malli/define-getters-and-setters}}
|
||||
{clojurewerkz.quartzite.jobs/build macros.quartz/build-job
|
||||
clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule
|
||||
clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule
|
||||
clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world
|
||||
metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! macros.metabase-enterprise.advanced-permissions.api.util-test/with-impersonations!
|
||||
metabase.api.card-test/with-ordered-items macros.metabase.api.card-test/with-ordered-items
|
||||
metabase.api.collection-test/with-collection-hierarchy macros.metabase.api.collection-test/with-collection-hierarchy
|
||||
metabase.api.collection-test/with-some-children-of-collection macros.metabase.api.collection-test/with-some-children-of-collection
|
||||
metabase.api.common/defendpoint macros.metabase.api.common/defendpoint
|
||||
metabase.api.common/defendpoint-async macros.metabase.api.common/defendpoint
|
||||
metabase.api.common/defendpoint-async-schema macros.metabase.api.common/defendpoint
|
||||
metabase.api.common/define-routes macros.metabase.api.common/define-routes
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing macros.metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing macros.metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing macros.metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing macros.metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing
|
||||
metabase.domain-entities.malli/define-getters-and-setters macros.metabase.domain-entities.malli/define-getters-and-setters
|
||||
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
|
||||
metabase.lib.common/defop macros.metabase.lib.common/defop
|
||||
metabase.models.params.chain-filter-test/chain-filter macros.metabase.models.params.chain-filter-test/chain-filter
|
||||
metabase.models.params.chain-filter-test/chain-filter-search macros.metabase.models.params.chain-filter-test/chain-filter
|
||||
metabase.models.query-field-test/with-test-setup macros.metabase.models.query-field-test/with-test-setup
|
||||
metabase.models.user-test/with-groups macros.metabase.models.user-test/with-groups
|
||||
metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response
|
||||
metabase.related-test/with-world macros.metabase.related-test/with-world
|
||||
metabase.shared.util.namespaces/import-fn macros.metabase.shared.util.namespaces/import-fn
|
||||
metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user
|
||||
metabase.test/with-persistence-enabled macros.metabase.test.persistence/with-persistence-enabled
|
||||
metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value!
|
||||
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values}}
|
||||
|
||||
:config-in-comment
|
||||
{:linters {:unresolved-symbol {:level :off}}}
|
||||
@@ -987,14 +642,14 @@
|
||||
;; the following patterns are considered to be test namespaces:
|
||||
;;
|
||||
;; - Any namespace ending in `-test` or `-test.whatever`
|
||||
;; - Any namespace containing in `test-util`
|
||||
;; - Any namespace ending in `test-util`
|
||||
;; - Any namespace that starts with `metabase.test`
|
||||
;;
|
||||
;; this list isn't exhaustive because it misses some stuff like the test runner and HTTP client but it's easier to go
|
||||
;; fix those namespaces than it is to make this regex super hairy.
|
||||
[{:pattern "(?:.*-test(?:\\..*)?$)|(?:.*test-util)|(?:^metabase\\.test.*)"
|
||||
[{:pattern "(?:.*-test(?:\\..*)?$)|(?:.*test-util$)|(?:^metabase\\.test.*)"
|
||||
:name test-namespaces}
|
||||
{:pattern "^(?!.*-test(?:\\..*)?$)(?!.*test-util)(?!^metabase\\.test.*).*$"
|
||||
{:pattern "^(?!.*-test(?:\\..*)?$)(?!.*test-util$)(?!^metabase\\.test.*).*$"
|
||||
:name source-namespaces}
|
||||
{:pattern "(?:(?:^metabase\\.cmd.*)|(?:^build.*)|(?:^metabuild-common.*)|(?:^release.*)|(?:^i18n.*)|(?:^lint-migrations-file$))"
|
||||
:name printable-namespaces}
|
||||
@@ -1005,19 +660,7 @@
|
||||
;; `metabase.driver.*`, excluding `-test` namespaces.
|
||||
;;
|
||||
{:pattern "metabase(?:(?:(?:-enterprise\\.sandbox)?\\.query-processor\\.)|(?:\\.driver\\.)).*(?<!-test)$"
|
||||
:name qp-and-driver-source-namespaces}
|
||||
;;
|
||||
;; driver namespaces
|
||||
;;
|
||||
{:pattern "metabase\\.driver.*$"
|
||||
:name driver-namespaces}
|
||||
;;
|
||||
;; general QP tests are ones that run across multiple drivers. Anything `metabase.query-processor*-test`,
|
||||
;; `metabase.driver.sql*-test` (excluding SQLServer and SQLite), or
|
||||
;; `metabase-enterprise.sandbox.query-processor*-test`
|
||||
;;
|
||||
{:pattern "^metabase(?:((?:-enterprise\\.sandbox)?\\.query-processor)|(?:\\.driver\\.sql(?!server)(?!ite))).*-test$"
|
||||
:name general-qp-and-driver-test-namespaces}]
|
||||
:name qp-and-driver-source-namespaces}]
|
||||
|
||||
:config-in-ns
|
||||
{test-namespaces
|
||||
@@ -1025,9 +668,7 @@
|
||||
{:inline-def {:level :off}
|
||||
:missing-docstring {:level :off}
|
||||
:private-call {:level :off}
|
||||
:metabase/defsetting-must-specify-export {:level :off}
|
||||
:discouraged-var {metabase.driver/database-supports? {:level :off}}}
|
||||
|
||||
:metabase/defsetting-must-specify-export {:level :off}}
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{clojure.core/defmacro hooks.clojure.core/non-thread-safe-form-should-end-with-exclamation
|
||||
@@ -1077,11 +718,6 @@
|
||||
toucan.util.test {:message "Don't use application database inside MLv2 code"}
|
||||
toucan2.core {:message "Don't use application database inside MLv2 code"}}}}
|
||||
|
||||
driver-namespaces
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
{metabase.driver/database-supports? {:level :off}}}}
|
||||
|
||||
qp-and-driver-source-namespaces
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
@@ -1095,8 +731,4 @@
|
||||
{medley.core/map-keys {:message "Use clojure.core/update-keys"}
|
||||
medley.core/map-vals {:message "Use clojure.core/update-vals"}
|
||||
metabase.db.query/query {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
metabase.db.query/reducible-query {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}}}}
|
||||
|
||||
general-qp-and-driver-test-namespaces
|
||||
{:linters
|
||||
{:metabase/disallow-hardcoded-driver-names-in-tests {:level :warning}}}}}
|
||||
metabase.db.query/reducible-query {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}}}}}}
|
||||
|
||||
205
.clj-kondo/hooks/clojure/core.clj
Normal file
205
.clj-kondo/hooks/clojure/core.clj
Normal file
@@ -0,0 +1,205 @@
|
||||
(ns hooks.clojure.core
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- node->qualified-symbol [node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(let [resolved (hooks/resolve {:name sexpr})]
|
||||
(when-not (= :clj-kondo/unknown-namespace (:ns resolved))
|
||||
(symbol (name (:ns resolved)) (name (:name resolved))))))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(def ^:private white-card-symbols
|
||||
'#{;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
|
||||
toucan2.core/delete!
|
||||
toucan2.core/update!
|
||||
toucan2.core/insert!
|
||||
toucan2.core/insert-returning-instance!
|
||||
toucan2.core/insert-returning-instances!
|
||||
toucan2.core/insert-returning-pk!
|
||||
toucan2.core/insert-returning-pks!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.memoize/memo-clear!
|
||||
clojure.core/conj!
|
||||
clojure.core/persistent!
|
||||
clojure.core/reset!
|
||||
clojure.core/swap!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.java.jdbc/execute!
|
||||
methodical.core/add-aux-method-with-unique-key!
|
||||
methodical.core/remove-aux-method-with-unique-key!
|
||||
next.jdbc/execute!
|
||||
|
||||
;; Definitely thread safe
|
||||
metabase.test.util.dynamic-redefs/patch-vars!
|
||||
|
||||
;; TODO: most of these symbols shouldn't be here, we should go through them and
|
||||
;; find the functions/macros that use them and make sure their names end with !
|
||||
;; best way to do this is try remove each of these and rely on kondo output to find places where it's used
|
||||
clojure.test/grant-collection-perms!
|
||||
clojure.test/grant-collection-perms-fn!
|
||||
clojure.test/grant-perms-fn!
|
||||
clojure.test/purge-old-entries!
|
||||
clojure.test/revoke-collection-perms!
|
||||
clojure.test/save-results!
|
||||
metabase-enterprise.advanced-permissions.models.permissions/update-db-download-permissions!
|
||||
metabase-enterprise.internal-user/install-internal-user!
|
||||
metabase-enterprise.sso.integrations.saml-test/call-with-login-attributes-cleared!
|
||||
metabase.actions/perform-action!
|
||||
metabase.analytics.snowplow-test/fake-track-event-impl!
|
||||
metabase.analytics.snowplow/track-event-impl!
|
||||
metabase.api.public-test/add-card-to-dashboard!
|
||||
metabase.cmd.dump-to-h2/dump-to-h2!
|
||||
metabase.cmd.load-from-h2/load-from-h2!
|
||||
metabase.core/ensure-audit-db-installed!
|
||||
metabase.db.schema-migrations-test.impl/run-migrations-in-range!
|
||||
metabase.db.setup/migrate!
|
||||
metabase.db.setup/setup-db!
|
||||
metabase.db/migrate!
|
||||
metabase.db/setup-db!
|
||||
metabase.driver.mongo-test/create-database-from-row-maps!
|
||||
metabase.driver.postgres-test/create-enums-db!
|
||||
metabase.driver.postgres-test/drop-if-exists-and-create-db!
|
||||
metabase.driver.sql-jdbc.execute/execute-statement!
|
||||
metabase.email-test/reset-inbox!
|
||||
metabase.email/send-email!
|
||||
metabase.models.action/insert!
|
||||
metabase.models.collection.graph-test/clear-graph-revisions!
|
||||
metabase.models.collection.graph-test/do-with-n-temp-users-with-personal-collections!
|
||||
metabase.models.field-values/create-or-update-full-field-values!
|
||||
metabase.models.model-index/add-values!
|
||||
metabase.models.moderation-review/create-review!
|
||||
metabase.models.on-demand-test/add-dashcard-with-parameter-mapping!
|
||||
metabase.models.permissions/grant-application-permissions!
|
||||
metabase.models.permissions/grant-collection-read-permissions!
|
||||
metabase.models.permissions/grant-collection-readwrite-permissions!
|
||||
metabase.models.permissions/grant-full-data-permissions!
|
||||
metabase.models.permissions/grant-native-readwrite-permissions!
|
||||
metabase.models.permissions/grant-permissions!
|
||||
metabase.models.permissions/revoke-application-permissions!
|
||||
metabase.models.permissions/revoke-data-perms!
|
||||
metabase.models.permissions/update-data-perms-graph!
|
||||
metabase.models.permissions/update-group-permissions!
|
||||
metabase.models.persisted-info/ready-database!
|
||||
metabase.models.revision/revert!
|
||||
metabase.models.setting-test/test-user-local-allowed-setting!
|
||||
metabase.models.setting-test/test-user-local-only-setting!
|
||||
metabase.models.setting.cache/restore-cache!
|
||||
metabase.models.setting/set!
|
||||
metabase.models.setting/validate-settings-formatting!
|
||||
metabase.permissions.test-util/with-restored-perms!
|
||||
metabase.pulse/send-notifications!
|
||||
metabase.pulse/send-pulse!
|
||||
metabase.query-processor.streaming.interface/begin!
|
||||
metabase.query-processor.streaming.interface/finish!
|
||||
metabase.query-processor.streaming.interface/write-row!
|
||||
metabase.sample-data/try-to-extract-sample-database!
|
||||
metabase.setup/create-token!
|
||||
metabase.sync.sync-metadata.fields.sync-metadata/update-field-metadata-if-needed!
|
||||
metabase.sync.sync-metadata/sync-db-metadata!
|
||||
metabase.sync.util-test/sync-database!
|
||||
metabase.sync.util/store-sync-summary!
|
||||
metabase.sync/sync-database!
|
||||
metabase.task.index-values/job-init!
|
||||
metabase.task.persist-refresh/job-init!
|
||||
metabase.task.persist-refresh/refresh-tables!
|
||||
metabase.task.persist-refresh/schedule-persistence-for-database!
|
||||
metabase.task/delete-task!
|
||||
metabase.test.data.bigquery-cloud-sdk/execute!
|
||||
metabase.test.data.impl/copy-db-tables-and-fields!
|
||||
metabase.test.data.impl/get-or-create-database!
|
||||
metabase.test.data.impl/get-or-create-default-dataset!
|
||||
metabase.test.data.impl.get-or-create/set-test-db-permissions!
|
||||
metabase.test.data.interface/create-db!
|
||||
metabase.test.data.interface/destroy-db!
|
||||
metabase.test.data.oracle/create-user!
|
||||
metabase.test.data.oracle/drop-user!
|
||||
metabase.test.data.sql-jdbc.load-data/make-insert!
|
||||
metabase.test.data.users/clear-cached-session-tokens!
|
||||
metabase.test.initialize/do-initialization!
|
||||
metabase.test.initialize/initialize-if-needed!
|
||||
metabase.test.integrations.ldap/start-ldap-server!
|
||||
metabase.test.util.log/ensure-unique-logger!
|
||||
metabase.test.util.log/set-ns-log-level!
|
||||
metabase.test.util/do-with-temp-env-var-value!
|
||||
metabase.test.util/restore-raw-setting!
|
||||
metabase.test.util/upsert-raw-setting!
|
||||
metabase.test/initialize-if-needed!
|
||||
metabase.test/test-helpers-set-global-values!
|
||||
metabase.test/with-temp-env-var-value!
|
||||
metabase.upload-test/set-local-infile!
|
||||
metabase.util.files/create-dir-if-not-exists!
|
||||
metabase.util.ssh-test/start-mock-servers!
|
||||
metabase.util.ssh-test/stop-mock-servers!})
|
||||
|
||||
(defn- end-with-exclamation?
|
||||
[s]
|
||||
(str/ends-with? s "!"))
|
||||
|
||||
(defn- non-thread-safe-form-should-end-with-exclamation*
|
||||
[{[defn-or-defmacro form-name] :children, :as node}]
|
||||
(when-not (and (:string-value form-name)
|
||||
(end-with-exclamation? (:string-value form-name)))
|
||||
(letfn [(walk [f form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk f child)))]
|
||||
(walk (fn [form]
|
||||
(when-let [qualified-symbol (node->qualified-symbol form)]
|
||||
(when (and (not (contains? white-card-symbols qualified-symbol))
|
||||
(end-with-exclamation? qualified-symbol))
|
||||
(hooks/reg-finding! (assoc (meta form-name)
|
||||
:message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`."
|
||||
(:string-value defn-or-defmacro) qualified-symbol)
|
||||
:type :metabase/test-helpers-use-non-thread-safe-functions)))))
|
||||
node))
|
||||
node))
|
||||
|
||||
(defn non-thread-safe-form-should-end-with-exclamation
|
||||
"Used to ensure defn and defmacro in test namespace to have name ending with `!` if it's non-thread-safe.
|
||||
A function or a macro can be defined as 'not thread safe' when their funciton name ends with a `!`.
|
||||
|
||||
Only used in tests to identify thread-safe/non-thread-safe test helpers. See #37126"
|
||||
[{:keys [node cljc lang]}]
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(non-thread-safe-form-should-end-with-exclamation* node))
|
||||
{:node node})
|
||||
|
||||
(comment
|
||||
(require '[clj-kondo.core :as clj-kondo])
|
||||
(def form (str '(defmacro a
|
||||
[x]
|
||||
`(fun-call x))))
|
||||
|
||||
(def form "(defmacro a
|
||||
[x]
|
||||
`(some! ~x))")
|
||||
|
||||
(def form "(defun f
|
||||
[x]
|
||||
(let [g! (fn [] 1)]
|
||||
(g!)))")
|
||||
|
||||
(str (hooks/parse-string form))
|
||||
(hooks/sexpr (hooks/parse-string form))
|
||||
|
||||
(binding [hooks/*reload* true]
|
||||
(-> form
|
||||
(with-in-str (clj-kondo/run! {:lint ["-"]}))
|
||||
:findings))
|
||||
|
||||
(do (non-thread-safe-form-should-end-with-exclamation* (hooks/parse-string form)) nil))
|
||||
242
.clj-kondo/hooks/clojure/test.clj
Normal file
242
.clj-kondo/hooks/clojure/test.clj
Normal file
@@ -0,0 +1,242 @@
|
||||
(ns hooks.clojure.test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(def ^:private disallowed-parallel-forms
|
||||
"Things you should not be allowed to use inside parallel tests. Besides these, anything ending in `!` not whitelisted
|
||||
in [[allowed-parallel-forms]] is not allowed."
|
||||
'#{clojure.core/alter-var-root
|
||||
clojure.core/with-redefs
|
||||
clojure.core/with-redefs-fn
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps!
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user!
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes
|
||||
metabase-enterprise.test/with-gtaps!
|
||||
metabase-enterprise.test/with-gtaps-for-user!
|
||||
metabase-enterprise.test/with-user-attributes
|
||||
metabase.actions.test-util/with-actions
|
||||
metabase.actions.test-util/with-actions-disabled
|
||||
metabase.actions.test-util/with-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled
|
||||
metabase.actions.test-util/with-actions-test-data-tables
|
||||
metabase.email-test/with-expected-messages
|
||||
metabase.email-test/with-fake-inbox
|
||||
metabase.test.data.users/with-group
|
||||
metabase.test.data.users/with-group-for-user
|
||||
metabase.test.persistence/with-persistence-enabled
|
||||
metabase.test.util.log/with-log-level
|
||||
metabase.test.util.log/with-log-messages-for-level
|
||||
metabase.test.util.misc/with-single-admin-user
|
||||
metabase.test.util/with-all-users-permission
|
||||
metabase.test.util/with-column-remappings
|
||||
metabase.test.util/with-discarded-collections-perms-changes
|
||||
metabase.test.util/with-env-keys-renamed-by
|
||||
metabase.test.util/with-locale
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test.util/with-temp-env-var-value
|
||||
metabase.test.util/with-temp-vals-in-db
|
||||
metabase.test.util/with-temporary-raw-setting-values
|
||||
metabase.test.util/with-temporary-setting-values
|
||||
metabase.test.util/with-user-in-groups
|
||||
metabase.test/with-actions
|
||||
metabase.test/with-actions-disabled
|
||||
metabase.test/with-actions-enabled
|
||||
metabase.test/with-actions-test-data
|
||||
metabase.test/with-actions-test-data-and-actions-enabled
|
||||
metabase.test/with-actions-test-data-tables
|
||||
metabase.test/with-all-users-permission
|
||||
metabase.test/with-column-remappings
|
||||
metabase.test/with-discarded-collections-perms-changes
|
||||
metabase.test/with-env-keys-renamed-by
|
||||
metabase.test/with-expected-messages
|
||||
metabase.test/with-fake-inbox
|
||||
metabase.test/with-group
|
||||
metabase.test/with-group-for-user
|
||||
metabase.test/with-locale
|
||||
metabase.test/with-log-level
|
||||
metabase.test/with-log-messages-for-level
|
||||
metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test/with-non-admin-groups-no-root-collection-perms
|
||||
metabase.test/with-persistence-enabled
|
||||
metabase.test/with-single-admin-user
|
||||
metabase.test/with-temp-env-var-value
|
||||
metabase.test/with-temp-vals-in-db
|
||||
metabase.test/with-temporary-raw-setting-values
|
||||
metabase.test/with-temporary-setting-values
|
||||
metabase.test/with-user-in-groups})
|
||||
|
||||
;;; TODO -- we should disallow `metabase.test/user-http-request` with any method other than `:get`
|
||||
|
||||
(def ^:private allowed-parallel-forms
|
||||
"These fns are destructive, but are probably fine inside ^:parallel tests because it usually means you're doing
|
||||
something to an atom or something like that."
|
||||
'#{clojure.core/assoc!
|
||||
clojure.core/compare-and-set!
|
||||
clojure.core/conj!
|
||||
clojure.core/disj!
|
||||
clojure.core/dissoc!
|
||||
clojure.core/persistent!
|
||||
clojure.core/pop!
|
||||
clojure.core/reset!
|
||||
clojure.core/reset-vals!
|
||||
clojure.core/run!
|
||||
clojure.core/swap!
|
||||
clojure.core/swap-vals!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.core.async/<!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alt!
|
||||
clojure.core.async/alt!!
|
||||
clojure.core.async/alts!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/ioc-alts!
|
||||
clojure.core.async/offer!
|
||||
clojure.core.async/onto-chan!
|
||||
clojure.core.async/onto-chan!!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.async/put!
|
||||
clojure.core.async/take!
|
||||
clojure.core.async/to-chan!
|
||||
clojure.core.async/to-chan!!
|
||||
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
|
||||
metabase.pulse/send-pulse!
|
||||
metabase.query-processor.store/store-database!
|
||||
next.jdbc/execute!})
|
||||
|
||||
(defn- warn-about-disallowed-parallel-forms [form]
|
||||
(letfn [(error! [form message]
|
||||
(hooks/reg-finding! (assoc (meta form)
|
||||
:message message
|
||||
:type :metabase/validate-deftest)))
|
||||
(f [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(cond
|
||||
(disallowed-parallel-forms qualified-symbol)
|
||||
(error! form (format "%s is not allowed inside a ^:parallel test or test fixture" qualified-symbol))
|
||||
|
||||
(and (not (allowed-parallel-forms qualified-symbol))
|
||||
(str/ends-with? (name qualified-symbol) "!"))
|
||||
(error! form (format "destructive functions like %s are not allowed inside a ^:parallel test or test fixture. If this should be allowed, add it to the whitelist in .clj-kondo/hooks/clojure/test.clj"
|
||||
qualified-symbol)))))
|
||||
(walk [form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk child)))]
|
||||
(walk form)))
|
||||
|
||||
(defn- deftest-check-parallel
|
||||
"1. Check if test is marked ^:parallel / ^:synchronized correctly
|
||||
2. Make sure disallowed forms are not used in ^:parallel tests"
|
||||
[{[_ test-name & body] :children, :as _node}]
|
||||
(let [test-metadata (:meta test-name)
|
||||
metadata-sexprs (map hooks/sexpr test-metadata)
|
||||
combined-metadata (transduce
|
||||
(map (fn [x]
|
||||
(if (map? x)
|
||||
x
|
||||
{x true})))
|
||||
(completing merge)
|
||||
{}
|
||||
metadata-sexprs)
|
||||
parallel? (:parallel combined-metadata)
|
||||
synchronized? (:synchronized combined-metadata)]
|
||||
(when (and parallel? synchronized?)
|
||||
(hooks/reg-finding! (assoc (meta test-name)
|
||||
:message "Test should not be marked both ^:parallel and ^:synchronized"
|
||||
:type :metabase/validate-deftest)))
|
||||
;; only when the custom `:metabase/deftest-not-marked-parallel` is enabled: complain if tests are not explicitly
|
||||
;; marked `^:parallel` or `^:synchronized`. This is mostly to encourage people to mark everything `^:parallel` in
|
||||
;; places like `metabase.lib` tests unless there is a really good reason not to.
|
||||
(when-not (or parallel? synchronized?)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta test-name)
|
||||
:message "Test should be marked either ^:parallel or ^:synchronized"
|
||||
:type :metabase/deftest-not-marked-parallel-or-synchronized)))
|
||||
(when parallel?
|
||||
(doseq [form body]
|
||||
(warn-about-disallowed-parallel-forms form)))))
|
||||
|
||||
(def ^:private number-of-lines-for-a-test-to-be-considered-horrifically-long
|
||||
200)
|
||||
|
||||
(defn- deftest-check-not-horrifically-long
|
||||
[node]
|
||||
(let [{:keys [row end-row]} (meta node)]
|
||||
(when (and row end-row)
|
||||
(let [num-lines (- end-row row)]
|
||||
(when (>= num-lines number-of-lines-for-a-test-to-be-considered-horrifically-long)
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (str (format "This test is horrifically long, it's %d lines! 😱 " num-lines)
|
||||
"Do you really want to try to debug it if it fails? 💀 "
|
||||
"Split it up into smaller tests! 🥰")
|
||||
:type :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests)))))))
|
||||
|
||||
(defn deftest [{:keys [node cljc lang]}]
|
||||
;; run [[deftest-check-parallel]] only once... if this is a `.cljc` file only run it for the `:clj` analysis, no point
|
||||
;; in running it twice.
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(deftest-check-parallel node))
|
||||
(deftest-check-not-horrifically-long node)
|
||||
{:node node})
|
||||
|
||||
;;; this is a hacky way to determine whether these namespaces are required in the `ns` form or not... basically `:ns`
|
||||
;;; will come back as `nil` if they are not.
|
||||
(defn- approximately-equal-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.approximately-equal/=?-report}))
|
||||
'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
(defn- malli-equals-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.malli-equals/malli=-report}))
|
||||
'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
(defn- warn-about-missing-test-expr-requires-in-cljs [{:keys [children], :as _is-node}]
|
||||
(let [[_is assertion-node] children]
|
||||
(when (hooks/list-node? assertion-node)
|
||||
(let [[assertion-symb-node] (:children assertion-node)]
|
||||
(when (hooks/token-node? assertion-symb-node)
|
||||
(let [assertion-symb-token (hooks/sexpr assertion-symb-node)
|
||||
warn! (fn [ns-to-require]
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta assertion-symb-node)
|
||||
:message (format "You must require %s to use %s in ClojureScript"
|
||||
ns-to-require
|
||||
assertion-symb-token)
|
||||
:type :metabase/missing-test-expr-requires-in-cljs)))]
|
||||
(condp = assertion-symb-token
|
||||
'=?
|
||||
(when-not (approximately-equal-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
'malli=
|
||||
(when-not (malli-equals-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
nil)))))))
|
||||
|
||||
(defn- warn-about-schema= [{[_is assertion-node] :children, :as _is-node}]
|
||||
(let [{[assertion-symb-node] :children} assertion-node]
|
||||
(when (and (hooks/token-node? assertion-symb-node)
|
||||
(= (hooks/sexpr assertion-symb-node) 'schema=))
|
||||
(hooks/reg-finding! (assoc (meta assertion-symb-node)
|
||||
:message "Use =? or malli= instead of schema="
|
||||
:type :metabase/warn-about-schema=)))))
|
||||
|
||||
(defn is [{:keys [node lang]}]
|
||||
(when (= lang :cljs)
|
||||
(warn-about-missing-test-expr-requires-in-cljs node))
|
||||
(warn-about-schema= node)
|
||||
{:node node})
|
||||
|
||||
(defn use-fixtures [{:keys [node]}]
|
||||
(warn-about-disallowed-parallel-forms node)
|
||||
{:node node})
|
||||
402
.clj-kondo/hooks/common.clj
Normal file
402
.clj-kondo/hooks/common.clj
Normal file
@@ -0,0 +1,402 @@
|
||||
(ns hooks.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.pprint]))
|
||||
|
||||
(defn with-macro-meta
|
||||
"When introducing internal nodes (let, defn, etc) it is important to provide a meta of an existing token
|
||||
as the current version of kondo will use the whole form by default."
|
||||
[new-node hook-node]
|
||||
(with-meta new-node (meta (first (:children hook-node)))))
|
||||
|
||||
;;; This stuff is to help debug hooks. Trace a function and it will pretty-print the before & after sexprs.
|
||||
;;;
|
||||
;;; (hooks.common/trace #'calculate-bird-scarcity)
|
||||
|
||||
(defn- trace* [f]
|
||||
{:pre [(fn? f)]}
|
||||
(fn traced-fn [node]
|
||||
(println \newline)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node)))
|
||||
(let [node* (f node)]
|
||||
(println '=>)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node*)))
|
||||
node*)))
|
||||
|
||||
;;; this doesn't seem to work with SCI. Use [[defn-traced]] instead.
|
||||
|
||||
;; (defn trace [varr]
|
||||
;; {:pre [(var? varr)]}
|
||||
;; (when-not (::traced? (meta varr))
|
||||
;; (alter-var-root varr trace*)
|
||||
;; (alter-meta! varr assoc ::traced? true)
|
||||
;; (println "Traced" varr)))
|
||||
|
||||
(defmacro defn-traced
|
||||
"Replace a hook [[defn]] with this to trace the before and after sexprs."
|
||||
[fn-name & fn-tail]
|
||||
`(do
|
||||
(defn fn# ~@fn-tail)
|
||||
(def ~fn-name (trace* fn#))))
|
||||
|
||||
;;;; Common hook definitions
|
||||
|
||||
(defn do*
|
||||
"This is the same idea as [[clojure.core/do]] but doesn't cause Kondo to complain about redundant dos or unused values."
|
||||
[{{[_ & args] :children, :as node} :node}]
|
||||
(let [node* (-> (hooks/list-node
|
||||
(list*
|
||||
(with-meta (hooks/token-node 'do) {:clj-kondo/ignore [:redundant-do]})
|
||||
(for [arg args]
|
||||
(vary-meta arg update :clj-kondo/ignore #(conj (vec %) :unused-value)))))
|
||||
(with-meta (meta node)))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)
|
||||
|
||||
Binding is optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-three-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y z]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil, z nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y z] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or z (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-four-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-five-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-six-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-seven-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f g]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil, g nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f g] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or g (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-top-level-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)"
|
||||
[{{[_ x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-top-level-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x y
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)"
|
||||
[{{[_ x y & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)
|
||||
y (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn let-one-with-optional-value
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the binding, `value`, is optional, and only one
|
||||
binding can be established.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {[binding value] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[binding (or value (hooks/token-node 'nil))])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn- let-second-inner [body bindings]
|
||||
(let [binding-infos (for [[model {[binding value] :children}] (partition 2 bindings)]
|
||||
{:model model
|
||||
:binding binding
|
||||
:value (or value
|
||||
(hooks/token-node 'nil))})]
|
||||
(-> (hooks/vector-node
|
||||
[(hooks/vector-node (map :model binding-infos))
|
||||
(-> (hooks/list-node (list* (hooks/token-node `let)
|
||||
(hooks/vector-node (mapcat (juxt :binding :value) binding-infos))
|
||||
body))
|
||||
(with-meta (meta body)))])
|
||||
(with-meta (meta body)))))
|
||||
|
||||
(defn let-second
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x [y]
|
||||
...)
|
||||
|
||||
where the let is for second arg.
|
||||
|
||||
=>
|
||||
|
||||
(let [y nil]
|
||||
...)"
|
||||
[{:keys [node]}]
|
||||
(let [[_ first-arg-ref binding+opts & body] (:children node)]
|
||||
{:node (let-second-inner body [first-arg-ref binding+opts])}))
|
||||
|
||||
(defn let-with-optional-value-for-last-binding
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the *last* binding, `value`, is optional.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding value]]
|
||||
[binding (or value (hooks/token-node 'nil))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-ignored-first-arg
|
||||
"For macros like
|
||||
|
||||
(discard-setting-changes [setting-1 setting-2]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(do ...)
|
||||
|
||||
where the first arg ought to be ignored for linting purposes."
|
||||
[{{[_ _x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-used-first-arg
|
||||
"For macros like
|
||||
|
||||
(with-drivers (filter pred? some-drivers)
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [_1234 (filter pred? some-drivers)]
|
||||
...)
|
||||
|
||||
where the first arg should be linted and appear to be used."
|
||||
[{{[_ arg & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node (gensym "_"))
|
||||
arg])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn node->qualified-symbol [node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(when-let [resolved (hooks/resolve {:name sexpr})]
|
||||
(symbol (name (:ns resolved)) (name (:name resolved)))))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(defn format-string-specifier-count
|
||||
"Number of things like `%s` in a format string, not counting newlines (`%n`) or escaped percent signs (`%%`). For
|
||||
checking the number of args to something that takes a format string."
|
||||
[format-string]
|
||||
(count (re-seq #"(?<!%)%(?![%n])" format-string)))
|
||||
|
||||
(comment
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%s %%")
|
||||
|
||||
;; should be 2
|
||||
(format-string-specifier-count "%s %%%n%s")
|
||||
|
||||
;; should be 0
|
||||
(format-string-specifier-count "%n%%%%")
|
||||
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%-02d"))
|
||||
@@ -4,8 +4,8 @@
|
||||
(comment
|
||||
|
||||
(def input (hooks/parse-string
|
||||
"(with-indexed-model! [{:keys [model model-index model-index-value]}
|
||||
{:query :some-query}]
|
||||
"(with-indexed-model [{:keys [model model-index model-index-value]}
|
||||
{:query :some-query}]
|
||||
:foo :bar :body)"))
|
||||
|
||||
(println (hooks/sexpr input))
|
||||
@@ -13,12 +13,12 @@
|
||||
(let [f (load-file ".clj-kondo/hooks/metabase/api/automagic_dashboards_test.clj")]
|
||||
(f {:node input})))
|
||||
|
||||
(defn with-indexed-model!
|
||||
(defn with-indexed-model
|
||||
[{node :node}]
|
||||
(let [[_macro-call binding-and-query-info & body] (:children node)
|
||||
[binding query-info] (:children binding-and-query-info)
|
||||
node* (hooks/list-node
|
||||
[(hooks/token-node 'do-with-indexed-model!)
|
||||
[(hooks/token-node 'do-with-testing-model)
|
||||
query-info
|
||||
(hooks/list-node
|
||||
(list* (hooks/token-node 'fn)
|
||||
12
.clj-kondo/hooks/metabase/lib/test_util/macros.clj
Normal file
12
.clj-kondo/hooks/metabase/lib/test_util/macros.clj
Normal file
@@ -0,0 +1,12 @@
|
||||
(ns hooks.metabase.lib.test-util.macros
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn with-testing-against-standard-queries [{{[_ & args] :children} :node}]
|
||||
(let [[query-sym & body] args
|
||||
node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[query-sym (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,7 +1,5 @@
|
||||
(ns hooks.metabase.models.interface
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn define-hydration-method
|
||||
"This is used for both [[metabase.models.interface/define-simple-hydration-method]] and
|
||||
@@ -16,6 +14,5 @@
|
||||
(hooks/token-node 'defn)
|
||||
fn-name
|
||||
fn-tail))))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)]
|
||||
(with-meta (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))]
|
||||
{:node node}))
|
||||
@@ -38,6 +38,8 @@
|
||||
engines
|
||||
enum-cardinality-threshold
|
||||
follow-up-email-sent
|
||||
ga-code
|
||||
ga-enabled
|
||||
google-auth-auto-create-accounts-domain
|
||||
google-auth-client-id
|
||||
google-auth-configured
|
||||
@@ -162,6 +164,7 @@
|
||||
token-status
|
||||
toucan-name
|
||||
uncached-setting
|
||||
uploads-table-prefix
|
||||
user-visibility
|
||||
version
|
||||
version-info
|
||||
@@ -181,13 +184,12 @@
|
||||
setter-node (-> (list
|
||||
(hooks/token-node 'defn)
|
||||
(with-meta
|
||||
(hooks/token-node (symbol (str (hooks/sexpr setting-name) \!)))
|
||||
(meta setting-name))
|
||||
(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)]
|
||||
(with-meta (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))]
|
||||
|
||||
(when (nil? (second (drop-while (comp not #{[:k :export?]} first) options-list)))
|
||||
(when-not (contains? ignored-implicit-export? (:value setting-name))
|
||||
@@ -243,14 +245,14 @@
|
||||
(comment
|
||||
(defn- defsetting* [form]
|
||||
(hooks/sexpr
|
||||
(:node
|
||||
(defsetting
|
||||
{:node
|
||||
(hooks/parse-string
|
||||
(with-out-str
|
||||
#_{:clj-kondo/ignore [:unresolved-namespace]}
|
||||
(clojure.pprint/pprint
|
||||
form)))}))))
|
||||
(:node
|
||||
(defsetting
|
||||
{:node
|
||||
(hooks/parse-string
|
||||
(with-out-str
|
||||
#_{:clj-kondo/ignore [:unresolved-namespace]}
|
||||
(clojure.pprint/pprint
|
||||
form)))}))))
|
||||
|
||||
(defn x []
|
||||
(defsetting*
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns hooks.metabase.public-settings.premium-features
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn defenterprise [{node :node}]
|
||||
(let [[_defenterprise fn-name & args] (:children node)
|
||||
@@ -27,8 +26,7 @@
|
||||
fn-name
|
||||
docstring))
|
||||
fn-tail))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)))}))
|
||||
(with-meta (update (meta node) :clj-kondo/ignore #(hooks/vector-node (cons :clojure-lsp/unused-public-var (:children %))))))))}))
|
||||
|
||||
(comment
|
||||
(defn- defenterprise* [form]
|
||||
@@ -11,10 +11,6 @@
|
||||
[]
|
||||
(not-empty (set (keys (:clj (hooks/ns-analysis 'metabase.test.data.dataset-definitions))))))
|
||||
|
||||
(defn- local-def?
|
||||
[ns* sexpr]
|
||||
(= ns* (:ns (hooks/resolve {:name sexpr}))))
|
||||
|
||||
(defn- dataset-type
|
||||
"`dataset` can be one of:
|
||||
|
||||
@@ -25,7 +21,7 @@
|
||||
|
||||
We can only determine if an unqualified symbol refers to something in the dataset definitions namespace if there are
|
||||
cached results available from [[global-dataset-symbols]]."
|
||||
[this-ns dataset]
|
||||
[dataset]
|
||||
(let [sexpr (hooks/sexpr dataset)
|
||||
global-defs (global-dataset-symbols)]
|
||||
(cond
|
||||
@@ -35,19 +31,20 @@
|
||||
(namespace sexpr)
|
||||
:qualified
|
||||
|
||||
(empty? global-defs)
|
||||
:unqualified/unknown
|
||||
|
||||
(contains? global-defs sexpr)
|
||||
:unqualified/from-dataset-defs-namespace
|
||||
|
||||
(local-def? this-ns sexpr)
|
||||
:unqualified/local-def
|
||||
|
||||
;; either something defined in the current namespace or let-bound in the current scope.
|
||||
:else
|
||||
:unqualified/unknown)))
|
||||
:unqualified/local-def)))
|
||||
|
||||
(defn dataset
|
||||
[{{[_ dataset & body] :children} :node this-ns :ns}]
|
||||
[{{[_ dataset & body] :children} :node}]
|
||||
(let [noop (constantly nil)
|
||||
body (case (dataset-type this-ns dataset)
|
||||
body (case (dataset-type dataset)
|
||||
;; non-symbol, qualified symbols, and unqualified symbols from the current namespace/let-bound can all
|
||||
;; get converted from something like
|
||||
;;
|
||||
60
.clj-kondo/hooks/metabase/test/util.clj
Normal file
60
.clj-kondo/hooks/metabase/test/util.clj
Normal file
@@ -0,0 +1,60 @@
|
||||
(ns hooks.metabase.test.util
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(defn- namespaced-symbol-node? [node]
|
||||
(when (hooks/token-node? node)
|
||||
(let [symb (hooks/sexpr node)]
|
||||
(and (symbol? symb)
|
||||
(namespace symb)))))
|
||||
|
||||
(defn with-temporary-setting-values
|
||||
"Rewrite a form like
|
||||
|
||||
(with-temporary-setting-values [x 1, some.ns/y 2]
|
||||
...)
|
||||
|
||||
as one like
|
||||
|
||||
(let [_ 1, _ some.ns/y, _ 2]
|
||||
...)
|
||||
|
||||
for analysis purposes. We only need to 'capture' namespaced Setting bindings with a `_` so Kondo considers their
|
||||
namespace to be 'used' and to catch undefined var usage."
|
||||
[{:keys [node]}]
|
||||
(let [{[_ bindings & body] :children} node
|
||||
bindings (if (hooks/vector-node? bindings)
|
||||
(hooks/vector-node (into []
|
||||
(mapcat (fn [[setting-name v]]
|
||||
(concat
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) v]
|
||||
;; if the setting name is namespace-qualified add a `_`
|
||||
;; entry for it too.
|
||||
(when (namespaced-symbol-node? setting-name)
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) setting-name]))))
|
||||
(partition 2 (:children bindings))))
|
||||
bindings)]
|
||||
{:node (-> (list*
|
||||
(hooks/token-node 'let)
|
||||
bindings
|
||||
body)
|
||||
(with-meta (meta body))
|
||||
hooks/list-node
|
||||
(with-meta (meta body)))}))
|
||||
|
||||
(defn with-temp-file
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding filename]]
|
||||
[binding (or (when (and filename
|
||||
(not (nil? (hooks/sexpr filename))))
|
||||
filename)
|
||||
(hooks/string-node "filename"))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
157
.clj-kondo/hooks/metabase/util/log.clj
Normal file
157
.clj-kondo/hooks/metabase/util/log.clj
Normal file
@@ -0,0 +1,157 @@
|
||||
(ns hooks.metabase.util.log
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[clojure.walk :as walk]
|
||||
[hooks.common]))
|
||||
|
||||
;;;; Checks for both log + logf
|
||||
|
||||
(defn- disallow-i18n [node]
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol x)]
|
||||
(when (and (= (namespace qualified-symbol) "metabase.util.i18n")
|
||||
(or (str/includes? (name qualified-symbol) "tru")
|
||||
(str/includes? (name qualified-symbol) "trs")))
|
||||
(api/reg-finding! (assoc (meta x)
|
||||
:message "do not i18n the logs!"
|
||||
:type :metabase/validate-logging)))))
|
||||
node))
|
||||
|
||||
(defn- check-position-of-exception-arg
|
||||
"Tell people to do (info e \"whatever\") instead of (info \"whatever\" e)."
|
||||
[node]
|
||||
(let [[f _first-arg & args] (:children node)]
|
||||
(doseq [arg args]
|
||||
(when (and (api/token-node? arg)
|
||||
(= (api/sexpr arg) 'e))
|
||||
(api/reg-finding! (assoc (meta arg)
|
||||
:message (format "pass exceptions as the first arg to %s" (api/sexpr f))
|
||||
:type :metabase/validate-logging))))))
|
||||
|
||||
(defn- common-checks [node]
|
||||
(check-position-of-exception-arg node)
|
||||
(disallow-i18n node))
|
||||
|
||||
(defn- check-for-i18n-format-specifiers
|
||||
"Make sure people aren't using things like {0} in their log messages."
|
||||
[node string-node]
|
||||
(when (re-find #"\{\d+\}" (api/sexpr string-node))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message "this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
;;; log only
|
||||
|
||||
(defn- check-not-format-string
|
||||
"Make sure we're not using something like log/trace with a format string."
|
||||
[{[f] :children, :as node} string-node]
|
||||
(when (pos? (hooks.common/format-string-specifier-count (api/sexpr string-node)))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "%s used with a format string, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str (api/sexpr f) "f"))
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
(defn- warn-against-log+format-instead-of-logf
|
||||
"Tell people to use things like infof instead of info + format."
|
||||
[{[f] :children, :as node} [x :as args]]
|
||||
(when (and (= (count args) 1)
|
||||
(api/list-node? x))
|
||||
(let [[y] (:children x)]
|
||||
(when (and (api/token-node? y)
|
||||
(= (hooks.common/node->qualified-symbol y) 'clojure.core/format))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Use %s instead of %s + format"
|
||||
(str (api/sexpr f) "f")
|
||||
(api/sexpr f))
|
||||
:type :metabase/validate-logging))))))
|
||||
|
||||
(defn info
|
||||
"Valid log, debug, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e & [x :as args]] (if (and (api/token-node? (first args))
|
||||
(api/string-node? (second args)))
|
||||
args
|
||||
(cons nil args))]
|
||||
(when (api/string-node? x)
|
||||
(check-for-i18n-format-specifiers node x)
|
||||
(check-not-format-string node x))
|
||||
(warn-against-log+format-instead-of-logf node args))
|
||||
x)
|
||||
|
||||
;;;; logf only
|
||||
|
||||
(defn- check-format-string-arg-count
|
||||
"Make sure the number of args passed to something like tracef match the number of format string specifiers."
|
||||
[{[f] :children, :as node} format-string args]
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string))
|
||||
actual-arg-count (count args)]
|
||||
(when (zero? expected-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Don't use %s with no format string arguments, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str/replace (str (api/sexpr f)) #"f$" ""))
|
||||
:type :metabase/validate-logging)))
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "log format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
|
||||
|
||||
(defn infof
|
||||
"Valid logf, debugf, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e format-string & args] (if (api/string-node? (first args))
|
||||
(cons nil args)
|
||||
args)]
|
||||
;; TODO -- maybe we can error if you did not pass a format string, e.g. (log/infof 1 2) is obviously incorrect.
|
||||
(when (api/string-node? format-string)
|
||||
(check-for-i18n-format-specifiers node format-string)
|
||||
(check-format-string-arg-count node format-string args)))
|
||||
x)
|
||||
|
||||
(comment
|
||||
;; should fail, missing a format arg
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf "Migration lock was acquired after %d retries.")))})
|
||||
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf e "Migration lock was acquired after %d retries.")))})
|
||||
|
||||
;; should fail, too many format args
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf "Migration lock was acquired after %d retries." 1 2)))})
|
||||
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf e "Migration lock was acquired after %d retries." 1 2)))})
|
||||
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf "Migration lock was acquired after {0} retries." 1)))})
|
||||
|
||||
;; should fail, has format args but is warn rather than warnf
|
||||
(info
|
||||
{:node (api/parse-string
|
||||
(str '(log/warn e "Migration lock was acquired after %d retries." 1 2)))})
|
||||
|
||||
;; should fail -- should not use i18n/tru or i18n/trs
|
||||
(infof
|
||||
{:node (api/parse-string
|
||||
(str '(log/warnf (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1))))})
|
||||
|
||||
(info
|
||||
{:node (api/parse-string
|
||||
(str '(log/warn (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1))))}))
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns macros.metabase.api.collection-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-collection-hierarchy! [bindings & body]
|
||||
(defmacro with-collection-hierarchy [bindings & body]
|
||||
`(let [~(macros.common/ignore-unused 'a) nil
|
||||
~(macros.common/ignore-unused 'b) nil
|
||||
~(macros.common/ignore-unused 'c) nil
|
||||
@@ -12,6 +12,6 @@
|
||||
~bindings
|
||||
~@body))
|
||||
|
||||
(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body]
|
||||
(defmacro with-some-children-of-collection [collection-or-id-or-nil & body]
|
||||
`(let [~(macros.common/ignore-unused '&ids) ~collection-or-id-or-nil]
|
||||
~@body))
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
(ns macros.metabase.api.common)
|
||||
(ns macros.metabase.api.common
|
||||
(:require
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defmacro define-routes
|
||||
"Macro for api.common/define-routes"
|
||||
[& args]
|
||||
`(do (def ~'routes "docstring" nil)
|
||||
~@args))
|
||||
|
||||
(defn route-fn-name
|
||||
"route fn hook"
|
||||
[method route]
|
||||
(let [route (if (vector? route) (first route) route)]
|
||||
(-> (str (name method) route)
|
||||
(str/replace #"/" "_")
|
||||
symbol)))
|
||||
|
||||
(defmacro defendpoint
|
||||
"Macro for api.common/defendpoint*"
|
||||
[method route & body]
|
||||
(let [method-fn (symbol "compojure.core" (str method))
|
||||
route-name (route-fn-name method route)]
|
||||
`(do
|
||||
~method-fn
|
||||
(defn ~route-name ~@body))))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(ns macros.metabase.api.embed-test)
|
||||
|
||||
(defmacro with-embedding-enabled-and-temp-card-referencing!
|
||||
(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!
|
||||
(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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
(ns macros.metabase.api.public-test)
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-card-referencing!
|
||||
(defmacro with-sharing-enabled-and-temp-card-referencing
|
||||
[table-kw field-kw [card-binding] & body]
|
||||
`(let [~card-binding [~table-kw ~field-kw]]
|
||||
~@body))
|
||||
|
||||
(defmacro with-sharing-enabled-and-temp-dashcard-referencing!
|
||||
(defmacro with-sharing-enabled-and-temp-dashcard-referencing
|
||||
[table-kw field-kw [dash-binding card-binding dashcard-binding] & body]
|
||||
`(let [~dash-binding [~table-kw ~field-kw]
|
||||
~(or card-binding '_) nil
|
||||
|
||||
16
.clj-kondo/macros/metabase/domain_entities/malli.clj
Normal file
16
.clj-kondo/macros/metabase/domain_entities/malli.clj
Normal file
@@ -0,0 +1,16 @@
|
||||
(ns macros.metabase.domain-entities.malli)
|
||||
|
||||
(defmacro define-getters-and-setters [schema & defs]
|
||||
`(do
|
||||
(comment ~schema) ;; Reference the schema
|
||||
~@(for [[sym _path] (partition 2 defs)]
|
||||
`(do
|
||||
(defn ~(vary-meta sym
|
||||
assoc :export true)
|
||||
"docs" [_x#] nil)
|
||||
(defn ~(vary-meta (symbol (str "with-" (name sym)))
|
||||
assoc :export true)
|
||||
"docs" [x# _new#] x#)
|
||||
(defn ~(vary-meta (symbol (str (name sym) "-js"))
|
||||
assoc :export true)
|
||||
"docs" [x#] x#)))))
|
||||
@@ -1,9 +0,0 @@
|
||||
(ns macros.metabase.models.query-analysis-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup [& body]
|
||||
`(let [~(macros.common/ignore-unused 'card-id) 1
|
||||
~(macros.common/ignore-unused 'table-id) 2
|
||||
~(macros.common/ignore-unused 'tax-id) 3
|
||||
~(macros.common/ignore-unused 'total-id) 4]
|
||||
~@body))
|
||||
9
.clj-kondo/macros/metabase/models/query_field_test.clj
Normal file
9
.clj-kondo/macros/metabase/models/query_field_test.clj
Normal file
@@ -0,0 +1,9 @@
|
||||
(ns macros.metabase.models.query-field-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup [& body]
|
||||
`(let [~(macros.common/ignore-unused 'card-id) 1
|
||||
~(macros.common/ignore-unused 'table-id) 2
|
||||
~(macros.common/ignore-unused 'tax-id) 3
|
||||
~(macros.common/ignore-unused 'total-id) 4]
|
||||
~@body))
|
||||
8
.clj-kondo/macros/metabase/shared/util/namespaces.clj
Normal file
8
.clj-kondo/macros/metabase/shared/util/namespaces.clj
Normal file
@@ -0,0 +1,8 @@
|
||||
(ns macros.metabase.shared.util.namespaces)
|
||||
|
||||
(defmacro import-fn
|
||||
"Kondo macro replacement for [[metabase.shared.util.namespaces/import-fn]]."
|
||||
([sym]
|
||||
`(def ~(-> sym name symbol) "docstring" ~sym))
|
||||
([sym name]
|
||||
`(def ~name "docstring" ~sym)))
|
||||
@@ -1,13 +0,0 @@
|
||||
(ns macros.metabase.task.setup.query-analysis-setup
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup!
|
||||
"Set up the data required to test the Query Analysis related tasks"
|
||||
[& body]
|
||||
`(let [~(macros.common/ignore-unused 'c1) 1
|
||||
~(macros.common/ignore-unused 'c2) 2
|
||||
~(macros.common/ignore-unused 'c3) 3
|
||||
~(macros.common/ignore-unused 'c4) 4
|
||||
~(macros.common/ignore-unused 'archived) 5
|
||||
~(macros.common/ignore-unused 'invalid) 6]
|
||||
~@body))
|
||||
@@ -1,5 +1,5 @@
|
||||
(ns macros.metabase.test.persistence)
|
||||
|
||||
(defmacro with-persistence-enabled! [[persist-fn-sym] & body]
|
||||
(defmacro with-persistence-enabled [[persist-fn-sym] & body]
|
||||
`(clojure.core/let [~persist-fn-sym (fn [])]
|
||||
~@body))
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
(ns macros.metabase.util.namespaces)
|
||||
|
||||
(defmacro import-fn
|
||||
"Kondo macro replacement for [[metabase.util.namespaces/import-fn]]."
|
||||
([sym]
|
||||
`(def ~(-> sym name symbol) "docstring" ~sym))
|
||||
([sym name]
|
||||
`(def ~name "docstring" ~sym)))
|
||||
@@ -1,16 +0,0 @@
|
||||
(ns macros.metabase.xrays.domain-entities.malli)
|
||||
|
||||
(defmacro define-getters-and-setters [schema & defs]
|
||||
`(do
|
||||
(comment ~schema) ;; Reference the schema
|
||||
~@(for [[sym _path] (partition 2 defs)]
|
||||
`(do
|
||||
(defn ~(vary-meta sym
|
||||
assoc :export true)
|
||||
"docs" [_x#] nil)
|
||||
(defn ~(vary-meta (symbol (str "with-" (name sym)))
|
||||
assoc :export true)
|
||||
"docs" [x# _new#] x#)
|
||||
(defn ~(vary-meta (symbol (str (name sym) "-js"))
|
||||
assoc :export true)
|
||||
"docs" [x#] x#)))))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns macros.metabase-enterprise.query-reference-validation.api-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-test-setup! [& body]
|
||||
`(let [~(macros.common/ignore-unused 'card-1) 1
|
||||
~(macros.common/ignore-unused 'card-2) 2
|
||||
~(macros.common/ignore-unused 'card-3) 3
|
||||
~(macros.common/ignore-unused 'card-4) 4
|
||||
~(macros.common/ignore-unused 'card-5) 5
|
||||
~(macros.common/ignore-unused 'coll-2) 100
|
||||
~(macros.common/ignore-unused 'coll-3) 200]
|
||||
~@body))
|
||||
@@ -1,329 +0,0 @@
|
||||
(ns hooks.clojure.core
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
;;; TODO -- seems silly to maintain different blacklists and whitelists here than we use for the deftest `^:parallel`
|
||||
;;; checker... those lists live in the Clj Kondo config file
|
||||
(def ^:private symbols-allowed-in-fns-not-ending-in-an-exclamation-point
|
||||
'#{;; these toucan methods might actually set global values if it's used outside of a transaction,
|
||||
;; but since mt/with-temp runs in a transaction, so we'll ignore them in this case.
|
||||
toucan2.core/delete!
|
||||
toucan2.core/update!
|
||||
toucan2.core/insert!
|
||||
toucan2.core/insert-returning-instance!
|
||||
toucan2.core/insert-returning-instances!
|
||||
toucan2.core/insert-returning-pk!
|
||||
toucan2.core/insert-returning-pks!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/poll!
|
||||
clojure.core.memoize/memo-clear!
|
||||
clojure.core/conj!
|
||||
clojure.core/persistent!
|
||||
clojure.core/reset!
|
||||
clojure.core/swap!
|
||||
clojure.core/volatile!
|
||||
clojure.core/vreset!
|
||||
clojure.core/vswap!
|
||||
clojure.java.jdbc/execute!
|
||||
methodical.core/add-aux-method-with-unique-key!
|
||||
methodical.core/remove-aux-method-with-unique-key!
|
||||
next.jdbc/execute!
|
||||
|
||||
;; Definitely thread safe
|
||||
metabase.test.util.dynamic-redefs/patch-vars!
|
||||
|
||||
;; TODO: most of these symbols shouldn't be here, we should go through them and
|
||||
;; find the functions/macros that use them and make sure their names end with !
|
||||
;; best way to do this is try remove each of these and rely on kondo output to find places where it's used
|
||||
clojure.test/grant-collection-perms!
|
||||
clojure.test/grant-collection-perms-fn!
|
||||
clojure.test/grant-perms-fn!
|
||||
clojure.test/purge-old-entries!
|
||||
clojure.test/revoke-collection-perms!
|
||||
clojure.test/save-results!
|
||||
metabase-enterprise.advanced-permissions.models.permissions/update-db-download-permissions!
|
||||
metabase-enterprise.internal-user/install-internal-user!
|
||||
metabase-enterprise.sso.integrations.saml-test/call-with-login-attributes-cleared!
|
||||
metabase.actions/perform-action!
|
||||
metabase.analytics.snowplow-test/fake-track-event-impl!
|
||||
metabase.analytics.snowplow/track-event-impl!
|
||||
metabase.api.public-test/add-card-to-dashboard!
|
||||
metabase.cmd.dump-to-h2/dump-to-h2!
|
||||
metabase.cmd.load-from-h2/load-from-h2!
|
||||
metabase.core/ensure-audit-db-installed!
|
||||
metabase.db.schema-migrations-test.impl/run-migrations-in-range!
|
||||
metabase.db.setup/migrate!
|
||||
metabase.db.setup/setup-db!
|
||||
metabase.db/migrate!
|
||||
metabase.db/setup-db!
|
||||
metabase.driver.mongo-test/create-database-from-row-maps!
|
||||
metabase.driver.postgres-test/create-enums-db!
|
||||
metabase.driver.postgres-test/drop-if-exists-and-create-db!
|
||||
metabase.driver.sql-jdbc.execute/execute-statement!
|
||||
metabase.email-test/reset-inbox!
|
||||
metabase.email/send-email!
|
||||
metabase.models.action/insert!
|
||||
metabase.models.collection.graph-test/clear-graph-revisions!
|
||||
metabase.models.collection.graph-test/do-with-n-temp-users-with-personal-collections!
|
||||
metabase.models.field-values/create-or-update-full-field-values!
|
||||
metabase.models.model-index/add-values!
|
||||
metabase.models.moderation-review/create-review!
|
||||
metabase.models.on-demand-test/add-dashcard-with-parameter-mapping!
|
||||
metabase.models.permissions/grant-application-permissions!
|
||||
metabase.models.permissions/grant-collection-read-permissions!
|
||||
metabase.models.permissions/grant-collection-readwrite-permissions!
|
||||
metabase.models.permissions/grant-full-data-permissions!
|
||||
metabase.models.permissions/grant-native-readwrite-permissions!
|
||||
metabase.models.permissions/grant-permissions!
|
||||
metabase.models.permissions/revoke-application-permissions!
|
||||
metabase.models.permissions/revoke-data-perms!
|
||||
metabase.models.permissions/update-data-perms-graph!
|
||||
metabase.models.permissions/update-group-permissions!
|
||||
metabase.models.persisted-info/ready-database!
|
||||
metabase.models.setting-test/test-user-local-allowed-setting!
|
||||
metabase.models.setting-test/test-user-local-only-setting!
|
||||
metabase.models.setting.cache/restore-cache!
|
||||
metabase.models.setting/set!
|
||||
metabase.models.setting/validate-settings-formatting!
|
||||
metabase.permissions.test-util/with-restored-perms!
|
||||
metabase.pulse.send/send-notifications!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase.query-processor.streaming.interface/begin!
|
||||
metabase.query-processor.streaming.interface/finish!
|
||||
metabase.query-processor.streaming.interface/write-row!
|
||||
metabase.sample-data/try-to-extract-sample-database!
|
||||
metabase.setup/create-token!
|
||||
metabase.sync.sync-metadata.fields.sync-metadata/update-field-metadata-if-needed!
|
||||
metabase.sync.sync-metadata/sync-db-metadata!
|
||||
metabase.sync.util-test/sync-database!
|
||||
metabase.sync.util/store-sync-summary!
|
||||
metabase.sync/sync-database!
|
||||
metabase.task.index-values/job-init!
|
||||
metabase.task.persist-refresh/job-init!
|
||||
metabase.task.persist-refresh/refresh-tables!
|
||||
metabase.task.persist-refresh/schedule-persistence-for-database!
|
||||
metabase.task/delete-task!
|
||||
metabase.test.data.bigquery-cloud-sdk/execute!
|
||||
metabase.test.data.impl/copy-db-tables-and-fields!
|
||||
metabase.test.data.impl/get-or-create-database!
|
||||
metabase.test.data.impl/get-or-create-default-dataset!
|
||||
metabase.test.data.impl.get-or-create/set-test-db-permissions!
|
||||
metabase.test.data.interface/create-db!
|
||||
metabase.test.data.interface/destroy-db!
|
||||
metabase.test.data.oracle/create-user!
|
||||
metabase.test.data.oracle/drop-user!
|
||||
metabase.test.data.sql-jdbc.load-data/make-insert!
|
||||
metabase.test.data.users/clear-cached-session-tokens!
|
||||
metabase.test.initialize/do-initialization!
|
||||
metabase.test.initialize/initialize-if-needed!
|
||||
metabase.test.integrations.ldap/start-ldap-server!
|
||||
metabase.test.util.log/ensure-unique-logger!
|
||||
metabase.test.util.log/set-ns-log-level!
|
||||
metabase.test.util/do-with-temp-env-var-value!
|
||||
metabase.test.util/restore-raw-setting!
|
||||
metabase.test.util/upsert-raw-setting!
|
||||
metabase.test/initialize-if-needed!
|
||||
metabase.test/test-helpers-set-global-values!
|
||||
metabase.test/with-temp-env-var-value!
|
||||
metabase.upload-test/set-local-infile!
|
||||
metabase.util.files/create-dir-if-not-exists!
|
||||
metabase.util.ssh-test/start-mock-servers!
|
||||
metabase.util.ssh-test/stop-mock-servers!})
|
||||
|
||||
(defn- end-with-exclamation?
|
||||
[s]
|
||||
(str/ends-with? s "!"))
|
||||
|
||||
(defn- explicitly-safe? [qualified-symbol]
|
||||
(contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol))
|
||||
|
||||
(defn- explicitly-unsafe? [config qualified-symbol]
|
||||
(contains? (get-in config [:linters :metabase/validate-deftest :parallel/unsafe]) qualified-symbol))
|
||||
|
||||
(defn- unsafe? [config qualified-symbol]
|
||||
(and (or (end-with-exclamation? qualified-symbol)
|
||||
(explicitly-unsafe? config qualified-symbol))
|
||||
(not (explicitly-safe? qualified-symbol))))
|
||||
|
||||
(defn- non-thread-safe-form-should-end-with-exclamation*
|
||||
[{[defn-or-defmacro form-name] :children, :as node} config]
|
||||
(when-not (and (:string-value form-name)
|
||||
(end-with-exclamation? (:string-value form-name)))
|
||||
(letfn [(walk [f form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk f child)))
|
||||
(check-node [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(when (unsafe? config qualified-symbol)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta form-name)
|
||||
:message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`. [:metabase/test-helpers-use-non-thread-safe-functions]"
|
||||
(:string-value defn-or-defmacro) qualified-symbol)
|
||||
:type :metabase/test-helpers-use-non-thread-safe-functions)))))]
|
||||
(walk check-node node))
|
||||
node))
|
||||
|
||||
(defn non-thread-safe-form-should-end-with-exclamation
|
||||
"Used to ensure defn and defmacro in test namespace to have name ending with `!` if it's non-thread-safe.
|
||||
A function or a macro can be defined as 'not thread safe' when their funciton name ends with a `!`.
|
||||
|
||||
Only used in tests to identify thread-safe/non-thread-safe test helpers. See #37126"
|
||||
[{:keys [node cljc lang config]}]
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(non-thread-safe-form-should-end-with-exclamation* node config))
|
||||
{:node node})
|
||||
|
||||
(comment
|
||||
(require '[clj-kondo.core :as clj-kondo])
|
||||
(def form (str '(defmacro a
|
||||
[x]
|
||||
`(fun-call x))))
|
||||
|
||||
(def form "(defmacro a
|
||||
[x]
|
||||
`(some! ~x))")
|
||||
|
||||
(def form "(defun f
|
||||
[x]
|
||||
(let [g! (fn [] 1)]
|
||||
(g!)))")
|
||||
|
||||
(str (hooks/parse-string form))
|
||||
(hooks/sexpr (hooks/parse-string form))
|
||||
|
||||
(binding [hooks/*reload* true]
|
||||
(-> form
|
||||
(with-in-str (clj-kondo/run! {:lint ["-"]}))
|
||||
:findings))
|
||||
|
||||
(do (non-thread-safe-form-should-end-with-exclamation* (hooks/parse-string form)) nil))
|
||||
|
||||
(defn- ns-form-node->require-node [ns-form-node]
|
||||
(some (fn [node]
|
||||
(when (and (hooks/list-node? node)
|
||||
(let [first-child (first (:children node))]
|
||||
(and (hooks/keyword-node? first-child)
|
||||
(= (hooks/sexpr first-child) :require))))
|
||||
node))
|
||||
(:children ns-form-node)))
|
||||
|
||||
(defn- lint-require-shapes [ns-form-node]
|
||||
(doseq [node (-> ns-form-node
|
||||
ns-form-node->require-node
|
||||
:children
|
||||
rest)]
|
||||
(cond
|
||||
(not (hooks/vector-node? node))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "All :required namespaces should be wrapped in vectors [:metabase/require-shape-checker]"
|
||||
:type :metabase/require-shape-checker))
|
||||
|
||||
(hooks/vector-node? (second (:children node)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message "Don't use prefix forms inside :require [:metabase/require-shape-checker]"
|
||||
:type :metabase/require-shape-checker)))))
|
||||
|
||||
(defn- require-node->namespace-symb-nodes [require-node]
|
||||
(let [[_ns & args] (:children require-node)]
|
||||
(into []
|
||||
;; prefixed namespace forms are NOT SUPPORTED!!!!!!!!1
|
||||
(keep (fn [node]
|
||||
(cond
|
||||
(hooks/vector-node? node)
|
||||
;; propagate the metadata attached to this vector in case there's a `:clj-kondo/ignore` form.
|
||||
(vary-meta (first (:children node)) (partial merge (meta require-node) (meta node)))
|
||||
|
||||
;; this should also be dead code since we require requires to be vectors
|
||||
(hooks/token-node? node)
|
||||
(vary-meta node (partial merge (meta require-node)))
|
||||
|
||||
:else
|
||||
(printf "Don't know how to figure out what namespace is being required in %s\n" (pr-str node)))))
|
||||
args)))
|
||||
|
||||
(defn- ns-form-node->ns-symb [ns-form-node]
|
||||
(some-> (some (fn [node]
|
||||
(when (and (hooks/token-node? node)
|
||||
(not= (hooks/sexpr node) 'ns))
|
||||
node))
|
||||
(:children ns-form-node))
|
||||
hooks/sexpr))
|
||||
|
||||
(defn- module
|
||||
"E.g.
|
||||
|
||||
(module 'metabase.qp.middleware.wow) => 'metabase.qp"
|
||||
[ns-symb]
|
||||
(some-> (re-find #"^metabase\.[^.]+" (str ns-symb)) symbol))
|
||||
|
||||
(defn- ignored-namespace? [ns-symb config]
|
||||
(some
|
||||
(fn [pattern-str]
|
||||
(re-find (re-pattern pattern-str) (str ns-symb)))
|
||||
(:ignored-namespace-patterns config)))
|
||||
|
||||
(defn- module-api-namespaces
|
||||
"Set API namespaces for a given module. `:any` means you can use anything, there are no API namespaces for this
|
||||
module (yet). If unspecified, the default is just the namespace with the same name as the module e.g.
|
||||
`metabase.db`."
|
||||
[module config]
|
||||
(let [module-config (get-in config [:api-namespaces module])]
|
||||
(cond
|
||||
(= module-config :any)
|
||||
nil
|
||||
|
||||
(set? module-config)
|
||||
module-config
|
||||
|
||||
:else
|
||||
#{module})))
|
||||
|
||||
(defn- lint-modules [ns-form-node config]
|
||||
(let [ns-symb (ns-form-node->ns-symb ns-form-node)]
|
||||
(when-not (ignored-namespace? ns-symb config)
|
||||
(when-let [current-module (module ns-symb)]
|
||||
(let [allowed-modules (get-in config [:allowed-modules current-module])
|
||||
required-namespace-symb-nodes (-> ns-form-node
|
||||
ns-form-node->require-node
|
||||
require-node->namespace-symb-nodes)]
|
||||
(doseq [node required-namespace-symb-nodes
|
||||
:let [clj-kondo-ignore (some-> (meta node) :clj-kondo/ignore hooks/sexpr set)]
|
||||
:when (not (contains? clj-kondo-ignore :metabase/ns-module-checker))
|
||||
:let [required-namespace (hooks/sexpr node)
|
||||
required-module (module required-namespace)]
|
||||
;; ignore stuff not in a module i.e. non-Metabase stuff.
|
||||
:when required-module
|
||||
:let [in-current-module? (= required-module current-module)]
|
||||
:when (not in-current-module?)
|
||||
:let [allowed-module? (or (= allowed-modules :any)
|
||||
(contains? (set allowed-modules) required-module))
|
||||
module-api-namespaces (module-api-namespaces required-module config)
|
||||
allowed-module-namespace? (or (empty? module-api-namespaces)
|
||||
(contains? module-api-namespaces required-namespace))]]
|
||||
(when-let [error (cond
|
||||
(not allowed-module?)
|
||||
(format "Module %s should not be used in the %s module. [:metabase/ns-module-checker :allowed-modules %s]"
|
||||
required-module
|
||||
current-module
|
||||
current-module)
|
||||
|
||||
(not allowed-module-namespace?)
|
||||
(format "Namespace %s is not an allowed external API namespace for the %s module. [:metabase/ns-module-checker :api-namespaces %s]"
|
||||
required-namespace
|
||||
required-module
|
||||
required-module))]
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message error
|
||||
:type :metabase/ns-module-checker)))))))))
|
||||
|
||||
(defn lint-ns [x]
|
||||
(lint-require-shapes (:node x))
|
||||
(lint-modules (:node x) (get-in x [:config :linters :metabase/ns-module-checker]))
|
||||
x)
|
||||
@@ -1,160 +0,0 @@
|
||||
(ns hooks.clojure.test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- warn-about-disallowed-parallel-forms [form config]
|
||||
(letfn [(error! [form message]
|
||||
(hooks/reg-finding! (assoc (meta form)
|
||||
:message message
|
||||
:type :metabase/validate-deftest)))
|
||||
(f [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(cond
|
||||
(contains? (:parallel/unsafe config) qualified-symbol)
|
||||
(error! form (format "%s is not allowed inside a ^:parallel test or test fixture [:metabase/validate-deftest]" qualified-symbol))
|
||||
|
||||
(and (not (contains? (:parallel/safe config) qualified-symbol))
|
||||
(str/ends-with? (name qualified-symbol) "!"))
|
||||
(error! form (format "destructive functions like %s are not allowed inside a ^:parallel test or test fixture. If this should be allowed, add it to the whitelist in the Kondo config file [:metabase/validate-deftest]"
|
||||
qualified-symbol)))))
|
||||
(walk [form]
|
||||
(f form)
|
||||
(doseq [child (:children form)]
|
||||
(walk child)))]
|
||||
(walk form)))
|
||||
|
||||
(defn- deftest-check-parallel
|
||||
"1. Check if test is marked ^:parallel / ^:synchronized correctly
|
||||
2. Make sure disallowed forms are not used in ^:parallel tests"
|
||||
[{[_ test-name & body] :children, :as _node} config]
|
||||
(let [test-metadata (:meta test-name)
|
||||
metadata-sexprs (map hooks/sexpr test-metadata)
|
||||
combined-metadata (transduce
|
||||
(map (fn [x]
|
||||
(if (map? x)
|
||||
x
|
||||
{x true})))
|
||||
(completing merge)
|
||||
{}
|
||||
metadata-sexprs)
|
||||
parallel? (:parallel combined-metadata)
|
||||
synchronized? (:synchronized combined-metadata)]
|
||||
(when (and parallel? synchronized?)
|
||||
(hooks/reg-finding! (assoc (meta test-name)
|
||||
:message "Test should not be marked both ^:parallel and ^:synchronized"
|
||||
:type :metabase/validate-deftest)))
|
||||
;; only when the custom `:metabase/deftest-not-marked-parallel-or-synchronized` is enabled: complain if tests are
|
||||
;; not explicitly marked `^:parallel` or `^:synchronized`. This is mostly to encourage people to mark everything
|
||||
;; `^:parallel` in places like `metabase.lib` tests unless there is a really good reason not to.
|
||||
(when-not (or parallel? synchronized?)
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta test-name)
|
||||
:message "Test should be marked either ^:parallel or ^:synchronized"
|
||||
:type :metabase/deftest-not-marked-parallel-or-synchronized)))
|
||||
(when parallel?
|
||||
(doseq [form body]
|
||||
(warn-about-disallowed-parallel-forms form config)))))
|
||||
|
||||
(defn- deftest-check-not-horrifically-long
|
||||
[node {:keys [max-length], :as _config}]
|
||||
(let [{:keys [row end-row]} (meta node)]
|
||||
(when (and row end-row)
|
||||
(let [num-lines (- end-row row)]
|
||||
(when (and max-length
|
||||
(>= num-lines max-length))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (str (format "This test is horrifically long, it's %d lines! 😱 " num-lines)
|
||||
"Do you really want to try to debug it if it fails? 💀 "
|
||||
"Split it up into smaller tests! 🥰")
|
||||
:type :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests)))))))
|
||||
|
||||
(defn- ignore? [node error-type]
|
||||
(when-let [ignores (some-> node meta :clj-kondo/ignore hooks/sexpr)]
|
||||
(when-let [ignores (cond
|
||||
(coll? ignores) (set ignores)
|
||||
(keyword? ignores) #{ignores})]
|
||||
(contains? ignores error-type))))
|
||||
|
||||
(defn deftest-check-no-driver-keywords [node {:keys [drivers], :as _config}]
|
||||
;; fail fast after we see the first error, where we see one hardcoded driver name there are likely several more and we
|
||||
;; don't need multiple warnings for a single test.
|
||||
(letfn [(f [node]
|
||||
(when (and (hooks/keyword-node? node)
|
||||
(set? drivers)
|
||||
(contains? drivers (hooks/sexpr node)))
|
||||
(hooks/reg-finding! (assoc (meta node)
|
||||
:message (format "Do not hardcode driver name %s in driver tests! [:metabase/disallow-hardcoded-driver-names-in-tests]"
|
||||
(hooks/sexpr node))
|
||||
:type :metabase/disallow-hardcoded-driver-names-in-tests))
|
||||
::error))
|
||||
(walk [node]
|
||||
(cond
|
||||
(ignore? node :metabase/disallow-hardcoded-driver-names-in-tests)
|
||||
nil
|
||||
|
||||
(= (f node) ::error)
|
||||
::error
|
||||
|
||||
:else
|
||||
(reduce
|
||||
(fn [_acc child]
|
||||
(when (= (walk child) ::error)
|
||||
(reduced ::error)))
|
||||
nil
|
||||
(:children node))))]
|
||||
(walk node)))
|
||||
|
||||
(defn deftest [{:keys [node cljc lang config]}]
|
||||
;; run [[deftest-check-parallel]] only once... if this is a `.cljc` file only run it for the `:clj` analysis, no point
|
||||
;; in running it twice.
|
||||
(when (or (not cljc)
|
||||
(= lang :clj))
|
||||
(deftest-check-parallel node (get-in config [:linters :metabase/validate-deftest])))
|
||||
(deftest-check-not-horrifically-long node (get-in config [:linters :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests]))
|
||||
(deftest-check-no-driver-keywords node (get-in config [:linters :metabase/disallow-hardcoded-driver-names-in-tests]))
|
||||
{:node node})
|
||||
|
||||
;;; this is a hacky way to determine whether these namespaces are required in the `ns` form or not... basically `:ns`
|
||||
;;; will come back as `nil` if they are not.
|
||||
(defn- approximately-equal-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.approximately-equal/=?-report}))
|
||||
'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
(defn- malli-equals-ns-required? []
|
||||
(= (:ns (hooks/resolve {:name 'metabase.test-runner.assert-exprs.malli-equals/malli=-report}))
|
||||
'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
(defn- warn-about-missing-test-expr-requires-in-cljs [{:keys [children], :as _is-node}]
|
||||
(let [[_is assertion-node] children]
|
||||
(when (hooks/list-node? assertion-node)
|
||||
(let [[assertion-symb-node] (:children assertion-node)]
|
||||
(when (hooks/token-node? assertion-symb-node)
|
||||
(let [assertion-symb-token (hooks/sexpr assertion-symb-node)
|
||||
warn! (fn [ns-to-require]
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta assertion-symb-node)
|
||||
:message (format "You must require %s to use %s in ClojureScript"
|
||||
ns-to-require
|
||||
assertion-symb-token)
|
||||
:type :metabase/missing-test-expr-requires-in-cljs)))]
|
||||
(condp = assertion-symb-token
|
||||
'=?
|
||||
(when-not (approximately-equal-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.approximately-equal))
|
||||
|
||||
'malli=
|
||||
(when-not (malli-equals-ns-required?)
|
||||
(warn! 'metabase.test-runner.assert-exprs.malli-equals))
|
||||
|
||||
nil)))))))
|
||||
|
||||
(defn is [{:keys [node lang]}]
|
||||
(when (= lang :cljs)
|
||||
(warn-about-missing-test-expr-requires-in-cljs node))
|
||||
{:node node})
|
||||
|
||||
(defn use-fixtures [{:keys [node config]}]
|
||||
(warn-about-disallowed-parallel-forms node config)
|
||||
{:node node})
|
||||
@@ -1,447 +0,0 @@
|
||||
(ns hooks.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.pprint]))
|
||||
|
||||
(defn with-macro-meta
|
||||
"When introducing internal nodes (let, defn, etc) it is important to provide a meta of an existing token
|
||||
as the current version of kondo will use the whole form by default."
|
||||
[new-node hook-node]
|
||||
(with-meta new-node (meta (first (:children hook-node)))))
|
||||
|
||||
;;; This stuff is to help debug hooks. Trace a function and it will pretty-print the before & after sexprs.
|
||||
;;;
|
||||
;;; (hooks.common/trace #'calculate-bird-scarcity)
|
||||
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(defn- trace* [f]
|
||||
{:pre [(fn? f)]}
|
||||
(fn traced-fn [node]
|
||||
(println \newline)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node)))
|
||||
(let [node* (f node)]
|
||||
(println '=>)
|
||||
(clojure.pprint/pprint (hooks/sexpr (:node node*)))
|
||||
node*)))
|
||||
|
||||
;;; this doesn't seem to work with SCI. Use [[defn-traced]] instead.
|
||||
|
||||
;; (defn trace [varr]
|
||||
;; {:pre [(var? varr)]}
|
||||
;; (when-not (::traced? (meta varr))
|
||||
;; (alter-var-root varr trace*)
|
||||
;; (alter-meta! varr assoc ::traced? true)
|
||||
;; (println "Traced" varr)))
|
||||
|
||||
(defmacro defn-traced
|
||||
"Replace a hook [[defn]] with this to trace the before and after sexprs."
|
||||
[fn-name & fn-tail]
|
||||
`(do
|
||||
(defn fn# ~@fn-tail)
|
||||
(def ~fn-name (trace* fn#))))
|
||||
|
||||
;;;; Common hook definitions
|
||||
|
||||
(defn do*
|
||||
"This is the same idea as [[clojure.core/do]] but doesn't cause Kondo to complain about redundant dos or unused
|
||||
values."
|
||||
[{{[_ & args] :children, :as node} :node}]
|
||||
(let [node* (-> (hooks/list-node
|
||||
(list*
|
||||
(with-meta (hooks/token-node 'do) {:clj-kondo/ignore [:redundant-do]})
|
||||
(for [arg args]
|
||||
(vary-meta arg update :clj-kondo/ignore #(conj (vec %) :unused-value)))))
|
||||
(with-meta (meta node)))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)
|
||||
|
||||
Binding is optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-three-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [x y z]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil, z nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[x y z] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or y (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or z (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-four-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-five-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-six-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-seven-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro [a b c d e f g]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [a nil, b nil, c nil, d nil, e nil, f nil, g nil]
|
||||
...)
|
||||
|
||||
All bindings are optional and `_` will be substituted if not supplied."
|
||||
[{{[_ {[a b c d e f g] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or a (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or b (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or c (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or d (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or e (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or f (hooks/token-node '_)) (hooks/token-node 'nil)
|
||||
(or g (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-one-top-level-binding
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil]
|
||||
...)"
|
||||
[{{[_ x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-two-top-level-bindings
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x y
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [x nil, y nil]
|
||||
...)"
|
||||
[{{[_ x y & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[x (hooks/token-node 'nil)
|
||||
y (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn let-one-with-optional-value
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the binding, `value`, is optional, and only one
|
||||
binding can be established.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {[binding value] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[binding (or value (hooks/token-node 'nil))])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn- let-second-inner [body bindings]
|
||||
(let [binding-infos (for [[model {[binding value] :children}] (partition 2 bindings)]
|
||||
{:model model
|
||||
:binding binding
|
||||
:value (or value
|
||||
(hooks/token-node 'nil))})]
|
||||
(-> (hooks/vector-node
|
||||
[(hooks/vector-node (map :model binding-infos))
|
||||
(-> (hooks/list-node (list* (hooks/token-node `let)
|
||||
(hooks/vector-node (mapcat (juxt :binding :value) binding-infos))
|
||||
body))
|
||||
(with-meta (meta body)))])
|
||||
(with-meta (meta body)))))
|
||||
|
||||
(defn let-second
|
||||
"Helper for macros that have a shape like
|
||||
|
||||
(my-macro x [y]
|
||||
...)
|
||||
|
||||
where the let is for second arg.
|
||||
|
||||
=>
|
||||
|
||||
(let [y nil]
|
||||
...)"
|
||||
[{:keys [node]}]
|
||||
(let [[_ first-arg-ref binding+opts & body] (:children node)]
|
||||
{:node (let-second-inner body [first-arg-ref binding+opts])}))
|
||||
|
||||
(defn let-with-optional-value-for-last-binding
|
||||
"This is exactly like [[clojure.core/let]] but the right-hand side of the *last* binding, `value`, is optional.
|
||||
|
||||
(some-macro [x] ...)
|
||||
=>
|
||||
(let [x nil] ...)
|
||||
|
||||
(some-macro [x 100] ...)
|
||||
=>
|
||||
(let [x 100] ...)"
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding value]]
|
||||
[binding (or value (hooks/token-node 'nil))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-ignored-first-arg
|
||||
"For macros like
|
||||
|
||||
(discard-setting-changes [setting-1 setting-2]
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(do ...)
|
||||
|
||||
where the first arg ought to be ignored for linting purposes."
|
||||
[{{[_ _x & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-used-first-arg
|
||||
"For macros like
|
||||
|
||||
(with-drivers (filter pred? some-drivers)
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [_1234 (filter pred? some-drivers)]
|
||||
...)
|
||||
|
||||
where the first arg should be linted and appear to be used."
|
||||
[{{[_ arg & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node (gensym "_"))
|
||||
arg])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-vec-first-binding
|
||||
"For macros like
|
||||
|
||||
(with-temp-file [binding \"temp.txt\" \"content\"]
|
||||
(slurp binding))
|
||||
|
||||
=>
|
||||
(let [binding nil]
|
||||
(slurp binding))
|
||||
|
||||
where the first arg is a vector of bindings."
|
||||
[{{[_ {[x & _args] :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[(or x (hooks/token-node '_)) (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn node->qualified-symbol
|
||||
"If the node is a symbol, return it as a qualified symbol if it's not already. Otherwise return nil."
|
||||
[node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(let [resolved (hooks/resolve {:name sexpr})]
|
||||
(cond
|
||||
(and resolved (:ns resolved))
|
||||
(symbol (name (:ns resolved)) (name (:name resolved)))
|
||||
|
||||
;; if it wasn't resolved but is still qualified it's probably using the full namespace name rather than an
|
||||
;; alias.
|
||||
(qualified-symbol? sexpr)
|
||||
sexpr)))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(defn format-string-specifier-count
|
||||
"Number of things like `%s` in a format string, not counting newlines (`%n`) or escaped percent signs (`%%`). For
|
||||
checking the number of args to something that takes a format string."
|
||||
[format-string]
|
||||
(count (re-seq #"(?<!%)%(?![%n])" format-string)))
|
||||
|
||||
(comment
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%s %%")
|
||||
|
||||
;; should be 2
|
||||
(format-string-specifier-count "%s %%%n%s")
|
||||
|
||||
;; should be 0
|
||||
(format-string-specifier-count "%n%%%%")
|
||||
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%-02d"))
|
||||
|
||||
(defn add-lsp-ignore-unused-public-var-metadata
|
||||
"Add
|
||||
|
||||
^{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
|
||||
metadata to a node to suppress LSP warnings."
|
||||
[node]
|
||||
(letfn [(update-ignores [existing-ignores]
|
||||
(hooks/vector-node
|
||||
(cons
|
||||
(hooks/keyword-node :clojure-lsp/unused-public-var)
|
||||
(:children existing-ignores))))]
|
||||
(vary-meta node update :clj-kondo/ignore update-ignores)))
|
||||
@@ -1,32 +0,0 @@
|
||||
(ns hooks.metabase.api.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
|
||||
(defn route-fn-name
|
||||
"route fn hook"
|
||||
[method route]
|
||||
(let [route (if (vector? route) (first route) route)]
|
||||
(-> (str (name method) route)
|
||||
(str/replace #"/" "_")
|
||||
symbol)))
|
||||
|
||||
(defn defendpoint
|
||||
[arg]
|
||||
(letfn [(update-defendpoint [node]
|
||||
(let [[_defendpoint method route & body] (:children node)]
|
||||
(-> (api/list-node
|
||||
(list
|
||||
(api/token-node 'do)
|
||||
(-> (api/token-node (symbol "compojure.core" (str (api/sexpr method))))
|
||||
(with-meta (meta method)))
|
||||
(api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/defn)
|
||||
(-> (api/token-node (route-fn-name (api/sexpr method) (api/sexpr route)))
|
||||
(with-meta (meta route)))
|
||||
body))))
|
||||
(with-meta (meta node))
|
||||
hooks.common/add-lsp-ignore-unused-public-var-metadata)))]
|
||||
(update arg :node update-defendpoint)))
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns hooks.metabase.channel.template.handlebars-helper
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn defhelper
|
||||
"defhelper hook"
|
||||
[{:keys [node]}]
|
||||
(let [[_ helper-name description argvec & body] (:children node)
|
||||
helper-fn-name (symbol (str (hooks/sexpr helper-name) "*"))]
|
||||
{:node (hooks/list-node
|
||||
(list (hooks/token-node 'do)
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'defn)
|
||||
(hooks/token-node helper-fn-name)
|
||||
description
|
||||
argvec
|
||||
(hooks/list-node body)))
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'def)
|
||||
helper-name
|
||||
description
|
||||
(hooks/list-node
|
||||
(into [(hooks/token-node 'fn)
|
||||
argvec]
|
||||
body))))))}))
|
||||
@@ -1,12 +0,0 @@
|
||||
(ns hooks.metabase.lib.test-util.macros
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn with-testing-against-standard-queries [{{[_ & args] :children} :node}]
|
||||
(let [[query-sym & body] args
|
||||
node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
[query-sym (hooks/token-node 'nil)])
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,56 +0,0 @@
|
||||
(ns hooks.metabase.test.data.datasets
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn- only-core-drivers?
|
||||
"Whether we're for sure only testing against the 'core' drivers that are also app DB types: `:postgres`, `:mysql`, or
|
||||
`:h2`. Tests like these are ok to mark `:mb/once` or `:mb/driver-tests` since we always run the full test suite
|
||||
against app DB drivers anyway."
|
||||
[node]
|
||||
(let [drivers (hooks/sexpr (second (:children node)))
|
||||
drivers (cond
|
||||
(keyword? drivers) #{drivers}
|
||||
(set? drivers) drivers
|
||||
:else nil)]
|
||||
(when drivers
|
||||
(empty? (set/difference drivers #{:h2 :mysql :postgres})))))
|
||||
|
||||
(defn- validate-mb-once [x]
|
||||
(when (:mb/once (meta (:ns x)))
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta (:ns x))
|
||||
:message (str "You should not use test-driver or test-drivers in a namespace marked ^:mb/once"
|
||||
" [:metabase/validate-mb-once]")
|
||||
:type :metabase/validate-mb-once))))
|
||||
|
||||
(defn- validate-mb-driver-tests [x]
|
||||
(when-not (:mb/driver-tests (meta (:ns x)))
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta (:ns x))
|
||||
:message (str "Namespaces that run driver tests (test-driver or test-drivers) should be marked"
|
||||
" ^:mb/driver-tests [:metabase/validate-mb-driver-tests]")
|
||||
:type :metabase/validate-mb-driver-tests))))
|
||||
|
||||
(defn- validate-ns-tags [x]
|
||||
(when-not (only-core-drivers? (:node x))
|
||||
(validate-mb-once x)
|
||||
(validate-mb-driver-tests x)))
|
||||
|
||||
(defn test-drivers [x]
|
||||
(validate-ns-tags x)
|
||||
(letfn [(update-node [node]
|
||||
(let [[test-drivers drivers-expr & body] (:children node)]
|
||||
(-> (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'do)
|
||||
test-drivers
|
||||
(-> drivers-expr
|
||||
(vary-meta update :clj-kondo/ignore #(conj (vec %) :unused-value)))
|
||||
body))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
|
||||
(defn test-driver
|
||||
"We can use the same hook for `test-driver` as we use for `test-drivers`."
|
||||
[x]
|
||||
(test-drivers x))
|
||||
@@ -1,60 +0,0 @@
|
||||
(ns hooks.metabase.test.util
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common :as common]))
|
||||
|
||||
(defn- namespaced-symbol-node? [node]
|
||||
(when (hooks/token-node? node)
|
||||
(let [symb (hooks/sexpr node)]
|
||||
(and (symbol? symb)
|
||||
(namespace symb)))))
|
||||
|
||||
(defn with-temporary-setting-values
|
||||
"Rewrite a form like
|
||||
|
||||
(with-temporary-setting-values [x 1, some.ns/y 2]
|
||||
...)
|
||||
|
||||
as one like
|
||||
|
||||
(let [_ 1, _ some.ns/y, _ 2]
|
||||
...)
|
||||
|
||||
for analysis purposes. We only need to 'capture' namespaced Setting bindings with a `_` so Kondo considers their
|
||||
namespace to be 'used' and to catch undefined var usage."
|
||||
[{:keys [node]}]
|
||||
(let [{[_ bindings & body] :children} node
|
||||
bindings (if (hooks/vector-node? bindings)
|
||||
(hooks/vector-node (into []
|
||||
(mapcat (fn [[setting-name v]]
|
||||
(concat
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) v]
|
||||
;; if the setting name is namespace-qualified add a `_`
|
||||
;; entry for it too.
|
||||
(when (namespaced-symbol-node? setting-name)
|
||||
[(with-meta (hooks/token-node '_) (meta setting-name)) setting-name]))))
|
||||
(partition 2 (:children bindings))))
|
||||
bindings)]
|
||||
{:node (-> (list*
|
||||
(hooks/token-node 'let)
|
||||
bindings
|
||||
body)
|
||||
(with-meta (meta body))
|
||||
hooks/list-node
|
||||
(with-meta (meta body)))}))
|
||||
|
||||
(defn with-temp-file
|
||||
[{{[_ {bindings :children} & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat (fn [[binding filename]]
|
||||
[binding (or (when (and filename
|
||||
(not (nil? (hooks/sexpr filename))))
|
||||
filename)
|
||||
(hooks/string-node "filename"))])))
|
||||
bindings))
|
||||
body))]
|
||||
{:node node*}))
|
||||
@@ -1,124 +0,0 @@
|
||||
(ns hooks.metabase.util.log
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[clojure.walk :as walk]
|
||||
[hooks.common]))
|
||||
|
||||
;;;; Checks for both log + logf
|
||||
|
||||
(defn- disallow-i18n [node]
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol x)]
|
||||
(when (and (= (namespace qualified-symbol) "metabase.util.i18n")
|
||||
(or (str/includes? (name qualified-symbol) "tru")
|
||||
(str/includes? (name qualified-symbol) "trs")))
|
||||
(api/reg-finding! (assoc (meta x)
|
||||
:message "do not i18n the logs!"
|
||||
:type :metabase/validate-logging)))))
|
||||
node))
|
||||
|
||||
(defn- check-position-of-exception-arg
|
||||
"Tell people to do (info e \"whatever\") instead of (info \"whatever\" e)."
|
||||
[node]
|
||||
(let [[f _first-arg & args] (:children node)]
|
||||
(doseq [arg args]
|
||||
(when (and (api/token-node? arg)
|
||||
(= (api/sexpr arg) 'e))
|
||||
(api/reg-finding! (assoc (meta arg)
|
||||
:message (format "pass exceptions as the first arg to %s" (api/sexpr f))
|
||||
:type :metabase/validate-logging))))))
|
||||
|
||||
(defn- common-checks [node]
|
||||
(check-position-of-exception-arg node)
|
||||
(disallow-i18n node))
|
||||
|
||||
(defn- check-for-i18n-format-specifiers
|
||||
"Make sure people aren't using things like {0} in their log messages."
|
||||
[node string-node]
|
||||
(when (re-find #"\{\d+\}" (api/sexpr string-node))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message "this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
;;; log only
|
||||
|
||||
(defn- check-not-format-string
|
||||
"Make sure we're not using something like log/trace with a format string."
|
||||
[{[f] :children, :as node} string-node]
|
||||
(when (pos? (hooks.common/format-string-specifier-count (api/sexpr string-node)))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "%s used with a format string, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str (api/sexpr f) "f"))
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
(defn- format-node? [node]
|
||||
(when (api/list-node? node)
|
||||
(let [[x] (:children node)]
|
||||
(and (api/token-node? x)
|
||||
(or (= (hooks.common/node->qualified-symbol x) 'clojure.core/format)
|
||||
(= (api/sexpr x) 'format))))))
|
||||
|
||||
(defn- warn-against-log+format-instead-of-logf
|
||||
"Tell people to use things like infof instead of info + format."
|
||||
[{[f] :children, :as node} [x :as args]]
|
||||
(when (and (= (count args) 1)
|
||||
(api/list-node? x))
|
||||
(when (format-node? x)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Use %s instead of %s + format"
|
||||
(str (api/sexpr f) "f")
|
||||
(api/sexpr f))
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
(defn info
|
||||
"Valid log, debug, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e & [x :as args]] (if (and (api/token-node? (first args))
|
||||
(or (api/string-node? (second args))
|
||||
(format-node? (second args))))
|
||||
args
|
||||
(cons nil args))]
|
||||
(when (api/string-node? x)
|
||||
(check-for-i18n-format-specifiers node x)
|
||||
(check-not-format-string node x))
|
||||
(warn-against-log+format-instead-of-logf node args))
|
||||
x)
|
||||
|
||||
;;;; logf only
|
||||
|
||||
(defn- check-format-string-arg-count
|
||||
"Make sure the number of args passed to something like tracef match the number of format string specifiers."
|
||||
[{[f] :children, :as node} format-string args]
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string))
|
||||
actual-arg-count (count args)]
|
||||
(when (zero? expected-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Don't use %s with no format string arguments, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str/replace (str (api/sexpr f)) #"f$" ""))
|
||||
:type :metabase/validate-logging)))
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "log format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
(defn infof
|
||||
"Valid logf, debugf, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e format-string & args] (if (api/string-node? (first args))
|
||||
(cons nil args)
|
||||
args)]
|
||||
;; TODO -- maybe we can error if you did not pass a format string, e.g. (log/infof 1 2) is obviously incorrect.
|
||||
(when (api/string-node? format-string)
|
||||
(check-for-i18n-format-specifiers node format-string)
|
||||
(check-format-string-arg-count node format-string args)))
|
||||
x)
|
||||
@@ -1,26 +0,0 @@
|
||||
(ns hooks.metabase.util.log.capture
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn with-log-messages-for-level [x]
|
||||
(letfn [(update-binding [[lhs-node _rhs-node]]
|
||||
[lhs-node (api/list-node
|
||||
(list
|
||||
(api/token-node 'clojure.core/fn)
|
||||
(api/vector-node [])))])
|
||||
(update-bindings [bindings-node]
|
||||
(-> (api/vector-node
|
||||
(into []
|
||||
(comp (partition-all 2)
|
||||
(mapcat update-binding))
|
||||
(:children bindings-node)))
|
||||
(with-meta (meta bindings-node))))
|
||||
(update-node [node]
|
||||
(let [[_with-log-messages-for-level bindings & body] (:children node)]
|
||||
(-> (api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/let)
|
||||
(update-bindings bindings)
|
||||
body))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,55 +0,0 @@
|
||||
(ns hooks.clojure.test-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.clojure.test]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- deftest-warnings
|
||||
[form]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/disallow-hardcoded-driver-names-in-tests {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(hooks.clojure.test/deftest {:node (api/parse-string form)
|
||||
:config {:linters
|
||||
{:metabase/disallow-hardcoded-driver-names-in-tests
|
||||
{:drivers
|
||||
#{:athena}}}}})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*))))
|
||||
|
||||
(deftest ^:parallel disallow-hardcoded-driver-names-in-tests-test
|
||||
(is (= []
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
(do-something))")))
|
||||
(is (= ["Do not hardcode driver name :athena in driver tests! [:metabase/disallow-hardcoded-driver-names-in-tests]"]
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
(when-not (= driver/*driver* :athena)
|
||||
(do-something)))")))
|
||||
(testing "make sure :clj-kondo/ignore is propagated correctly"
|
||||
(is (= []
|
||||
(deftest-warnings
|
||||
"(mt/test-drivers (mt/normal-drivers)
|
||||
#_{:clj-kondo/ignore [:metabase/disallow-hardcoded-driver-names-in-tests]}
|
||||
(when-not (= driver/*driver* :athena)
|
||||
(do-something)))")))))
|
||||
|
||||
(deftest ^:parallel check-driver-keywords-test
|
||||
(testing "Make sure we keep hooks.clojure.test/driver-keywords up to date"
|
||||
(let [driver-keywords (-> (slurp ".clj-kondo/config.edn")
|
||||
edn/read-string
|
||||
(get-in [:linters :metabase/disallow-hardcoded-driver-names-in-tests :drivers]))
|
||||
driver-modules (->> (slurp "modules/drivers/deps.edn")
|
||||
edn/read-string
|
||||
:deps
|
||||
vals
|
||||
(keep (comp keyword :local/root))
|
||||
(into #{}))]
|
||||
(doseq [driver driver-modules]
|
||||
(is (contains? driver-keywords driver)
|
||||
(format "hooks.clojure.test/driver-keywords should contain %s, please add it" driver))))))
|
||||
@@ -1,19 +0,0 @@
|
||||
(ns ^:mb/once hooks.common-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.common]))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom nil)}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "metabase.util.i18n/tru"))))))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test-2
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom {:clj {:clj '{hooks.common-test {:qualify-ns {i18n metabase.util.i18n}}}}})
|
||||
:lang :clj
|
||||
:base-lang :clj
|
||||
:ns {:name 'hooks.common-test}}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "i18n/tru"))))))
|
||||
@@ -1,35 +0,0 @@
|
||||
(ns hooks.metabase.api.common-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.api.common]))
|
||||
|
||||
(deftest ^:parallel defendpoint-test
|
||||
(is (= '(do
|
||||
compojure.core/POST
|
||||
(clojure.core/defn
|
||||
POST_:id_copy
|
||||
"Copy a `Card`, with the new name 'Copy of _name_'"
|
||||
[id]
|
||||
{id [:maybe ms/PositiveInt]}
|
||||
(let [orig-card (api/read-check Card id)
|
||||
new-name (str (trs "Copy of ") (:name orig-card))
|
||||
new-card (assoc orig-card :name new-name)]
|
||||
(-> (card/create-card! new-card @api/*current-user*)
|
||||
hydrate-card-details
|
||||
(assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*))))))
|
||||
(-> {:node (-> '(api/defendpoint POST "/:id/copy"
|
||||
"Copy a `Card`, with the new name 'Copy of _name_'"
|
||||
[id]
|
||||
{id [:maybe ms/PositiveInt]}
|
||||
(let [orig-card (api/read-check Card id)
|
||||
new-name (str (trs "Copy of ") (:name orig-card))
|
||||
new-card (assoc orig-card :name new-name)]
|
||||
(-> (card/create-card! new-card @api/*current-user*)
|
||||
hydrate-card-details
|
||||
(assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*)))))
|
||||
pr-str
|
||||
api/parse-string)}
|
||||
hooks.metabase.api.common/defendpoint
|
||||
:node
|
||||
api/sexpr))))
|
||||
@@ -1,19 +0,0 @@
|
||||
(ns hooks.metabase.util.log.capture-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.util.log.capture]))
|
||||
|
||||
(deftest ^:parallel with-log-messages-for-level-test
|
||||
(is (= '(clojure.core/let [messages-1 (clojure.core/fn [])
|
||||
messages-2 (clojure.core/fn [])]
|
||||
(concat (messages-1)
|
||||
(messages-2)))
|
||||
(-> (hooks.metabase.util.log.capture/with-log-messages-for-level
|
||||
{:node (-> '(with-log-messages-for-level [messages-1 [my.namespace :debug]
|
||||
messages-2 :info]
|
||||
(concat (messages-1) (messages-2)))
|
||||
pr-str
|
||||
api/parse-string)})
|
||||
:node
|
||||
api/sexpr))))
|
||||
@@ -1,77 +0,0 @@
|
||||
(ns ^:mb/once hooks.metabase.util.log-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.util.log]))
|
||||
|
||||
(defn- warnings
|
||||
[form]
|
||||
(let [f (if (str/ends-with? (name (first form)) "f")
|
||||
hooks.metabase.util.log/infof
|
||||
hooks.metabase.util.log/info)]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-logging {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(f {:node (api/parse-string (pr-str form))})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*)))))
|
||||
|
||||
(deftest ^:parallel warn-on-missing-format-args-test
|
||||
(testing "should fail, missing a format arg"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 0."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries.")
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries."))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-1
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 2."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries." 1 2)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries." 1 2))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-2
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
"Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."
|
||||
"log format string expects 0 arguments instead of 1."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after {0} retries." 1)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after {0} retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-args-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["metabase.util.log/warn used with a format string, use metabase.util.log/warnf instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn "Migration lock was acquired after %d retries." 1)
|
||||
(metabase.util.log/warn e "Migration lock was acquired after %d retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Use metabase.util.log/warnf instead of metabase.util.log/warn + format"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (format "Migration lock was acquired after %d retries." 1))
|
||||
(metabase.util.log/warn e (format "Migration lock was acquired after %d retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-logf-with-no-format-args-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock cleared.")
|
||||
(metabase.util.log/warnf e "Migration lock cleared."))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-1
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warnf e (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-2
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warn e (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1)))))
|
||||
370
.cljfmt.edn
370
.cljfmt.edn
@@ -1,339 +1,31 @@
|
||||
;; Cljfmt config. See https://github.com/weavejester/cljfmt#formatting-options
|
||||
{:sort-ns-references?
|
||||
true
|
||||
|
||||
:function-arguments-indentation
|
||||
:community
|
||||
|
||||
:parallel?
|
||||
true
|
||||
|
||||
:paths
|
||||
["src"
|
||||
"test"
|
||||
"enterprise/backend"
|
||||
"bin/build"
|
||||
"modules/drivers"
|
||||
".clj-kondo/src"
|
||||
".clj-kondo/test"
|
||||
#_"dev"]
|
||||
|
||||
;; See https://github.com/weavejester/cljfmt/blob/master/cljfmt/resources/cljfmt/indents/clojure.clj for the default
|
||||
;; definitions
|
||||
;;
|
||||
;; [[:block 0]]
|
||||
;; ^ number of special args
|
||||
;;
|
||||
;; `:block` indentation means format with all arguments indented 2 spaces except for the 'special' arguments which
|
||||
;; normally are supposed to go on the first line. e.g. `let` gets [[:block 1]] because it has one 'special' arg.
|
||||
;;
|
||||
;; [[:inner 0]]
|
||||
;; ^ depth
|
||||
;; [[:inner 2 0]]
|
||||
;; ^ argument index to restrict this rule to
|
||||
;;
|
||||
;; `:inner` indentation is like `:defn` indentation. `[[:inner 0]]` (default for `defn`) means all arguments at depth
|
||||
;; zero get indented 2 spaces if not on the first line regardless of number of elements on the first line.
|
||||
:extra-indents
|
||||
{;; clojure.core stuff
|
||||
fn* [[:inner 0]]
|
||||
let* [[:block 1]]
|
||||
with-meta [[:default]]
|
||||
;; library stuff. Libraries that specify their own `:style/indent` specs are included in the section after this
|
||||
;; since it is generated automatically.
|
||||
clojure.core.logic/fresh [[:block 1]]
|
||||
clojure.core.logic/matcha [[:block 1]]
|
||||
clojure.core.logic/run [[:block 2]]
|
||||
clojure.core.match/match [[:block 1]]
|
||||
clojure.test.check.properties/for-all [[:block 1]]
|
||||
clojure.test.check.properties/for-all* [[:block 1]]
|
||||
clojure.test.check/quick-check [[:block 1]]
|
||||
clojure.tools.macro/macrolet [[:block 1] [:inner 2 0]]
|
||||
clojurewerkz.quartzite.jobs/build [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-description [[:default]]
|
||||
clojurewerkz.quartzite.jobs/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/schedule [[:default]]
|
||||
clojurewerkz.quartzite.schedule.cron/with-misfire-handling-instruction-do-nothing [[:default]]
|
||||
clojurewerkz.quartzite.triggers/build [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-description [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-identity [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-priority [[:default]]
|
||||
clojurewerkz.quartzite.triggers/with-schedule [[:default]]
|
||||
def-map-type [[:block 2] [:inner 1]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
defrecord+ [[:block 2] [:inner 1]]
|
||||
deftype+ [[:block 2] [:inner 1]]
|
||||
net.cgrand.macrovich/case [[:block 0]]
|
||||
potemkin.namespaces/import-vars [[:default]]
|
||||
potemkin.types/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin.types/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin.types/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin.types/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/def-map-type [[:block 2] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defrecord+ [[:block 2] [:inner 1]]
|
||||
potemkin/deftype+ [[:block 2] [:inner 1]]
|
||||
potemkin/import-vars [[:default]]
|
||||
taoensso.nippy/extend-freeze [[:inner 0]]
|
||||
taoensso.nippy/extend-thaw [[:inner 0]]
|
||||
throttle.core/with-throttling [[:block 1]]
|
||||
;; the rest of the stuff in this list is generated automatically based on `:style/indent` specs
|
||||
;; by running [[dev.generate-cljfmt-config/specs]]... use that code to update this list
|
||||
metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! [[:inner 0]]
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models [[:block 1]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.sandbox.test-util/with-user-attributes [[:block 2]]
|
||||
metabase-enterprise.serialization.test-util/with-dbs [[:inner 0]]
|
||||
metabase-enterprise.serialization.test-util/with-random-dump-dir [[:block 1]]
|
||||
metabase-enterprise.serialization.test-util/with-world [[:block 0]]
|
||||
metabase-enterprise.sso.integrations.jwt-test/with-users-with-email-deleted [[:block 1]]
|
||||
metabase-enterprise.test/with-gtaps! [[:inner 0]]
|
||||
metabase-enterprise.test/with-gtaps-for-user! [[:inner 0]]
|
||||
metabase-enterprise.test/with-user-attributes [[:block 2]]
|
||||
metabase.actions-test/with-actions-test-data-and-actions-permissively-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions [[:block 1]]
|
||||
metabase.actions.test-util/with-actions-disabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data [[:inner 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.actions.test-util/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.actions.test-util/with-empty-db [[:inner 0]]
|
||||
metabase.actions.test-util/with-temp-test-data [[:inner 0]]
|
||||
metabase.api.alert-test/with-alert-in-collection [[:block 1]]
|
||||
metabase.api.card-test/with-cards-in-readable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-cards-in-writeable-collection [[:block 1]]
|
||||
metabase.api.card-test/with-ordered-items [[:inner 0]]
|
||||
metabase.api.card-test/with-temp-native-card! [[:block 1]]
|
||||
metabase.api.card-test/with-temp-native-card-with-params! [[:block 1]]
|
||||
metabase.api.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.api.collection-test/with-french-user-and-personal-collection [[:block 2]]
|
||||
metabase.api.collection-test/with-some-children-of-collection [[:block 1]]
|
||||
metabase.api.common/check [[:default]]
|
||||
metabase.api.common/define-routes [[:block 0]]
|
||||
metabase.api.common/let-404 [[:block 1]]
|
||||
metabase.api.common.internal/auto-coerce [[:block 1]]
|
||||
metabase.api.common.internal/auto-parse [[:block 1]]
|
||||
metabase.api.common.internal-test/no-route-regexes [[:block 0]]
|
||||
metabase.api.embed-test/do-response-formats [[:block 1]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.api.embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.api.embed-test/with-temp-card [[:block 1]]
|
||||
metabase.api.embed-test/with-temp-dashcard [[:block 1]]
|
||||
metabase.api.preview-embed-test/with-embedding-enabled-and-new-secret-key [[:block 0]]
|
||||
metabase.api.preview-embed-test/with-new-secret-key [[:block 0]]
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing [[:block 3]]
|
||||
metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing [[:block 3]]
|
||||
metabase.api.public-test/with-temp-public-card [[:block 1]]
|
||||
metabase.api.public-test/with-temp-public-dashboard [[:block 1]]
|
||||
metabase.api.public-test/with-temp-public-dashboard-and-card [[:block 1]]
|
||||
metabase.api.search-test/do-test-users [[:block 1]]
|
||||
metabase.api.session/http-401-on-error [[:block 0]]
|
||||
metabase.async.streaming-response/streaming-response [[:block 2]]
|
||||
metabase.async.streaming-response-test/with-streaming-response-thread-pool [[:block 0]]
|
||||
metabase.async.streaming-response-test/with-test-driver-db! [[:block 0]]
|
||||
metabase.async.util/cancelable-thread [[:block 0]]
|
||||
metabase.cmd.copy/step [[:block 1]]
|
||||
metabase.cmd.copy/with-connection-rollback-only [[:block 1]]
|
||||
metabase.cmd.copy/with-disabled-db-constraints [[:block 2]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard [[:block 2]]
|
||||
metabase.db.liquibase/with-liquibase [[:block 1]]
|
||||
metabase.db.liquibase/with-scope-locked [[:block 1]]
|
||||
metabase.db.schema-migrations-test.impl/test-migrations [[:block 2]]
|
||||
metabase.db/with-application-db [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
metabase.driver.druid.client/POST [[:default]]
|
||||
metabase.driver.druid.query-processor-test/druid-query [[:block 0]]
|
||||
metabase.driver.druid.query-processor-test/druid-query-returning-rows [[:block 0]]
|
||||
metabase.driver.mongo.query-processor/mongo-let [[:block 1]]
|
||||
metabase.driver.sql-jdbc.actions/with-jdbc-transaction [[:block 1]]
|
||||
metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection [[:block 1]]
|
||||
metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info [[:block 1]]
|
||||
metabase.driver.sql.query-processor/apply-top-level-clause [[:default]]
|
||||
metabase.driver/with-driver [[:block 1]]
|
||||
metabase.lib.binning/with-binning [[:default]]
|
||||
metabase.lib.core/with-binning [[:default]]
|
||||
metabase.lib.core/with-join-alias [[:default]]
|
||||
metabase.lib.core/with-join-conditions [[:default]]
|
||||
metabase.lib.join/with-join-alias [[:default]]
|
||||
metabase.lib.join/with-join-conditions [[:default]]
|
||||
metabase.lib.test-util.macros/$ids [[:inner 0]]
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.collection-test/with-collection-hierarchy [[:block 1]]
|
||||
metabase.models.collection-test/with-current-user-perms-for-collections [[:block 1]]
|
||||
metabase.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
metabase.models.interface/define-simple-hydration-method [[:inner 0]]
|
||||
metabase.models.params.chain-filter-test/with-clean-field-values-for-field [[:block 1]]
|
||||
metabase.models.params.chain-filter-test/with-fk-field-to-field-remapping [[:block 0]]
|
||||
metabase.models.params.chain-filter-test/with-human-readable-values-remapping [[:block 0]]
|
||||
metabase.models.pulse-test/with-dashboard-subscription-in-collection [[:block 1]]
|
||||
metabase.models.pulse-test/with-pulse-in-collection [[:inner 0]]
|
||||
metabase.models.setting/defsetting [[:block 1]]
|
||||
metabase.models.setting.multi-setting/define-multi-setting [[:inner 0]]
|
||||
metabase.models.task-history/with-task-history [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query [[:block 1]]
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query [[:block 1]]
|
||||
metabase.query-processor.debug/debug> [[:block 0]]
|
||||
metabase.query-processor.error-type/deferror [[:block 1]]
|
||||
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
|
||||
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
|
||||
metabase.query-processor.store/cached [[:block 1]]
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.sample-data-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
metabase.sync.fetch-metadata/log-if-error [[:block 1]]
|
||||
metabase.sync.sync-metadata.fields-test/with-test-db [[:block 0]]
|
||||
metabase.sync.util/do-with-start-and-finish-debug-logging [[:default]]
|
||||
metabase.sync.util/sum-for [[:block 1]]
|
||||
metabase.sync.util/sync-operation [[:block 3]]
|
||||
metabase.sync.util/with-db-logging-disabled [[:default]]
|
||||
metabase.sync.util/with-duplicate-ops-prevented [[:default]]
|
||||
metabase.sync.util/with-emoji-progress-bar [[:block 1]]
|
||||
metabase.sync.util/with-error-handling [[:block 1]]
|
||||
metabase.sync.util/with-start-and-finish-logging [[:default]]
|
||||
metabase.sync.util/with-start-and-finish-logging* [[:default]]
|
||||
metabase.sync.util/with-sync-events [[:default]]
|
||||
metabase.test/$ids [[:inner 0]]
|
||||
metabase.test/as-admin [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/discard-setting-changes [[:block 1]]
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/native-query [[:block 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.test/throw-if-called! [[:block 1]]
|
||||
metabase.test/with-actions [[:block 1]]
|
||||
metabase.test/with-actions-disabled [[:block 0]]
|
||||
metabase.test/with-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data [[:inner 0]]
|
||||
metabase.test/with-actions-test-data-and-actions-enabled [[:block 0]]
|
||||
metabase.test/with-actions-test-data-tables [[:block 1]]
|
||||
metabase.test/with-additional-premium-features [[:block 1]]
|
||||
metabase.test/with-current-user [[:inner 0]]
|
||||
metabase.test/with-driver [[:block 1]]
|
||||
metabase.test/with-empty-db [[:inner 0]]
|
||||
metabase.test/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test/with-metadata-provider [[:inner 0]]
|
||||
metabase.test/with-premium-features [[:block 1]]
|
||||
metabase.test/with-temp [[:inner 0]]
|
||||
metabase.test/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test/with-temp-scheduler [[:block 0]]
|
||||
metabase.test/with-temp-test-data [[:inner 0]]
|
||||
metabase.test/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test/with-test-user [[:inner 0]]
|
||||
metabase.test/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/native-query [[:block 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db [[:block 0]]
|
||||
metabase.test.data/with-temp-copy-of-db [[:block 0]]
|
||||
metabase.test.data.datasets/test-driver [[:inner 0]]
|
||||
metabase.test.data.datasets/test-drivers [[:inner 0]]
|
||||
metabase.test.data.one-off-dbs/with-blank-db [[:block 0]]
|
||||
metabase.test.data.one-off-dbs/with-blueberries-db [[:block 0]]
|
||||
metabase.test.data.users/with-test-user [[:inner 0]]
|
||||
metabase.test.util/discard-setting-changes [[:block 1]]
|
||||
metabase.test.util/let-url [[:block 1]]
|
||||
metabase.test.util/throw-if-called! [[:block 1]]
|
||||
metabase.test.util/with-temp-scheduler [[:block 0]]
|
||||
metabase.test.util/with-temp-vals-in-db [[:block 3]]
|
||||
metabase.test.util/with-user-in-groups [[:inner 0]]
|
||||
metabase.test.util.public-settings/with-additional-premium-features [[:block 1]]
|
||||
metabase.test.util.public-settings/with-premium-features [[:block 1]]
|
||||
metabase.test.util.thread-local/test-helpers-set-global-values! [[:block 0]]
|
||||
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
|
||||
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
|
||||
metabase.upload-test/with-upload-table! [[:inner 0]]
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/varargs [[:inner 0]]
|
||||
metabase.util/with-us-locale [[:block 0]]
|
||||
metabase.util.date-2/with-time-zone-same-instant [[:default]]
|
||||
metabase.util.encryption-test/with-secret-key [[:block 1]]
|
||||
metabase.util.honey-sql-2/with-database-type-info [[:default]]
|
||||
metabase.util.i18n/deferred-trs [[:default]]
|
||||
metabase.util.i18n/deferred-tru [[:default]]
|
||||
metabase.util.i18n/trs [[:default]]
|
||||
metabase.util.i18n/trsn [[:default]]
|
||||
metabase.util.i18n/tru [[:default]]
|
||||
metabase.util.i18n/trun [[:default]]
|
||||
metabase.util.jvm/auto-retry [[:block 1]]
|
||||
metabase.util.jvm/varargs [[:block 1]]
|
||||
metabase.util.jvm/with-us-locale [[:block 0]]
|
||||
metabase.util.malli/disable-enforcement [[:block 0]]
|
||||
metabase.util.malli/with [[:default]]
|
||||
metabase.util.malli/with-api-error-message [[:default]]
|
||||
metabase.util.namespaces/import-fns [[:default]]
|
||||
metabuild-common.core/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.core/step [[:block 1]]
|
||||
metabuild-common.entrypoint/exit-when-finished-nonzero-on-exception [[:block 0]]
|
||||
metabuild-common.steps/step [[:block 1]]
|
||||
methodical.core/defmethod [[:inner 0]]
|
||||
methodical.core/defmulti [[:inner 0]]
|
||||
methodical.core/with-dispatcher [[:default]]
|
||||
methodical.core/with-method-table [[:default]]
|
||||
methodical.core/with-prefers [[:default]]
|
||||
methodical.interface/with-dispatcher [[:default]]
|
||||
methodical.interface/with-method-table [[:default]]
|
||||
methodical.interface/with-prefers [[:default]]
|
||||
methodical.macros/defmethod [[:inner 0]]
|
||||
methodical.macros/defmulti [[:inner 0]]
|
||||
saml20-clj.sp.response/validate-confirmation-datas [[:block 1]]
|
||||
toucan2.connection/with-transaction [[:block 1]]
|
||||
toucan2.core/build [[:block 0]]
|
||||
toucan2.core/compile [[:block 0]]
|
||||
toucan2.core/debug [[:block 0]]
|
||||
toucan2.core/define-after-insert [[:inner 0]]
|
||||
toucan2.core/define-after-select [[:inner 0]]
|
||||
toucan2.core/define-after-update [[:inner 0]]
|
||||
toucan2.core/define-before-delete [[:inner 0]]
|
||||
toucan2.core/define-before-insert [[:inner 0]]
|
||||
toucan2.core/define-before-select [[:inner 0]]
|
||||
toucan2.core/define-default-fields [[:inner 0]]
|
||||
toucan2.core/define-named-query [[:block 1]]
|
||||
toucan2.core/deftransforms [[:block 1]]
|
||||
toucan2.core/with-transaction [[:block 1]]
|
||||
toucan2.tools.after/define-after-impl [[:default]]
|
||||
toucan2.tools.after-insert/define-after-insert [[:inner 0]]
|
||||
toucan2.tools.after-select/define-after-select [[:inner 0]]
|
||||
toucan2.tools.after-update/define-after-update [[:inner 0]]
|
||||
toucan2.tools.before-delete/define-before-delete [[:inner 0]]
|
||||
toucan2.tools.before-insert/define-before-insert [[:inner 0]]
|
||||
toucan2.tools.before-select/define-before-select [[:inner 0]]
|
||||
toucan2.tools.compile/build [[:block 0]]
|
||||
toucan2.tools.compile/compile [[:block 0]]
|
||||
toucan2.tools.compile/resolved [[:block 0]]
|
||||
toucan2.tools.debug/debug [[:block 0]]
|
||||
toucan2.tools.default-fields/define-default-fields [[:inner 0]]
|
||||
toucan2.tools.named-query/define-named-query [[:block 1]]
|
||||
toucan2.tools.simple-out-transform/define-out-transform [[:inner 0]]
|
||||
toucan2.tools.transformed/deftransforms [[:block 1]]
|
||||
toucan2.tools.with-temp/with-temp [[:inner 0]]
|
||||
toucan2.util/try-with-error-context [[:inner 0]]}}
|
||||
;; https://github.com/weavejester/cljfmt/blob/master/cljfmt/resources/cljfmt/indents/clojure.clj
|
||||
{:extra-indents
|
||||
{auto-parse [[:inner 0]]
|
||||
cond-> [[:inner 0]]
|
||||
check [[:inner 0]]
|
||||
deferred-tru [[:inner 0]]
|
||||
defprotocol+ [[:block 1] [:inner 1]]
|
||||
potemkin/defprotocol+ [[:block 1] [:inner 1]]
|
||||
let-404 [[:block 1]]
|
||||
metabase.legacy-mbql.util/match-one [[:inner 0]]
|
||||
merge-with [[:inner 0]]
|
||||
metabase.test/dataset [[:inner 0]]
|
||||
metabase.test/formatted-rows [[:inner 0]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/test-drivers [[:inner 0]]
|
||||
metabase.test/with-temporary-setting-values [[:block 1]]
|
||||
;; If we just treat these as functions we can ignore this, despite some files not being consistent
|
||||
;metabase.models.permissions/set-has-full-permissions-for-set? [[:inner 0]]
|
||||
;metabase.models.permissions/set-has-full-permissions? [[:inner 0]]
|
||||
;metabase.models.permissions/set-has-partial-permissions? [[:inner 0]]
|
||||
metabase.models.permissions/set-has-partial-permissions-for-set? [[:inner 0]]
|
||||
publish-event! [[:block 1]]
|
||||
metabase.query-processor.streaming/streaming-response [[:inner 0]]
|
||||
metabase.util/prog1 [[:inner 0]]
|
||||
metabase.util/select-keys-when [[:inner 0]]
|
||||
;; This supports a lot of existing formatting, but these are functions so the
|
||||
;; existing formatting is probably wrong.
|
||||
;toucan2.core/select [[:inner 0]]
|
||||
;toucan2.core/update! [[:inner 0]]
|
||||
}
|
||||
:sort-ns-references? true}
|
||||
|
||||
@@ -20,67 +20,67 @@
|
||||
(clojure-mode
|
||||
;; Specify which arg is the docstring for certain macros
|
||||
;; (Add more as needed)
|
||||
(eval . (put 'defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'define-premium-feature 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'api/defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'setting/defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 's/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'p.types/defprotocol+ 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'methodical/defmethod 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'methodical/defmulti 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mi/define-simple-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'define-premium-feature 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'api/defendpoint-schema 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'api/defendpoint-async 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'setting/defsetting 'clojure-doc-string-elt 2))
|
||||
(eval . (put 's/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'p.types/defprotocol+ 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'methodical/defmethod 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'methodical/defmulti 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mi/define-simple-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mi/define-batched-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mr/def 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mu/defn 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mr/def 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mu/defn 'clojure-doc-string-elt 2))
|
||||
;; Define custom indentation for functions inside metabase.
|
||||
;; This list isn't complete; add more forms as we come across them.
|
||||
;;
|
||||
;; `put-clojure-indent' is a safe-local-eval-function, so use a bunch of calls to that
|
||||
;; instead of one call to `define-clojure-indent'
|
||||
(eval . (put-clojure-indent 'api/let-404 1))
|
||||
(eval . (put-clojure-indent 'c/step 1))
|
||||
(eval . (put-clojure-indent 'impl/test-migrations 2))
|
||||
(eval . (put-clojure-indent 'let-404 0))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match-one '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace-in '(:defn)))
|
||||
(eval . (put-clojure-indent 'lib.util.match/replace '(:defn)))
|
||||
(eval . (put-clojure-indent 'macros/case 0))
|
||||
(eval . (put-clojure-indent 'match 1))
|
||||
(eval . (put-clojure-indent 'mt/dataset 1))
|
||||
(eval . (put-clojure-indent 'mt/format-rows-by '(:form)))
|
||||
(eval . (put-clojure-indent 'mt/query 1))
|
||||
(eval . (put-clojure-indent 'mt/test-driver 1))
|
||||
(eval . (put-clojure-indent 'mt/test-drivers 1))
|
||||
(eval . (put-clojure-indent 'mt/with-anaphora 1))
|
||||
(eval . (put-clojure-indent 'prop/for-all 1))
|
||||
(eval . (put-clojure-indent 'api/let-404 1))
|
||||
(eval . (put-clojure-indent 'c/step 1))
|
||||
(eval . (put-clojure-indent 'impl/test-migrations 2))
|
||||
(eval . (put-clojure-indent 'let-404 0))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/match-one 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace 1))
|
||||
(eval . (put-clojure-indent 'lib.schema.match/replace-in 2))
|
||||
(eval . (put-clojure-indent 'lib.util.match/replace 1))
|
||||
(eval . (put-clojure-indent 'macros/case 0))
|
||||
(eval . (put-clojure-indent 'match 1))
|
||||
(eval . (put-clojure-indent 'mi/define-methods '(:form)))
|
||||
(eval . (put-clojure-indent 'mt/dataset 1))
|
||||
(eval . (put-clojure-indent 'mt/format-rows-by 1))
|
||||
(eval . (put-clojure-indent 'mt/query 1))
|
||||
(eval . (put-clojure-indent 'mt/test-driver 1))
|
||||
(eval . (put-clojure-indent 'mt/test-drivers 1))
|
||||
(eval . (put-clojure-indent 'prop/for-all 1))
|
||||
(eval . (put-clojure-indent 'qp.streaming/streaming-response 1))
|
||||
(eval . (put-clojure-indent 'tc/quick-check 1))
|
||||
(eval . (put-clojure-indent 'u/profile 1))
|
||||
(eval . (put-clojure-indent 'u/prog1 1))
|
||||
(eval . (put-clojure-indent 'u/select-keys-when '(:form)))
|
||||
(eval . (put-clojure-indent 'with-meta '(:form)))
|
||||
(eval . (put-clojure-indent 'tc/quick-check 1))
|
||||
(eval . (put-clojure-indent 'u/profile 1))
|
||||
(eval . (put-clojure-indent 'u/prog1 1))
|
||||
(eval . (put-clojure-indent 'u/select-keys-when 1))
|
||||
(eval . (put-clojure-indent 'with-meta '(:form)))
|
||||
;; these ones have to be done with `define-clojure-indent' for now because of upstream bug
|
||||
;; https://github.com/clojure-emacs/clojure-mode/issues/600 once that's resolved we should use `put-clojure-indent'
|
||||
;; instead. Please don't add new entries unless they don't work with `put-clojure-indent'
|
||||
(eval . (define-clojure-indent
|
||||
(l/matcha '(1 (:defn)))
|
||||
(l/matche '(1 (:defn)))
|
||||
(l/matcha '(1 (:defn)))
|
||||
(l/matche '(1 (:defn)))
|
||||
(p.types/def-abstract-type '(1 (:defn)))
|
||||
(p.types/defprotocol+ '(1 (:defn)))
|
||||
(p.types/defrecord+ '(2 nil nil (:defn)))
|
||||
(p.types/deftype+ '(2 nil nil (:defn)))
|
||||
(p/def-map-type '(2 nil nil (:defn)))
|
||||
(p/defprotocol+ '(1 (:defn)))
|
||||
(p/defrecord+ '(2 nil nil (:defn)))
|
||||
(p/deftype+ '(2 nil nil (:defn)))
|
||||
(tools.macro/macrolet '(1 ((:defn)) :form))))
|
||||
(p.types/defprotocol+ '(1 (:defn)))
|
||||
(p.types/defrecord+ '(2 nil nil (:defn)))
|
||||
(p.types/deftype+ '(2 nil nil (:defn)))
|
||||
(p/def-map-type '(2 nil nil (:defn)))
|
||||
(p/defprotocol+ '(1 (:defn)))
|
||||
(p/defrecord+ '(2 nil nil (:defn)))
|
||||
(p/deftype+ '(2 nil nil (:defn)))
|
||||
(tools.macro/macrolet '(1 ((:defn)) :form))))
|
||||
(cider-clojure-cli-aliases . "dev:drivers:drivers-dev:ee:ee-dev:user")
|
||||
(clojure-indent-style . always-align)
|
||||
(cljr-favor-prefix-notation . nil)
|
||||
|
||||
70
.eslintrc.js
70
.eslintrc.js
@@ -62,13 +62,6 @@ module.exports = {
|
||||
warnOnUnassignedImports: false,
|
||||
},
|
||||
],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
// allows this rule to work with import/order
|
||||
ignoreDeclarationSort: true,
|
||||
},
|
||||
],
|
||||
"no-console": [2, { allow: ["warn", "error", "errorBuffer"] }],
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
@@ -85,10 +78,9 @@ module.exports = {
|
||||
"react/forbid-component-props": [2, { forbid: ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ additionalHooks: "(useSafeAsyncFunction)" },
|
||||
{ additionalHooks: "(useSyncedQueryString|useSafeAsyncFunction)" },
|
||||
],
|
||||
"prefer-const": [1, { destructuring: "all" }],
|
||||
"no-restricted-globals": ["error", "close"],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": [
|
||||
"error",
|
||||
@@ -141,8 +133,6 @@ module.exports = {
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:depend/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
settings: {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
@@ -170,17 +160,6 @@ module.exports = {
|
||||
rules: {
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
"depend/ban-dependencies": [
|
||||
"error",
|
||||
{
|
||||
allowed: [
|
||||
"underscore",
|
||||
"moment",
|
||||
"lodash.orderby",
|
||||
"lodash.debounce",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -210,13 +189,7 @@ module.exports = {
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
fixStyle: "inline-type-imports",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
@@ -235,9 +208,8 @@ module.exports = {
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react",
|
||||
"plugin:jest-formatting/recommended",
|
||||
],
|
||||
plugins: ["jest", "jest-dom", "testing-library", "jest-formatting"],
|
||||
plugins: ["jest", "jest-dom", "testing-library"],
|
||||
files: [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
@@ -248,41 +220,5 @@ module.exports = {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Enable jest formatting for cypress tests too, the plugin logic just works
|
||||
extends: ["plugin:jest-formatting/recommended"],
|
||||
files: ["*.cy.spec.ts", "*.cy.spec.js"],
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["frontend/src/metabase/**/*"],
|
||||
rules: {
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "Literal[value=/mb-base-color-/]",
|
||||
message:
|
||||
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.property.name='legacyQuery'] > ObjectExpression > Property[key.name='useStructuredQuery'][value.value=true]",
|
||||
message: "StructuredQuery usage is forbidden. Use MLv2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -1,12 +1,14 @@
|
||||
# Before changing this file, make sure you have read how we use it:
|
||||
# https://www.notion.so/metabase/Pull-Request-Code-Review-Guide-374f05df889748b69d67af753f9bc9b8?pvs=4#36550c04ceab40b5a8b8bee6264b090c
|
||||
# TL;DR: if @camsaul hasn't been involved you're probably doing it wrong
|
||||
|
||||
src/metabase @camsaul
|
||||
modules @camsaul
|
||||
enterprise/backend @camsaul
|
||||
enterprise/backend/src/metabase_enterprise/advanced_permissions @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/audit_app @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/sso @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/task/truncate_audit_table.clj @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/internal_user.clj @noahmoss
|
||||
frontend/src/metabase/ui @metabase/core-frontend-admin-webapp
|
||||
src/metabase/**/*permissions* @noahmoss
|
||||
src/metabase/integrations @noahmoss
|
||||
snowplow/* @metabase/data
|
||||
|
||||
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -66,13 +66,12 @@ body:
|
||||
Copy and paste the JSON from `Admin -> Troubleshooting`.
|
||||
_This will be automatically formatted into code, so no need for backticks._
|
||||
placeholder: |
|
||||
{"browser-info": {
|
||||
"language": "en-US",
|
||||
"platform": "MacIntel",
|
||||
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"vendor": "Google Inc."},
|
||||
"metabase-info": {
|
||||
"databases": [ "postgres",...
|
||||
- Your browser and the version: (e.g., Chrome 52.1, Firefox 48.0, Safari 11.1, …)
|
||||
- Your operating system: (e.g., OS X 10.10, Windows 10.1809, Ubuntu 16.04, …)
|
||||
- Your databases: (e.g, MySQL, Postgres, MongoDB, …)
|
||||
- Metabase version: (e.g., 0.19.3)
|
||||
- Metabase hosting environment: (e.g., Elastic Beanstalk, Docker, Heroku, Jar-file on Ubuntu, …)
|
||||
- Metabase internal database: (e.g., H2 (default), Postgres or MySQL)
|
||||
render: JSON
|
||||
validations:
|
||||
required: true
|
||||
|
||||
23
.github/ISSUE_TEMPLATE/epic.md
vendored
23
.github/ISSUE_TEMPLATE/epic.md
vendored
@@ -5,19 +5,20 @@ about: This issue is used to track a feature/project that may span several days
|
||||
and manage work.
|
||||
title: "[Epic] Title"
|
||||
labels: ".Epic"
|
||||
|
||||
---
|
||||
|
||||
### Links
|
||||
**Links**
|
||||
- product doc: _link to product doc_
|
||||
- eng doc: _link to technical design doc, if any_
|
||||
- feature branch: `branch-name` _this should be the feature branch where this work will be done in. PRs will be delivered against this branch_
|
||||
- issue links: _related issues if any_
|
||||
|
||||
- [product doc](LINK_TO_PRODUCT_DOC)
|
||||
- [technical design doc](LINK_TO_TECHNICAL_DESIGN_DOC)
|
||||
- [testing plan](LINK_TO_TESTING_PLAN)
|
||||
- feature branch: `BRANCH_NAME`
|
||||
- issue links: _related issues, if any_
|
||||
**Implementation Plan**
|
||||
|
||||
### Implementation Plan
|
||||
|
||||
```[tasklist]
|
||||
#### Milestone 1
|
||||
- [ ] _Add tasks here_
|
||||
```
|
||||
***Milestone 1***
|
||||
_insert tasklist here_
|
||||
|
||||
***Milestone 2***
|
||||
|
||||
|
||||
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,9 @@
|
||||
> [!IMPORTANT]
|
||||
> For those employed by Metabase: if you are merging into master, please add either a `backport` or a `no-backport` label to this PR. You will not be able to merge until you do this step. Refer to the section [Do I need to backport this PR?](https://www.notion.so/metabase/Metabase-Branching-Strategy-6eb577d5f61142aa960a626d6bbdfeb3?pvs=4#89f80d6f17714a0198aeb66c0efd1b71) in the Metabase Branching Strategy document for more details. If you're not employed by Metabase, this section does not apply to you, and the label will be taken care of by your reviewer.
|
||||
> For those employed by Metabase: if you are merging into master, please add either a `backport` or a `no-backport` label
|
||||
> to this PR. You will not be able to merge until you do this step. Refer to the section [Do I need to backport this
|
||||
> PR?](https://www.notion.so/metabase/Metabase-Branching-Strategy-6eb577d5f61142aa960a626d6bbdfeb3?pvs=4#89f80d6f17714a0198aeb66c0efd1b71)
|
||||
> in the Metabase Branching Strategy document for more details. If you're not employed by Metabase, this section does not
|
||||
> apply to you, and the label will be taken care of by your reviewer.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
|
||||
15
.github/actions/await-port/action.yml
vendored
15
.github/actions/await-port/action.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Wait for localhost port to be ready
|
||||
|
||||
inputs:
|
||||
port:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Wait for port ${{ inputs.port }} to be ready
|
||||
shell: bash
|
||||
run: |
|
||||
while ! nc -z localhost ${{ inputs.port }}; do
|
||||
sleep 0.1;
|
||||
done
|
||||
9
.github/actions/build-e2e-matrix/action.yml
vendored
9
.github/actions/build-e2e-matrix/action.yml
vendored
@@ -13,9 +13,10 @@ runs:
|
||||
id: matrix
|
||||
with:
|
||||
script: | # js
|
||||
const java = 21;
|
||||
const java = 11;
|
||||
|
||||
const defaultRunner = "ubuntu-22.04";
|
||||
const beefierRunner = "buildjet-2vcpu-ubuntu-2204";
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
@@ -34,13 +35,9 @@ runs:
|
||||
["dashboard", {} ],
|
||||
["dashboard-cards", {} ],
|
||||
["dashboard-filters", {} ],
|
||||
["dashboard-filters-2", {} ],
|
||||
["embedding", {} ],
|
||||
["embedding-sdk", { context: "embedding-sdk" } ],
|
||||
["filters", {} ],
|
||||
["filters-reproductions", {}],
|
||||
["joins", {} ],
|
||||
["metrics", {} ],
|
||||
["models", {} ],
|
||||
["native", {} ],
|
||||
["native-filters", {} ],
|
||||
@@ -48,8 +45,6 @@ runs:
|
||||
["organization", {} ],
|
||||
["permissions", {} ],
|
||||
["question", {} ],
|
||||
["question-reproductions", {} ],
|
||||
["search", {}],
|
||||
["sharing", {} ],
|
||||
["visualizations-charts", {} ],
|
||||
["visualizations-tabular", {} ],
|
||||
|
||||
132
.github/actions/build-ee-extra/action.yml
vendored
132
.github/actions/build-ee-extra/action.yml
vendored
@@ -1,132 +0,0 @@
|
||||
name: Build ee-extra
|
||||
description: Build Metabase Enterprise Edition with extra features
|
||||
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
# build with Java 11 since that's still officially what we target as a minimum and we don't want Java >11 bytecode
|
||||
# sneaking in.
|
||||
default: 11
|
||||
iam-role:
|
||||
description: "The IAM role to assume"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'temurin'
|
||||
- name: Reveal its original version.properties
|
||||
run: jar xf ./bin/docker/metabase.jar version.properties && cat version.properties
|
||||
shell: bash
|
||||
- name: get major version
|
||||
id: major_version
|
||||
run: echo "major_version=$(cat version.properties | grep 'tag=' | cut -d'.' -f2)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: get drivers registry
|
||||
run: |
|
||||
curl -OL https://raw.githubusercontent.com/metabase/metabase-registry/main/registry.yaml
|
||||
shell: bash
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/Hydrogen # 18.x.x
|
||||
- run: rm package.json && npm install yaml
|
||||
shell: bash
|
||||
- name: parse drivers registry
|
||||
uses: actions/github-script@v7
|
||||
id: drivers_registry
|
||||
with:
|
||||
script: | # js
|
||||
const { parse } = require('yaml');
|
||||
const fs = require('fs');
|
||||
const registry = parse(fs.readFileSync('registry.yaml', 'utf8'));
|
||||
|
||||
const drivers = registry.drivers;
|
||||
const majorVersion = '${{ steps.major_version.outputs.major_version }}';
|
||||
|
||||
const getDriverUrl = (driverName) => {
|
||||
const driver = drivers.find(driver => driver.name === driverName);
|
||||
|
||||
if (!driver) {
|
||||
throw new Error(`Driver ${driverName} not found in the registry`);
|
||||
}
|
||||
|
||||
const driverUrl = driver.versions[majorVersion] ?? driver.versions.default;
|
||||
|
||||
if (!driverUrl) {
|
||||
throw new Error(`Driver ${driverName} version ${majorVersion} not found in the registry`);
|
||||
}
|
||||
|
||||
return driverUrl;
|
||||
};
|
||||
|
||||
const extraDrivers = {
|
||||
firebolt: getDriverUrl('firebolt'),
|
||||
starburst: getDriverUrl('starburst'),
|
||||
exasol: getDriverUrl('exasol'),
|
||||
exasol_jdbc: drivers.find(d => d.name === 'exasol').deps[0],
|
||||
clickhouse: getDriverUrl('clickhouse'),
|
||||
ocient: getDriverUrl('ocient'),
|
||||
materialize: getDriverUrl('materialize'),
|
||||
};
|
||||
|
||||
console.log(extraDrivers);
|
||||
|
||||
return extraDrivers;
|
||||
- name: Download Firebolt driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).firebolt }}
|
||||
working-directory: modules
|
||||
shell: bash
|
||||
- name: Download Starburst driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).starburst }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Exasol driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).exasol }}
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).exasol_jdbc }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Clickhouse driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).clickhouse }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Ocient driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).ocient }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Download Materialize driver
|
||||
run: |
|
||||
curl -OL ${{ fromJson(steps.drivers_registry.outputs.result).materialize }}
|
||||
shell: bash
|
||||
working-directory: modules
|
||||
- name: Bundle additional drivers into the Uberjar
|
||||
run: jar uf bin/docker/metabase.jar modules/*.jar
|
||||
shell: bash
|
||||
- name: Launch uberjar
|
||||
run: >-
|
||||
java --add-opens java.base/java.nio=ALL-UNNAMED -jar ./bin/docker/metabase.jar &
|
||||
shell: bash
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
shell: bash
|
||||
- name: Kill the java process
|
||||
run: pkill -f metabase.jar
|
||||
shell: bash
|
||||
- name: configure aws credentials
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ inputs.iam-role }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Login to Amazon ECR
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Cross-branch migration test
|
||||
|
||||
inputs:
|
||||
base-ref:
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref }}
|
||||
|
||||
- name: Prepare backend environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: cross-branch-migrations
|
||||
|
||||
- name: Run backend on base branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Stop Metabase on base branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
- name: Check out pull request branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Run backend on pull request branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited. Check that your migration changes work on an instance that has been previously initialized on your PR's target branch."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: Stop Metabase on pull request branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
@@ -10,27 +10,24 @@ inputs:
|
||||
maildev:
|
||||
description: Maildev container
|
||||
required: true
|
||||
default: false
|
||||
default: 'false'
|
||||
openldap:
|
||||
description: OpenLDAP container
|
||||
required: true
|
||||
default: false
|
||||
default: 'false'
|
||||
postgres:
|
||||
description: Postgres container
|
||||
required: true
|
||||
default: false
|
||||
default: 'false'
|
||||
mysql:
|
||||
description: MySQL container
|
||||
required: true
|
||||
default: false
|
||||
default: 'false'
|
||||
mongo:
|
||||
description: Mongo container
|
||||
required: true
|
||||
default: false
|
||||
webhook:
|
||||
description: Webhook tester container
|
||||
required: false
|
||||
default: false
|
||||
default: 'false'
|
||||
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -87,11 +84,4 @@ runs:
|
||||
while ! nc -z localhost 27004; do sleep 1; done &&
|
||||
echo -e "${G}Mongo is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.webhook }}; then
|
||||
echo -e "${Y}Starting webhook test container..." &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
while ! nc -z localhost 9080; do sleep 1; done &&
|
||||
echo -e "${G}Webhook tester is up and running!"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
2
.github/actions/prepare-backend/action.yml
vendored
2
.github/actions/prepare-backend/action.yml
vendored
@@ -2,7 +2,7 @@ name: Prepare back-end environment
|
||||
inputs:
|
||||
java-version:
|
||||
required: true
|
||||
default: '21'
|
||||
default: '11'
|
||||
clojure-version:
|
||||
required: true
|
||||
default: '1.11.1.1262'
|
||||
|
||||
14
.github/actions/prepare-cypress/action.yml
vendored
14
.github/actions/prepare-cypress/action.yml
vendored
@@ -1,20 +1,12 @@
|
||||
name: Prepare cypress environment
|
||||
description: Cypress preparation steps
|
||||
|
||||
outputs:
|
||||
chrome-path:
|
||||
description: Path to the custom Chrome executable
|
||||
value: ${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Chrome v111
|
||||
uses: browser-actions/setup-chrome@v1
|
||||
with:
|
||||
# https://chromium.cypress.io/linux/stable/111.0.5563.146
|
||||
chrome-version: 1097615
|
||||
id: setup-chrome
|
||||
- name: Install Replay.io browser
|
||||
run: npx @replayio/cypress install
|
||||
shell: bash
|
||||
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
|
||||
24
.github/actions/prepare-frontend/action.yml
vendored
24
.github/actions/prepare-frontend/action.yml
vendored
@@ -1,17 +1,11 @@
|
||||
name: Prepare front-end environment
|
||||
inputs:
|
||||
node-version:
|
||||
description: "The version of Node.js to use, overriding .nvmrc"
|
||||
required: false
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }} # this overrides, if present
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Check to see if dependencies should be cached
|
||||
if: ${{ contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
run: |
|
||||
@@ -24,27 +18,11 @@ runs:
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-cljs-${{ hashFiles('**/shadow-cljs.edn') }}
|
||||
|
||||
- name: Get node_modules cache
|
||||
uses: actions/cache@v4
|
||||
id: node-modules-cache
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock', '**/patches/*.patch') }}
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v4
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-
|
||||
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Run Embedding SDK Storybook
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare frontend environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
|
||||
- name: Compile CLJS
|
||||
run: yarn build-pure:cljs
|
||||
shell: bash
|
||||
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook-embedding-sdk
|
||||
shell: bash
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Storybook to become available
|
||||
run: |
|
||||
echo "Waiting for Storybook to be served..."
|
||||
while ! curl --silent --fail http://localhost:6006; do
|
||||
sleep 5
|
||||
echo "Retrying..."
|
||||
done
|
||||
echo "Storybook is being served!"
|
||||
shell: bash
|
||||
6
.github/actions/test-driver/action.yml
vendored
6
.github/actions/test-driver/action.yml
vendored
@@ -2,7 +2,7 @@ name: Test database driver
|
||||
inputs:
|
||||
junit-name:
|
||||
required: true
|
||||
default: "driver"
|
||||
default: 'driver'
|
||||
test-args:
|
||||
required: false
|
||||
|
||||
@@ -14,7 +14,7 @@ runs:
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: driver
|
||||
m2-cache-key: driver-${{ inputs.junit-name }}
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
@@ -25,7 +25,7 @@ runs:
|
||||
uses: dorny/test-reporter@v1
|
||||
if: failure()
|
||||
with:
|
||||
path: "target/junit/**/*_test.xml"
|
||||
path: 'target/junit/**/*_test.xml'
|
||||
name: JUnit Test Report ${{ inputs.junit-name }}
|
||||
reporter: java-junit
|
||||
list-suites: failed
|
||||
|
||||
17
.github/auto_request_review.yml
vendored
17
.github/auto_request_review.yml
vendored
@@ -1,17 +0,0 @@
|
||||
# The reviewers configuration for the auto_request_review workflow
|
||||
# See https://github.com/necojackarc/auto-request-review/tree/master?tab=readme-ov-file#reviewers-configuration
|
||||
# for the spec
|
||||
|
||||
files:
|
||||
# migrations related files
|
||||
'resources/migrations/**':
|
||||
- team:core-workflows
|
||||
'src/metabase/db/custom_migrations.clj':
|
||||
- team:core-workflows
|
||||
|
||||
options:
|
||||
ignore_draft: true
|
||||
ignored_keywords:
|
||||
- DO NOT REVIEW
|
||||
- WIP
|
||||
enable_group_assignment: false
|
||||
16
.github/file-paths.yaml
vendored
16
.github/file-paths.yaml
vendored
@@ -35,22 +35,14 @@ frontend_specs: &frontend_specs
|
||||
- "frontend/test/**"
|
||||
- "frontend/**/tests/**"
|
||||
- "frontend/**/*.unit.*"
|
||||
- "jest.config.js"
|
||||
- "jest.config.json"
|
||||
- "jest.tz.unit.conf.json"
|
||||
|
||||
release_source: &release_sources
|
||||
- "release/**"
|
||||
|
||||
frontend_all: &frontend_all
|
||||
- *frontend_ci
|
||||
- *frontend_sources
|
||||
- *frontend_specs
|
||||
|
||||
frontend_loki_ci: &frontend_loki_ci
|
||||
- ".github/workflows/loki.yml"
|
||||
- ".loki/**"
|
||||
- ".storybook/**"
|
||||
|
||||
backend_ci: &backend_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
@@ -61,7 +53,6 @@ backend_ci: &backend_ci
|
||||
backend_presto_kerberos:
|
||||
- "**/presto_jdbc/**"
|
||||
- "**/presto_jdbc.clj"
|
||||
- ".github/workflows/presto-kerberos-integration-test.yml"
|
||||
|
||||
backend_sources: &backend_sources
|
||||
- *shared_sources
|
||||
@@ -80,7 +71,6 @@ backend_specs: &backend_specs
|
||||
- "modules/drivers/*/test/**"
|
||||
- "test_config/**"
|
||||
- "dev/**"
|
||||
- "test_resources/**"
|
||||
|
||||
backend_all: &backend_all
|
||||
- *default
|
||||
@@ -110,10 +100,6 @@ e2e_specs: &e2e_specs
|
||||
- "e2e/support/**"
|
||||
- "e2e/snapshot*/**"
|
||||
|
||||
e2e_cross_version: &e2e_cross_version
|
||||
- ".github/workflows/e2e-cross-version.yml"
|
||||
- "e2e/test/scenarios/cross-version/**"
|
||||
|
||||
e2e_all:
|
||||
- *default
|
||||
- *e2e_ci
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
exports.updateComment = updateComment;
|
||||
|
||||
/** @param {import('@types/github-script').AsyncFunctionArguments} AsyncFunctionArguments */
|
||||
async function updateComment({ github, context }) {
|
||||
const githubUsername = context.payload.sender.login;
|
||||
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\`
|
||||
|
||||
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): Add interactive dashboard component\`
|
||||
- \`feat(sdk): Support theming pie chart\`
|
||||
- \`fix(sdk): Fix static dashboard crash\``;
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pullRequestNumber = context.issue.number;
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullRequestNumber,
|
||||
});
|
||||
|
||||
const existingComment = getExistingConventionalCommitReminderComment(
|
||||
comments.data,
|
||||
);
|
||||
|
||||
if (existingComment) {
|
||||
return await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existingComment.id,
|
||||
body: comment,
|
||||
});
|
||||
} else {
|
||||
return await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pullRequestNumber,
|
||||
body: comment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER =
|
||||
"<!---conventional commit reminder comment ID-->";
|
||||
/**
|
||||
* @typedef Comment
|
||||
* @property {string} body
|
||||
*
|
||||
* @param {Comment[]} comments
|
||||
*/
|
||||
function getExistingConventionalCommitReminderComment(comments) {
|
||||
for (const comment of comments) {
|
||||
if (
|
||||
comment.body.startsWith(CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER)
|
||||
) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
.github/scripts/handle-mochawesome-report.js
vendored
110
.github/scripts/handle-mochawesome-report.js
vendored
@@ -1,110 +0,0 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const logger = { debug: console.debug, error: console.error };
|
||||
|
||||
const collectTestsByType = ({ type, suite, path, cache = [] }) => {
|
||||
logger.debug(`Collecting tests of type ${type}`);
|
||||
const localCache = cache;
|
||||
const { [type]: typeList, suites, tests } = suite;
|
||||
|
||||
for (const uuid of typeList) {
|
||||
const foundTestByUuid = tests.find(test => test.uuid === uuid);
|
||||
if (!foundTestByUuid) {
|
||||
logger.error(`Test with uuid ${uuid} not found`);
|
||||
throw new Error(`Test with uuid ${uuid} not found`);
|
||||
}
|
||||
logger.debug(`Found test with uuid ${uuid}`);
|
||||
foundTestByUuid.path = path;
|
||||
localCache.push({ path, ...foundTestByUuid });
|
||||
}
|
||||
|
||||
for (const subSuite of suites) {
|
||||
collectTestsByType({
|
||||
type,
|
||||
suite: subSuite,
|
||||
path: subSuite.file || path,
|
||||
cache: localCache,
|
||||
});
|
||||
}
|
||||
|
||||
return localCache;
|
||||
};
|
||||
|
||||
const extractTestResultsInfo = ({ results, stats }) => {
|
||||
logger.debug("Extracting test results information");
|
||||
|
||||
const testType = "failures";
|
||||
|
||||
const failedTests = results.flatMap(result =>
|
||||
collectTestsByType({
|
||||
type: testType,
|
||||
suite: result,
|
||||
path: result.file,
|
||||
}),
|
||||
);
|
||||
|
||||
logger.debug("Finished extracting test results information");
|
||||
|
||||
return {
|
||||
failedTests,
|
||||
};
|
||||
};
|
||||
|
||||
function parseReport() {
|
||||
const content = fs.readFileSync(
|
||||
"./cypress/reports/cypress-test-report.json",
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const data = JSON.parse(content);
|
||||
|
||||
return extractTestResultsInfo(data).failedTests.map(test => ({
|
||||
title: test.fullTitle,
|
||||
error: test.err?.message,
|
||||
path: test.path,
|
||||
}));
|
||||
}
|
||||
|
||||
exports.parseReport = parseReport;
|
||||
|
||||
const groupTestsByPath = report => {
|
||||
return report.reduce((acc, item) => {
|
||||
if (!acc[item.path]) {
|
||||
acc[item.path] = [];
|
||||
}
|
||||
acc[item.path].push(item);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
function formatSummary(report) {
|
||||
const failedTestsByPath = groupTestsByPath(report);
|
||||
|
||||
let summary = "### Failed Tests Summary\n\n";
|
||||
|
||||
for (const [path, tests] of Object.entries(failedTestsByPath)) {
|
||||
summary += `| ${path} |\n`;
|
||||
summary += "| :--- |\n";
|
||||
|
||||
tests.forEach(test => {
|
||||
summary += `| ${test.title}`;
|
||||
summary += "<p></p>"; // adds extra space after title
|
||||
summary += `<details>`;
|
||||
summary += "<summary>❌ ";
|
||||
summary += `<code>${truncateError(test.error)}</code>`;
|
||||
summary += "</summary>";
|
||||
summary += `<pre>${test.error}</pre>`.replaceAll("\n", "<br>");
|
||||
summary += `</details> |\n`;
|
||||
});
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
function truncateError(error, maxLength = 100) {
|
||||
if (error.length <= maxLength) return error;
|
||||
return error.replaceAll("\n", "").substring(0, maxLength) + "...";
|
||||
}
|
||||
|
||||
exports.formatSummary = formatSummary;
|
||||
53
.github/team.json
vendored
53
.github/team.json
vendored
@@ -1,53 +1,50 @@
|
||||
{
|
||||
"teams": [
|
||||
{
|
||||
"name": "Querying",
|
||||
"label": ".Team/Querying",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/97/views/1",
|
||||
"name": "Querying Components",
|
||||
"label": ".Team/QueryingComponents",
|
||||
"members": [
|
||||
"ranquild",
|
||||
"uladzimirdev",
|
||||
"kamilmielnik",
|
||||
"romeovs",
|
||||
"bshepherdson",
|
||||
"metamben",
|
||||
"lbrdnk",
|
||||
"appleby",
|
||||
"uladzimirdev"
|
||||
"nemanjaglumac",
|
||||
"romeovs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Drivers",
|
||||
"label": ".Team/Drivers",
|
||||
"members": ["snoe"]
|
||||
},
|
||||
{
|
||||
"name": "Embedding",
|
||||
"label": ".Team/Embedding",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/88/views/1",
|
||||
"members": ["WiNloSt", "oisincoveney", "npretto", "heypoom"]
|
||||
"members": ["WiNloSt", "deniskaber", "oisincoveney", "npretto", "heypoom"]
|
||||
},
|
||||
{
|
||||
"name": "Workflows",
|
||||
"label": ".Team/Workflows",
|
||||
"name": "BEC",
|
||||
"label": ".Team/BackendComponents",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/50/views/1",
|
||||
"members": [
|
||||
"tsmacdonald",
|
||||
"qnkhuat",
|
||||
"calherries",
|
||||
"piranha",
|
||||
"crisptrutski",
|
||||
"deniskaber"
|
||||
"crisptrutski"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Query Processor",
|
||||
"label": ".Team/QueryProcessor",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/85/views/1",
|
||||
"members": ["camsaul", "snoe", "bshepherdson", "metamben", "lbrdnk"]
|
||||
},
|
||||
{
|
||||
"name": "Admin Webapp",
|
||||
"label": ".Team/AdminWebapp",
|
||||
"members": [
|
||||
"noahmoss",
|
||||
"escherize",
|
||||
"iethree",
|
||||
"npfitz",
|
||||
"johnswanson",
|
||||
"rafpaf",
|
||||
"sloansparger",
|
||||
"iethree"
|
||||
"sloansparger"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -55,16 +52,10 @@
|
||||
"label": ".Team/DashViz",
|
||||
"members": [
|
||||
"alxnddr",
|
||||
"EmmadUsmani",
|
||||
"JesseSDevaney",
|
||||
"kulyk",
|
||||
"adam-james-v",
|
||||
"tsplude"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DevEx",
|
||||
"label": ".Team/DevEx",
|
||||
"members": [
|
||||
"camsaul"
|
||||
"adam-james-v"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
10
.github/workflows/auto-backport.yml
vendored
10
.github/workflows/auto-backport.yml
vendored
@@ -11,7 +11,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
- uses: ./.github/actions/find-squashed-commit
|
||||
name: Find commit
|
||||
id: find_commit
|
||||
@@ -78,7 +82,7 @@ jobs:
|
||||
TARGET_BRANCH: ${{ steps.get_latest_release_branch.outputs.branch-name }}
|
||||
ORIGINAL_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
|
||||
ORIGINAL_TITLE: ${{ github.event.pull_request.title }}
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
COMMIT: ${{ steps.find_commit.outputs.commit }}
|
||||
|
||||
- name: Auto approve backport PR
|
||||
@@ -92,7 +96,7 @@ jobs:
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
uses: peter-evans/enable-pull-request-automerge@v2
|
||||
with:
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
pull-request-number: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
merge-method: squash
|
||||
|
||||
|
||||
21
.github/workflows/auto-request-review.yml
vendored
21
.github/workflows/auto-request-review.yml
vendored
@@ -1,21 +0,0 @@
|
||||
# Config is in .github/auto_request_review.yml
|
||||
name: Auto Request Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
auto-request-review:
|
||||
# Don't run on forked repos
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
name: Auto Request Review
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request review based on files changes and/or groups the author belongs to
|
||||
uses: necojackarc/auto-request-review@v0.13.0
|
||||
with:
|
||||
token: '${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}'
|
||||
29
.github/workflows/backend-cloverage.yml
vendored
29
.github/workflows/backend-cloverage.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Backend-Cloverage
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1-5' # every weekday at 5am US eastern time
|
||||
|
||||
jobs:
|
||||
be-linter-cloverage:
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cloverage"
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
- name: Collect the test coverage
|
||||
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
58
.github/workflows/backend.yml
vendored
58
.github/workflows/backend.yml
vendored
@@ -12,6 +12,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CLJ_KONDO_VERSION: "2023.09.07"
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
@@ -60,6 +63,29 @@ jobs:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/static-viz-sources.yaml
|
||||
|
||||
be-linter-cloverage:
|
||||
needs: files-changed
|
||||
if: (github.ref_name == 'master' && needs.files-changed.outputs.backend_all == 'true')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-cloverage')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cloverage"
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
- name: Collect the test coverage
|
||||
run: clojure -X:dev:ci:ee:ee-dev:test:cloverage
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
|
||||
be-linter-clj-kondo:
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
if: |
|
||||
@@ -67,12 +93,23 @@ jobs:
|
||||
(needs.files-changed.outputs.backend_all == 'true' || needs.static-viz-files-changed.outputs.static_viz == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
DOWNLOAD_URL: https://github.com/clj-kondo/clj-kondo/releases/download
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'kondo'
|
||||
- name: Install clj-kondo
|
||||
run: |
|
||||
curl -OL ${DOWNLOAD_URL}/v${CLJ_KONDO_VERSION}/clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip
|
||||
curl -OL ${DOWNLOAD_URL}/v${CLJ_KONDO_VERSION}/clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip.sha256
|
||||
cat clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip.sha256 >> SHA256sum.txt
|
||||
echo " clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip" >> SHA256sum.txt
|
||||
sha256sum -c SHA256sum.txt
|
||||
unzip -d /usr/local/bin clj-kondo-${CLJ_KONDO_VERSION}-linux-static-amd64.zip
|
||||
- run: clj-kondo --version
|
||||
- name: Run clj-kondo
|
||||
run: ./bin/kondo.sh
|
||||
|
||||
@@ -168,6 +205,12 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
id: run-java-tests
|
||||
if: matrix.java-version != 21
|
||||
run: clojure -X:dev:ci:test:${{ matrix.edition }}:${{ matrix.edition }}-dev
|
||||
|
||||
- name: If using Java 21, only run tests on `master`
|
||||
id: run-java-tests-21
|
||||
if: matrix.java-version == 21 && github.ref_name == 'master'
|
||||
run: clojure -X:dev:ci:test:${{ matrix.edition }}:${{ matrix.edition }}-dev
|
||||
|
||||
- name: Upload Test Results
|
||||
@@ -248,18 +291,3 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
|
||||
be-cljfmt:
|
||||
needs: [files-changed]
|
||||
if: |
|
||||
!cancelled() && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: 'cljfmt'
|
||||
- name: Run cljfmt
|
||||
run: clojure -T:cljfmt check
|
||||
|
||||
19
.github/workflows/backport.yml
vendored
19
.github/workflows/backport.yml
vendored
@@ -107,23 +107,8 @@ jobs:
|
||||
echo "PR has been merged, searching for a squashed commit in the base branch"
|
||||
echo "searching for a commit in a ${ORIGINAL_BASE_REF} that contains pull request number ${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
SQUASHED_COMMIT=$(env -i git log ${ORIGINAL_BASE_REF} --grep="(#${ORIGINAL_PULL_REQUEST_NUMBER})" --format="%H")
|
||||
|
||||
if [ -n "${SQUASHED_COMMIT}" ]; then
|
||||
echo "found commit ${SQUASHED_COMMIT}"
|
||||
git cherry-pick ${SQUASHED_COMMIT}
|
||||
else
|
||||
echo "probably a PR wasn't merged with Squash And Merge button, searching again"
|
||||
SQUASHED_COMMIT=$(env -i git log ${ORIGINAL_BASE_REF} --grep="#${ORIGINAL_PULL_REQUEST_NUMBER}" --format="%H")
|
||||
|
||||
if [ -n "${SQUASHED_COMMIT}" ]; then
|
||||
echo "found commit ${SQUASHED_COMMIT}"
|
||||
git cherry-pick ${SQUASHED_COMMIT}
|
||||
else
|
||||
echo "No squashed commit found for PR #${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "found commit ${SQUASHED_COMMIT}"
|
||||
git cherry-pick ${SQUASHED_COMMIT}
|
||||
else
|
||||
echo "PR has not been merged, copying all commits"
|
||||
git cherry-pick ${ORIGINAL_BASE_SHA}..${ORIGINAL_HEAD_SHA}
|
||||
|
||||
@@ -3,13 +3,14 @@ name: Bugs Slack Notification
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
- opened
|
||||
jobs:
|
||||
on-issue-labeled:
|
||||
on-issue-opened:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
if: |
|
||||
github.event.label.name == '.Needs Triage' && contains(github.event.issue.labels.*.name, 'Type:Bug')
|
||||
contains(github.event.issue.labels.*.name, '.Needs Triage') &&
|
||||
contains(github.event.issue.labels.*.name, 'Type:Bug')
|
||||
steps:
|
||||
- name: Setting title
|
||||
uses: actions/github-script@v7
|
||||
@@ -33,7 +34,7 @@ jobs:
|
||||
- name: Send the issue ${{ github.event.issue.number }} to Slack
|
||||
id: slack
|
||||
if: steps.check_labels.outputs.has_team_label == 'false'
|
||||
uses: slackapi/slack-github-action@v1.26.0
|
||||
uses: slackapi/slack-github-action@v1.23.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
|
||||
63
.github/workflows/build-for-release.yml
vendored
63
.github/workflows/build-for-release.yml
vendored
@@ -17,52 +17,30 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Send build start message
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
SLACK_RELEASE_CHANNEL: ${{ vars.SLACK_RELEASE_CHANNEL }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
with:
|
||||
script: | # js
|
||||
const { sendPreReleaseMessage } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const { sendReleaseMessage } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
await sendPreReleaseMessage({
|
||||
await sendReleaseMessage({
|
||||
stage: 'build-start',
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: '${{ inputs.version }}',
|
||||
runId: '${{ github.run_id }}',
|
||||
releaseSha: '${{ inputs.commit }}',
|
||||
userName: '${{ github.actor }}',
|
||||
channelName: '${{ vars.SLACK_RELEASE_CHANNEL }}',
|
||||
}).catch(console.error);
|
||||
|
||||
trigger-milestone-check:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
needs: start-message
|
||||
steps:
|
||||
- name: trigger pre-release milestone check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'release-milestone-check.yml',
|
||||
ref: '${{ github.ref }}',
|
||||
inputs: {
|
||||
commit: '${{ inputs.commit }}',
|
||||
version: '${{ inputs.version }}',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
check-version:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
@@ -135,7 +113,7 @@ jobs:
|
||||
uses: actions/github-script@v7
|
||||
id: release_branch
|
||||
with:
|
||||
result-encoding: string
|
||||
result_encoding: string
|
||||
script: | # js
|
||||
const { getReleaseBranch } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const version = '${{ inputs.version }}';
|
||||
@@ -243,11 +221,24 @@ jobs:
|
||||
./COMMIT-ID
|
||||
./SHA256.sum
|
||||
|
||||
run-pre-release-tests:
|
||||
name: Run pre-release tests for ${{ inputs.version }}
|
||||
needs: build-uberjar-for-release
|
||||
uses: ./.github/workflows/pre-release.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
commit: ${{ inputs.commit }}
|
||||
version: ${{ inputs.version }}
|
||||
trigger-tests:
|
||||
needs: [build-uberjar-for-release]
|
||||
permissions:
|
||||
actions: write
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: trigger pre-release testing
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'pre-release.yml',
|
||||
ref: '${{ github.ref }}',
|
||||
inputs: {
|
||||
commit: '${{ inputs.commit }}',
|
||||
version: '${{ inputs.version }}',
|
||||
}
|
||||
});
|
||||
|
||||
113
.github/workflows/check-milestone.yml
vendored
113
.github/workflows/check-milestone.yml
vendored
@@ -1,85 +1,80 @@
|
||||
name: Automatically set milestone
|
||||
run-name: Set milestone for ${{ github.event.head_commit.message || inputs.version }}
|
||||
name: Milestone Reminder
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- release-x.**
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Major version number (e.g. 52, 78, 109)'
|
||||
type: number
|
||||
required: true
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
set-milestone:
|
||||
name: Automatically set milestone
|
||||
milestone-reminder:
|
||||
if: github.event.pull_request.merged == true
|
||||
name: Remind to set milestone
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
isNewReleaseBranch: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
contents: read
|
||||
issues: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
fetch-depth: ${{ env.isNewReleaseBranch == 'false' && 3 || 0 }} # we need the full history to get all the commit messages
|
||||
ref: master # we want the logic from master, even though we're triggering on a release branch
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- uses: actions/github-script@v7
|
||||
if: ${{ env.isNewReleaseBranch == 'false' }}
|
||||
name: Set milestone for single commit
|
||||
with:
|
||||
script: | # js
|
||||
const { setMilestoneForCommits } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const branchName = `${{ github.ref }}`;
|
||||
const commitMessage = context?.payload?.head_commit?.message ?? '';
|
||||
const pullNumber = '${{ github.event.pull_request.number }}';
|
||||
|
||||
await setMilestoneForCommits({
|
||||
github,
|
||||
// the github API doesn't expose linked issues, so we have to parse the body ourselves
|
||||
function getLinkedIssueId(pull) {
|
||||
// https://regexr.com/7sk94
|
||||
const match = pull.data.body.match(/(close(s|d)?|fixe?(s|d)?|resolve(s|d)?) (#|https?:\/\/github.com\/.+\/issues\/)(\d+)/i);
|
||||
return match && match?.[2];
|
||||
}
|
||||
|
||||
function getMilestone(pullOrIssue) {
|
||||
// pull has pull.data.milestone, issue has issue.milestone
|
||||
return pullOrIssue?.data?.milestone || pullOrIssue?.milestone;
|
||||
}
|
||||
|
||||
const pull = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
branchName,
|
||||
commitMessages: [commitMessage],
|
||||
pull_number: pullNumber,
|
||||
});
|
||||
- name: Get commit messages for release branch
|
||||
if: ${{ env.isNewReleaseBranch == 'true' }}
|
||||
run: |
|
||||
npm i -g tsx
|
||||
tsx release/src/release-branch-commits.ts ${{ inputs.version }} > commit-messages.json
|
||||
- uses: actions/github-script@v7
|
||||
if: ${{ env.isNewReleaseBranch == 'true' }}
|
||||
name: Set milestones for new release branch
|
||||
env:
|
||||
# this token has a higher rate limit than the default token, and
|
||||
# we will need it for this workflow to work
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
with:
|
||||
script: | # js
|
||||
const { setMilestoneForCommits } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const commitMessages = require('${{ github.workspace }}/commit-messages.json');
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
if (getMilestone(pull)) {
|
||||
console.log("Pull request has milestone", pull.data.milestone?.title);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const versionNumber = context.payload.inputs.version;
|
||||
const branchName = `release-x.${versionNumber}.x`;
|
||||
const issueId = getLinkedIssueId(pull);
|
||||
|
||||
console.log(`Found ${commitMessages.length} new commits for ${branchName}`);
|
||||
if (issueId) {
|
||||
console.log('linked issue found', issueId);
|
||||
|
||||
await setMilestoneForCommits({
|
||||
github,
|
||||
const issue = await github.rest.issues.get({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issueId,
|
||||
});
|
||||
|
||||
const milestone = getMilestone(issue);
|
||||
|
||||
if (milestone) {
|
||||
console.log("Linked issue has milestone", milestone.title);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("No milestone found");
|
||||
|
||||
const author = pull.data.user.login;
|
||||
const guideLink = "https://www.notion.so/metabase/Metabase-Branching-Strategy-6eb577d5f61142aa960a626d6bbdfeb3?pvs=4#3dea255ffa3b4f74942a227844e889fa";
|
||||
const message = `@${author} Did you forget to add a milestone to the issue for this PR? _[When and where should I add a milestone?](${guideLink})_`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
branchName,
|
||||
commitMessages: commitMessages,
|
||||
ignoreExistingMilestones: true,
|
||||
issue_number: pullNumber,
|
||||
body: message,
|
||||
});
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: Conventional commit PR Title Reminder
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- enterprise/frontend/src/embedding-sdk/**
|
||||
|
||||
jobs:
|
||||
pr-title-reminder:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- 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 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'
|
||||
}
|
||||
|
||||
- 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: |
|
||||
const { updateComment } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
await updateComment({github, context})
|
||||
162
.github/workflows/cross-branch-migrations.yml
vendored
162
.github/workflows/cross-branch-migrations.yml
vendored
@@ -1,162 +0,0 @@
|
||||
name: Cross-branch migration tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "resources/migrations/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
migrations-h2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
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: ${{ github.base_ref }}
|
||||
|
||||
migrations-postgres-12-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_PORT: 5432
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: circle_test
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
POSTGRES_USER: circle_test
|
||||
POSTGRES_DB: circle_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-postgres-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_PORT: 5432
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: circle_test
|
||||
services:
|
||||
postgres:
|
||||
image: circleci/postgres:latest
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
POSTGRES_USER: circle_test
|
||||
POSTGRES_DB: circle_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mysql-8-0-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
services:
|
||||
mysql:
|
||||
image: cimg/mysql:8.0
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mysql-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
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
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mariadb-10-2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
services:
|
||||
mariadb:
|
||||
image: circleci/mariadb:10.2.23
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
|
||||
migrations-mariadb-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
services:
|
||||
mariadb:
|
||||
image: circleci/mariadb:latest
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
37
.github/workflows/drivers-stress-test.yml
vendored
37
.github/workflows/drivers-stress-test.yml
vendored
@@ -21,6 +21,7 @@ on:
|
||||
- Athena
|
||||
- BigQuery
|
||||
- Druid
|
||||
- Google Analytics
|
||||
- Google Classpath Test
|
||||
- Maria DB
|
||||
- Maria DB (latest)
|
||||
@@ -108,6 +109,42 @@ jobs:
|
||||
junit-name: 'be-tests-druid-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
be-tests-googleanalytics-ee:
|
||||
if: inputs.driver == 'Google Analytics'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: googleanalytics
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Google Analytics driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-googleanalytics-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
be-google-related-drivers-classpath-test:
|
||||
if: inputs.driver == 'Google Classpath Test'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: 'googleanalytics,bigquery-cloud-sdk'
|
||||
MB_BIGQUERY_TEST_PROJECT_ID: ${{ secrets.BIGQUERY_TEST_PROJECT_ID }}
|
||||
MB_BIGQUERY_TEST_CLIENT_ID: ${{ secrets.MB_BIGQUERY_TEST_CLIENT_ID }}
|
||||
MB_BIGQUERY_TEST_CLIENT_SECRET: ${{ secrets.MB_BIGQUERY_TEST_CLIENT_SECRET }}
|
||||
MB_BIGQUERY_TEST_ACCESS_TOKEN: ${{ secrets.MB_BIGQUERY_TEST_ACCESS_TOKEN }}
|
||||
MB_BIGQUERY_TEST_REFRESH_TOKEN: ${{ secrets.MB_BIGQUERY_TEST_REFRESH_TOKEN }}
|
||||
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Google Related Drivers Classpath
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-google-related-drivers-classpath-test'
|
||||
test-args: ':only "[metabase.query-processor-test.expressions-test metabase.driver.google-test metabase.driver.googleanalytics-test ${{ inputs.test }}] :times ${{ inputs.times }}"'
|
||||
|
||||
be-tests-mariadb-10-2-ee:
|
||||
if: inputs.driver == 'Maria DB'
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
391
.github/workflows/drivers.yml
vendored
391
.github/workflows/drivers.yml
vendored
@@ -40,17 +40,13 @@ jobs:
|
||||
MB_ATHENA_TEST_ACCESS_KEY: ${{ secrets.MB_ATHENA_TEST_ACCESS_KEY }}
|
||||
MB_ATHENA_TEST_SECRET_KEY: ${{ secrets.MB_ATHENA_TEST_SECRET_KEY }}
|
||||
MB_ATHENA_TEST_S3_STAGING_DIR: ${{ secrets.MB_ATHENA_TEST_S3_STAGING_DIR }}
|
||||
# These credentials are used to test the driver when the user does not have the athena:GetTableMetadata permission
|
||||
MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_ACCESS_KEY: ${{ secrets.MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_ACCESS_KEY }}
|
||||
MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY: ${{ secrets.MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Athena driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-athena-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -83,8 +79,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-bigquery-cloud-sdk-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -118,38 +113,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-druid-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- 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-databricks-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: databricks
|
||||
MB_DATABRICKS_TEST_HOST: ${{ secrets.MB_DATABRICKS_JDBC_TEST_HOST }}
|
||||
MB_DATABRICKS_TEST_HTTP_PATH: ${{ secrets.MB_DATABRICKS_JDBC_TEST_HTTP_PATH }}
|
||||
MB_DATABRICKS_TEST_TOKEN: ${{ secrets.MB_DATABRICKS_JDBC_TEST_TOKEN }}
|
||||
MB_DATABRICKS_TEST_CATALOG: 'metabase_ci'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Databricks driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-databricks-ee'
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
@@ -184,8 +147,67 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-druid-jdbc-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- 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-googleanalytics-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: googleanalytics
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Google Analytics driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-googleanalytics-ee'
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- 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-google-related-drivers-classpath-test:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: 'googleanalytics,bigquery-cloud-sdk'
|
||||
MB_BIGQUERY_TEST_PROJECT_ID: ${{ secrets.BIGQUERY_TEST_PROJECT_ID }}
|
||||
MB_BIGQUERY_TEST_CLIENT_ID: ${{ secrets.MB_BIGQUERY_TEST_CLIENT_ID }}
|
||||
MB_BIGQUERY_TEST_CLIENT_SECRET: ${{ secrets.MB_BIGQUERY_TEST_CLIENT_SECRET }}
|
||||
MB_BIGQUERY_TEST_ACCESS_TOKEN: ${{ secrets.MB_BIGQUERY_TEST_ACCESS_TOKEN }}
|
||||
MB_BIGQUERY_TEST_REFRESH_TOKEN: ${{ secrets.MB_BIGQUERY_TEST_REFRESH_TOKEN }}
|
||||
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Google Related Drivers Classpath
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-google-related-drivers-classpath-test'
|
||||
test-args: ':only "[metabase.query-processor-test.expressions-test metabase.driver.google-test metabase.driver.googleanalytics-test]"'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -203,10 +225,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
@@ -227,9 +245,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mariadb-10-2-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -247,10 +262,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
@@ -271,9 +282,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mariadb-latest-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -307,9 +315,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-4-4-ee'
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -336,19 +342,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Spin up Mongo docker container
|
||||
run: >-
|
||||
docker run -d
|
||||
-p 27017:27017
|
||||
--name metamongo
|
||||
metabase/qa-databases:mongo-sample-4.4
|
||||
mongod
|
||||
--dbpath /data/db2/
|
||||
--tlsMode requireTLS
|
||||
--tlsCertificateKeyFile /etc/mongo/metamongo.pem
|
||||
--tlsCAFile /etc/mongo/metaca.crt
|
||||
- uses: ./.github/actions/await-port
|
||||
with:
|
||||
port: 27017
|
||||
run: docker run -d -p 27017:27017 --name metamongo metabase/qa-databases:mongo-sample-4.4 mongod --dbpath /data/db2/ --tlsMode requireTLS --tlsCertificateKeyFile /etc/mongo/metamongo.pem --tlsCAFile /etc/mongo/metaca.crt
|
||||
- name: Wait until the port 27017 is ready
|
||||
run: while ! nc -z localhost 27017; do sleep 1; done
|
||||
timeout-minutes: 5
|
||||
- name: Make SSL certificates for Mongo available
|
||||
run: |
|
||||
@@ -360,13 +356,12 @@ jobs:
|
||||
|
||||
curl https://raw.githubusercontent.com/metabase/metabase-qa/master/dbs/mongo/certificates/metaca.crt \
|
||||
-o ./test_resources/ssl/mongo/metaca.crt
|
||||
|
||||
- name: Test MongoDB SSL driver (4.4)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-4-4-ee'
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -400,9 +395,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-5-0-ee'
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -429,19 +422,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Spin up Mongo docker container
|
||||
run: >-
|
||||
docker run -d
|
||||
-p 27017:27017
|
||||
--name metamongo
|
||||
metabase/qa-databases:mongo-sample-5.0
|
||||
mongod
|
||||
--dbpath /data/db2/
|
||||
--tlsMode requireTLS
|
||||
--tlsCertificateKeyFile /etc/mongo/metamongo.pem
|
||||
--tlsCAFile /etc/mongo/metaca.crt
|
||||
- uses: ./.github/actions/await-port
|
||||
with:
|
||||
port: 27017
|
||||
run: docker run -d -p 27017:27017 --name metamongo metabase/qa-databases:mongo-sample-5.0 mongod --dbpath /data/db2/ --tlsMode requireTLS --tlsCertificateKeyFile /etc/mongo/metamongo.pem --tlsCAFile /etc/mongo/metaca.crt
|
||||
- name: Wait until the port 27017 is ready
|
||||
run: while ! nc -z localhost 27017; do sleep 1; done
|
||||
timeout-minutes: 5
|
||||
- name: Make SSL certificates for Mongo available
|
||||
run: |
|
||||
@@ -453,13 +436,12 @@ jobs:
|
||||
|
||||
curl https://raw.githubusercontent.com/metabase/metabase-qa/master/dbs/mongo/certificates/metaca.crt \
|
||||
-o ./test_resources/ssl/mongo/metaca.crt
|
||||
|
||||
- name: Test MongoDB SSL driver (5.0)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-5-0-ee'
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -496,9 +478,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-latest-ee'
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -548,10 +528,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
@@ -572,30 +548,12 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mysql-8-0-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- 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-latest-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
@@ -631,9 +589,6 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mysql-latest-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -671,8 +626,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-oracle-18-4-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -719,8 +673,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-oracle-21-3-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -738,28 +691,6 @@ jobs:
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
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
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: App DB Tests
|
||||
test-args: >-
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: postgres
|
||||
@@ -769,13 +700,9 @@ jobs:
|
||||
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 }}
|
||||
image: postgres:12-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
@@ -783,24 +710,55 @@ jobs:
|
||||
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:
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
- 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 }}
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Postgres driver (12)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-postgres-ee'
|
||||
|
||||
be-tests-postgres-latest-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: postgres
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_PORT: 5432
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: circle_test
|
||||
MB_POSTGRESQL_TEST_USER: circle_test
|
||||
MB_POSTGRES_SSL_TEST_SSL: true
|
||||
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: circleci/postgres:latest
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
POSTGRES_USER: circle_test
|
||||
POSTGRES_DB: circle_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Postgres driver (latest)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-postgres-latest-ee'
|
||||
- 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-presto-jdbc-ee:
|
||||
needs: files-changed
|
||||
@@ -827,37 +785,30 @@ jobs:
|
||||
JAVA_TOOL_OPTIONS: "-Xmx2g"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/await-port
|
||||
with:
|
||||
port: ${{ env.MB_PRESTO_JDBC_TEST_PORT }}
|
||||
timeout-minutes: 5
|
||||
- name: Wait for port ${{ env.MB_PRESTO_JDBC_TEST_PORT }} to be ready
|
||||
run: while ! nc -z localhost ${{ env.MB_PRESTO_JDBC_TEST_PORT }}; do sleep 0.1; done
|
||||
timeout-minutes: 15
|
||||
- name: Create temp cacerts file based on bundled JDK one
|
||||
run: cp $JAVA_HOME/lib/security/cacerts /tmp/cacerts-with-presto-ssl.jks
|
||||
- name: Capture Presto server self signed CA
|
||||
run: |
|
||||
while [[ ! -s /tmp/presto-ssl-ca.pem ]]; do
|
||||
echo "Waiting to capture SSL CA" \
|
||||
while [[ ! -s /tmp/presto-ssl-ca.pem ]];
|
||||
do echo "Waiting to capture SSL CA" \
|
||||
&& openssl s_client -connect localhost:8443 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/presto-ssl-ca.pem \
|
||||
&& sleep 1;
|
||||
done
|
||||
&& sleep 1; done
|
||||
- name: Convert Presto CA from PEM to DER
|
||||
run: openssl x509 -outform der -in /tmp/presto-ssl-ca.pem -out /tmp/presto-ssl-ca.der
|
||||
- name: Add write permission on cacerts file
|
||||
run: chmod u+w /tmp/cacerts-with-presto-ssl.jks
|
||||
- name: Import Presto CA into temp cacerts file
|
||||
run: >-
|
||||
keytool -noprompt -import
|
||||
-alias presto
|
||||
-keystore /tmp/cacerts-with-presto-ssl.jks
|
||||
-storepass changeit
|
||||
-file /tmp/presto-ssl-ca.der
|
||||
-trustcacerts
|
||||
run: |
|
||||
keytool -noprompt -import -alias presto -keystore /tmp/cacerts-with-presto-ssl.jks \
|
||||
-storepass changeit -file /tmp/presto-ssl-ca.der -trustcacerts
|
||||
- name: Test Presto JDBC driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-presto-jdbc-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -888,8 +839,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-redshift-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -916,19 +866,13 @@ jobs:
|
||||
MB_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.MB_SNOWFLAKE_TEST_WAREHOUSE }}
|
||||
MB_SNOWFLAKE_TEST_PK_USER: METABASE PK
|
||||
MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY: ${{ secrets.MB_SNOWFLAKE_TEST_PK_PRIVATE_KEY }}
|
||||
# RSA Role testing:
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DEFAULT_USER: RSA_ROLE_TEST_DEFAULT_USER
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_CUSTOM_USER: RSA_ROLE_TEST_CUSTOM_USER
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_ROLE: RSA_ROLE_TEST_ROLE
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Snowflake driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-snowflake-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -960,8 +904,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sparksql-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -988,8 +931,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sqlite-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1005,7 +947,7 @@ jobs:
|
||||
be-tests-sqlserver-2017-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -1028,8 +970,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sqlserver-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1068,8 +1009,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-sqlserver-2022-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1103,8 +1043,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-vertica-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1116,41 +1055,3 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
# Dummy job generator to spit out fake runs for matrix jobs we skipped. See
|
||||
# https://github.com/orgs/community/discussions/9141 and
|
||||
# https://metaboat.slack.com/archives/C0669P4AF9N/p1732315370301059
|
||||
be-driver-tests-skipped-stub:
|
||||
needs: files-changed
|
||||
if: |
|
||||
!cancelled() &&
|
||||
(github.event.pull_request.draft == true ||
|
||||
needs.files-changed.outputs.backend_all == 'false')
|
||||
strategy:
|
||||
matrix:
|
||||
job:
|
||||
- "Test Postgres 12.x (App DB Tests)"
|
||||
- "Test Postgres 12.x (Driver Tests)"
|
||||
- "Test Postgres Latest (App DB Tests)"
|
||||
- "Test Postgres Latest (Driver Tests)"
|
||||
- "be-tests-mariadb-10-2-ee (0)"
|
||||
- "be-tests-mariadb-10-2-ee (1)"
|
||||
- "be-tests-mariadb-10-2-ee (2)"
|
||||
- "be-tests-mariadb-10-2-ee (3)"
|
||||
- "be-tests-mariadb-latest-ee (0)"
|
||||
- "be-tests-mariadb-latest-ee (1)"
|
||||
- "be-tests-mariadb-latest-ee (2)"
|
||||
- "be-tests-mariadb-latest-ee (3)"
|
||||
- "be-tests-mysql-8-0-ee (0)"
|
||||
- "be-tests-mysql-8-0-ee (1)"
|
||||
- "be-tests-mysql-8-0-ee (2)"
|
||||
- "be-tests-mysql-8-0-ee (3)"
|
||||
- "be-tests-mysql-latest-ee (0)"
|
||||
- "be-tests-mysql-latest-ee (1)"
|
||||
- "be-tests-mysql-latest-ee (2)"
|
||||
- "be-tests-mysql-latest-ee (3)"
|
||||
name: ${{ matrix.job }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: >-
|
||||
echo 'Skipping job due to conditional filtering'
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
name: E2E Cross-version Component Tests for Embedding SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "release-**"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- "release-**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
e2e_all: ${{ steps.changes.outputs.e2e_all }}
|
||||
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
|
||||
|
||||
# if this is a test on a release branch, we need to check the build requirements
|
||||
get-build-requirements:
|
||||
if: |
|
||||
!cancelled()
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
|
||||
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get build dependencies
|
||||
uses: actions/github-script@v7
|
||||
id: dependencies
|
||||
with:
|
||||
script: | # js
|
||||
const {
|
||||
getBuildRequirements,
|
||||
getVersionFromReleaseBranch,
|
||||
} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const targetBranchName = '${{ github.base_ref || github.ref }}';
|
||||
|
||||
const version = getVersionFromReleaseBranch(targetBranchName);
|
||||
const requirements = getBuildRequirements(version);
|
||||
|
||||
return {
|
||||
java_version: requirements.java,
|
||||
node_version: requirements.node,
|
||||
};
|
||||
|
||||
build:
|
||||
needs: [files-changed, get-build-requirements]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
INTERACTIVE: false
|
||||
# make sure that builds on release branches get licenses, because we use them for releases
|
||||
SKIP_LICENSES: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
with:
|
||||
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: uberjar
|
||||
java-version: "${{ needs.get-build-requirements.outputs.java_version || 11 }}"
|
||||
|
||||
- name: Build uberjar with ./bin/build.sh
|
||||
run: ./bin/build.sh
|
||||
|
||||
- name: Prepare uberjar artifact
|
||||
uses: ./.github/actions/prepare-uberjar-artifact
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
resolve-sdk-version:
|
||||
if: |
|
||||
!cancelled()
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
sdk_version: ${{ fromJson(steps.dependencies.outputs.result).sdk_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get latest SDK release for this branch
|
||||
uses: actions/github-script@v7
|
||||
id: dependencies
|
||||
with:
|
||||
script: | # js
|
||||
const { getSdkVersionFromReleaseBranchName } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const branchName = '${{ github.base_ref || github.ref }}';
|
||||
|
||||
const sdk_version = await getSdkVersionFromReleaseBranchName({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branchName,
|
||||
});
|
||||
|
||||
return {
|
||||
sdk_version,
|
||||
};
|
||||
|
||||
e2e-tests:
|
||||
needs: [get-build-requirements, build, resolve-sdk-version]
|
||||
if: |
|
||||
!cancelled() && needs.build.result == 'success'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-tests-embedding-sdk
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
DISPLAY: ""
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Needed to omit newly added tests for functionality that is not yet released
|
||||
- name: Checkout SDK release commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: embedding-sdk-${{ needs.resolve-sdk-version.outputs.sdk_version }}
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare JDK ${{ needs.get-build-requirements.outputs.java_version || 11 }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ needs.get-build-requirements.outputs.java_version || 11 }}
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Install Embedding SDK package v${{ needs.resolve-sdk-version.outputs.sdk_version }}
|
||||
run: yarn add @metabase/embedding-sdk-react@${{ needs.resolve-sdk-version.outputs.sdk_version }}
|
||||
|
||||
- name: Run E2E tests for Embedding SDK
|
||||
run: yarn run test-cypress-run-component-sdk --browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit
|
||||
output-name: e2e-embedding-sdk
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-recording-embedding-sdk-cross-version
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const {
|
||||
parseReport,
|
||||
formatSummary
|
||||
} = require("./.github/scripts/handle-mochawesome-report.js");
|
||||
|
||||
const report = parseReport();
|
||||
const summary = formatSummary(report);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
|
||||
e2e-tests-skipped-stub:
|
||||
needs: [e2e-tests]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.e2e-tests.result == 'skipped'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
name: e2e-component-tests-embedding-sdk-cross-version
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
@@ -1,212 +0,0 @@
|
||||
name: E2E Component Tests for Embedding SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release-**"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
e2e_all: ${{ steps.changes.outputs.e2e_all }}
|
||||
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
|
||||
|
||||
# if this is a test on a release branch, we need to check the build requirements
|
||||
get-build-requirements:
|
||||
if: |
|
||||
!cancelled() &&
|
||||
contains(github.base_ref || github.ref, 'release-x')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
|
||||
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get build dependencies
|
||||
uses: actions/github-script@v7
|
||||
id: dependencies
|
||||
with:
|
||||
script: | # js
|
||||
const {
|
||||
getBuildRequirements,
|
||||
getVersionFromReleaseBranch,
|
||||
} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
const targetBranchName = '${{ github.base_ref || github.ref }}';
|
||||
|
||||
const version = getVersionFromReleaseBranch(targetBranchName);
|
||||
const requirements = getBuildRequirements(version);
|
||||
|
||||
return {
|
||||
java_version: requirements.java,
|
||||
node_version: requirements.node,
|
||||
};
|
||||
|
||||
build:
|
||||
needs: [files-changed, get-build-requirements]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
needs.files-changed.outputs.e2e_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
INTERACTIVE: false
|
||||
# make sure that builds on release branches get licenses, because we use them for releases
|
||||
SKIP_LICENSES: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
with:
|
||||
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: uberjar
|
||||
java-version: "${{ needs.get-build-requirements.outputs.java_version || 11 }}"
|
||||
|
||||
- name: Build uberjar with ./bin/build.sh
|
||||
run: ./bin/build.sh
|
||||
|
||||
- name: Prepare uberjar artifact
|
||||
uses: ./.github/actions/prepare-uberjar-artifact
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
e2e-tests:
|
||||
needs: [get-build-requirements, build]
|
||||
if: |
|
||||
!cancelled() && needs.build.result == 'success'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 45
|
||||
name: e2e-tests-embedding-sdk
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
DISPLAY: ""
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare JDK ${{ needs.get-build-requirements.outputs.java_version || 11 }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ needs.get-build-requirements.outputs.java_version || 11 }}
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
|
||||
- name: Compile CLJS
|
||||
run: yarn build-pure:cljs
|
||||
shell: bash
|
||||
|
||||
- name: Build Embedding SDK package
|
||||
run: yarn build-embedding-sdk
|
||||
|
||||
- name: Run E2E tests for Embedding SDK
|
||||
run: yarn run test-cypress-run-component-sdk --browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit
|
||||
output-name: e2e-embedding-sdk
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-recording-embedding-sdk-latest
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const {
|
||||
parseReport,
|
||||
formatSummary
|
||||
} = require("./.github/scripts/handle-mochawesome-report.js");
|
||||
|
||||
const report = parseReport();
|
||||
const summary = formatSummary(report);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
|
||||
e2e-tests-skipped-stub:
|
||||
needs: [e2e-tests]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.e2e-tests.result == 'skipped'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
name: e2e-component-tests-embedding-sdk
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
54
.github/workflows/e2e-cross-version.yml
vendored
54
.github/workflows/e2e-cross-version.yml
vendored
@@ -2,33 +2,12 @@ name: E2E Cross-version Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * 1-5'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
- cron: '0 9 * * 0'
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
e2e_cross_version: ${{ steps.changes.outputs.e2e_cross_version }}
|
||||
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
|
||||
|
||||
cross-version-testing:
|
||||
needs: files-changed
|
||||
if: ${{ needs.files-changed.outputs.e2e_cross_version == 'true' || github.event_name == 'schedule'}}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
version: [
|
||||
@@ -73,12 +52,11 @@ jobs:
|
||||
- name: Wait for Metabase to start on port 3000
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- name: Run Cypress on the source
|
||||
run: |
|
||||
yarn cypress run \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
|
||||
--browser chrome \
|
||||
--config-file e2e/test/scenarios/cross-version/source/shared/cross-version-source.config.js \
|
||||
--spec e2e/test/scenarios/cross-version/source/**/*.cy.spec.js
|
||||
- name: Stop Metabase ${{ matrix.version.source }}
|
||||
@@ -106,7 +84,7 @@ jobs:
|
||||
- name: Run Cypress on the target
|
||||
run: |
|
||||
yarn cypress run \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
|
||||
--browser chrome \
|
||||
--config-file e2e/test/scenarios/cross-version/target/shared/cross-version-target.config.js \
|
||||
--spec e2e/test/scenarios/cross-version/target/**/*.cy.spec.js
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
@@ -119,11 +97,20 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
notify-on-failure:
|
||||
needs: cross-version-testing
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') }}
|
||||
name: Cross-version test failure
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.event.workflow_run.head_branch }}
|
||||
AUTHOR_NAME: ${{ github.event.workflow_run.head_commit.author.name }}
|
||||
steps:
|
||||
- name: Generate job summary
|
||||
run: |
|
||||
echo "# ${{ github.event.workflow_run.name }} workflow failed! :x:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "View the failed run attempt (#${{ github.event.workflow_run.run_attempt }}) using the following link:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${{ github.event.workflow_run.html_url }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/Hydrogen # 18.x.x
|
||||
@@ -132,8 +119,11 @@ jobs:
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
const FAILED_RUN_URL = "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}";
|
||||
const REF_NAME = "${{ github.ref_name }}";
|
||||
const FAILED_RUN_URL = "${{ github.event.workflow_run.html_url }}";
|
||||
const FAILED_RUN_NAME = "${{ github.event.workflow_run.name }}";
|
||||
const BREAKING_COMMIT = "${{ github.event.workflow_run.head_sha }}";
|
||||
const AUTHOR = process.env.AUTHOR_NAME;
|
||||
const BRANCH = process.env.BRANCH_NAME;
|
||||
|
||||
// notify slack of failure
|
||||
const { WebClient } = require('@slack/web-api');
|
||||
@@ -147,7 +137,7 @@ jobs:
|
||||
"type": "header",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": `:broken_heart: Cross-version tests are failing on ${REF_NAME}`,
|
||||
"text": `:broken_heart: Cross-version tests are failing on ${BRANCH}`,
|
||||
"emoji": true,
|
||||
}
|
||||
},
|
||||
@@ -159,7 +149,7 @@ jobs:
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": `See <${FAILED_RUN_URL}|failing run details>.`
|
||||
"text": `Commit <https://github.com/metabase/metabase/commit/${BREAKING_COMMIT}|${BREAKING_COMMIT.slice(0,7)}> has failing <${FAILED_RUN_URL}|${FAILED_RUN_NAME}> tests on the <https://github.com/metabase/metabase/commits/${BRANCH}|\`${BRANCH}\`> branch.`
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
28
.github/workflows/e2e-stress-test-flake-fix.yml
vendored
28
.github/workflows/e2e-stress-test-flake-fix.yml
vendored
@@ -20,15 +20,6 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: "ee"
|
||||
qa_db:
|
||||
description: 'Select the type of Database you want to run your tests with'
|
||||
type: choice
|
||||
required: true
|
||||
default: "none"
|
||||
options:
|
||||
- none
|
||||
- sql
|
||||
- mongo
|
||||
|
||||
jobs:
|
||||
workflow-summary:
|
||||
@@ -44,7 +35,6 @@ jobs:
|
||||
echo '- `spec`: ${{ inputs.spec }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '- `burn_in`: ${{ inputs.burn_in }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '- `grep`: "${{ inputs.grep }}"' >> $GITHUB_STEP_SUMMARY
|
||||
echo '- `qa_db`: "${{ inputs.qa_db }}"' >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -54,8 +44,9 @@ jobs:
|
||||
name: Stress test E2E flake fix
|
||||
env:
|
||||
DISPLAY: ""
|
||||
QA_DB_ENABLED: ${{ contains(fromJSON('["sql", "mongo"]'), inputs.qa_db) }}
|
||||
CYPRESS_QA_DB_MONGO: ${{ inputs.qa_db == 'mongo' }}
|
||||
QA_DB_ENABLED: true
|
||||
CYPRESS_QA_DB_MONGO: true
|
||||
CYPRESS_REPLAYIO_ENABLED: 1
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
@@ -76,9 +67,9 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: true
|
||||
postgres: ${{ inputs.qa_db == 'sql' }}
|
||||
mysql: ${{ inputs.qa_db == 'sql' }}
|
||||
mongo: ${{ inputs.qa_db == 'mongo' }}
|
||||
postgres: true
|
||||
mysql: true
|
||||
mongo: true
|
||||
|
||||
- name: Download Metabase ${{ matrix.edition }} uberjar
|
||||
uses: ./.github/actions/e2e-download-uberjar
|
||||
@@ -87,13 +78,12 @@ jobs:
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare JDK 21
|
||||
- name: Prepare JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
java-version: 11
|
||||
distribution: "temurin"
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
@@ -106,7 +96,7 @@ jobs:
|
||||
--spec '${{ github.event.inputs.spec }}' \
|
||||
--env burn=${{ github.event.inputs.burn_in }},grep='${{ github.event.inputs.grep }}' \
|
||||
--config-file e2e/support/cypress-stress-test.config.js \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
145
.github/workflows/e2e-tests.yml
vendored
145
.github/workflows/e2e-tests.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release-**"
|
||||
- 'release-**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
@@ -39,39 +39,8 @@ jobs:
|
||||
id: e2e-matrix
|
||||
uses: ./.github/actions/build-e2e-matrix
|
||||
|
||||
# if this is a test on a release branch, we need to check the build requirements
|
||||
get-build-requirements:
|
||||
if: |
|
||||
!cancelled() &&
|
||||
contains(github.ref, 'release-x')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
java_version: ${{ fromJson(steps.dependencies.outputs.result).java_version }}
|
||||
node_version: ${{ fromJson(steps.dependencies.outputs.result).node_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn && yarn build
|
||||
- name: Get build dependencies
|
||||
uses: actions/github-script@v7
|
||||
id: dependencies
|
||||
with:
|
||||
script: | # js
|
||||
const { getBuildRequirements, getVersionFromReleaseBranch } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const version = getVersionFromReleaseBranch('${{ github.ref }}');
|
||||
const requirements = getBuildRequirements(version);
|
||||
|
||||
return {
|
||||
java_version: requirements.java,
|
||||
node_version: requirements.node,
|
||||
};
|
||||
|
||||
build:
|
||||
needs: [files-changed, e2e-matrix-builder, get-build-requirements]
|
||||
needs: [files-changed, e2e-matrix-builder]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
@@ -85,19 +54,14 @@ jobs:
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.edition }}
|
||||
INTERACTIVE: false
|
||||
# make sure that builds on release branches get licenses, because we use them for releases
|
||||
SKIP_LICENSES: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
with:
|
||||
node-version: "${{ needs.get-build-requirements.outputs.node_version }}"
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: uberjar
|
||||
java-version: "${{ needs.get-build-requirements.outputs.java_version || 11 }}"
|
||||
m2-cache-key: e2e-tests
|
||||
|
||||
- name: Build uberjar with ./bin/build.sh
|
||||
run: ./bin/build.sh
|
||||
@@ -123,11 +87,14 @@ jobs:
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
COMMIT_INFO_MESSAGE : ${{ github.event.pull_request.title || github.event.head_commit.message || github.even.head.sha }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
CYPRESS_REPLAYIO_ENABLED: 1
|
||||
RECORD_REPLAY_METADATA_FILE: /tmp/replay-metadata.json
|
||||
RECORD_REPLAY_METADATA_TEST_RUN_ID: ${{ github.run_id }}
|
||||
REPLAY_API_KEY: ${{ secrets.REPLAY_IO_TOKEN }}
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.e2e-matrix-builder.outputs.matrix) }}
|
||||
@@ -145,7 +112,6 @@ jobs:
|
||||
postgres: ${{ matrix.name != 'mongo'}}
|
||||
mysql: ${{ matrix.name != 'mongo'}}
|
||||
mongo: ${{ matrix.name == 'mongo'}}
|
||||
webhook: true
|
||||
|
||||
- name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -167,7 +133,6 @@ jobs:
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Run Snowplow micro
|
||||
@@ -178,24 +143,24 @@ jobs:
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags=@OSS,grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run slow and resource-intensive Cypress tests
|
||||
if: matrix.name == 'slow' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@slow",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run Flaky Cypress tests
|
||||
if: matrix.name == 'flaky' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run EE Cypress tests on ${{ matrix.name }}
|
||||
if: matrix.context == 'folder' && github.event_name != 'schedule'
|
||||
@@ -203,33 +168,18 @@ jobs:
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="-@slow+-@mongo+-@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--folder ${{ matrix.name }} \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run E2E tests that depend on Mongo
|
||||
if: matrix.name == 'mongo'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@mongo --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
env:
|
||||
CYPRESS_QA_DB_MONGO: true
|
||||
|
||||
- name: Run Embedding SDK Storybook
|
||||
if: matrix.context == 'embedding-sdk' && github.event_name != 'schedule'
|
||||
uses: ./.github/actions/run-embedding-sdk-storybook
|
||||
|
||||
- name: Run E2E tests for Embedding SDK
|
||||
if: matrix.context == 'embedding-sdk' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="-@slow+-@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--folder 'embedding-sdk' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
env:
|
||||
CYPRESS_IS_EMBEDDING_SDK: true
|
||||
shell: bash
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -252,21 +202,6 @@ jobs:
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const {
|
||||
parseReport,
|
||||
formatSummary
|
||||
} = require("./.github/scripts/handle-mochawesome-report.js");
|
||||
|
||||
const report = parseReport();
|
||||
const summary = formatSummary(report);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
|
||||
e2e-tests-skipped-stub:
|
||||
needs: [e2e-tests, e2e-matrix-builder]
|
||||
if: |
|
||||
@@ -282,10 +217,46 @@ jobs:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
|
||||
pr-env:
|
||||
visual-regression-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
needs: [build]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
contains(github.event.pull_request.labels.*.name, 'PR-Env')
|
||||
uses: ./.github/workflows/pr-env.yml
|
||||
secrets: inherit
|
||||
!cancelled() && needs.build.result == 'success'
|
||||
name: percy-visual-regression-tests
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
|
||||
- name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Percy Test
|
||||
run: yarn run test-visual-run
|
||||
env:
|
||||
PERCY_TOKEN: ${{ github.ref == 'refs/heads/master' && secrets.PERCY_TOKEN || ''}}
|
||||
|
||||
36
.github/workflows/flake-issue-creator.yml
vendored
36
.github/workflows/flake-issue-creator.yml
vendored
@@ -1,36 +0,0 @@
|
||||
name: Flake Issue Creator
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1-5' # every weekday at 1am US eastern time
|
||||
|
||||
jobs:
|
||||
flake-issue-creator:
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn --frozen-lockfile && yarn build
|
||||
- name: Update flake issues
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
with:
|
||||
script: | # js
|
||||
const { updateFlakeIssues } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
if (!process.env.SLACK_BOT_TOKEN) {
|
||||
throw new Error('SLACK_BOT_TOKEN is required');
|
||||
}
|
||||
|
||||
await updateFlakeIssues({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
37
.github/workflows/flake-status.yml
vendored
37
.github/workflows/flake-status.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Flake Status Report
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * 2,5' # Tuesdays and Fridays at 1am US eastern time
|
||||
|
||||
jobs:
|
||||
flake-status-report:
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: Prepare build scripts
|
||||
run: cd ${{ github.workspace }}/release && yarn --frozen-lockfile && yarn build
|
||||
- name: Post Flake Status Report
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
with:
|
||||
script: | # js
|
||||
const { summarizeFlakes } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
if (!process.env.SLACK_BOT_TOKEN) {
|
||||
throw new Error('SLACK_BOT_TOKEN is required');
|
||||
}
|
||||
|
||||
await summarizeFlakes({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
channelName: 'engineering-ci'
|
||||
});
|
||||
@@ -4,15 +4,15 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
spec:
|
||||
description: "Relative path of the target spec"
|
||||
description: 'Relative path of the target spec'
|
||||
type: string
|
||||
required: true
|
||||
burn_in:
|
||||
description: "Number of times to run the test (e.g. 20)"
|
||||
description: 'Number of times to run the test (e.g. 20)'
|
||||
type: string
|
||||
required: true
|
||||
grep:
|
||||
description: "Grep and filter tests to run in isolation"
|
||||
description: 'Grep and filter tests to run in isolation'
|
||||
type: string
|
||||
required: false
|
||||
|
||||
@@ -45,12 +45,10 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
- name: Build cljs
|
||||
run: yarn build:cljs
|
||||
- name: Stress-test ${{ github.event.inputs.spec }} ${{ github.event.inputs.burn_in }} times
|
||||
run: |
|
||||
for i in {1..${{ github.event.inputs.burn_in }}}; do
|
||||
yarn jest --runInBand --ci --silent --no-cache \
|
||||
yarn test-unit \
|
||||
--testPathPattern '${{ github.event.inputs.spec }}' \
|
||||
--testNamePattern '${{ github.event.inputs.grep }}'
|
||||
if [[ "$?" != 0 ]]; then
|
||||
|
||||
27
.github/workflows/loki-update.yml
vendored
27
.github/workflows/loki-update.yml
vendored
@@ -10,9 +10,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Prepare frontend environment
|
||||
@@ -27,9 +26,7 @@ jobs:
|
||||
run: yarn build-pure:cljs
|
||||
|
||||
- name: Build Storybook
|
||||
# We won't be able to use Storybook play's `userEvent` with production build
|
||||
# see https://github.com/storybookjs/storybook/issues/19758
|
||||
run: NODE_ENV=development yarn build-storybook
|
||||
run: yarn build-storybook
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
@@ -47,23 +44,29 @@ jobs:
|
||||
- name: Update Loki Snapshots
|
||||
run: yarn test-visual:loki-update
|
||||
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Commit and Push Changes
|
||||
run: |
|
||||
git config --global user.name "Metabase Automation"
|
||||
git config --global user.email "github-automation@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git add .loki/**
|
||||
git commit -m "Update Loki Snapshots"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
- name: Remove 'loki-update' label
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{secrets.METABASE_AUTOMATION_USER_TOKEN}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'loki-update'
|
||||
});
|
||||
})
|
||||
|
||||
28
.github/workflows/loki.yml
vendored
28
.github/workflows/loki.yml
vendored
@@ -3,26 +3,7 @@ name: Loki Visual Regression Testing
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_ci: ${{ steps.changes.outputs.frontend_ci }}
|
||||
frontend_sources: ${{ steps.changes.outputs.frontend_sources }}
|
||||
frontend_loki_ci: ${{ steps.changes.outputs.frontend_loki_ci }}
|
||||
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
|
||||
|
||||
visual-test:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.frontend_ci == 'true' || needs.files-changed.outputs.frontend_sources == 'true' || needs.files-changed.outputs.frontend_loki_ci == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
docker:
|
||||
@@ -38,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare frontend environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
@@ -52,9 +33,7 @@ jobs:
|
||||
run: yarn build-pure:cljs
|
||||
|
||||
- name: Build Storybook
|
||||
# We won't be able to use Storybook play's `userEvent` with production build
|
||||
# see https://github.com/storybookjs/storybook/issues/19758
|
||||
run: NODE_ENV=development yarn build-storybook
|
||||
run: yarn build-storybook
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
@@ -71,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Run Loki Visual Tests
|
||||
run: |
|
||||
yarn test-visual:loki
|
||||
yarn loki test
|
||||
|
||||
- name: Generate Visual Report on Failure
|
||||
if: failure()
|
||||
@@ -83,6 +62,5 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: loki-report
|
||||
include-hidden-files: true
|
||||
path: .loki/
|
||||
if-no-files-found: ignore
|
||||
|
||||
18
.github/workflows/migrations.yml
vendored
18
.github/workflows/migrations.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Lint migrations
|
||||
name: Migrations
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -9,14 +9,14 @@ on:
|
||||
|
||||
jobs:
|
||||
|
||||
lint-migrations:
|
||||
check-migrations:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: lint-migrations
|
||||
- name: Verify Liquibase Migrations
|
||||
run: ./bin/lint-migrations-file.sh
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: check-migrations
|
||||
- name: Verify Liquibase Migrations
|
||||
run: ./bin/lint-migrations-file.sh
|
||||
|
||||
48
.github/workflows/on-epic-close.yml
vendored
48
.github/workflows/on-epic-close.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: A New Feature Slack Notification
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
on-epic-closed:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
if: ${{ contains(github.event.issue.labels.*.name, '.Epic') || contains(github.event.issue.labels.*.name, 'Type:New Feature') }}
|
||||
steps:
|
||||
- name: Setting title
|
||||
uses: actions/github-script@v6
|
||||
id: vars
|
||||
with:
|
||||
script: |
|
||||
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
|
||||
- name: Extracting team name
|
||||
id: team_name
|
||||
run: |
|
||||
team_label=$(echo "${{ join(github.event.issue.labels.*.name, ',') }}" | grep -o '\.Team/[^,]*' | head -n 1)
|
||||
echo $team_label
|
||||
echo "team_label=${team_label}" >> "$GITHUB_OUTPUT"
|
||||
team_name=${team_label#*.Team/}
|
||||
echo $team_name
|
||||
echo "team_name=${team_name^^}_SLACK_EPIC_WEBHOOK_URL" >> "$GITHUB_OUTPUT"
|
||||
- name: Send the issue ${{ github.event.issue.number }} to Slack
|
||||
id: slack
|
||||
if: ${{ steps.team_name.outputs.team_label }}
|
||||
uses: slackapi/slack-github-action@v1.26.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "${{ steps.vars.outputs.issue_title }}\n${{ github.event.issue.html_url }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets[steps.team_name.outputs.team_name] }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
91
.github/workflows/percy-issue-comment.yml
vendored
Normal file
91
.github/workflows/percy-issue-comment.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
# Triggers Percy job by "@metabase-bot run visual tests" comment in a PR
|
||||
name: PercyIssueComment
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
pr_info:
|
||||
if: github.event.comment.body == '@metabase-bot run visual tests'
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
pull_request_number: ${{ fromJson(steps.fetch_pr.outputs.data).head.number }}
|
||||
branch_name: ${{ fromJson(steps.fetch_pr.outputs.data).head.ref }}
|
||||
commit_sha: ${{ fromJson(steps.fetch_pr.outputs.data).head.sha }}
|
||||
steps:
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.pr_info.outputs.branch_name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify Membership
|
||||
id: verify-membership
|
||||
uses: ./.github/actions/verify-membership
|
||||
with:
|
||||
github_token: ${{ steps.generate-token.outputs.token }}
|
||||
organization_name: metabase
|
||||
username: ${{ github.event.comment.user.login }}
|
||||
- run: |
|
||||
result=${{ steps.verify-membership.outputs.is_member }}
|
||||
if [ $result == false ]; then
|
||||
echo User ${{ github.event.comment.user.login }} is not a member of Metabase organization
|
||||
exit 1
|
||||
fi
|
||||
- name: Fetch issue
|
||||
uses: octokit/request-action@v2.x
|
||||
id: fetch_issue
|
||||
with:
|
||||
route: GET ${{ github.event.issue.url }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Fetch PR
|
||||
uses: octokit/request-action@v2.x
|
||||
id: fetch_pr
|
||||
with:
|
||||
route: GET ${{ fromJson(steps.fetch_issue.outputs.data).pull_request.url }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
percy:
|
||||
timeout-minutes: 30
|
||||
runs-on: buildjet-4vcpu-ubuntu-2004
|
||||
needs: [pr_info]
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MB_EDITION: ee
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.pr_info.outputs.branch_name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: percy-issue-comment
|
||||
|
||||
- name: Prepare cypress environment
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- run: ./bin/build.sh
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
- name: Run maildev
|
||||
run: docker run -d -p 1080:1080 -p 1025:1025 maildev/maildev:2.0.5
|
||||
- name: Percy Test
|
||||
run: yarn run test-visual-run
|
||||
env:
|
||||
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||
PERCY_COMMIT: ${{ needs.pr_info.outputs.commit_sha }}
|
||||
PERCY_BRANCH: ${{ needs.pr_info.outputs.branch_name }}
|
||||
PERCY_PULL_REQUEST: ${{ needs.pr_info.outputs.pull_request_number }}
|
||||
39
.github/workflows/percy-visual-label.yml
vendored
Normal file
39
.github/workflows/percy-visual-label.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# Triggers Percy job on pull requests that contain "visual" label
|
||||
name: Percy visual tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, labeled]
|
||||
|
||||
jobs:
|
||||
percy:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'visual')
|
||||
timeout-minutes: 30
|
||||
runs-on: buildjet-4vcpu-ubuntu-2004
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MB_EDITION: ee
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
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: percy-visual-label
|
||||
- name: Prepare cypress environment
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- run: ./bin/build.sh
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
- name: Run maildev
|
||||
run: docker run -d -p 1080:1080 -p 1025:1025 maildev/maildev:2.0.5
|
||||
- name: Percy Test
|
||||
run: yarn run test-visual-run
|
||||
env:
|
||||
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||
17
.github/workflows/perf-test-labeled.yml
vendored
17
.github/workflows/perf-test-labeled.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: Perf test command
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ labeled ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-perf-test:
|
||||
if: ${{ github.event.label.name == 'Run-perf' }}
|
||||
uses: ./.github/workflows/perf-test.yml
|
||||
with:
|
||||
wait_for_uberjar: true
|
||||
secrets: inherit
|
||||
150
.github/workflows/perf-test.yml
vendored
150
.github/workflows/perf-test.yml
vendored
@@ -1,150 +0,0 @@
|
||||
name: CI for Perf tests
|
||||
run-name: Run perf test for ${{ github.ref_name }} by @${{ github.actor }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
wait_for_uberjar:
|
||||
description: "Wait for uberjar build"
|
||||
required: false
|
||||
type: boolean
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Build Metabase Docker image
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Login to Amazon ECR
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
with:
|
||||
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
|
||||
- name: Wait for uberjar
|
||||
id: wait_for_uberjar
|
||||
if: ${{ inputs.wait_for_uberjar == true }}
|
||||
run: |
|
||||
## Get workflow run id for uberjar build
|
||||
curl -Ls --output e2e-tests.json \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/workflows/e2e-tests.yml/runs?head_sha=${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ID=$(jq -r '.workflow_runs[0].id' e2e-tests.json)
|
||||
## Wait for uberjar build to complete
|
||||
while [ true ]; do
|
||||
curl -Ls --output uberjar.json \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/runs/${ID}/jobs?filter=latest
|
||||
jq -r '.jobs[] | select(.name == "build (ee)") | .steps[] | select(.name == "Prepare uberjar artifact") | .status' uberjar.json | grep -q "completed" && break
|
||||
echo "Waiting for uberjar build..."
|
||||
sleep 10
|
||||
done
|
||||
echo "run_id=$(jq -r '.workflow_runs[0].id' e2e-tests.json)" >> $GITHUB_OUTPUT
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ inputs.wait_for_uberjar && steps.wait_for_uberjar.outputs.run_id || github.run_id }}
|
||||
- name: Move uberjar to bin/docker
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar
|
||||
mv target/uberjar/metabase.jar bin/docker/metabase.jar
|
||||
- name: Build container
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: bin/docker/
|
||||
platforms: linux/amd64
|
||||
network: host
|
||||
tags: ${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/metabase-enterprise:pr${{ github.event.number }}
|
||||
push: true
|
||||
launch_perf_test:
|
||||
needs: [ build ]
|
||||
runs-on: ubuntu-latest
|
||||
name: Launch Perf Test
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v2
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
|
||||
tags: tag:ci
|
||||
version: 1.50.1
|
||||
sha256sum: d9fe6b480fb5078f0aa57dace686898dda7e2a768884271159faa74846bfb576
|
||||
- name: Create OIDC Token
|
||||
id: create-oidc-token
|
||||
shell: bash
|
||||
run: |
|
||||
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
|
||||
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
|
||||
echo "::add-mask::${IDTOKEN}"
|
||||
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
|
||||
- name: Setup Kube Context
|
||||
uses: azure/k8s-set-context@v2
|
||||
with:
|
||||
method: kubeconfig
|
||||
kubeconfig: |
|
||||
kind: Config
|
||||
apiVersion: v1
|
||||
current-context: default
|
||||
clusters:
|
||||
- name: default
|
||||
cluster:
|
||||
certificate-authority-data: ${{ secrets.PR_ENV_K8S_CERTIFICATE_AUTHORITY_DATA }}
|
||||
server: ${{ secrets.PR_ENV_K8S_SERVER }}
|
||||
users:
|
||||
- name: oidc-token
|
||||
user:
|
||||
token: ${{ steps.create-oidc-token.outputs.IDTOKEN }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: default
|
||||
namespace: default
|
||||
user: oidc-token
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Download Deployment YAML template
|
||||
run: aws s3 cp s3://metabase-pr-env/perf-test-pr.yml.tmpl ./perf-test-pr.yml.tmpl
|
||||
- name: Trim SHA
|
||||
env:
|
||||
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
id: split
|
||||
run: echo "::set-output name=fragment::${SHA:5}"
|
||||
- name: Render Deployment YAML
|
||||
uses: nowactions/envsubst@v1
|
||||
with:
|
||||
input: ./perf-test-pr.yml.tmpl
|
||||
output: ./perf-test-pr.yml
|
||||
env:
|
||||
IMAGE_TAG: pr${{ github.event.number }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
RUN_ID: ${{ steps.split.outputs.fragment }}
|
||||
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
TEST_NAME: test
|
||||
- name: Deploy PR Review ENV
|
||||
run: |
|
||||
kubectl apply -f ./perf-test-pr.yml
|
||||
15
.github/workflows/pr-env-close-unlabel.yml
vendored
15
.github/workflows/pr-env-close-unlabel.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: PR Closed or Unlabeled
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ closed, unlabeled ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr-env:
|
||||
if: ${{ github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'PR-Env') || github.event.label.name == 'PR-Env' }}
|
||||
uses: ./.github/workflows/pr-env-destroy.yml
|
||||
secrets: inherit
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user