Compare commits
5 Commits
attach-ren
...
atomic-rep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76e4061dc0 | ||
|
|
23861c5304 | ||
|
|
388e397633 | ||
|
|
6a4b9e4959 | ||
|
|
95ffe8e78e |
@@ -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,20 +14,11 @@
|
||||
: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/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/require-shape-checker {: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}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
|
||||
;;
|
||||
;; disabled linters
|
||||
@@ -44,7 +34,6 @@
|
||||
;; :docstring-leading-trailing-whitespace {:level :warning}
|
||||
;; :docstring-no-summary {:level :warning}
|
||||
;; :shadowed-var {:level :warning}
|
||||
;; :def-fn {:level :warning}
|
||||
|
||||
:unresolved-symbol
|
||||
{:exclude
|
||||
@@ -125,191 +114,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} ; 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.bootstrap #{metabase.bootstrap}
|
||||
metabase.cmd #{} ; there are no namespaces here since you shouldn't be using this in any other module.
|
||||
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} ; 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.native-query-analyzer #{metabase.native-query-analyzer}
|
||||
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
|
||||
metabase.pulse.attachment
|
||||
metabase.pulse.markdown
|
||||
metabase.pulse.parameters
|
||||
metabase.pulse.preview
|
||||
metabase.pulse.render
|
||||
metabase.pulse.render.image-bundle
|
||||
metabase.pulse.render.js-svg
|
||||
metabase.pulse.render.style} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.query-processor :any ; TODO omg scream, 29 namespaces used outside of the module. WHAT THE HECC
|
||||
metabase.related #{metabase.related}
|
||||
metabase.sample-data #{metabase.sample-data}
|
||||
metabase.search #{metabase.search}
|
||||
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.shared #{metabase.shared.dashboards.constants
|
||||
metabase.shared.formatting.constants
|
||||
metabase.shared.formatting.date
|
||||
metabase.shared.formatting.numbers
|
||||
metabase.shared.models.visualization-settings
|
||||
metabase.shared.parameters.parameters
|
||||
metabase.shared.util.currency
|
||||
metabase.shared.util.i18n
|
||||
metabase.shared.util.internal.time-common
|
||||
metabase.shared.util.namespaces
|
||||
metabase.shared.util.time} ; TODO -- consolidate these into a real API namespace.
|
||||
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} ; 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.bootstrap #{}
|
||||
metabase.cmd :any
|
||||
metabase.compatibility :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.native-query-analyzer :any
|
||||
metabase.permissions :any
|
||||
metabase.plugins :any
|
||||
metabase.public-settings :any
|
||||
metabase.pulse :any
|
||||
metabase.query-processor :any
|
||||
metabase.related :any
|
||||
metabase.sample-data :any
|
||||
metabase.search :any
|
||||
metabase.server :any
|
||||
metabase.setup :any
|
||||
metabase.shared :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
|
||||
@@ -344,7 +149,7 @@
|
||||
honeysql.helpers hh
|
||||
honeysql.types htypes
|
||||
java-time t
|
||||
macaw.core macaw
|
||||
macaw.core mac
|
||||
malli.core mc
|
||||
malli.error me
|
||||
malli.experimental mx
|
||||
@@ -357,15 +162,6 @@
|
||||
metabase-enterprise.sandbox.api.table table
|
||||
metabase-enterprise.test met
|
||||
metabase.analytics.stats stats
|
||||
metabase.analyze.query-results qr
|
||||
metabase.analyze.classifiers.category classifiers.category
|
||||
metabase.analyze.classifiers.core classifiers
|
||||
metabase.analyze.classifiers.name classifiers.name
|
||||
metabase.analyze.classifiers.no-preview-display classifiers.no-preview-display
|
||||
metabase.analyze.classifiers.text-fingerprint classifiers.text-fingerprint
|
||||
metabase.analyze.fingerprint.fingerprinters fingerprinters
|
||||
metabase.analyze.fingerprint.insights insights
|
||||
metabase.analyze.fingerprint.schema fingerprint.schema
|
||||
metabase.api.activity api.activity
|
||||
metabase.api.alert api.alert
|
||||
metabase.api.automagic-dashboards api.magic
|
||||
@@ -407,9 +203,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.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
|
||||
@@ -419,6 +219,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
|
||||
@@ -429,6 +230,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
|
||||
@@ -457,7 +262,6 @@
|
||||
metabase.legacy-mbql.schema mbql.s
|
||||
metabase.legacy-mbql.util mbql.u
|
||||
metabase.lib.util lib.util
|
||||
metabase.lib.util.match lib.util.match
|
||||
metabase.models.activity activity
|
||||
metabase.models.application-permissions-revision a-perm-revision
|
||||
metabase.models.bookmark bookmark
|
||||
@@ -485,7 +289,6 @@
|
||||
metabase.models.timeline timeline
|
||||
metabase.models.timeline-event timeline-event
|
||||
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
|
||||
@@ -539,7 +342,14 @@
|
||||
metabase.server.protocols server.protocols
|
||||
metabase.shared.util shared.u
|
||||
metabase.shared.util.currency currency
|
||||
metabase.sync.analyze.fingerprint sync.fingerprint
|
||||
metabase.sync.analyze.classifiers.category classifiers.category
|
||||
metabase.sync.analyze.classifiers.name classifiers.name
|
||||
metabase.sync.analyze.classifiers.no-preview-display classifiers.no-preview-display
|
||||
metabase.sync.analyze.classifiers.text-fingerprint classifiers.text-fingerprint
|
||||
metabase.sync.analyze.fingerprint fingerprint
|
||||
metabase.sync.analyze.fingerprint.fingerprinters fingerprinters
|
||||
metabase.sync.analyze.fingerprint.insights insights
|
||||
metabase.sync.analyze.query-results qr
|
||||
metabase.sync.field-values field-values
|
||||
metabase.sync.interface i
|
||||
metabase.sync.schedules sync.schedules
|
||||
@@ -559,6 +369,9 @@
|
||||
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.date-2 u.date
|
||||
metabase.util.date-2.common u.date.common
|
||||
@@ -579,7 +392,6 @@
|
||||
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
|
||||
@@ -590,7 +402,12 @@
|
||||
ring.adapter.jetty9.servlet servlet
|
||||
saml20-clj.core saml
|
||||
toucan.db db
|
||||
toucan.models models}}}
|
||||
toucan.models models}}
|
||||
|
||||
:metabase/validate-deftest {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
:metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests {:level :warning}
|
||||
:metabase/warn-about-schema= {:level :warning}}
|
||||
|
||||
:lint-as
|
||||
{clojure.core.logic/defne clj-kondo.lint-as/def-catch-all
|
||||
@@ -611,6 +428,7 @@
|
||||
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
|
||||
@@ -641,33 +459,32 @@
|
||||
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
|
||||
metabase.xrays.domain-entities.malli/defn schema.core/defn
|
||||
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}
|
||||
|
||||
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
|
||||
{clojure.core/ns hooks.clojure.core/lint-ns
|
||||
cljs.test/deftest hooks.clojure.test/deftest
|
||||
{cljs.test/deftest hooks.clojure.test/deftest
|
||||
cljs.test/is hooks.clojure.test/is
|
||||
cljs.test/use-fixtures hooks.clojure.test/use-fixtures
|
||||
clojure.test/deftest hooks.clojure.test/deftest
|
||||
@@ -684,9 +501,6 @@
|
||||
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.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
|
||||
@@ -706,7 +520,6 @@
|
||||
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/register! hooks.metabase.driver/register!
|
||||
metabase.legacy-mbql.schema.macros/defclause hooks.metabase.legacy-mbql.schemas.macros/defclause
|
||||
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
|
||||
@@ -758,19 +571,6 @@
|
||||
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/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.malli.registry/def hooks.metabase.util.malli.registry/def}
|
||||
|
||||
:macroexpand
|
||||
@@ -781,15 +581,18 @@
|
||||
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
|
||||
@@ -805,8 +608,7 @@
|
||||
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.xrays.domain-entities.malli/define-getters-and-setters macros.metabase.xrays.domain-entities.malli/define-getters-and-setters}}
|
||||
metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values}}
|
||||
|
||||
:config-in-comment
|
||||
{:linters {:unresolved-symbol {:level :off}}}
|
||||
@@ -818,18 +620,18 @@
|
||||
;; 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}
|
||||
{:pattern "^metabase\\.(?:(?:lib)|(?:legacy-mbql))\\.*"
|
||||
{:pattern "^metabase\\.lib\\.*"
|
||||
:name metabase-lib}
|
||||
;;
|
||||
;; QP source namespaces: `metabase.query-processor.*`, `metabase-enterprise.sandbox.query-processor.*`, and
|
||||
@@ -841,10 +643,12 @@
|
||||
:config-in-ns
|
||||
{test-namespaces
|
||||
{:linters
|
||||
{:inline-def {:level :off}
|
||||
:missing-docstring {:level :off}
|
||||
:private-call {:level :off}
|
||||
:metabase/defsetting-must-specify-export {:level :off}}
|
||||
{:inline-def {:level :off}
|
||||
:missing-docstring {:level :off}
|
||||
:private-call {:level :off}
|
||||
:metabase/defsetting-must-specify-export {:level :off}
|
||||
:hooks.metabase.test.data/mbql-query-first-arg {:level :error}
|
||||
:metabase/test-helpers-use-non-thread-safe-functions {:level :warning}}
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{clojure.core/defmacro hooks.clojure.core/non-thread-safe-form-should-end-with-exclamation
|
||||
@@ -885,26 +689,19 @@
|
||||
metabase.test/with-temp {:message "Don't use application database inside MLv2 code"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{metabase.driver {:message "MLv2 code should be independent of driver code/methods"}
|
||||
metabase.test {:message "MLv2 tests should only use MLv2 test helpers"}
|
||||
metabase.test.data {:message "MLv2 tests should only use MLv2 test helpers"}
|
||||
metabase.util.malli.schema {:message "Prefer metabase.lib.schema schemas in MLv2 code"}
|
||||
toucan.db {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.models {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.util.test {:message "Don't use application database inside MLv2 code"}
|
||||
toucan2.core {:message "Don't use application database inside MLv2 code"}}}}
|
||||
{toucan.db {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.models {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.util.test {:message "Don't use application database inside MLv2 code"}
|
||||
toucan2.core {:message "Don't use application database inside MLv2 code"}}}}
|
||||
|
||||
qp-and-driver-source-namespaces
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
{metabase.util.malli.schema {:message "Prefer metabase.lib.schema or metabase.legacy-mbql.schema schemas in QP/driver code"}
|
||||
toucan.db {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.models {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.util.test {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan2.core {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}}
|
||||
{toucan.db {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.models {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.util.test {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan2.core {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}}
|
||||
|
||||
:discouraged-var
|
||||
{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/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"}}}}}}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns hooks.clojure.core
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn- node->qualified-symbol [node]
|
||||
@@ -16,7 +15,7 @@
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(def ^:private symbols-allowed-in-fns-not-ending-in-an-exclamation-point
|
||||
(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!
|
||||
@@ -122,8 +121,7 @@
|
||||
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.impl/get-or-create-test-data-db!
|
||||
metabase.test.data.interface/create-db!
|
||||
metabase.test.data.interface/destroy-db!
|
||||
metabase.test.data.oracle/create-user!
|
||||
@@ -160,7 +158,7 @@
|
||||
(walk f child)))]
|
||||
(walk (fn [form]
|
||||
(when-let [qualified-symbol (node->qualified-symbol form)]
|
||||
(when (and (not (contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol))
|
||||
(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`."
|
||||
@@ -204,126 +202,3 @@
|
||||
: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,8 +1,6 @@
|
||||
(ns hooks.clojure.test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]
|
||||
[hooks.common]))
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(def ^:private disallowed-parallel-forms
|
||||
"Things you should not be allowed to use inside parallel tests. Besides these, anything ending in `!` not whitelisted
|
||||
@@ -22,7 +20,6 @@
|
||||
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
|
||||
@@ -32,6 +29,7 @@
|
||||
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
|
||||
@@ -49,6 +47,7 @@
|
||||
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
|
||||
@@ -107,9 +106,18 @@
|
||||
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!})
|
||||
metabase.query-processor.store/store-database!})
|
||||
|
||||
(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- warn-about-disallowed-parallel-forms [form]
|
||||
(letfn [(error! [form message]
|
||||
@@ -117,7 +125,7 @@
|
||||
:message message
|
||||
:type :metabase/validate-deftest)))
|
||||
(f [form]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol form)]
|
||||
(when-let [qualified-symbol (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))
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns hooks.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.pprint]))
|
||||
(: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
|
||||
@@ -370,40 +369,3 @@
|
||||
arg])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn node->qualified-symbol [node]
|
||||
(try
|
||||
(when (hooks/token-node? node)
|
||||
(let [sexpr (hooks/sexpr node)]
|
||||
(when (symbol? sexpr)
|
||||
(let [resolved (hooks/resolve {:name sexpr})]
|
||||
(cond
|
||||
(and resolved (:ns resolved))
|
||||
(symbol (name (:ns resolved)) (name (:name resolved)))
|
||||
|
||||
;; if it wasn't resolved but is still qualified it's probably using the full namespace name rather than an
|
||||
;; alias.
|
||||
(qualified-symbol? sexpr)
|
||||
sexpr)))))
|
||||
;; some symbols like `*count/Integer` aren't resolvable.
|
||||
(catch Exception _
|
||||
nil)))
|
||||
|
||||
(defn format-string-specifier-count
|
||||
"Number of things like `%s` in a format string, not counting newlines (`%n`) or escaped percent signs (`%%`). For
|
||||
checking the number of args to something that takes a format string."
|
||||
[format-string]
|
||||
(count (re-seq #"(?<!%)%(?![%n])" format-string)))
|
||||
|
||||
(comment
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%s %%")
|
||||
|
||||
;; should be 2
|
||||
(format-string-specifier-count "%s %%%n%s")
|
||||
|
||||
;; should be 0
|
||||
(format-string-specifier-count "%n%%%%")
|
||||
|
||||
;; should be 1
|
||||
(format-string-specifier-count "%-02d"))
|
||||
@@ -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
|
||||
@@ -90,8 +92,6 @@
|
||||
metabot-get-prompt-templates-url
|
||||
metabot-prompt-generator-token-limit
|
||||
multi-setting-read-only
|
||||
notebook-native-preview-shown
|
||||
notebook-native-preview-sidebar-width
|
||||
notification-link-base-url
|
||||
num-metabot-choices
|
||||
openai-api-key
|
||||
@@ -162,6 +162,7 @@
|
||||
token-status
|
||||
toucan-name
|
||||
uncached-setting
|
||||
uploads-table-prefix
|
||||
user-visibility
|
||||
version
|
||||
version-info
|
||||
@@ -139,7 +139,7 @@
|
||||
(when-not ((some-fn symbol? nil?) (hooks/sexpr table))
|
||||
(hooks/reg-finding! (assoc (meta table)
|
||||
:message "First arg to mbql-query should be either a table name symbol or nil."
|
||||
:type :metabase/mbql-query-first-arg)))
|
||||
:type ::mbql-query-first-arg)))
|
||||
(let [result (replace-$id-special-tokens query)
|
||||
;; HACK I'm not sure WHY it works but I ran into https://github.com/clj-kondo/clj-kondo/issues/1773 when
|
||||
;; trying to get this working -- for some magical reason wrapping the whole thing in a `do` form seems to fix
|
||||
@@ -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,4 +1,4 @@
|
||||
(ns macros.metabase.xrays.domain-entities.malli)
|
||||
(ns macros.metabase.domain-entities.malli)
|
||||
|
||||
(defmacro define-getters-and-setters [schema & defs]
|
||||
`(do
|
||||
@@ -1,6 +0,0 @@
|
||||
(ns macros.metabase-enterprise.advanced-permissions.api.util-test
|
||||
(:require [macros.common]))
|
||||
|
||||
(defmacro with-impersonations! [impersonations-and-attributes-map & body]
|
||||
`(let [~(macros.common/ignore-unused '&group) ~impersonations-and-attributes-map]
|
||||
~@body))
|
||||
@@ -1,45 +0,0 @@
|
||||
(ns hooks.metabase.api.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn route-fn-name
|
||||
"route fn hook"
|
||||
[method route]
|
||||
(let [route (if (vector? route) (first route) route)]
|
||||
(-> (str (name method) route)
|
||||
(str/replace #"/" "_")
|
||||
symbol)))
|
||||
|
||||
(defn defendpoint
|
||||
[arg]
|
||||
(letfn [(update-defendpoint [node]
|
||||
(let [[_defendpoint method route & body] (:children node)]
|
||||
(api/list-node
|
||||
(list
|
||||
(api/token-node 'do)
|
||||
(api/token-node (symbol "compojure.core" (str (api/sexpr method))))
|
||||
(-> (api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/defn)
|
||||
(api/token-node (route-fn-name (api/sexpr method) (api/sexpr route)))
|
||||
body))
|
||||
(with-meta (meta node)))))))]
|
||||
(update arg :node update-defendpoint)))
|
||||
|
||||
(comment
|
||||
(-> {:node (-> '(api/defendpoint POST "/:id/copy"
|
||||
"Copy a `Card`, with the new name 'Copy of _name_'"
|
||||
[id]
|
||||
{id [:maybe ms/PositiveInt]}
|
||||
(let [orig-card (api/read-check Card id)
|
||||
new-name (str (trs "Copy of ") (:name orig-card))
|
||||
new-card (assoc orig-card :name new-name)]
|
||||
(-> (card/create-card! new-card @api/*current-user*)
|
||||
hydrate-card-details
|
||||
(assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*)))))
|
||||
pr-str
|
||||
api/parse-string)}
|
||||
defendpoint
|
||||
:node
|
||||
api/sexpr))
|
||||
@@ -1,18 +0,0 @@
|
||||
(ns hooks.metabase.driver
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn register!
|
||||
"Register the driver keyword so LSP can jump to it."
|
||||
[x]
|
||||
(letfn [(update-driver-keyword [k]
|
||||
(api/reg-keyword! k 'metabase.driver/register!))
|
||||
(update-children [[_register! k :as children]]
|
||||
(if (api/keyword-node? k)
|
||||
(update (vec children) 1 update-driver-keyword)
|
||||
children))
|
||||
(update-node [node]
|
||||
(if (api/list-node? node)
|
||||
(update node :children update-children)
|
||||
node))]
|
||||
(update x :node update-node)))
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns hooks.metabase.util
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[hooks.common]))
|
||||
|
||||
(defn format-color [{:keys [node], :as x}]
|
||||
(let [[_format-color _color format-string-node & args] (:children node)]
|
||||
(when (api/string-node? format-string-node)
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string-node))
|
||||
actual-arg-count (count args)]
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "metabase.util/format-color format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :format))))))
|
||||
x)
|
||||
@@ -1,126 +0,0 @@
|
||||
(ns hooks.metabase.util.log
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.string :as str]
|
||||
[clojure.walk :as walk]
|
||||
[hooks.common]))
|
||||
|
||||
;;;; Checks for both log + logf
|
||||
|
||||
(defn- disallow-i18n [node]
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(when-let [qualified-symbol (hooks.common/node->qualified-symbol x)]
|
||||
(when (and (= (namespace qualified-symbol) "metabase.util.i18n")
|
||||
(or (str/includes? (name qualified-symbol) "tru")
|
||||
(str/includes? (name qualified-symbol) "trs")))
|
||||
(api/reg-finding! (assoc (meta x)
|
||||
:message "do not i18n the logs!"
|
||||
:type :metabase/validate-logging)))))
|
||||
node))
|
||||
|
||||
(defn- check-position-of-exception-arg
|
||||
"Tell people to do (info e \"whatever\") instead of (info \"whatever\" e)."
|
||||
[node]
|
||||
(let [[f _first-arg & args] (:children node)]
|
||||
(doseq [arg args]
|
||||
(when (and (api/token-node? arg)
|
||||
(= (api/sexpr arg) 'e))
|
||||
(api/reg-finding! (assoc (meta arg)
|
||||
:message (format "pass exceptions as the first arg to %s" (api/sexpr f))
|
||||
:type :metabase/validate-logging))))))
|
||||
|
||||
(defn- common-checks [node]
|
||||
(check-position-of-exception-arg node)
|
||||
(disallow-i18n node))
|
||||
|
||||
(defn- check-for-i18n-format-specifiers
|
||||
"Make sure people aren't using things like {0} in their log messages."
|
||||
[node string-node]
|
||||
(when (re-find #"\{\d+\}" (api/sexpr string-node))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message "this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
;;; log only
|
||||
|
||||
(defn- check-not-format-string
|
||||
"Make sure we're not using something like log/trace with a format string."
|
||||
[{[f] :children, :as node} string-node]
|
||||
(when (pos? (hooks.common/format-string-specifier-count (api/sexpr string-node)))
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "%s used with a format string, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str (api/sexpr f) "f"))
|
||||
:type :metabase/validate-logging))))
|
||||
|
||||
(defn- format-node? [node]
|
||||
(when (api/list-node? node)
|
||||
(let [[x] (:children node)]
|
||||
(and (api/token-node? x)
|
||||
(or (= (hooks.common/node->qualified-symbol x) 'clojure.core/format)
|
||||
(= (api/sexpr x) 'format))))))
|
||||
|
||||
(defn- warn-against-log+format-instead-of-logf
|
||||
"Tell people to use things like infof instead of info + format."
|
||||
[{[f] :children, :as node} [x :as args]]
|
||||
(when (and (= (count args) 1)
|
||||
(api/list-node? x))
|
||||
(when (format-node? x)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Use %s instead of %s + format"
|
||||
(str (api/sexpr f) "f")
|
||||
(api/sexpr f))
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
(defn info
|
||||
"Valid log, debug, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e & [x :as args]] (if (and (api/token-node? (first args))
|
||||
(or (api/string-node? (second args))
|
||||
(format-node? (second args))))
|
||||
args
|
||||
(cons nil args))]
|
||||
(when (api/string-node? x)
|
||||
(check-for-i18n-format-specifiers node x)
|
||||
(check-not-format-string node x))
|
||||
(warn-against-log+format-instead-of-logf node args))
|
||||
x)
|
||||
|
||||
;;;; logf only
|
||||
|
||||
(defn- check-format-string-arg-count
|
||||
"Make sure the number of args passed to something like tracef match the number of format string specifiers."
|
||||
[{[f] :children, :as node} format-string args]
|
||||
(let [expected-arg-count (hooks.common/format-string-specifier-count (api/sexpr format-string))
|
||||
actual-arg-count (count args)]
|
||||
(when (zero? expected-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "Don't use %s with no format string arguments, use %s instead."
|
||||
(api/sexpr f)
|
||||
(str/replace (str (api/sexpr f)) #"f$" ""))
|
||||
:type :metabase/validate-logging)))
|
||||
(when-not (= expected-arg-count actual-arg-count)
|
||||
(api/reg-finding! (assoc (meta node)
|
||||
:message (format "log format string expects %d arguments instead of %d."
|
||||
expected-arg-count
|
||||
actual-arg-count)
|
||||
:type :metabase/validate-logging)))))
|
||||
|
||||
|
||||
|
||||
(defn infof
|
||||
"Valid logf, debugf, etc."
|
||||
[{:keys [node], :as x}]
|
||||
(common-checks node)
|
||||
(let [[_f & args] (:children node)
|
||||
[_e format-string & args] (if (api/string-node? (first args))
|
||||
(cons nil args)
|
||||
args)]
|
||||
;; TODO -- maybe we can error if you did not pass a format string, e.g. (log/infof 1 2) is obviously incorrect.
|
||||
(when (api/string-node? format-string)
|
||||
(check-for-i18n-format-specifiers node format-string)
|
||||
(check-format-string-arg-count node format-string args)))
|
||||
x)
|
||||
@@ -1,19 +0,0 @@
|
||||
(ns ^:mb/once hooks.common-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.common]))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom nil)}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "metabase.util.i18n/tru"))))))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test-2
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom {:clj {:clj '{hooks.common-test {:qualify-ns {i18n metabase.util.i18n}}}}})
|
||||
:lang :clj
|
||||
:base-lang :clj
|
||||
:ns {:name 'hooks.common-test}}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
(hooks.common/node->qualified-symbol (api/parse-string "i18n/tru"))))))
|
||||
@@ -1,77 +0,0 @@
|
||||
(ns ^:mb/once hooks.metabase.util.log-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.util.log]))
|
||||
|
||||
(defn- warnings
|
||||
[form]
|
||||
(let [f (if (str/ends-with? (name (first form)) "f")
|
||||
hooks.metabase.util.log/infof
|
||||
hooks.metabase.util.log/info)]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-logging {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(f {:node (api/parse-string (pr-str form))})
|
||||
(mapv :message @(:findings clj-kondo.impl.utils/*ctx*)))))
|
||||
|
||||
(deftest ^:parallel warn-on-missing-format-args-test
|
||||
(testing "should fail, missing a format arg"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 0."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries.")
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries."))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-1
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["log format string expects 1 arguments instead of 2."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after %d retries." 1 2)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after %d retries." 1 2))))
|
||||
|
||||
(deftest ^:parallel warn-on-too-many-format-args-test-2
|
||||
(testing "should fail, too many format args"
|
||||
(are [form] (= ["this looks like an i18n format string. Don't use identifiers like {0} in logging."
|
||||
"Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."
|
||||
"log format string expects 0 arguments instead of 1."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock was acquired after {0} retries." 1)
|
||||
(metabase.util.log/warnf e "Migration lock was acquired after {0} retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-args-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["metabase.util.log/warn used with a format string, use metabase.util.log/warnf instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn "Migration lock was acquired after %d retries." 1)
|
||||
(metabase.util.log/warn e "Migration lock was acquired after %d retries." 1))))
|
||||
|
||||
(deftest ^:parallel warn-on-format-without-logf-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Use metabase.util.log/warnf instead of metabase.util.log/warn + format"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (format "Migration lock was acquired after %d retries." 1))
|
||||
(metabase.util.log/warn e (format "Migration lock was acquired after %d retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-logf-with-no-format-args-test
|
||||
(testing "should fail, has format args but is warn rather than warnf"
|
||||
(are [form] (= ["Don't use metabase.util.log/warnf with no format string arguments, use metabase.util.log/warn instead."]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf "Migration lock cleared.")
|
||||
(metabase.util.log/warnf e "Migration lock cleared."))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-1
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warnf (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warnf e (metabase.util.i18n/tru "Migration lock was acquired after {0} retries." 1)))))
|
||||
|
||||
(deftest ^:parallel warn-on-i18n-test-2
|
||||
(testing "should fail -- should not use i18n/tru or i18n/trs"
|
||||
(are [form] (= ["do not i18n the logs!"]
|
||||
(warnings (quote form)))
|
||||
(metabase.util.log/warn (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1))
|
||||
(metabase.util.log/warn e (metabase.util.i18n/trs "Migration lock was acquired after {0} retries." 1)))))
|
||||
@@ -35,7 +35,6 @@
|
||||
(eval . (put 'methodical/defmulti 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mi/define-simple-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mi/define-batched-hydration-method 'clojure-doc-string-elt 3))
|
||||
(eval . (put 'mr/def 'clojure-doc-string-elt 2))
|
||||
(eval . (put 'mu/defn 'clojure-doc-string-elt 2))
|
||||
;; Define custom indentation for functions inside metabase.
|
||||
;; This list isn't complete; add more forms as we come across them.
|
||||
@@ -85,7 +84,6 @@
|
||||
(clojure-indent-style . always-align)
|
||||
(cljr-favor-prefix-notation . nil)
|
||||
(cljr-insert-newline-after-require . t)
|
||||
(cljr-print-right-margin . 118)
|
||||
(clojure-docstring-fill-column . 118)
|
||||
(cider-preferred-build-tool . clojure-cli)
|
||||
(cider-default-cljs-repl . shadow-select)
|
||||
|
||||
210
.eslintrc
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"rules": {
|
||||
"strict": [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
],
|
||||
"no-empty": [1, { "allowEmptyCatch": true }],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "moment",
|
||||
"message": "Moment is deprecated, please use dayjs"
|
||||
},
|
||||
{
|
||||
"name": "moment-timezone",
|
||||
"message": "Moment is deprecated, please use dayjs"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"curly": [1, "all"],
|
||||
"eqeqeq": [1, "smart"],
|
||||
"import/no-default-export": 2,
|
||||
"import/no-named-as-default": 0,
|
||||
"import/no-commonjs": 1,
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
"alphabetize": {
|
||||
"order": "asc",
|
||||
"orderImportKind": "asc",
|
||||
"caseInsensitive": false
|
||||
},
|
||||
"groups": [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index"
|
||||
],
|
||||
"warnOnUnassignedImports": false
|
||||
}
|
||||
],
|
||||
"no-console": [2, { "allow": ["warn", "error", "errorBuffer"] }],
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/display-name": 1,
|
||||
"react/prop-types": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-find-dom-node": 0,
|
||||
"react/no-children-prop": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { "forbid": ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ "additionalHooks": "(useSyncedQueryString|useSafeAsyncFunction)" }
|
||||
],
|
||||
"prefer-const": [1, { "destructuring": "all" }],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": [
|
||||
"error",
|
||||
{
|
||||
"block": [
|
||||
"describe",
|
||||
"it",
|
||||
"context",
|
||||
"test",
|
||||
"tape",
|
||||
"fixture",
|
||||
"serial",
|
||||
"Feature",
|
||||
"Scenario",
|
||||
"Given",
|
||||
"And",
|
||||
"When",
|
||||
"Then",
|
||||
"describeWithSnowplow",
|
||||
"describeEE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"complexity": ["error", { "max": 54 }]
|
||||
},
|
||||
"globals": {
|
||||
"before": true,
|
||||
"cy": true,
|
||||
"Cypress": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"commonjs": true,
|
||||
"jest": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": ["react", "no-only-tests"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"settings": {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"typescript": true
|
||||
}
|
||||
},
|
||||
"import/ignore": ["\\.css$"],
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"legacyDecorators": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
"rules": {
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-literal-metabase-strings": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.unit.spec.*",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*"
|
||||
],
|
||||
"rules": {
|
||||
"no-unconditional-metabase-links-render": "off",
|
||||
"no-literal-metabase-strings": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true,
|
||||
"destructuredArrayIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
// This was introduced in 6.0.0
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"extends": [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"plugins": ["jest", "jest-dom", "testing-library"],
|
||||
"files": [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
"*.unit.spec.js",
|
||||
"*.unit.spec.jsx"
|
||||
],
|
||||
"rules": {
|
||||
"jest/valid-title": ["error", { "ignoreTypeOfDescribeName": true }],
|
||||
// In unit tests, you should use `act(() => {})` to invoke things causing state updates
|
||||
// and rerenders to remove lengthy warnings from jest. Disabling the rule allows this.
|
||||
"testing-library/no-unnecessary-act": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
224
.eslintrc.js
@@ -1,224 +0,0 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
// `postcss-modules` lints css modules class names, but it currently crashes
|
||||
// eslint on vscode. If you use webstorm or want to run the lint for the cli, you
|
||||
// can use this flag to enable it. This is set to true in CI
|
||||
const shouldLintCssModules =
|
||||
process.env.LINT_CSS_MODULES === "true" || process.env.CI;
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
strict: [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
vars: "all",
|
||||
args: "none",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
"no-empty": [1, { allowEmptyCatch: true }],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "moment",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
{
|
||||
name: "moment-timezone",
|
||||
message: "Moment is deprecated, please use dayjs",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
curly: [1, "all"],
|
||||
eqeqeq: [1, "smart"],
|
||||
"import/no-default-export": 2,
|
||||
"import/no-named-as-default": 0,
|
||||
"import/no-commonjs": 1,
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
orderImportKind: "asc",
|
||||
caseInsensitive: false,
|
||||
},
|
||||
groups: [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index",
|
||||
],
|
||||
warnOnUnassignedImports: false,
|
||||
},
|
||||
],
|
||||
"no-console": [2, { allow: ["warn", "error", "errorBuffer"] }],
|
||||
"react/no-is-mounted": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/display-name": 1,
|
||||
"react/prop-types": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 0,
|
||||
"react/no-find-dom-node": 0,
|
||||
"react/no-children-prop": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/jsx-no-target-blank": 2,
|
||||
"react/jsx-key": 2,
|
||||
"react/forbid-component-props": [2, { forbid: ["sx"] }],
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{ additionalHooks: "(useSyncedQueryString|useSafeAsyncFunction)" },
|
||||
],
|
||||
"prefer-const": [1, { destructuring: "all" }],
|
||||
"no-useless-escape": 0,
|
||||
"no-only-tests/no-only-tests": [
|
||||
"error",
|
||||
{
|
||||
block: [
|
||||
"describe",
|
||||
"it",
|
||||
"context",
|
||||
"test",
|
||||
"tape",
|
||||
"fixture",
|
||||
"serial",
|
||||
"Feature",
|
||||
"Scenario",
|
||||
"Given",
|
||||
"And",
|
||||
"When",
|
||||
"Then",
|
||||
"describeWithSnowplow",
|
||||
"describeEE",
|
||||
],
|
||||
},
|
||||
],
|
||||
complexity: ["error", { max: 54 }],
|
||||
...(shouldLintCssModules
|
||||
? {
|
||||
"postcss-modules/no-undef-class": "error",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
globals: {
|
||||
before: true,
|
||||
cy: true,
|
||||
Cypress: true,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
commonjs: true,
|
||||
jest: true,
|
||||
"jest/globals": true,
|
||||
},
|
||||
parser: "babel-eslint",
|
||||
plugins: ["react", "no-only-tests", "postcss-modules"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
],
|
||||
settings: {
|
||||
"import/internal-regex": "^metabase/|^metabase-lib/",
|
||||
"import/resolver": {
|
||||
webpack: {
|
||||
typescript: true,
|
||||
},
|
||||
},
|
||||
"import/ignore": ["\\.css$"],
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
"postcss-modules": {
|
||||
baseDir: "./frontend/src",
|
||||
},
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true,
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js", "*.jsx", "*.ts", "*.tsx"],
|
||||
rules: {
|
||||
"no-unconditional-metabase-links-render": "error",
|
||||
"no-literal-metabase-strings": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"*.unit.spec.*",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"release/**/*",
|
||||
],
|
||||
rules: {
|
||||
"no-unconditional-metabase-links-render": "off",
|
||||
"no-literal-metabase-strings": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
ignoreRestSiblings: true,
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
// This was introduced in 6.0.0
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react",
|
||||
],
|
||||
plugins: ["jest", "jest-dom", "testing-library"],
|
||||
files: [
|
||||
"*.unit.spec.ts",
|
||||
"*.unit.spec.tsx",
|
||||
"*.unit.spec.js",
|
||||
"*.unit.spec.jsx",
|
||||
],
|
||||
rules: {
|
||||
"jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
11
.github/CODEOWNERS
vendored
@@ -1,14 +1,3 @@
|
||||
# Before changing this file, make sure you have read how we use it:
|
||||
# https://www.notion.so/metabase/Pull-Request-Code-Review-Guide-374f05df889748b69d67af753f9bc9b8?pvs=4#36550c04ceab40b5a8b8bee6264b090c
|
||||
# TL;DR: if @camsaul hasn't been involved you're probably doing it wrong
|
||||
|
||||
src/metabase @camsaul
|
||||
modules @camsaul
|
||||
enterprise/backend @camsaul
|
||||
enterprise/backend/src/metabase_enterprise/advanced_permissions @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/audit_app @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/sso @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/task/truncate_audit_table.clj @noahmoss
|
||||
enterprise/backend/src/metabase_enterprise/internal_user.clj @noahmoss
|
||||
src/metabase/**/*permissions* @noahmoss
|
||||
src/metabase/integrations @noahmoss
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -17,6 +17,7 @@ A clear and concise description of what you want to happen.
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**How important is this feature to you?**
|
||||
Note: the more honest and specific you are here the more we will take you seriously.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,9 +1,5 @@
|
||||
> [!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.
|
||||
> 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.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
|
||||
9
.github/actions/build-e2e-matrix/action.yml
vendored
@@ -16,6 +16,7 @@ runs:
|
||||
const java = 11;
|
||||
|
||||
const defaultRunner = "ubuntu-22.04";
|
||||
const beefierRunner = "buildjet-2vcpu-ubuntu-2204";
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
@@ -36,9 +37,7 @@ runs:
|
||||
["dashboard-filters", {} ],
|
||||
["embedding", {} ],
|
||||
["filters", {} ],
|
||||
["filters-reproductions", {}],
|
||||
["joins", {} ],
|
||||
["metrics", {} ],
|
||||
["models", {} ],
|
||||
["native", {} ],
|
||||
["native-filters", {} ],
|
||||
@@ -46,14 +45,12 @@ runs:
|
||||
["organization", {} ],
|
||||
["permissions", {} ],
|
||||
["question", {} ],
|
||||
["question-reproductions", {} ],
|
||||
["search", {}],
|
||||
["sharing", {} ],
|
||||
["visualizations-charts", {} ],
|
||||
["visualizations-tabular", {} ],
|
||||
["oss-subset", { edition: 'oss', context: "special" } ],
|
||||
["slow", { context: "special" } ],
|
||||
["flaky", { context: "special" } ],
|
||||
["slow", { runner: beefierRunner, context: "special" } ],
|
||||
["flaky", { runner: beefierRunner, context: "special" } ],
|
||||
["mongo", { context: "qa-database" } ],
|
||||
];
|
||||
|
||||
|
||||
@@ -8,23 +8,23 @@ inputs:
|
||||
description: DockerHub password/token
|
||||
required: true
|
||||
maildev:
|
||||
description: Maildev container
|
||||
description: Maildev
|
||||
required: true
|
||||
default: 'false'
|
||||
openldap:
|
||||
description: OpenLDAP container
|
||||
description: Maildev
|
||||
required: true
|
||||
default: 'false'
|
||||
postgres:
|
||||
description: Postgres container
|
||||
description: Maildev
|
||||
required: true
|
||||
default: 'false'
|
||||
mysql:
|
||||
description: MySQL container
|
||||
description: Maildev
|
||||
required: true
|
||||
default: 'false'
|
||||
mongo:
|
||||
description: Mongo container
|
||||
description: Maildev
|
||||
required: true
|
||||
default: 'false'
|
||||
|
||||
|
||||
2
.github/actions/prepare-frontend/action.yml
vendored
@@ -23,6 +23,6 @@ runs:
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci nocache]') }}
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock', '**/patches/*.patch') }}
|
||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile --prefer-offline
|
||||
shell: bash
|
||||
|
||||
4
.github/file-paths.yaml
vendored
@@ -23,12 +23,10 @@ frontend_sources: &frontend_sources
|
||||
- "**/tsconfig*.json"
|
||||
- "package.json"
|
||||
- "babel.config.json"
|
||||
- ".eslintrc.js"
|
||||
- ".eslintrc"
|
||||
- "postcss.config.js"
|
||||
- "webpack.config.js"
|
||||
- "webpack.embedding-sdk.config.js"
|
||||
- "webpack.static-viz.config.js"
|
||||
- ".nvmrc"
|
||||
|
||||
frontend_specs: &frontend_specs
|
||||
- *shared_specs
|
||||
|
||||
86
.github/github-slack-map.json
vendored
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"abdhabli": "U06BAV8JFDW",
|
||||
"adam-james-v": "U02P27ZRRR8",
|
||||
"agodbey": "U05UJR2C35Z",
|
||||
"albertoperdomo": "U05811B1WLX",
|
||||
"AlessioLaiso": "U06MAAUDTS6",
|
||||
"alexandermkinn": "U023UTA4MUL",
|
||||
"alexyarosh": "U06GXH3RVPU",
|
||||
"alxnddr": "U01S9BUHH8X",
|
||||
"brunobergher": "U01M2JJVA80",
|
||||
"bshepherdson": "U03AK3B43GA",
|
||||
"calherries": "U03KFGJ7X8S",
|
||||
"camsaul": "U07DG6C3X",
|
||||
"cbalusek": "U02M2Q0DJLD",
|
||||
"cdeweyx": "U02BX2BKPQS",
|
||||
"crisptrutski": "U06C5MNFF0S",
|
||||
"dahyeik": "U03MG3BCS22",
|
||||
"darksciencebase": "U04P2DVHG3U",
|
||||
"deniskaber": "U04QX6Z5N00",
|
||||
"devurandom": "U03N1ATKF7T",
|
||||
"dhuniverse": "U04RRCGJPJN",
|
||||
"dpsutton": "U01BP8Z3CVA",
|
||||
"EmmadUsmani": "U04QX6YUP7A",
|
||||
"escherize": "U02U4E60EHG",
|
||||
"filipesilva": "U04S45X3FLN",
|
||||
"FilmonK": "U05J39TB4A1",
|
||||
"gusaiani": "U020JGYQHK8",
|
||||
"heypoom": "U06TVDSUPGE",
|
||||
"iethree": "U5A85JL4V",
|
||||
"ignacio-mb": "U0558P1CZ5L",
|
||||
"imrkd": "U02MJBX8G65",
|
||||
"ixipixi": "U06C5JCNKM1",
|
||||
"jeff-bruemmer": "U014JGU9XK2",
|
||||
"JesseSDevaney": "U0511SK58DC",
|
||||
"jessillions": "U04P2AKMX4K",
|
||||
"jesusfloresmetabase": "U04MZ6TGQLA",
|
||||
"jill-lafond": "U0537V0L2DN",
|
||||
"johnswanson": "U0600BZ1XMK",
|
||||
"jrayski": "U03C28SM11N",
|
||||
"k8beeman": "U06PYT3N7EV",
|
||||
"kamilmielnik": "U057ENEC60M",
|
||||
"kdoh": "U078VJFED",
|
||||
"kidd": "U01JM8WM6N4",
|
||||
"kulyk": "U01TH98M6J2",
|
||||
"lbrdnk": "U06035L3V34",
|
||||
"le0li0n": "U06RDA3M6BZ",
|
||||
"likeshumidity": "U047NDX34UU",
|
||||
"losrebellos": "U022B12KVK6",
|
||||
"luizarakaki": "U02KWASDKHC",
|
||||
"margaretrimek": "U02AZVBQQU9",
|
||||
"maxzheng": "U03TBN466EN",
|
||||
"mazameli": "U0ALDAVHA",
|
||||
"metamben": "U03AN4TRM6Y",
|
||||
"mngr": "U06GXDTJQQ3",
|
||||
"mwsmales": "U043FSUUSNL",
|
||||
"nemanjaglumac": "U01BA9754AC",
|
||||
"NevRA": "U04T9044T7G",
|
||||
"ngapuileung": "U04HP1D66N4",
|
||||
"noahmoss": "U01V8S8DG78",
|
||||
"npfitz": "U037YBQFRR7",
|
||||
"npretto": "U05K43X7RV5",
|
||||
"oisincoveney": "U0511SKLXCN",
|
||||
"Onlinehead": "U02S8HGAB8A",
|
||||
"oreonl": "U05V7TSK1QQ",
|
||||
"paoliniluis": "U01GWGPG2CF",
|
||||
"perivamsi": "U05K2EFAT5G",
|
||||
"piranha": "U060FQBFE5P",
|
||||
"psalinasy": "U04LYLXV207",
|
||||
"qnkhuat": "U02LA6FF5N1",
|
||||
"rafpaf": "U0697CU8TTP",
|
||||
"ranquild": "U028HG073GS",
|
||||
"romeovs": "U06DHN2JA5D",
|
||||
"salsakran": "U078V228G",
|
||||
"sloansparger": "U06CJAUPV3K",
|
||||
"snoe": "U03AG715B61",
|
||||
"Somtom": "U06BREDB5V3",
|
||||
"taqattack": "U06C70CJ6RM",
|
||||
"Tony-metabase": "U03RFTD5SLR",
|
||||
"trinya": "U06CJJB3JJZ",
|
||||
"tsmacdonald": "U01BKUL0MHV",
|
||||
"uladzimirdev": "U04TU6TNPLZ",
|
||||
"vbenedetti": "U04MRS3JTLH",
|
||||
"victoriaspek": "U02KD3E703E",
|
||||
"WiNloSt": "U02MYNXD605",
|
||||
"zbodi74": "U05EV72TPN2"
|
||||
}
|
||||
6
.github/team.json
vendored
@@ -7,6 +7,7 @@
|
||||
"ranquild",
|
||||
"uladzimirdev",
|
||||
"kamilmielnik",
|
||||
"oleggromov",
|
||||
"nemanjaglumac",
|
||||
"romeovs"
|
||||
]
|
||||
@@ -14,8 +15,7 @@
|
||||
{
|
||||
"name": "Embedding",
|
||||
"label": ".Team/Embedding",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/88/views/1",
|
||||
"members": ["WiNloSt", "deniskaber", "oisincoveney", "npretto", "heypoom"]
|
||||
"members": ["WiNloSt", "deniskaber", "oisincoveney", "npretto"]
|
||||
},
|
||||
{
|
||||
"name": "BEC",
|
||||
@@ -32,7 +32,7 @@
|
||||
{
|
||||
"name": "Query Processor",
|
||||
"label": ".Team/QueryProcessor",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/83/views/8",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/85/views/1",
|
||||
"members": ["camsaul", "snoe", "bshepherdson", "metamben", "lbrdnk"]
|
||||
},
|
||||
{
|
||||
|
||||
8
.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
|
||||
|
||||
3
.github/workflows/backend.yml
vendored
@@ -80,9 +80,8 @@ jobs:
|
||||
- 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
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./target/coverage/codecov.json
|
||||
flags: back-end
|
||||
|
||||
|
||||
2
.github/workflows/build-for-release.yml
vendored
@@ -113,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 }}';
|
||||
|
||||
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
|
||||
|
||||
90
.github/workflows/drivers.yml
vendored
@@ -126,27 +126,20 @@ jobs:
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-druid-jdbc-ee:
|
||||
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: druid-jdbc
|
||||
services:
|
||||
druid:
|
||||
image: metabase/druid:29.0.1-json-no-ingestion
|
||||
ports:
|
||||
- "8888:8888"
|
||||
env:
|
||||
CLUSTER_SIZE: nano-quickstart
|
||||
DRIVERS: googleanalytics
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Druid JDBC driver
|
||||
- name: Test Google Analytics driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-druid-jdbc-ee'
|
||||
junit-name: 'be-tests-googleanalytics-ee'
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
@@ -160,6 +153,39 @@ jobs:
|
||||
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()
|
||||
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-mariadb-10-2-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
@@ -255,7 +281,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-4-4-ee'
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -301,7 +327,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-4-4-ee'
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -335,7 +361,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-5-0-ee'
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -381,7 +407,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-5-0-ee'
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-tests]'"
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -418,39 +444,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-latest-ee'
|
||||
test-args: ":exclude-tags '[:mb/once :mongo-sharded-cluster-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-mongo-sharded-cluster-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: mongo
|
||||
services:
|
||||
mongodb:
|
||||
image: metabase/mongo-sharded-cluster:6
|
||||
ports:
|
||||
- "27017:17017"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test MongoDB driver (Sharded cluster)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mongo-sharded-cluster-ee'
|
||||
test-args: ':only "[metabase.driver.mongo.sharded-cluster-test]"'
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
|
||||
@@ -45,7 +45,6 @@ jobs:
|
||||
env:
|
||||
DISPLAY: ""
|
||||
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 }}
|
||||
|
||||
57
.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]
|
||||
|
||||
@@ -87,11 +87,10 @@ 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.even.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
|
||||
CYPRESS_REPLAYIO_ENABLE_UPLOAD: 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 }}
|
||||
@@ -144,7 +143,7 @@ jobs:
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags=@OSS,grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run slow and resource-intensive Cypress tests
|
||||
@@ -152,7 +151,7 @@ jobs:
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@slow",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run Flaky Cypress tests
|
||||
@@ -160,7 +159,7 @@ jobs:
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
|
||||
- name: Run EE Cypress tests on ${{ matrix.name }}
|
||||
@@ -176,7 +175,7 @@ jobs:
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@mongo --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.js' \
|
||||
--browser 'replay-chromium'
|
||||
env:
|
||||
CYPRESS_QA_DB_MONGO: true
|
||||
@@ -217,3 +216,47 @@ jobs:
|
||||
steps:
|
||||
- run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
|
||||
visual-regression-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
needs: [build]
|
||||
if: |
|
||||
!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 || ''}}
|
||||
|
||||
39
.github/workflows/embedding-sdk.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: 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:
|
||||
sdk-build:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'embedding-sdk-build')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "cljs"
|
||||
- name: Build SDK bundle
|
||||
run: yarn run build-embedding-sdk
|
||||
- name: Generate SDK package.json
|
||||
run: yarn run embedding-sdk:generate-package "$GITHUB_SHA"
|
||||
- name: Generate .npmrc for Github npm registry
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
registry-url: "https://npm.pkg.github.com"
|
||||
- name: Publish package
|
||||
run: yarn run embedding-sdk:publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
5
.github/workflows/frontend.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
fe-tests-unit:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.frontend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
JEST_JUNIT_OUTPUT_DIR: ./target/junit
|
||||
@@ -91,11 +91,10 @@ jobs:
|
||||
run: yarn run test-unit --silent
|
||||
- name: Upload coverage to codecov.io
|
||||
if: github.ref_name == 'master'
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
flags: front-end
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
|
||||
67
.github/workflows/loki-update.yml
vendored
@@ -1,67 +0,0 @@
|
||||
name: Update Loki Snapshots
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
check-label-and-update-snapshot:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'loki-update')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- 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
|
||||
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook
|
||||
|
||||
- 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!"
|
||||
|
||||
- name: Update Loki Snapshots
|
||||
run: yarn test-visual:loki-update
|
||||
|
||||
- name: Commit and Push Changes
|
||||
run: |
|
||||
git config --global user.name "Metabase Automation"
|
||||
git config --global user.email "github-automation@metabase.com"
|
||||
git add .loki/**
|
||||
git commit -m "Update Loki Snapshots"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
- name: Remove 'loki-update' label
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{secrets.METABASE_AUTOMATION_USER_TOKEN}}
|
||||
script: |
|
||||
github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'loki-update'
|
||||
})
|
||||
66
.github/workflows/loki.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Loki Visual Regression Testing
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
visual-test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
docker:
|
||||
image: docker:19.03.12
|
||||
options: --privileged
|
||||
ports:
|
||||
- 2376:2376
|
||||
env:
|
||||
DOCKER_TLS_CERTDIR: /certs
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /tmp/docker-certs:/certs/client
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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
|
||||
|
||||
- name: Build Storybook
|
||||
run: yarn build-storybook
|
||||
|
||||
- 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!"
|
||||
|
||||
- name: Run Loki Visual Tests
|
||||
run: |
|
||||
yarn loki test
|
||||
|
||||
- name: Generate Visual Report on Failure
|
||||
if: failure()
|
||||
run: yarn test-visual:loki-report
|
||||
|
||||
- name: Upload Artifact
|
||||
id: artifact-upload-step
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: loki-report
|
||||
path: .loki/
|
||||
if-no-files-found: ignore
|
||||
32
.github/workflows/mbql-library-changelog.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Update changelog for MBQL library changes
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- labeled
|
||||
- unlabeled
|
||||
- synchronize
|
||||
paths:
|
||||
- src/metabase/lib/js.cljs
|
||||
jobs:
|
||||
mbql-library-changelog-check:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !contains( github.event.pull_request.labels.*.name, '.metabase-lib/no-change' ) }}
|
||||
steps:
|
||||
# - uses: jesusvasquez333/verify-pr-label-action@v1.4.0
|
||||
# with:
|
||||
# github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
# valid-labels: '.metabase-lib/no-change'
|
||||
# disable-reviews: true
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for updated MBQL library changelog
|
||||
run: |
|
||||
if [[ $(git show HEAD -- docs/developers-guide/mbql-library-changelog.md) ]]; then
|
||||
echo "Library and changelog updated together - all good"
|
||||
else
|
||||
echo "metabase.lib.js namespace changed but docs/developers/guide/mbql-library-changelog.md did not"
|
||||
echo "Update the changelog with the changes made to the library's API."
|
||||
echo "If the edits did not change the API, add the `.metabase-lib/no-change` label."
|
||||
exit 1
|
||||
fi
|
||||
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
@@ -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 }}
|
||||
265
.github/workflows/pre-release.yml
vendored
@@ -5,11 +5,11 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Metabase version (e.g. v0.46.3)"
|
||||
description: 'Metabase version (e.g. v0.46.3)'
|
||||
type: string
|
||||
required: true
|
||||
commit:
|
||||
description: "A full-length commit SHA-1 hash"
|
||||
description: 'A full-length commit SHA-1 hash'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
@@ -91,21 +91,21 @@ jobs:
|
||||
edition: [oss, ee]
|
||||
java-version: [11, 17]
|
||||
steps:
|
||||
- name: Prepare JRE (Java Run-time Environment)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-package: jre
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: "temurin"
|
||||
- run: java -version
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Launch uberjar (and keep it running)
|
||||
run: java -jar ./metabase.jar &
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
- name: Prepare JRE (Java Run-time Environment)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-package: jre
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: 'temurin'
|
||||
- run: java -version
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Launch uberjar (and keep it running)
|
||||
run: java -jar ./metabase.jar &
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
|
||||
run-sanity-check:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -114,16 +114,11 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
edition: [oss, ee]
|
||||
folder: [onboarding, search]
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.edition }}
|
||||
DISPLAY: ""
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_REPLAYIO_ENABLED: 1
|
||||
# disabled because of out of memory issues
|
||||
# probably related to https://github.com/cypress-io/cypress/issues/27415
|
||||
CYPRESS_NO_COMMAND_LOG: 1
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
TERM: xterm
|
||||
@@ -137,43 +132,43 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release-artifact.outputs.commit }}
|
||||
- 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: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
- name: Run a few important Cypress tests as sanity check
|
||||
run: |
|
||||
mkdir -p ./target/uberjar && cp metabase.jar ./target/uberjar/metabase.jar
|
||||
yarn test-cypress-run --folder ${{ matrix.folder }}
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release-artifact.outputs.commit }}
|
||||
- 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: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
- name: Run a few important Cypress tests as sanity check
|
||||
run: |
|
||||
mkdir -p ./target/uberjar && cp metabase.jar ./target/uberjar/metabase.jar
|
||||
yarn test-cypress-run --folder onboarding
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-failed-tests-recording
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-failed-tests-recording
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
containerize:
|
||||
needs: [release-artifact, run-sanity-check]
|
||||
@@ -190,71 +185,71 @@ jobs:
|
||||
outputs:
|
||||
image_tag: ${{ steps.target.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Get the version
|
||||
run: |
|
||||
if [[ "${{ matrix.edition }}" == "ee" ]]; then
|
||||
echo "VERSION=${{ needs.release-artifact.outputs.ee_version }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "VERSION=${{ needs.release-artifact.outputs.oss_version }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release-artifact.outputs.commit }}
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Move the Uberjar to the context dir
|
||||
run: mv metabase.jar bin/docker/.
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: network=host
|
||||
- name: Build ${{ matrix.edition }} container
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: bin/docker/.
|
||||
platforms: linux/amd64
|
||||
network: host
|
||||
tags: localhost:5000/local-metabase:${{ env.VERSION }}
|
||||
no-cache: true
|
||||
push: true
|
||||
- name: Get the version
|
||||
run: |
|
||||
if [[ "${{ matrix.edition }}" == "ee" ]]; then
|
||||
echo "VERSION=${{ needs.release-artifact.outputs.ee_version }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "VERSION=${{ needs.release-artifact.outputs.oss_version }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.release-artifact.outputs.commit }}
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Move the Uberjar to the context dir
|
||||
run: mv metabase.jar bin/docker/.
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: network=host
|
||||
- name: Build ${{ matrix.edition }} container
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: bin/docker/.
|
||||
platforms: linux/amd64
|
||||
network: host
|
||||
tags: localhost:5000/local-metabase:${{ env.VERSION }}
|
||||
no-cache: true
|
||||
push: true
|
||||
|
||||
- name: Launch container
|
||||
run: docker run --rm -dp 3000:3000 localhost:5000/local-metabase:${{ env.VERSION }}
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 3
|
||||
- name: Launch container
|
||||
run: docker run --rm -dp 3000:3000 localhost:5000/local-metabase:${{ env.VERSION }}
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
|
||||
- name: Retag and push container image to ${{ vars.DOCKERHUB_OWNER }} staging Docker Hub repo
|
||||
id: target
|
||||
run: |
|
||||
# SHA-1 truncated to 7 digits should be enough
|
||||
SHORT_HASH=${COMMIT:0:7}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
|
||||
- name: Retag and push container image to ${{ vars.DOCKERHUB_OWNER }} staging Docker Hub repo
|
||||
id: target
|
||||
run: |
|
||||
# SHA-1 truncated to 7 digits should be enough
|
||||
SHORT_HASH=${COMMIT:0:7}
|
||||
|
||||
IMAGE_TAG=$VERSION-$SHORT_HASH
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
IMAGE_TAG=$VERSION-$SHORT_HASH
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Retag local image for staging"
|
||||
docker tag localhost:5000/local-metabase:$VERSION $CONTAINER_IMAGE
|
||||
echo "Retag local image for staging"
|
||||
docker tag localhost:5000/local-metabase:$VERSION $CONTAINER_IMAGE
|
||||
|
||||
echo "Pushing container image $CONTAINER_IMAGE ..."
|
||||
docker push $CONTAINER_IMAGE
|
||||
echo "Pushing container image $CONTAINER_IMAGE ..."
|
||||
docker push $CONTAINER_IMAGE
|
||||
|
||||
echo "Finished!"
|
||||
shell: bash
|
||||
env:
|
||||
COMMIT: ${{ needs.release-artifact.outputs.commit }}
|
||||
echo "Finished!"
|
||||
shell: bash
|
||||
env:
|
||||
COMMIT: ${{ needs.release-artifact.outputs.commit }}
|
||||
|
||||
verify-docker-pull:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -266,22 +261,22 @@ jobs:
|
||||
env:
|
||||
IMAGE_TAG: ${{ needs.containerize.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Login to Docker Hub # authenticated, to avoid being rate-throttled
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
|
||||
- name: Pull the container image
|
||||
run: |
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
echo "Pulling container image $CONTAINER_IMAGE ..."
|
||||
docker pull $CONTAINER_IMAGE
|
||||
echo "Successful!"
|
||||
- name: Launch container
|
||||
run: |
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
docker run --rm -dp 3000:3000 $CONTAINER_IMAGE
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 3
|
||||
- name: Login to Docker Hub # authenticated, to avoid being rate-throttled
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
|
||||
- name: Pull the container image
|
||||
run: |
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
echo "Pulling container image $CONTAINER_IMAGE ..."
|
||||
docker pull $CONTAINER_IMAGE
|
||||
echo "Successful!"
|
||||
- name: Launch container
|
||||
run: |
|
||||
CONTAINER_IMAGE=${{ vars.DOCKERHUB_OWNER }}/${{ vars.DOCKERHUB_STAGING_REPO }}:$IMAGE_TAG
|
||||
docker run --rm -dp 3000:3000 $CONTAINER_IMAGE
|
||||
timeout-minutes: 5
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 3
|
||||
|
||||
254
.github/workflows/release-embedding-sdk.yml
vendored
@@ -1,254 +0,0 @@
|
||||
name: Release Metabase Embedding SDK for React
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# TODO: Add a version validation, so the workflow won't fail when publishing to npm
|
||||
sdk_version:
|
||||
description: 'SDK version (e.g. 0.1.3)'
|
||||
type: string
|
||||
required: true
|
||||
git_ref:
|
||||
description: 'Valid git reference e.g. branches, tags, hashes'
|
||||
type: string
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
# We want to ensure only one job is running at a time because
|
||||
# there could be a conflict when updating the readme file.
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-git-tag:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
tag: embedding-sdk-${{ inputs.sdk_version }}
|
||||
steps:
|
||||
- name: Check out the code using the provided ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: check tags
|
||||
run: |
|
||||
if [ $(git tag -l | grep $tag) ]
|
||||
then
|
||||
echo "::error::Tag '${{ env.tag }}' already exists. If you expect to run this workflow with the same tag, please remove the tag first and rerun this workflow again."
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
test:
|
||||
needs: check-git-tag
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Check out the code using the provided ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "release-sdk"
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn embedding-sdk:test-unit
|
||||
|
||||
build-sdk:
|
||||
needs: test
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Check out the code using the provided ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: "release-sdk"
|
||||
|
||||
- name: Update readme
|
||||
run: |
|
||||
./bin/embedding-sdk/release_utils.bash update_readme ${{ inputs.sdk_version }}
|
||||
|
||||
- name: Bump published npm package version
|
||||
run: |
|
||||
./bin/embedding-sdk/release_utils.bash update_package_json_template ${{ inputs.sdk_version }}
|
||||
|
||||
# TODO: Remove this step after the build utils script is merged to master
|
||||
- name: Upload release utils script, so it can be used later on when checking out `master` branch
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-util
|
||||
path: ./bin/embedding-sdk/release_utils.bash
|
||||
|
||||
- name: Update changelog
|
||||
run: |
|
||||
yarn embedding-sdk:generate-changelog
|
||||
|
||||
- name: Build SDK bundle
|
||||
run: yarn run build-embedding-sdk
|
||||
|
||||
- name: Generate SDK package.json in the build directory
|
||||
run: yarn run embedding-sdk:generate-package
|
||||
|
||||
- name: Upload built SDK package as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metabase-sdk
|
||||
path: ./resources/embedding-sdk
|
||||
|
||||
- name: Upload changelog
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdk-changelog
|
||||
path: ./enterprise/frontend/src/embedding-sdk/CHANGELOG.md
|
||||
|
||||
build-jar:
|
||||
needs: test
|
||||
uses: ./.github/workflows/uberjar-sdk.yml
|
||||
with:
|
||||
git_ref: ${{ inputs.git_ref }}
|
||||
image_name: embedding-sdk-${{ inputs.sdk_version }}
|
||||
secrets: inherit
|
||||
|
||||
upload-jar:
|
||||
needs: build-jar
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-ee-uberjar
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
|
||||
- name: Upload uberjar to S3
|
||||
env:
|
||||
BUCKET: ${{ vars.AWS_S3_DOWNLOADS_BUCKET }}
|
||||
BUCKET_PATH: sdk/v${{ inputs.sdk_version }}/metabase.jar
|
||||
FILE: ./target/uberjar/metabase.jar
|
||||
shell: bash
|
||||
run: |
|
||||
aws s3 cp $FILE s3://$BUCKET/$BUCKET_PATH
|
||||
|
||||
publish-npm:
|
||||
needs: [build-sdk, upload-jar]
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Check out master to prepare for a SDK version bump PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# when we created version update PR, it would only have diff from 2 files SDK readme and SDK package.json template
|
||||
ref: master
|
||||
|
||||
- name: Setup git user
|
||||
run: |
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
|
||||
# TODO: Remove this step after the build utils script is merged to master
|
||||
- name: Download release utils script
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-util
|
||||
path: ./bin/embedding-sdk/
|
||||
|
||||
- name: Update readme
|
||||
run: |
|
||||
bash ./bin/embedding-sdk/release_utils.bash update_readme ${{ inputs.sdk_version }}
|
||||
|
||||
- name: Bump published npm package version
|
||||
run: |
|
||||
bash ./bin/embedding-sdk/release_utils.bash update_package_json_template ${{ inputs.sdk_version }}
|
||||
|
||||
- name: Retrieve SDK changelog
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sdk-changelog
|
||||
|
||||
# We need to do this because it's impossible to download an artifact to an existing location
|
||||
- name: Move over the downloaded changelog
|
||||
run: |
|
||||
mv CHANGELOG.md ./enterprise/frontend/src/embedding-sdk/CHANGELOG.md
|
||||
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Create a PR updating readme + published version, and changelog
|
||||
run: |
|
||||
git checkout -b update-sdk-version-${{ inputs.sdk_version }}
|
||||
git commit -a -m 'Update Readme version references and published npm version to ${{ inputs.sdk_version }}'
|
||||
git push origin HEAD
|
||||
gh pr create --base master\
|
||||
--assignee "${GITHUB_ACTOR}"\
|
||||
--title "Update SDK version to ${{ inputs.sdk_version }}"\
|
||||
--body "Update Readme version references and published npm package version to ${{ inputs.sdk_version }}"
|
||||
gh pr edit --add-reviewer @metabase/embedding
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Retrieve build SDK package artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-sdk
|
||||
path: sdk
|
||||
|
||||
- name: Publish to NPM
|
||||
run: |
|
||||
cd sdk
|
||||
echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_RELEASE_TOKEN }} > .npmrc
|
||||
npm publish
|
||||
|
||||
git-tag:
|
||||
needs: publish-npm
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
tag: embedding-sdk-${{ inputs.sdk_version }}
|
||||
steps:
|
||||
- name: Check out the code using the provided ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
|
||||
- name: Setup git user
|
||||
run: |
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
|
||||
- name: Create a new git tag
|
||||
run: |
|
||||
git tag ${{ env.tag }}
|
||||
|
||||
- name: Push the new tag
|
||||
id : push-tag
|
||||
run: |
|
||||
git push origin ${{ env.tag }}
|
||||
44
.github/workflows/require-percy-tests.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Require Percy specs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
if: github.event.pull_request.draft == false && !contains(github.event.pull_request.labels.*.name, 'visual')
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
visualizations: ${{ steps.changes.outputs.visualizations }}
|
||||
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
|
||||
|
||||
add-label:
|
||||
needs: files-changed
|
||||
if: needs.files-changed.outputs.visualizations == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
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/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["visual"]
|
||||
})
|
||||
157
.github/workflows/uberjar-sdk.yml
vendored
@@ -1,157 +0,0 @@
|
||||
name: Build + Docker Uberjar for SDK
|
||||
|
||||
on:
|
||||
# This workflow will be called from "release-embedding-sdk.yml"
|
||||
workflow_call:
|
||||
inputs:
|
||||
git_ref:
|
||||
description: 'Valid git reference e.g. branches, tags, hashes'
|
||||
type: string
|
||||
required: true
|
||||
image_name:
|
||||
description: Docker image used to upload to Dockerhub
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build MB ${{ matrix.edition }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
matrix:
|
||||
edition: [ee]
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.edition }}
|
||||
INTERACTIVE: false
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: uberjar
|
||||
- name: Build
|
||||
run: ./bin/build.sh
|
||||
- name: Prepare uberjar artifact
|
||||
uses: ./.github/actions/prepare-uberjar-artifact
|
||||
with:
|
||||
name: metabase-ee-uberjar
|
||||
|
||||
check_jar_health:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Is ${{ matrix.edition }} (java ${{ matrix.java-version }}) healthy?
|
||||
needs: build
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
edition: [ee]
|
||||
java-version: [11, 17]
|
||||
steps:
|
||||
- name: Prepare JRE (Java Run-time Environment)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-package: jre
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: 'temurin'
|
||||
- run: java -version
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-ee-uberjar
|
||||
- name: Launch uberjar
|
||||
run: java -jar ./target/uberjar/metabase.jar &
|
||||
- name: Wait for Metabase to start
|
||||
run: while ! curl 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
|
||||
containerize_test_and_push_container:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Containerize ${{ matrix.edition }}
|
||||
needs: check_jar_health
|
||||
strategy:
|
||||
matrix:
|
||||
edition: [ee]
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- name: Extract and clean branch name
|
||||
shell: bash
|
||||
run: echo "branch=$(echo $GITHUB_REF_NAME | sed 's/[^-._a-zA-Z0-9]/-/g')" >> $GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
|
||||
- name: Set image name to env
|
||||
run: echo "image_name=${{ inputs.image_name || steps.extract_branch.outputs.branch }}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Verify the intended tag of the container image
|
||||
run: echo "Container image will be tagged as ${{ env.image_name }}-${{ matrix.edition }}"
|
||||
|
||||
- name: Check out the code (Dockerfile needed)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.git_ref }}
|
||||
|
||||
- name: Download uploaded artifacts to insert into container
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-ee-uberjar
|
||||
path: bin/docker/
|
||||
|
||||
- name: Move the ${{ matrix.edition }} uberjar to the context dir
|
||||
run: mv bin/docker/target/uberjar/metabase.jar bin/docker/.
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Build ${{ matrix.edition }} container
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: bin/docker/.
|
||||
platforms: linux/amd64
|
||||
network: host
|
||||
tags: localhost:5000/metabase-dev:${{ env.image_name }}-${{ matrix.edition }}
|
||||
no-cache: true
|
||||
push: true
|
||||
|
||||
- name: Launch ${{ matrix.edition }} container
|
||||
run: docker run --rm -dp 3000:3000 localhost:5000/metabase-dev:${{ env.image_name }}-${{ matrix.edition }}
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Is Docker running?
|
||||
run: docker ps
|
||||
|
||||
- name: Wait for Metabase to start and reach 100% health
|
||||
run: while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}'; do sleep 1; done
|
||||
timeout-minutes: 3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Retag and push images
|
||||
run: docker tag localhost:5000/metabase-dev:${{ env.image_name }}-ee ${{ github.repository_owner }}/metabase-dev:${{ env.image_name }} && docker push ${{ github.repository_owner }}/metabase-dev:${{ env.image_name }}
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
env:
|
||||
TRIVY_OFFLINE_SCAN: true
|
||||
with:
|
||||
image-ref: docker.io/${{ github.repository_owner }}/metabase-dev:${{ env.image_name }}
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
4
.github/workflows/uberjar.yml
vendored
@@ -116,8 +116,6 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
network: host
|
||||
tags: localhost:5000/metabase-dev:${{ steps.extract_branch.outputs.branch }}-${{ matrix.edition }}
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.sha }}
|
||||
no-cache: true
|
||||
push: true
|
||||
- name: Launch ${{ matrix.edition }} container
|
||||
@@ -242,8 +240,6 @@ jobs:
|
||||
file: bin/docker/Dockerfile_ubuntu
|
||||
network: host
|
||||
tags: localhost:5000/metabase-dev:${{ steps.extract_branch.outputs.branch }}-${{ matrix.edition }}-ubuntu
|
||||
build-args: |
|
||||
GIT_COMMIT_SHA=${{ github.sha }}
|
||||
no-cache: true
|
||||
push: true
|
||||
- name: Launch ${{ matrix.edition }} Ubuntu based container
|
||||
|
||||
4
.gitignore
vendored
@@ -51,7 +51,6 @@
|
||||
/resources/frontend_client/embed.html
|
||||
/resources/frontend_client/index.html
|
||||
/resources/frontend_client/public.html
|
||||
/resources/embedding-sdk
|
||||
/resources/i18n/*.edn
|
||||
/resources/instance_analytics.zip
|
||||
/resources/license-backend-third-party.txt
|
||||
@@ -96,8 +95,7 @@ modules/drivers/*/.lsp/*
|
||||
.clj-kondo/*
|
||||
!.clj-kondo/README.md
|
||||
!.clj-kondo/config.edn
|
||||
!.clj-kondo/src/
|
||||
!.clj-kondo/test/
|
||||
!.clj-kondo/hooks/
|
||||
!.clj-kondo/macros/
|
||||
|
||||
# Editor- or environment-specific local files
|
||||
|
||||
4
.loki/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
current
|
||||
difference
|
||||
report.html
|
||||
report.json
|
||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |