Compare commits
120 Commits
mage-start
...
nav-split-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16bbf677a5 | ||
|
|
e1d1d31a1a | ||
|
|
7b91252075 | ||
|
|
8dfb8c5b67 | ||
|
|
e8f85e28e0 | ||
|
|
63cbcc92e3 | ||
|
|
16ad7b75f2 | ||
|
|
eedda27c89 | ||
|
|
1a0ff4340b | ||
|
|
0bdad48e91 | ||
|
|
9b193b0937 | ||
|
|
1139be54dd | ||
|
|
32176e152a | ||
|
|
1d5c08b71d | ||
|
|
bc75a88888 | ||
|
|
cd6bcc23e0 | ||
|
|
381ab6c1a0 | ||
|
|
176d0682e2 | ||
|
|
daa0c256a6 | ||
|
|
2df3f6daa0 | ||
|
|
503f96cb55 | ||
|
|
f2e185369f | ||
|
|
9db952d568 | ||
|
|
9e27bdf31f | ||
|
|
20f3f6cbdc | ||
|
|
2f3fe9904e | ||
|
|
57a08becb3 | ||
|
|
fc5cf5c469 | ||
|
|
c03c02a9a1 | ||
|
|
af8ca49a25 | ||
|
|
cfb8cbd6f9 | ||
|
|
3eff0fac3d | ||
|
|
b6f625ed4b | ||
|
|
bb9482f0fd | ||
|
|
8317274709 | ||
|
|
c796ec01ec | ||
|
|
fa7362a1e4 | ||
|
|
361fc91d80 | ||
|
|
f4ab93af92 | ||
|
|
6c4b0182c1 | ||
|
|
0694a11c90 | ||
|
|
fa20179ff6 | ||
|
|
cdc7f386af | ||
|
|
d994a365f7 | ||
|
|
d7b9a71698 | ||
|
|
12a22c0392 | ||
|
|
6ec07f7518 | ||
|
|
aa9c3797e8 | ||
|
|
8ce3936116 | ||
|
|
5bcd89d33f | ||
|
|
916ee6554c | ||
|
|
401167cc00 | ||
|
|
fff70175e0 | ||
|
|
74216b3098 | ||
|
|
2947b47c23 | ||
|
|
abb710334f | ||
|
|
fed0dbfd58 | ||
|
|
edd44ef851 | ||
|
|
1cd4d62061 | ||
|
|
0509e1a5e2 | ||
|
|
81736f712a | ||
|
|
812e9b2254 | ||
|
|
d0ce86c19e | ||
|
|
72e2e9b234 | ||
|
|
426c9e6406 | ||
|
|
feb7aa645c | ||
|
|
93742a629c | ||
|
|
b485c0f7fa | ||
|
|
76979e6167 | ||
|
|
da7629bba6 | ||
|
|
280c1e7be2 | ||
|
|
3d924036db | ||
|
|
883e944cce | ||
|
|
9d1c919f6b | ||
|
|
b08a2f0e65 | ||
|
|
22b1a393dc | ||
|
|
f58c520891 | ||
|
|
f2364b1d27 | ||
|
|
39bd87ede3 | ||
|
|
56232fc3f8 | ||
|
|
61b6635f13 | ||
|
|
86b1f4bca4 | ||
|
|
ac1f4cdc00 | ||
|
|
0e83852557 | ||
|
|
61d0fa5d0f | ||
|
|
504b8ea9ee | ||
|
|
61ce133268 | ||
|
|
f24251fb4e | ||
|
|
7994ad35d8 | ||
|
|
eb845c2ea6 | ||
|
|
3d8a3901a9 | ||
|
|
16bd231ba7 | ||
|
|
89da5b2bc6 | ||
|
|
91f5798258 | ||
|
|
e49c2e60af | ||
|
|
496db401a1 | ||
|
|
188c8f412f | ||
|
|
62831f5e4d | ||
|
|
d5d6eba4c5 | ||
|
|
a3b46fdd3c | ||
|
|
61c0e2915e | ||
|
|
5701125c34 | ||
|
|
5349fb75d7 | ||
|
|
ecb8fe82b2 | ||
|
|
40ebe0bb9f | ||
|
|
0700179dad | ||
|
|
e96a5d884d | ||
|
|
587b659ee7 | ||
|
|
5073c20eb1 | ||
|
|
9d6d4c656e | ||
|
|
4b6744c1f4 | ||
|
|
2f02a29249 | ||
|
|
4c5146d536 | ||
|
|
3b9a1df5ac | ||
|
|
7e9ffc83ce | ||
|
|
e28ae43d1d | ||
|
|
d37f1a9dd3 | ||
|
|
1a32584bb8 | ||
|
|
35bf6ca092 | ||
|
|
d3ce9de3cc |
@@ -84,6 +84,7 @@
|
||||
:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
|
||||
:metabase/validate-defendpoint-query-params-use-kebab-case {:level :warning}
|
||||
:metabase/validate-logging {:level :warning}
|
||||
:metabase/validate-mb-app-db-migrations-test {:level :warning}
|
||||
:metabase/validate-mb-driver-tests {:level :warning}
|
||||
:metabase/validate-mb-once {:level :warning}
|
||||
|
||||
@@ -319,6 +320,12 @@
|
||||
{metabase-enterprise.entity-retrieval.core/search-unfiltered {:message "Permission bypass risk: library-index hits are unfiltered — resolve each entity and drop what the current user can't read"}
|
||||
metabase.entity-retrieval.core/search-unfiltered {:message "Permission bypass risk: library-index hits are unfiltered — resolve each entity and drop what the current user can't read"}
|
||||
metabase.entity-retrieval.mirror/search-unfiltered {:message "Permission bypass risk: library-index hits are unfiltered — resolve each entity and drop what the current user can't read"}
|
||||
metabase-enterprise.transform-testing.expectations.equals/->EqualsRows {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
metabase-enterprise.transform-testing.expectations.equals/map->EqualsRows {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
metabase-enterprise.transform-testing.expectations.equals/->EqualsSql {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
metabase-enterprise.transform-testing.expectations.equals/map->EqualsSql {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
metabase-enterprise.transform-testing.expectations.empty/->Empty {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
metabase-enterprise.transform-testing.expectations.empty/map->Empty {:message "Build an expectation with metabase-enterprise.transform-testing.expectations.protocol/build, over a value the schema has passed — the generated constructors skip validation"}
|
||||
clojure.core/print {:message "Use metabase.util.log instead of clojure.core/print"}
|
||||
clojure.core/println {:message "Use metabase.util.log instead of clojure.core/println"}
|
||||
clojure.core/printf {:message "Use metabase.util.log instead of clojure.core/printf"}
|
||||
@@ -617,7 +624,7 @@
|
||||
metabase-enterprise.transforms-python.drivers-test/with-test-table hooks.common/with-two-bindings
|
||||
metabase.api.common.internal-test/with-jetty-server hooks.common/with-one-binding
|
||||
metabase.api.macros/defendpoint hooks.metabase.api.macros/defendpoint
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations hooks.metabase.app-db.schema-migrations-test.impl/test-migrations
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations hooks.metabase.app-db.schema-migrations-test.impl/test-migrations!
|
||||
metabase.channel.render.js.pool/with-static-viz-context hooks.common/with-one-top-level-binding
|
||||
metabase.channel.template.handlebars-helper/defhelper hooks.metabase.channel.template.handlebars-helper/defhelper
|
||||
metabase.channel.template.handlebars-test/with-temp-template! hooks.common/with-vec-first-binding
|
||||
@@ -847,7 +854,13 @@
|
||||
metabase.users.models.user-test/with-groups! macros.metabase.users.models.user-test/with-groups!
|
||||
metabase.util.namespaces/import-fn macros.metabase.util.namespaces/import-fn
|
||||
metabase.util.namespaces/import-macro macros.metabase.util.namespaces/import-macro
|
||||
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world}}
|
||||
metabase.xrays.related-test/with-world macros.metabase.xrays.related-test/with-world
|
||||
potemkin/import-def macros.potemkin.namespaces/import-def
|
||||
potemkin/import-fn macros.potemkin.namespaces/import-fn
|
||||
potemkin/import-macro macros.potemkin.namespaces/import-macro
|
||||
potemkin.namespaces/import-def macros.potemkin.namespaces/import-def
|
||||
potemkin.namespaces/import-fn macros.potemkin.namespaces/import-fn
|
||||
potemkin.namespaces/import-macro macros.potemkin.namespaces/import-macro}}
|
||||
|
||||
:config-in-comment
|
||||
{:linters {:unresolved-symbol {:level :off}}}
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
:uses #{actions
|
||||
analytics
|
||||
api
|
||||
api-scope
|
||||
api.eid-translation
|
||||
collections
|
||||
lib
|
||||
@@ -317,7 +318,9 @@
|
||||
api-scope
|
||||
{:team "Metabot"
|
||||
:api #{metabase.api-scope.core
|
||||
metabase.api-scope.init}}
|
||||
metabase.api-scope.data-app
|
||||
metabase.api-scope.init}
|
||||
:uses #{util}}
|
||||
|
||||
api.eid-translation
|
||||
{:team "UX West"
|
||||
@@ -548,6 +551,7 @@
|
||||
query-processor.pivot
|
||||
request
|
||||
settings
|
||||
startup
|
||||
system
|
||||
task
|
||||
tiles
|
||||
@@ -640,6 +644,7 @@
|
||||
:ns-prefix "metabase.collections-rest"
|
||||
:api #{metabase.collections-rest.api}
|
||||
:uses #{api
|
||||
api-scope
|
||||
api.eid-translation
|
||||
collections
|
||||
documents
|
||||
@@ -795,6 +800,7 @@
|
||||
:uses #{actions
|
||||
analytics
|
||||
api
|
||||
api-scope
|
||||
api.eid-translation
|
||||
app-db
|
||||
channel
|
||||
@@ -904,7 +910,6 @@
|
||||
metabase.driver.settings
|
||||
metabase.driver.sql
|
||||
metabase.driver.sql-jdbc
|
||||
metabase.driver.sql-jdbc.connection.pool-lock
|
||||
metabase.driver.sql-jdbc.execute
|
||||
metabase.driver.sql-jdbc.execute.diagnostic
|
||||
metabase.driver.sql-jdbc.sync
|
||||
@@ -930,6 +935,7 @@
|
||||
query-processor.driver-api
|
||||
settings
|
||||
sql-tools
|
||||
startup
|
||||
system
|
||||
tracing
|
||||
util
|
||||
@@ -999,7 +1005,7 @@
|
||||
|
||||
explorations
|
||||
{:team "UX West"
|
||||
:api #{metabase.explorations.core metabase.explorations.init metabase.explorations.schema}
|
||||
:api #{metabase.explorations.api metabase.explorations.core metabase.explorations.init metabase.explorations.schema}
|
||||
:uses #{analytics
|
||||
analytics.interface
|
||||
api
|
||||
@@ -1079,7 +1085,7 @@
|
||||
|
||||
glossary
|
||||
{:team "Gadget"
|
||||
:api #{metabase.glossary.api}
|
||||
:api #{metabase.glossary.api metabase.glossary.core}
|
||||
:uses #{api
|
||||
events
|
||||
lib.schema
|
||||
@@ -1104,6 +1110,7 @@
|
||||
indexed-entities
|
||||
{:team "UX West"
|
||||
:api #{metabase.indexed-entities.api
|
||||
metabase.indexed-entities.core
|
||||
metabase.indexed-entities.init}
|
||||
:uses #{analytics
|
||||
api
|
||||
@@ -1318,6 +1325,7 @@
|
||||
comments
|
||||
dashboards
|
||||
documents
|
||||
glossary
|
||||
lib
|
||||
lib.be
|
||||
lib.metadata
|
||||
@@ -1346,6 +1354,7 @@
|
||||
util
|
||||
util.config
|
||||
warehouse-schema-overlay
|
||||
warehouses
|
||||
warehouses.schema}
|
||||
:model-imports #{:model/Card
|
||||
:model/Collection
|
||||
@@ -1378,6 +1387,7 @@
|
||||
mcp
|
||||
models
|
||||
request
|
||||
server
|
||||
settings
|
||||
system
|
||||
task
|
||||
@@ -1560,6 +1570,7 @@
|
||||
settings
|
||||
setup
|
||||
sql-tools
|
||||
startup
|
||||
sync
|
||||
util
|
||||
util.config
|
||||
@@ -1594,10 +1605,7 @@
|
||||
system
|
||||
upload
|
||||
util}
|
||||
:model-imports #{:model/AuthIdentity
|
||||
:model/Card
|
||||
:model/Database
|
||||
:model/MetabotMessage}}
|
||||
:model-imports #{:model/AuthIdentity :model/Card :model/MetabotMessage}}
|
||||
|
||||
metrics
|
||||
{:team "UX West"
|
||||
@@ -1736,6 +1744,7 @@
|
||||
{:team "Graphy"
|
||||
:api #{metabase.native-query-snippets.api metabase.native-query-snippets.core metabase.native-query-snippets.schema}
|
||||
:uses #{api
|
||||
api-scope
|
||||
collections
|
||||
events
|
||||
lib
|
||||
@@ -2110,6 +2119,7 @@
|
||||
:ns-prefix "metabase.queries-rest"
|
||||
:api #{metabase.queries-rest.api}
|
||||
:uses #{api
|
||||
api-scope
|
||||
api.eid-translation
|
||||
collections
|
||||
embedding
|
||||
@@ -2195,6 +2205,7 @@
|
||||
:uses #{analytics
|
||||
analytics.interface
|
||||
api
|
||||
api-scope
|
||||
app-db
|
||||
appearance
|
||||
audit-app
|
||||
@@ -2317,7 +2328,8 @@
|
||||
{:team "UX West"
|
||||
:api #{metabase.revisions.api
|
||||
metabase.revisions.core
|
||||
metabase.revisions.init}
|
||||
metabase.revisions.init
|
||||
metabase.revisions.models.revision}
|
||||
:uses #{api
|
||||
collections
|
||||
dashboards
|
||||
@@ -2347,7 +2359,8 @@
|
||||
:model/Measure
|
||||
:model/ParameterCard
|
||||
:model/Segment
|
||||
:model/Transform}}
|
||||
:model/Transform
|
||||
:model/TransformTest}}
|
||||
|
||||
run-tracking
|
||||
{:team "Gadget"
|
||||
@@ -2389,6 +2402,7 @@
|
||||
:uses #{analytics
|
||||
analytics.interface
|
||||
api
|
||||
api-scope
|
||||
app-db
|
||||
appearance
|
||||
audit-app
|
||||
@@ -2470,6 +2484,7 @@
|
||||
analytics.interface
|
||||
api
|
||||
api-keys
|
||||
api-scope
|
||||
app-db
|
||||
appearance
|
||||
core.initialization-status
|
||||
@@ -2504,6 +2519,7 @@
|
||||
metabase.session.schema
|
||||
metabase.session.settings} ; TODO -- shouldn't be exposed as an API namespace
|
||||
:uses #{api
|
||||
api-scope
|
||||
app-db
|
||||
channel
|
||||
events
|
||||
@@ -2626,6 +2642,7 @@
|
||||
server
|
||||
settings
|
||||
sso.auth-identity
|
||||
startup
|
||||
system
|
||||
util
|
||||
util.config}
|
||||
@@ -2801,6 +2818,7 @@
|
||||
premium-features
|
||||
query-processor
|
||||
settings
|
||||
startup
|
||||
util}
|
||||
:model-imports #{:model/Card :model/Dashboard :model/DashboardCard}}
|
||||
|
||||
@@ -2810,6 +2828,7 @@
|
||||
metabase.timeline.core}
|
||||
:uses #{analytics
|
||||
api
|
||||
api-scope
|
||||
collections
|
||||
events
|
||||
lib.schema
|
||||
@@ -3046,6 +3065,7 @@
|
||||
:api #{metabase.user-key-value.api
|
||||
metabase.user-key-value.init}
|
||||
:uses #{api
|
||||
api-scope
|
||||
lib.schema
|
||||
util
|
||||
util.config}}
|
||||
@@ -3089,6 +3109,7 @@
|
||||
:ns-prefix "metabase.users-rest"
|
||||
:api #{metabase.users-rest.api}
|
||||
:uses #{api
|
||||
api-scope
|
||||
appearance
|
||||
collections
|
||||
events
|
||||
@@ -3189,6 +3210,7 @@
|
||||
:api #{metabase.warehouses-rest.api}
|
||||
:uses #{analytics
|
||||
api
|
||||
api-scope
|
||||
app-db
|
||||
collections
|
||||
database-routing
|
||||
@@ -3208,13 +3230,11 @@
|
||||
sync
|
||||
upload
|
||||
util
|
||||
util.classloader
|
||||
util.config
|
||||
warehouse-schema-overlay
|
||||
warehouses
|
||||
warehouses.schema
|
||||
warehouses.secrets
|
||||
enterprise/permissions}
|
||||
warehouses.secrets}
|
||||
:model-imports #{:model/Card
|
||||
:model/Collection
|
||||
:model/Database
|
||||
@@ -3257,11 +3277,8 @@
|
||||
settings
|
||||
sync.analyze
|
||||
util
|
||||
util.classloader
|
||||
util.config
|
||||
warehouse-schema-overlay
|
||||
warehouses
|
||||
enterprise/permissions}
|
||||
warehouses}
|
||||
:model-exports #{:model/Dimension
|
||||
:model/Field
|
||||
:model/FieldUserSettings
|
||||
@@ -3282,6 +3299,7 @@
|
||||
:api #{metabase.warehouse-schema-rest.api}
|
||||
:uses #{analytics
|
||||
api
|
||||
api-scope
|
||||
app-db
|
||||
collections
|
||||
database-routing
|
||||
@@ -3331,6 +3349,7 @@
|
||||
collections
|
||||
dashboards
|
||||
driver
|
||||
indexed-entities
|
||||
lib
|
||||
lib.be
|
||||
lib.legacy-mbql
|
||||
@@ -3355,7 +3374,6 @@
|
||||
:model/Database
|
||||
:model/Field
|
||||
:model/ModelIndex
|
||||
:model/ModelIndexValue
|
||||
:model/Query
|
||||
:model/Revision
|
||||
:model/Segment
|
||||
@@ -3584,6 +3602,7 @@
|
||||
{:team "UX West"
|
||||
:api #{metabase-enterprise.content-translation.routes}
|
||||
:uses #{api
|
||||
api-scope
|
||||
content-translation
|
||||
embedding
|
||||
premium-features
|
||||
@@ -3613,6 +3632,7 @@
|
||||
metabase-enterprise.custom-viz-plugin.init
|
||||
metabase-enterprise.custom-viz-plugin.settings}
|
||||
:uses #{api
|
||||
api-scope
|
||||
events
|
||||
models
|
||||
premium-features
|
||||
@@ -3626,6 +3646,7 @@
|
||||
{:team "Embedding"
|
||||
:api #{metabase-enterprise.data-apps.api metabase-enterprise.data-apps.init metabase-enterprise.data-apps.sync}
|
||||
:uses #{api
|
||||
api-scope
|
||||
collections
|
||||
lib
|
||||
lib.be
|
||||
@@ -4016,6 +4037,7 @@
|
||||
:ns-prefix "metabase-enterprise.sandbox"
|
||||
:api #{metabase-enterprise.sandbox.api.routes metabase-enterprise.sandbox.api.util metabase-enterprise.sandbox.schema}
|
||||
:uses #{api
|
||||
api-scope
|
||||
app-db
|
||||
audit-app
|
||||
driver
|
||||
@@ -4098,6 +4120,7 @@
|
||||
:model/Timeline
|
||||
:model/Transform
|
||||
:model/TransformTag
|
||||
:model/TransformTest
|
||||
:model/User}}
|
||||
|
||||
enterprise/search
|
||||
@@ -4332,6 +4355,31 @@
|
||||
:model-exports #{:model/Tenant}
|
||||
:model-imports #{:model/Collection :model/User}}
|
||||
|
||||
enterprise/transform-testing
|
||||
{:team "Graphy"
|
||||
:api #{metabase-enterprise.transform-testing.api metabase-enterprise.transform-testing.init}
|
||||
:uses #{api
|
||||
audit-app
|
||||
driver
|
||||
events
|
||||
lib
|
||||
lib.schema
|
||||
models
|
||||
premium-features
|
||||
revisions
|
||||
run-tracking
|
||||
sql-tools
|
||||
sql-tools.parsing
|
||||
task
|
||||
transforms.base
|
||||
util
|
||||
warehouses}
|
||||
:model-exports #{:model/TransformTest}
|
||||
:model-imports #{:model/Collection
|
||||
:model/Database
|
||||
:model/Transform
|
||||
:model/User}}
|
||||
|
||||
enterprise/transforms
|
||||
{:team "Graphy"
|
||||
:module-exports #{enterprise/transforms.inspector
|
||||
|
||||
21
.clj-kondo/macros/potemkin/namespaces.clj
Normal file
@@ -0,0 +1,21 @@
|
||||
(ns macros.potemkin.namespaces)
|
||||
|
||||
(defn- import-var-expansion
|
||||
"Expansion shared by all of the `import-*` replacements below. Synthetic \"docstring\" is needed to silence
|
||||
`:missing-docstring` linter."
|
||||
([sym]
|
||||
(import-var-expansion sym (-> sym name symbol)))
|
||||
([sym new-name]
|
||||
`(def ~new-name "docstring" ~sym)))
|
||||
|
||||
(defmacro import-def
|
||||
([sym] (import-var-expansion sym))
|
||||
([sym new-name] (import-var-expansion sym new-name)))
|
||||
|
||||
(defmacro import-fn
|
||||
([sym] (import-var-expansion sym))
|
||||
([sym new-name] (import-var-expansion sym new-name)))
|
||||
|
||||
(defmacro import-macro
|
||||
([sym] (import-var-expansion sym))
|
||||
([sym new-name] (import-var-expansion sym new-name)))
|
||||
@@ -16,8 +16,8 @@
|
||||
:deprecated-namespace 95
|
||||
:deprecated-var 85
|
||||
:discouraged-java-method 4
|
||||
:discouraged-namespace 69
|
||||
:discouraged-var 130
|
||||
:discouraged-namespace 68
|
||||
:discouraged-var 132
|
||||
:duplicate-require 1
|
||||
:equals-true 1
|
||||
:main-without-gen-class 1
|
||||
@@ -32,7 +32,7 @@
|
||||
:metabase/validate-defendpoint-query-params-use-kebab-case 48
|
||||
:metabase/validate-defendpoint-route-uses-kebab-case 44
|
||||
:metabase/validate-deftest 9
|
||||
:missing-docstring 12
|
||||
:missing-docstring 4
|
||||
:missing-protocol-method 3
|
||||
:non-arg-vec-return-type-hint 1
|
||||
:reduce-without-init 3
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
(ns hooks.metabase.app-db.schema-migrations-test.impl
|
||||
(:require [clj-kondo.hooks-api :as hooks]))
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]))
|
||||
|
||||
(defn test-migrations
|
||||
[{{[_ migration-range {[binding] :children} & body] :children} :node}]
|
||||
(defn- validate-mb-app-db-migrations-test!
|
||||
"CI runs the namespaces tagged `:mb/app-db-migrations-test` on a shard of their own, because a count-based split
|
||||
can't balance them (see `.github/workflows/app-db.yml`). An untagged one still runs, just on a shard sized for
|
||||
everything else."
|
||||
[{:keys [ns node]}]
|
||||
(when-not (:mb/app-db-migrations-test (meta ns))
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta node)
|
||||
:message (str "Namespaces that run migration tests (test-migrations) should be marked"
|
||||
" ^:mb/app-db-migrations-test [:metabase/validate-mb-app-db-migrations-test]")
|
||||
:type :metabase/validate-mb-app-db-migrations-test))))
|
||||
|
||||
(defn test-migrations!
|
||||
[{{[_ migration-range {[binding] :children} & body] :children} :node, :as x}]
|
||||
(validate-mb-app-db-migrations-test! x)
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node `let)
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
|
||||
(def ^:private value-operators
|
||||
"Operators whose second argument is a value rather than a column."
|
||||
'#{= not= < > <= >= like not-like ilike not-ilike in not-in between})
|
||||
'#{= not= != <> is-distinct-from is-not-distinct-from
|
||||
< > <= >= like not-like ilike not-ilike in not-in between not-between})
|
||||
|
||||
(defn- marked?
|
||||
"Whether `node` is an `[:auto/param v]` marker."
|
||||
@@ -39,28 +40,138 @@
|
||||
(and (hooks/vector-node? node)
|
||||
(= :auto/param (some-> (first (:children node)) hooks/sexpr))))
|
||||
|
||||
(defn- distinct-by
|
||||
"`coll` with only the first element for each distinct `(f element)`."
|
||||
[f coll]
|
||||
(->> coll (reduce (fn [[seen acc] x]
|
||||
(let [k (f x)]
|
||||
(if (contains? seen k) [seen acc] [(conj seen k) (conj acc x)])))
|
||||
[#{} []])
|
||||
second))
|
||||
|
||||
(def ^:private identifier-clauses
|
||||
"Clause keys whose entries name a column or a table rather than carrying a value.
|
||||
|
||||
A symbol here is a name, and `metabase.app-db.value-guard` refuses a marker in one, so reporting
|
||||
it would ask for a fix that throws at compile. `:order-by` and `:group-by` are included even
|
||||
though the guard allows a marker there: it compiles to `ORDER BY ?` and loses the ordering, so
|
||||
asking for one is still wrong."
|
||||
#{:select :select-distinct :select-top :from :join :left-join :right-join :inner-join :full-join
|
||||
:cross-join :update :insert-into :delete-from :returning :with :with-columns :using
|
||||
:order-by :group-by :partition-by :window})
|
||||
|
||||
(def ^:private expression-entry-clauses
|
||||
"Identifier clauses where index 0 of an `[expr alias]` entry is an expression, not a name.
|
||||
|
||||
HoneySQL binds a parameter there -- `SELECT ? AS a` -- so the guard accepts a marker, and mirrors
|
||||
this set as `expression-entry-clauses` in `metabase.app-db.value-guard`. Everything after index 0
|
||||
is an alias, and a bare entry is a name; the guard refuses a marker in both."
|
||||
#{:select :select-distinct :select-top :returning :partition-by})
|
||||
|
||||
(defn- comparison-node?
|
||||
"Whether `node` is a keyword-headed comparison -- the one shape inside an identifier clause that
|
||||
does hold values, as in the computed projection `[[:= :engine v] :is_match]`."
|
||||
[node]
|
||||
(and (hooks/vector-node? node)
|
||||
(let [op (some-> (first (:children node)) hooks/sexpr)]
|
||||
(and (keyword? op) (contains? value-operators (symbol (name op)))))))
|
||||
|
||||
(declare value-nodes)
|
||||
|
||||
(defn- identifier-clause-value-nodes
|
||||
"The value slots reachable inside an identifier clause: a subquery's own clauses, and the
|
||||
arguments of a comparison. The names themselves are not candidates."
|
||||
[node]
|
||||
(cond
|
||||
(hooks/map-node? node) (value-nodes node)
|
||||
(comparison-node? node) (value-nodes node)
|
||||
(hooks/vector-node? node) (mapcat identifier-clause-value-nodes (:children node))
|
||||
(hooks/list-node? node) (mapcat identifier-clause-value-nodes (:children node))
|
||||
:else nil))
|
||||
|
||||
(defn- expression-entry-value-nodes
|
||||
"The value slots of an [[expression-entry-clauses]] clause: index 0 of each `[expr alias]` entry."
|
||||
[node]
|
||||
(when (hooks/vector-node? node)
|
||||
(mapcat (fn [entry]
|
||||
(when (hooks/vector-node? entry)
|
||||
(when-let [expr (first (:children entry))]
|
||||
;; Keep the expression AND descend, so a bare symbol is reported and a computed
|
||||
;; projection or a subquery sitting there has its own values checked.
|
||||
(cons expr (value-nodes expr)))))
|
||||
(:children node))))
|
||||
|
||||
(defn- clause-value-nodes
|
||||
"The value slots of the value under clause key `clause`."
|
||||
[clause node]
|
||||
(cond
|
||||
(contains? expression-entry-clauses clause) (expression-entry-value-nodes node)
|
||||
(contains? identifier-clauses clause) (identifier-clause-value-nodes node)
|
||||
:else (value-nodes node)))
|
||||
|
||||
(defn- value-nodes
|
||||
"The nodes sitting in a value slot of `node`, following the clause shapes a query map uses."
|
||||
[node]
|
||||
(cond
|
||||
;; A query map -- walk its clause values, not its keys.
|
||||
;; A query map -- walk its clause values, not its keys. A clause that names columns or tables
|
||||
;; contributes only what is nested inside it.
|
||||
(hooks/map-node? node)
|
||||
(mapcat value-nodes (take-nth 2 (rest (:children node))))
|
||||
(mapcat (fn [[k v]]
|
||||
(if (hooks/keyword-node? k)
|
||||
(clause-value-nodes (hooks/sexpr k) v)
|
||||
(value-nodes v)))
|
||||
(partition 2 (:children node)))
|
||||
|
||||
(hooks/vector-node? node)
|
||||
(let [[head & args] (:children node)
|
||||
op (some-> head hooks/sexpr)]
|
||||
(cond
|
||||
;; A marker IS the value slot -- never descend into its payload, or the marked value
|
||||
;; gets reported as though it were bare.
|
||||
(marked? node)
|
||||
nil
|
||||
|
||||
(contains? #{:and :or :not} op)
|
||||
(mapcat value-nodes args)
|
||||
|
||||
;; `[:= col v]`, `[:in col vs]`, `[:between col lo hi]` -- the first argument is the
|
||||
;; column and the rest are values, whatever the arity.
|
||||
;; column and the rest are values, whatever the arity. Keep each value AND descend into it,
|
||||
;; so a subquery in a value slot -- `[:in :id {:select ... :where [:= :z z]}]` -- has its
|
||||
;; own values checked too. Descending alone would drop the plain-token case.
|
||||
(and (keyword? op) (contains? value-operators (symbol (name op))))
|
||||
(rest args)
|
||||
(mapcat #(cons % (value-nodes %)) (rest args))
|
||||
|
||||
;; A function-call form -- `[:lower v]` -- or a literal collection -- `[a b]` in an `:in`.
|
||||
;; Both hold values, so yield the children themselves as candidates as well as
|
||||
;; descending, since a token child returns nothing on its own.
|
||||
:else
|
||||
(mapcat value-nodes (:children node))))
|
||||
(mapcat #(cons % (value-nodes %)) (:children node))))
|
||||
|
||||
;; A clause built conditionally -- `(when flag [:= :col v])`, `(if ... )`, `(cond-> ...)`. The
|
||||
;; value slots are inside, so walk the children rather than stopping. Without this a value
|
||||
;; wrapped in `when` is invisible to the check, which is a false negative in a security lint.
|
||||
;;
|
||||
;; An `assoc` call gets its clause context back: `(assoc q :order-by [[col dir]])` is the same
|
||||
;; clause as `{:order-by [[col dir]]}`, but written outside a map literal, where the walk would
|
||||
;; otherwise fall back to shape alone and report the column and the direction as values.
|
||||
;; `cond->` is covered because the walk descends into the nested `assoc`.
|
||||
(hooks/list-node? node)
|
||||
(let [children (vec (:children node))
|
||||
assoc? (= 'assoc (some-> (first children) hooks/sexpr))
|
||||
;; index of a clause value -> its clause key
|
||||
clauses (when assoc?
|
||||
(into {} (keep-indexed (fn [i child]
|
||||
(when (and (hooks/keyword-node? child)
|
||||
(contains? identifier-clauses (hooks/sexpr child))
|
||||
(< (inc i) (count children)))
|
||||
[(inc i) (hooks/sexpr child)]))
|
||||
children)))]
|
||||
(mapcat (fn [i child]
|
||||
(if-let [clause (get clauses i)]
|
||||
(clause-value-nodes clause child)
|
||||
(value-nodes child)))
|
||||
(range)
|
||||
children))
|
||||
|
||||
:else nil))
|
||||
|
||||
@@ -77,35 +188,102 @@
|
||||
(let [f (some-> (first (:children node)) hooks/sexpr)]
|
||||
(not (contains? write-fns (some-> f name symbol)))))
|
||||
|
||||
(defn- kv-arg-pair
|
||||
"The first `:column value` pair a query call passes after its model, if any.
|
||||
(def ^:private clause-keys
|
||||
"Query-map clause keys. A map keyed by these is a query map rather than a map of conditions."
|
||||
#{:select :select-distinct :from :where :join :left-join :right-join :inner-join :full-join
|
||||
:cross-join :group-by :having :order-by :limit :offset :for :union :union-all :with
|
||||
:with-columns :returning :values :set})
|
||||
|
||||
(defn- query-map-node?
|
||||
"Whether `node` is a query map rather than a map of column/value conditions.
|
||||
|
||||
`(t2/delete! :model/X {:where [:= :k k]})` passes a query map, whose values [[value-nodes]]
|
||||
already walks -- treating it as conditions too would report every value twice."
|
||||
[node]
|
||||
(boolean (some #(and (hooks/keyword-node? %) (contains? clause-keys (hooks/sexpr %)))
|
||||
(take-nth 2 (:children node)))))
|
||||
|
||||
(def ^:private changes-map-fns
|
||||
"Conditions-map fns whose arglist also ends in a changes map, so a lone trailing map is changes."
|
||||
'#{update! update-or-insert!})
|
||||
|
||||
(def ^:private conditions-map-fns
|
||||
"Calls whose first argument after the model is a map of CONDITIONS rather than a query map.
|
||||
|
||||
`(t2/update! :model/X {:key k} {:v 1})` filters on `k`, so it is a where-clause value -- but it
|
||||
reaches the query as a plain map entry rather than a `:where` clause, so neither the query-map
|
||||
walker nor the kv-arg walker sees it. It is a shape the sweep leaves in place, so it has to be
|
||||
checked here."
|
||||
'#{update! update-or-insert! delete!})
|
||||
|
||||
(defn- conditions-map-value-nodes
|
||||
"The values of a conditions map passed to one of [[conditions-map-fns]]."
|
||||
[f args]
|
||||
(when (contains? conditions-map-fns (some-> f name symbol))
|
||||
(let [after-model (->> args
|
||||
(drop-while #(not (and (hooks/keyword-node? %)
|
||||
(= "model" (namespace (hooks/sexpr %))))))
|
||||
rest)]
|
||||
;; Only the map immediately after the model is conditions. A later map is the changes map,
|
||||
;; whose values are written rather than filtered on, and a written value is never marked: a
|
||||
;; column's `:in` transform runs on the marker itself and stores it as data.
|
||||
;; `update!`'s arglist ENDS in the changes map, so its first map is conditions only when
|
||||
;; another argument follows: `(t2/update! model {:v written})` is the two-arity call whose
|
||||
;; single map is CHANGES, so flagging it would ask for a marker that corrupts the write.
|
||||
;; `delete!` has no changes map, so its map is always conditions.
|
||||
(when-let [m (when (or (not (contains? changes-map-fns (some-> f name symbol)))
|
||||
(next after-model))
|
||||
(first after-model))]
|
||||
(when (and (hooks/map-node? m) (not (query-map-node? m)))
|
||||
(mapcat (fn [v]
|
||||
;; The column comes from the map key, so an operator form here holds only
|
||||
;; values -- `{:key [:in ks]}` -- exactly as a kv-arg pair does.
|
||||
(if (and (hooks/vector-node? v) (not (marked? v)))
|
||||
(let [[head & op-args] (:children v)
|
||||
op (some-> head hooks/sexpr)]
|
||||
(if (and (keyword? op) (contains? value-operators (symbol (name op))))
|
||||
(mapcat #(cons % (value-nodes %)) op-args)
|
||||
(cons v (value-nodes v))))
|
||||
(cons v (value-nodes v))))
|
||||
(take-nth 2 (rest (:children m)))))))))
|
||||
|
||||
(defn- kv-arg-value-nodes
|
||||
"The value nodes of a call written as `:column value` pairs.
|
||||
|
||||
`(t2/select :model/X :locale locale)`. Several of these fns take an argument before the model --
|
||||
`(t2/select-one-fn :value :model/X :key k)` -- so the pairs do not start at a fixed offset; they
|
||||
start after the `:model/...` keyword. A call that does not name a literal model is not checked."
|
||||
start after the `:model/...` keyword. A call that does not name a literal model is not checked.
|
||||
|
||||
A trailing query map is not a pair, and its values are reached by [[value-nodes]] instead. A
|
||||
value that is itself an operator form -- `:id [:in ids]` -- has its own values walked, so the
|
||||
collection inside is checked rather than the form."
|
||||
[args]
|
||||
(let [after-model (->> args
|
||||
(drop-while #(not (and (hooks/keyword-node? %)
|
||||
(= "model" (namespace (hooks/sexpr %))))))
|
||||
rest)
|
||||
[k v] after-model]
|
||||
(when (and k v (hooks/keyword-node? k))
|
||||
k)))
|
||||
rest)]
|
||||
(->> after-model
|
||||
;; `partition-all` rather than `partition` so a trailing odd argument (a query map, which
|
||||
;; is not a pair) is still seen and skipped by the keyword-node? test below.
|
||||
(partition-all 2)
|
||||
(mapcat (fn [[k v]]
|
||||
(when (and v (hooks/keyword-node? k))
|
||||
(if-not (hooks/vector-node? v)
|
||||
[v]
|
||||
(let [[head & op-args] (:children v)
|
||||
op (some-> head hooks/sexpr)]
|
||||
(cond
|
||||
;; Already marked -- the marker is the value slot.
|
||||
(marked? v) [v]
|
||||
|
||||
(defn- lint-kv-args!
|
||||
"Register a finding for a query written as `:column value` pairs.
|
||||
;; `:id [:in ids]`. Toucan folds the column in from the pair key, so
|
||||
;; EVERY argument here is a value -- unlike a query-map clause, where
|
||||
;; the first argument is the column.
|
||||
(and (keyword? op)
|
||||
(contains? value-operators (symbol (name op))))
|
||||
(mapcat #(cons % (value-nodes %)) op-args)
|
||||
|
||||
Toucan builds those into the where clause itself, so the query's shape is not on the page: there
|
||||
is nothing for a reader, this linter, or the `.sql` extraction that follows this project to read.
|
||||
The same call takes a map -- `{:where [:= :locale v]}` for a select, `{:locale v}` for an
|
||||
`update!` -- which is the statement, near enough to read off."
|
||||
[node]
|
||||
(when-let [k (kv-arg-pair (rest (:children node)))]
|
||||
(hooks/reg-finding!
|
||||
(assoc (meta k)
|
||||
:message (format "Pass this query a map rather than `%s ...` pairs, so its shape is on the page."
|
||||
(hooks/sexpr k))
|
||||
:type :metabase/unsafe-app-db-query))))
|
||||
:else (cons v (value-nodes v)))))))))))
|
||||
|
||||
(defn- lint-unmarked-values!
|
||||
"Register a finding for each argument of the enclosing function that reaches a value slot unmarked.
|
||||
@@ -113,7 +291,15 @@
|
||||
Only a symbol is reported. A literal cannot carry a request value, and a value built inside the
|
||||
function is out of reach of a check that does not follow it across a call."
|
||||
[node]
|
||||
(doseq [value (mapcat value-nodes (rest (:children node)))
|
||||
(doseq [value (let [args (rest (:children node))
|
||||
f (some-> (first (:children node)) hooks/sexpr)]
|
||||
;; The three walkers overlap on some shapes -- a multi-arg operator in a kv-arg
|
||||
;; reaches both `value-nodes` and `kv-arg-value-nodes` -- so dedupe by source
|
||||
;; position rather than trying to keep them disjoint.
|
||||
(->> (concat (mapcat value-nodes args)
|
||||
(kv-arg-value-nodes args)
|
||||
(conditions-map-value-nodes f args))
|
||||
(distinct-by #(select-keys (meta %) [:row :col]))))
|
||||
:when (and (hooks/token-node? value)
|
||||
(symbol? (hooks/sexpr value))
|
||||
(not (marked? value)))]
|
||||
@@ -141,6 +327,5 @@
|
||||
(db-namespace? (modules/config input) ns)
|
||||
(not (test-file? filename))
|
||||
(read-call? node))
|
||||
(lint-unmarked-values! node)
|
||||
(lint-kv-args! node))
|
||||
(lint-unmarked-values! node))
|
||||
input)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
(ns hooks.metabase.app-db.schema-migrations-test.impl-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clj-kondo.impl.utils]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.app-db.schema-migrations-test.impl]))
|
||||
|
||||
(defn- finding-types!
|
||||
[ns-symb]
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/validate-mb-app-db-migrations-test {:level :warning}}}
|
||||
:ignores (atom nil)
|
||||
:findings (atom [])
|
||||
:namespaces (atom {})}]
|
||||
(hooks.metabase.app-db.schema-migrations-test.impl/test-migrations!
|
||||
{:ns ns-symb
|
||||
:node (hooks/parse-string (pr-str '(impl/test-migrations ["v64.2026-09-01T00:00:00"] [migrate!] (migrate!))))})
|
||||
(mapv :type @(:findings clj-kondo.impl.utils/*ctx*))))
|
||||
|
||||
(deftest ^:synchronized require-app-db-migrations-test-tag-test
|
||||
(testing "a namespace that runs migration tests must be tagged, so CI shards it with the other migration tests"
|
||||
(is (= [:metabase/validate-mb-app-db-migrations-test]
|
||||
(finding-types! 'metabase.some-migrations-test))))
|
||||
(testing "a tagged namespace is fine"
|
||||
(is (= []
|
||||
(finding-types! (with-meta 'metabase.some-migrations-test {:mb/app-db-migrations-test true}))))))
|
||||
@@ -103,24 +103,101 @@
|
||||
(is (empty? (lint-query-call '(t2/select :model/X {:where [:= :locale locale]})
|
||||
'metabase.foo.db "test/metabase/foo/db_test.clj")))))
|
||||
|
||||
(deftest ^:parallel kv-arg-style-test
|
||||
(testing "a query written as :column value pairs is flagged whatever the values are"
|
||||
(are [form] (=? [{:type :metabase/unsafe-app-db-query, :message #"Pass this query a map.*"}]
|
||||
(deftest ^:parallel kv-arg-style-is-not-flagged-test
|
||||
(testing "call style is not linted: a query written as :column value pairs is left alone"
|
||||
;; Extraction to a .sql file is semantic rather than a transcription of the HoneySQL shape, so a
|
||||
;; query map is no closer to that target than kv-args are. Converting also skips Toucan's type
|
||||
;; transforms, which only run in kv-arg position -- that bug shipped twice.
|
||||
(are [form] (empty? (filter #(re-find #"Pass this query a map" (str (:message %)))
|
||||
(lint-query-call form 'metabase.foo.db)))
|
||||
'(t2/select :model/X :locale [:auto/param locale])
|
||||
'(t2/select :model/X :archived false)
|
||||
'(t2/select-one :model/X :id (long id))
|
||||
'(t2/select-one-fn :value :model/Setting :key [:auto/param k])
|
||||
'(t2/select :model/X {:where [:= :locale [:auto/param locale]]})))
|
||||
(testing "an unmarked value in a kv-arg is still flagged -- only the style check went away"
|
||||
(is (=? [{:type :metabase/unsafe-app-db-query, :message #".*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call '(t2/select :model/X :locale locale) 'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel conditions-map-values-are-linted-test
|
||||
(testing "a conditions map -- a shape the sweep leaves in place -- is checked"
|
||||
;; `(t2/update! model {:col v} changes)` filters on v, but it is not a `:where` clause, so
|
||||
;; neither the query-map walker nor the kv-arg walker used to see it. A namespace written this
|
||||
;; way passed the lint vacuously.
|
||||
(are [form] (=? [{:type :metabase/unsafe-app-db-query
|
||||
:message #".*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
'(t2/update! :model/X {:key k} {:v 1})
|
||||
'(t2/delete! :model/X {:key k})
|
||||
'(t2/update! :model/X {:key [:in ks]} {:v 1})))
|
||||
(testing "a marked or coerced conditions value is not flagged"
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/update! :model/X {:key [:auto/param k]} {:v 1})
|
||||
'(t2/update! :model/X {:id (long id)} {:v 1})))
|
||||
(testing "only the map right after the model is conditions -- the changes map is exempt"
|
||||
;; rule 1: a written value must arrive as itself, so it is never flagged.
|
||||
(is (empty? (lint-query-call '(t2/update! :model/X {:id (long id)} {:v written})
|
||||
'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel subquery-values-are-linted-test
|
||||
(testing "a value inside a subquery in a value slot is reached"
|
||||
(is (=? [{:type :metabase/unsafe-app-db-query
|
||||
:message #".*`z`.*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call
|
||||
'(t2/select :model/X {:where [:in :id {:select [:id] :from [:y] :where [:= :z z]}]})
|
||||
'metabase.foo.db))))
|
||||
(testing "the plain token case still works -- descending must not replace it"
|
||||
(is (=? [{:message #"`v`.*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call '(t2/select :model/X {:where [:= :k v]}) 'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel conditionally-built-clause-test
|
||||
(testing "a value slot inside a conditional form is still reached"
|
||||
;; `value-nodes` used to stop at a list node, so anything a `when`/`if`/`cond->` built was
|
||||
;; invisible -- a false negative in a security lint. permissions/db.clj and collections/db.clj
|
||||
;; both build clauses this way.
|
||||
(are [form] (=? [{:type :metabase/unsafe-app-db-query
|
||||
:message #".*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X {:where [:and (when k [:= :key k])]})))
|
||||
(testing "both branches of an if are reached"
|
||||
(is (= 2 (count (lint-query-call
|
||||
'(t2/select :model/X {:where [:and (if flag [:= :key k] [:= :key other-k])]})
|
||||
'metabase.foo.db)))))
|
||||
(testing "a marked value inside a conditional form is not flagged"
|
||||
(is (empty? (lint-query-call
|
||||
'(t2/select :model/X {:where [:and (when k [:= :key [:auto/param k]])]})
|
||||
'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel kv-arg-values-are-linted-test
|
||||
(testing "an unmarked value in a kv-arg pair is flagged, so dropping the style check left no gap"
|
||||
(are [form] (=? [{:type :metabase/unsafe-app-db-query
|
||||
:message #".*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X :locale locale)
|
||||
'(t2/select :model/X :archived false)
|
||||
'(t2/select :model/X :locale [:auto/param locale])
|
||||
'(t2/select-one :model/X :id (long id))))
|
||||
(testing "one finding per call, not one per pair"
|
||||
(is (= 1 (count (lint-query-call '(t2/select :model/X :locale locale :msgid msgid)
|
||||
'metabase.foo.db)))))
|
||||
(testing "a fn with an argument before the model is still recognised"
|
||||
(is (=? [{:message #"Pass this query a map.*"}]
|
||||
(lint-query-call '(t2/select-one-fn :value :model/Setting :key k) 'metabase.foo.db))))
|
||||
(testing "a map query is not flagged"
|
||||
'(t2/select-one :model/X :id id)
|
||||
;; the pairs do not start at a fixed offset -- :value comes before the model here
|
||||
'(t2/select-one-fn :value :model/Setting :key k)
|
||||
;; a value inside an operator form is reached, rather than the form being treated as a value
|
||||
'(t2/select :model/X :id [:in ids])))
|
||||
(testing "a marked or coerced kv-arg value is not flagged"
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X {:where [:= :locale [:auto/param locale]]})
|
||||
'(t2/update! :model/X {:locale [:auto/param locale]} {:msgstr "x"}))))
|
||||
'(t2/select :model/X :locale [:auto/param locale])
|
||||
'(t2/select-one :model/X :id (long id))
|
||||
'(t2/select :model/X :id [:in (mapv long ids)])
|
||||
'(t2/select :model/X :id [:in [:auto/param ids]])))
|
||||
(testing "a literal kv-arg value is not flagged"
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X :archived false)
|
||||
'(t2/select :model/X :status "pending")
|
||||
'(t2/select :model/X :engine :appdb)))
|
||||
(testing "one unmarked value per pair is reported"
|
||||
(is (= 2 (count (lint-query-call '(t2/select :model/X :locale locale :msgid msgid)
|
||||
'metabase.foo.db)))))
|
||||
(testing "a call mixing kv-args with a trailing query map reports each value exactly once"
|
||||
;; The two walkers run over the same arguments, so this pins that they do not double-report.
|
||||
(is (= 2 (count (lint-query-call
|
||||
'(t2/select :model/X :locale locale {:where [:= :other other]})
|
||||
'metabase.foo.db))))))
|
||||
|
||||
(deftest ^:parallel write-calls-are-not-linted-for-values-test
|
||||
(testing "an insert's values are written, not filtered on, so they are not flagged"
|
||||
@@ -136,3 +213,112 @@
|
||||
(testing "a value operator with an unexpected arity still has its args examined"
|
||||
(is (seq (lint-query-call '(t2/select :model/X {:where [:= :a b c]}) 'metabase.foo.db)))
|
||||
(is (seq (lint-query-call '(t2/select :model/X {:where [:between :a lo hi]}) 'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel operator-coverage-test
|
||||
(testing "comparison operators HoneySQL accepts as aliases for not= are checked"
|
||||
;; The set used to list only `not=`, so `[:!= :col v]` -- live in collections/db.clj -- was
|
||||
;; silently unchecked even though HoneySQL binds it as `<>`.
|
||||
(are [form] (=? [{:message #"`v`.*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X {:where [:!= :k v]})
|
||||
'(t2/select :model/X {:where [:<> :k v]})
|
||||
'(t2/select :model/X {:where [:is-distinct-from :k v]})))
|
||||
(testing "a two-value operator reports both values"
|
||||
(is (= 2 (count (lint-query-call '(t2/select :model/X {:where [:not-between :k lo hi]})
|
||||
'metabase.foo.db)))))
|
||||
(testing "a value wrapped in a function-call form is reached"
|
||||
;; `[:lower v]` compiles to `LOWER(?)`, so `v` is a real bind slot.
|
||||
(are [form] (seq (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/select :model/X {:where [:= :k [:lower v]]})
|
||||
'(t2/select :model/X {:where [:= :k [:cast v :text]]})
|
||||
'(t2/select :model/X :k [:lower v])))
|
||||
(testing "values in a literal collection are reached"
|
||||
;; `[:in :k [a b]]` binds each element -- `IN (?, ?)`.
|
||||
(is (= 2 (count (lint-query-call '(t2/select :model/X {:where [:in :k [a b]]})
|
||||
'metabase.foo.db))))))
|
||||
|
||||
(deftest ^:parallel no-duplicate-findings-test
|
||||
(testing "a multi-arg operator in a kv-arg reports each value exactly once"
|
||||
;; value-nodes misreads `[:between lo hi]` as a clause (lo as column), while
|
||||
;; kv-arg-value-nodes reads it correctly, so the walkers overlap on `hi`. Deduping by source
|
||||
;; position makes that harmless.
|
||||
(is (= 2 (count (lint-query-call '(t2/select :model/X :d [:between lo hi]) 'metabase.foo.db))))
|
||||
(is (= 2 (count (lint-query-call '(t2/delete! :model/X {:d [:between lo hi]})
|
||||
'metabase.foo.db)))))
|
||||
(testing "a query map passed where conditions go reports each value once"
|
||||
;; `{:where ...}` is a query map that value-nodes already walks; treating it as conditions too
|
||||
;; reported everything twice. Live in mcp/db.clj, metabot/db.clj, testing_api/db.clj.
|
||||
(is (= 1 (count (lint-query-call '(t2/delete! :model/X {:where [:= :k k]}) 'metabase.foo.db))))
|
||||
(is (= 2 (count (lint-query-call '(t2/update! :model/X {:where [:and [:= :a a] [:= :b b]]} {:v 1})
|
||||
'metabase.foo.db))))))
|
||||
|
||||
(deftest ^:parallel two-arity-update-changes-map-test
|
||||
(testing "update!'s lone trailing map is CHANGES, not conditions, so its values are not flagged"
|
||||
;; Toucan's arglist ends in the changes map. remote_sync/db.clj's mark-all-rsos-synced! is this
|
||||
;; shape; flagging `written` would ask for a marker on a written value, which the column's
|
||||
;; `:in` transform stores as data.
|
||||
(is (empty? (lint-query-call '(t2/update! :model/X {:v written}) 'metabase.foo.db))))
|
||||
(testing "delete! has no changes map, so its lone map is always conditions"
|
||||
(is (=? [{:message #"`k`.*"}]
|
||||
(lint-query-call '(t2/delete! :model/X {:k k}) 'metabase.foo.db))))
|
||||
(testing "update!'s FIRST map is still conditions when a changes map follows"
|
||||
(is (=? [{:message #"`k`.*"}]
|
||||
(lint-query-call '(t2/update! :model/X {:k k} {:v 1}) 'metabase.foo.db)))))
|
||||
|
||||
(deftest ^:parallel identifier-clauses-are-not-value-slots-test
|
||||
(testing "a symbol naming a column or a table is not reported"
|
||||
;; `value-guard` refuses a marker in these clauses, so reporting one asks for a fix that throws
|
||||
;; at compile. data_studio/db.clj, task_history/db.clj and permissions/db.clj are written this
|
||||
;; way. `:select` is the exception: index 0 of its entries is an expression, so it has its own
|
||||
;; test in expression-entry-clauses-test.
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/query {:select [:*] :from [[union-query :subquery]]})
|
||||
'(t2/query {:select [:*] :from [:t] :order-by [[:sort_key sort-direction]]})
|
||||
'(t2/query {:select [:*] :from [:t] :group-by [group-column]})))
|
||||
(testing "a value nested inside an identifier clause is still reported"
|
||||
(are [form] (=? [{:message #"`v`.*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
;; a computed projection is a real comparison
|
||||
'(t2/query {:select [[[:= :engine v] :is_match]] :from [:t]})
|
||||
;; so is a CASE sort key
|
||||
'(t2/query {:select [:*] :from [:t] :order-by [[[:case [:= :a v] 1 :else 2] :asc]]})
|
||||
;; a subquery carries its own where clause
|
||||
'(t2/query {:select [:*] :from [[{:select [:*] :from [:u] :where [:= :k v]} :s]]}))))
|
||||
|
||||
(deftest ^:parallel expression-entry-clauses-test
|
||||
(testing "index 0 of an entry is a value slot, because HoneySQL binds a parameter there"
|
||||
;; `{:select [[[:auto/param "a"] :k]]}` compiles to `SELECT ? AS "K"`, so `value-guard` accepts
|
||||
;; a marker there. Treating the whole clause as names would hide a request value.
|
||||
(are [form] (=? [{:type :metabase/unsafe-app-db-query
|
||||
:message #"`v`.*reaches a SQL value slot unmarked.*"}]
|
||||
(lint-query-call form 'metabase.foo.db))
|
||||
'(t2/query {:select [[v :label]] :from [:t]})
|
||||
'(t2/query {:select-distinct [[v :label]] :from [:t]})
|
||||
'(t2/query {:insert-into [:t] :returning [[v :label]]})))
|
||||
(testing "the alias, a bare entry and a table entry are names"
|
||||
;; `value-guard` refuses a marker in each of these, so asking for one would break the query.
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/query {:select [[:key alias-sym]] :from [:t]})
|
||||
'(t2/query {:select [cols] :from [:t]})
|
||||
'(t2/query {:select [:*] :from [[tbl :x]]})))
|
||||
(testing "a computed projection and a subquery entry still reach their values"
|
||||
(are [form] (=? [{:message #"`v`.*"}] (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/query {:select [[[:= :engine v] :is_match]] :from [:t]})
|
||||
'(t2/query {:select [:*] :from [[{:select [:*] :from [:u] :where [:= :k v]} :s]]}))))
|
||||
|
||||
(deftest ^:parallel clause-context-survives-assoc-test
|
||||
(testing "a clause key in an assoc call gives its value the clause context back"
|
||||
;; task_history/db.clj:174 and five other namespaces build `:order-by` this way. Without this
|
||||
;; the lint asks for a marker on a column name and on a sort direction, and the fix the message
|
||||
;; names loses the ordering with no error.
|
||||
(are [form] (empty? (lint-query-call form 'metabase.foo.db))
|
||||
'(t2/query (assoc q :order-by [[sort-column sort-direction] [:id :desc]]))
|
||||
'(t2/query (cond-> q flag (assoc :order-by [[sort-column sort-direction]])))
|
||||
'(t2/query (assoc q :group-by [group-column]))
|
||||
'(t2/query (assoc q :select [[:key alias-sym]]))))
|
||||
(testing "a value clause built the same way is still reported"
|
||||
(is (=? [{:message #"`v`.*"}]
|
||||
(lint-query-call '(t2/query (assoc q :where [:= :k v])) 'metabase.foo.db))))
|
||||
(testing "an expression entry built the same way is still reported"
|
||||
(is (=? [{:message #"`v`.*"}]
|
||||
(lint-query-call '(t2/query (assoc q :select [[v :label]])) 'metabase.foo.db)))))
|
||||
|
||||
46
.env.example
@@ -121,6 +121,9 @@ MB_EDITION="ee"
|
||||
# Frontend dev server port (for hot reload). Must match the port used by `yarn build-hot`.
|
||||
# MB_FRONTEND_DEV_PORT=8080
|
||||
|
||||
# Frontend dev server bind address. Defaults to 127.0.0.1; set 0.0.0.0 to reach it from other devices.
|
||||
# MB_FRONTEND_DEV_HOST=127.0.0.1
|
||||
|
||||
# 3.2 E2E Local Runner Setup
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@@ -161,3 +164,46 @@ TZ="US/Pacific"
|
||||
# DO NOT USE Docker Desktop. Use Orbstack instead. Metabase will fail to connect
|
||||
# to the dockerized Mongo QA database otherwise.
|
||||
EXPERIMENTAL_DOCKER_DESKTOP_FORCE_QEMU=1
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 4. BACKEND DRIVER TESTS
|
||||
# ==============================================================================
|
||||
#
|
||||
# Connection details for running backend tests against a driver, e.g.
|
||||
# ./bin/test-agent --drivers=snowflake :only '[metabase.foo-test]'
|
||||
|
||||
# 4.1 BigQuery
|
||||
# ------------------------------------------------------------------------------
|
||||
# Service account with BigQuery access in the project. The test user creates and drops the test-data datasets itself.
|
||||
# https://cloud.google.com/iam/docs/keys-create-delete
|
||||
|
||||
# MB_BIGQUERY_CLOUD_SDK_TEST_PROJECT_ID="<project-id>"
|
||||
# MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON='{"type": "service_account", ...}'
|
||||
# Only for database routing tests
|
||||
# MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING='{"type": "service_account", ...}'
|
||||
|
||||
# 4.2 Redshift
|
||||
# ------------------------------------------------------------------------------
|
||||
# Test data is loaded into a unique schema per test run.
|
||||
|
||||
# MB_REDSHIFT_TEST_HOST="<cluster>.<id>.<region>.redshift.amazonaws.com"
|
||||
# Comma-separated hosts to pick one at random from, instead of MB_REDSHIFT_TEST_HOST
|
||||
# MB_REDSHIFT_TEST_HOSTS=""
|
||||
# MB_REDSHIFT_TEST_PORT=5439
|
||||
# MB_REDSHIFT_TEST_DB="testdb"
|
||||
# MB_REDSHIFT_TEST_USER="metabase_ci"
|
||||
# MB_REDSHIFT_TEST_PASSWORD="<password>"
|
||||
# Only for database routing tests
|
||||
# MB_REDSHIFT_TEST_DB_ROUTING="dev"
|
||||
|
||||
# 4.3 Snowflake
|
||||
# ------------------------------------------------------------------------------
|
||||
# Key-pair authentication. The test user creates and drops the test-data databases itself.
|
||||
# https://docs.snowflake.com/en/user-guide/key-pair-auth
|
||||
|
||||
# MB_SNOWFLAKE_TEST_ACCOUNT="<orgname>-<accountname>"
|
||||
# MB_SNOWFLAKE_TEST_USER="<username>"
|
||||
# MB_SNOWFLAKE_TEST_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
||||
# MB_SNOWFLAKE_TEST_WAREHOUSE="<warehouse>"
|
||||
# MB_SNOWFLAKE_TEST_ADDITIONAL_OPTIONS=""
|
||||
|
||||
14
.github/actions/collect-profile/action.yml
vendored
@@ -22,6 +22,9 @@ runs:
|
||||
# `awk 'NR<=25'` rather than `head -25`: the runner ignores SIGPIPE, so `head` exiting early
|
||||
# leaves `sort` with an EPIPE write error and `pipefail` fails the step. `|| true` covers `grep`
|
||||
# finding no junit files.
|
||||
#
|
||||
# `-r --include` rather than a `target/junit/*.xml` glob: a multi-JVM run nests one directory
|
||||
# per partition under `target/junit`, and a non-recursive glob matches none of it.
|
||||
- name: Summarize test wall times
|
||||
if: ${{ inputs.profiled == 'true' }}
|
||||
continue-on-error: true
|
||||
@@ -30,13 +33,13 @@ runs:
|
||||
{
|
||||
echo "### Slowest test namespaces (seconds)"
|
||||
echo '```'
|
||||
grep -h '<testsuite' target/junit/*.xml 2>/dev/null \
|
||||
grep -rh --include='*.xml' '<testsuite' target/junit 2>/dev/null \
|
||||
| sed -E 's|.*<testsuite name="([^"]*)" time="([^"]*)".*|\2 \1|' \
|
||||
| sort -rn | awk 'NR<=25' || true
|
||||
echo '```'
|
||||
echo "### Slowest tests (seconds)"
|
||||
echo '```'
|
||||
grep -h '<testcase' target/junit/*.xml 2>/dev/null \
|
||||
grep -rh --include='*.xml' '<testcase' target/junit 2>/dev/null \
|
||||
| sed -E 's|.*classname="([^"]*)" name="([^"]*)" time="([^"]*)".*|\3 \1/\2|' \
|
||||
| sort -rn | awk 'NR<=25' || true
|
||||
echo '```'
|
||||
@@ -53,9 +56,12 @@ runs:
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ inputs.artifact-name }}
|
||||
# Globs, because a multi-JVM run nests its junit XML one directory per partition and
|
||||
# names each log `test-log-p<index>.json`. The flamegraphs stay flat, with that same
|
||||
# `p<index>-` prefix.
|
||||
path: |
|
||||
target/clj-async-profiler/results/*flamegraph.html
|
||||
target/junit/*.xml
|
||||
logs/test-log.json
|
||||
target/junit/**/*.xml
|
||||
logs/test-log*.json
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
20
.github/actions/create-backport/action.yml
vendored
@@ -85,17 +85,27 @@ runs:
|
||||
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Approve and Merge the PR
|
||||
# Approving takes a different token than the one that opened the PR: GitHub won't let a token
|
||||
# approve its own pull request.
|
||||
- name: Approve the backport PR
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
run: |
|
||||
gh pr review --approve "$PR_NUMBER"
|
||||
gh pr merge --squash --auto "$PR_NUMBER"
|
||||
run: gh pr review --approve "$PR_NUMBER"
|
||||
shell: bash
|
||||
env:
|
||||
# uses a different token than the token that created the PR so we can approve and merge it
|
||||
GH_TOKEN: ${{ inputs.github-token-2 }}
|
||||
PR_NUMBER: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
|
||||
# Merging, though, has to use `github-token` rather than the `GITHUB_TOKEN` that approves: GitHub
|
||||
# starts no workflow run for a push made with `GITHUB_TOKEN`, so an auto-merge enabled with it
|
||||
# lands the backport on the release branch with no CI at all.
|
||||
- name: Merge the backport PR
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
run: gh pr merge --squash --auto "$PR_NUMBER"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.github-token }}
|
||||
PR_NUMBER: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
|
||||
- uses: ./.github/actions/notify-pull-request
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'true' }}
|
||||
with:
|
||||
|
||||
10
.github/actions/test-driver/action.yml
vendored
@@ -130,6 +130,7 @@ runs:
|
||||
if [ "$PROFILE_TESTS" = "true" ]; then
|
||||
echo "Profiling this run; a flamegraph will be attached to the job."
|
||||
echo "profiled=true" >> "$GITHUB_OUTPUT"
|
||||
ALIASES="$ALIASES:profile-tests"
|
||||
BASE="$BASE:profile-tests"
|
||||
fi
|
||||
# An explicit selection wins over the rerun narrowing below: it was asked for by hand, on this
|
||||
@@ -149,6 +150,15 @@ runs:
|
||||
shell: bash
|
||||
id: run-test
|
||||
env:
|
||||
# Both derived from test.profile-backend.yml, the sole dispatcher; its runs always profile. Inside
|
||||
# a called workflow `github.event` is the caller's payload, so these resolve here rather than
|
||||
# in each calling workflow. On a normal CI run there are no dispatch inputs, so these render
|
||||
# `false` and empty and the job's own selection stands.
|
||||
#
|
||||
# ONLY_TESTS replaces the job's own test selection, so every matrix leg of the chosen job
|
||||
# runs the whole list.
|
||||
PROFILE_TESTS: ${{ github.event.inputs.suite != '' }}
|
||||
ONLY_TESTS: ${{ github.event.inputs.tests }}
|
||||
TEST_RUNNER_INDEX: ${{ inputs.runner-index }}
|
||||
TEST_RUNNER_COUNT: ${{ inputs.runner-count }}
|
||||
TEST_JVMS_PER_RUNNER: ${{ inputs.jvms-per-runner }}
|
||||
|
||||
9
.github/scripts/run-backend-test-jvms.sh
vendored
@@ -98,6 +98,15 @@ merge_outputs() {
|
||||
if [ -f "$jvm_dir/logs/test-log.json" ]; then
|
||||
cp -a "$jvm_dir/logs/test-log.json" "$WORKSPACE/logs/test-log-p$idx.json" || true
|
||||
fi
|
||||
# Flamegraphs, when the run was profiled. Flat with a `p$idx-` prefix, not nested: the
|
||||
# collect-profile action globs `results/*flamegraph.html` without descending.
|
||||
if [ -d "$jvm_dir/target/clj-async-profiler/results" ]; then
|
||||
mkdir -p "$WORKSPACE/target/clj-async-profiler/results"
|
||||
for f in "$jvm_dir"/target/clj-async-profiler/results/*flamegraph.html; do
|
||||
[ -e "$f" ] || continue
|
||||
cp -a "$f" "$WORKSPACE/target/clj-async-profiler/results/p$idx-$(basename "$f")" || true
|
||||
done
|
||||
fi
|
||||
done
|
||||
}
|
||||
trap merge_outputs EXIT
|
||||
|
||||
175
.github/workflows/app-db.yml
vendored
@@ -11,30 +11,6 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Run one app-DB job on demand, against the ref you dispatch on, optionally under a profiler.
|
||||
# Everything else about the job -- services, matrix legs, test args, quarantine gating -- is
|
||||
# whatever the normal CI run uses, because it is the same job.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app-db:
|
||||
description: 'Which app-DB job to run. Only this one runs; the others are skipped.'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- mariadb
|
||||
- mysql
|
||||
- postgres
|
||||
tests:
|
||||
description: 'Optional: run only these tests, comma- or space-separated, e.g. metabase.db.custom-migrations-test/some-test. Bare namespaces work too. Blank runs the job''s usual set.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
profile:
|
||||
description: 'Profile the test run and attach a flamegraph to the job. Adds sampling overhead.'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-app-db
|
||||
cancel-in-progress: true
|
||||
@@ -45,14 +21,6 @@ concurrency:
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ vars.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
# Both read by the test-driver action, which is what actually runs the tests. Set here rather than
|
||||
# passed as inputs to each job so that turning either on is one line, not one line per job.
|
||||
# Empty (and so off) on workflow_call, where neither input exists.
|
||||
#
|
||||
# ONLY_TESTS replaces the job's own test selection, so every matrix leg of the chosen job runs the
|
||||
# whole list -- each app DB has three.
|
||||
PROFILE_TESTS: ${{ inputs.profile }}
|
||||
ONLY_TESTS: ${{ inputs.tests }}
|
||||
|
||||
jobs:
|
||||
app-db-matrix:
|
||||
@@ -64,8 +32,9 @@ jobs:
|
||||
mariadb: ${{ steps.versions.outputs.mariadb }}
|
||||
mysql: ${{ steps.versions.outputs.mysql }}
|
||||
postgres: ${{ steps.versions.outputs.postgres }}
|
||||
shards: ${{ steps.versions.outputs.shards }}
|
||||
steps:
|
||||
- name: Select versions to test
|
||||
- name: Select versions and shards to test
|
||||
id: versions
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
@@ -133,35 +102,42 @@ jobs:
|
||||
core.setOutput(db, JSON.stringify(selected));
|
||||
}
|
||||
|
||||
const commonExcludes = ":mb/driver-tests :mb/transforms-python-test :mb/old-migrations-test";
|
||||
|
||||
const shards = [
|
||||
{
|
||||
name: 'Enterprise Tests',
|
||||
'build-static-viz': false,
|
||||
'test-args': `:only '"enterprise/backend/test"' :exclude-tags '[${commonExcludes}]'`,
|
||||
},
|
||||
{
|
||||
name: 'EE App DB Tests (Part 1)', // migrations shard
|
||||
'build-static-viz': true,
|
||||
'test-args': `:only '"test"' :only-tags '[:mb/app-db-migrations-test]' :exclude-tags '[${commonExcludes}]'`,
|
||||
},
|
||||
{
|
||||
name: 'EE App DB Tests (Part 2)',
|
||||
'build-static-viz': true,
|
||||
'test-args': `:only '"test"' :partition/total 2 :partition/index 0 :exclude-tags '[${commonExcludes} :mb/app-db-migrations-test]'`,
|
||||
},
|
||||
{
|
||||
name: 'EE App DB Tests (Part 3)',
|
||||
'build-static-viz': true,
|
||||
'test-args': `:only '"test"' :partition/total 2 :partition/index 1 :exclude-tags '[${commonExcludes} :mb/app-db-migrations-test]'`,
|
||||
},
|
||||
];
|
||||
core.setOutput('shards', JSON.stringify(shards));
|
||||
|
||||
be-tests-mariadb:
|
||||
needs: app-db-matrix
|
||||
if: ${{ !inputs.skip && (inputs.app-db == '' || inputs.app-db == 'mariadb') }}
|
||||
if: ${{ !inputs.skip && (github.event.inputs.db == '' || github.event.inputs.db == 'mariadb') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ${{ fromJSON(needs.app-db-matrix.outputs.mariadb) }}
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
job: ${{ fromJSON(needs.app-db-matrix.outputs.shards) }}
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -175,10 +151,6 @@ jobs:
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
|
||||
MARIADB_DATABASE: mb_test
|
||||
env:
|
||||
# __ADDITIONAL_EXCLUDED_TAG__: ${{ !inputs.force-run && ':mb/old-migrations-test' || '' }}
|
||||
# GDGT-3126: re-enable and properly fix old migrations tests
|
||||
__ADDITIONAL_EXCLUDED_TAG__: ':mb/old-migrations-test'
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -205,7 +177,8 @@ jobs:
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-6-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
# Shared by every app DB job below via `*app-db-test-steps`.
|
||||
steps: &app-db-test-steps
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
@@ -214,39 +187,18 @@ jobs:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
artifact-suffix: ${{ matrix.job.name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
|
||||
be-tests-mysql:
|
||||
needs: app-db-matrix
|
||||
if: ${{ !inputs.skip && (inputs.app-db == '' || inputs.app-db == 'mysql') }}
|
||||
if: ${{ !inputs.skip && (github.event.inputs.db == '' || github.event.inputs.db == 'mysql') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ${{ fromJSON(needs.app-db-matrix.outputs.mysql) }}
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
job: ${{ fromJSON(needs.app-db-matrix.outputs.shards) }}
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -260,10 +212,6 @@ jobs:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: mb_test
|
||||
env:
|
||||
# __ADDITIONAL_EXCLUDED_TAG__: ${{ !inputs.force-run && ':mb/old-migrations-test' || '' }}
|
||||
# GDGT-3126: re-enable and properly fix old migrations tests
|
||||
__ADDITIONAL_EXCLUDED_TAG__: ':mb/old-migrations-test'
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -298,54 +246,20 @@ jobs:
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-6-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
artifact-suffix: ${{ matrix.job.name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
steps: *app-db-test-steps
|
||||
|
||||
be-tests-postgres:
|
||||
needs: app-db-matrix
|
||||
if: ${{ !inputs.skip && (inputs.app-db == '' || inputs.app-db == 'postgres') }}
|
||||
if: ${{ !inputs.skip && (github.event.inputs.db == '' || github.event.inputs.db == 'postgres') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ${{ fromJSON(needs.app-db-matrix.outputs.postgres) }}
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
job: ${{ fromJSON(needs.app-db-matrix.outputs.shards) }}
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
env:
|
||||
# __ADDITIONAL_EXCLUDED_TAG__: ${{ !inputs.force-run && ':mb/old-migrations-test' || '' }}
|
||||
# GDGT-3126: re-enable and properly fix old migrations tests
|
||||
__ADDITIONAL_EXCLUDED_TAG__: ':mb/old-migrations-test'
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: postgres
|
||||
MB_DB_TYPE: postgres
|
||||
@@ -374,18 +288,7 @@ jobs:
|
||||
POSTGRES_USER: mb_test
|
||||
POSTGRES_DB: mb_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
artifact-suffix: ${{ matrix.job.name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
steps: *app-db-test-steps
|
||||
|
||||
app-db-tests-result:
|
||||
needs:
|
||||
|
||||
33
.github/workflows/backend.yml
vendored
@@ -23,22 +23,6 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Run the backend test suite on demand, against the ref you dispatch on, optionally under a
|
||||
# profiler. Everything else about the jobs -- matrix legs, tag filters, quarantine gating -- is
|
||||
# whatever the normal CI run uses, because they are the same jobs.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tests:
|
||||
description: 'Optional: run only these tests, comma- or space-separated, e.g. metabase.query-processor-test/some-test. Bare namespaces work too. Blank runs each leg''s usual set.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
profile:
|
||||
description: 'Profile the test run and attach a flamegraph to the job. Adds sampling overhead.'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-backend
|
||||
cancel-in-progress: true
|
||||
@@ -49,13 +33,6 @@ concurrency:
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ vars.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
# Both read by the be-tests run step below. Set here rather than passed per job so that turning
|
||||
# either on is one line. Empty (and so off) on workflow_call, where neither input exists.
|
||||
#
|
||||
# ONLY_TESTS replaces the leg's own test selection, so every matrix leg runs the whole list: the
|
||||
# partition args no longer apply, and legs that hold none of the named tests run nothing.
|
||||
PROFILE_TESTS: ${{ inputs.profile }}
|
||||
ONLY_TESTS: ${{ inputs.tests }}
|
||||
|
||||
jobs:
|
||||
project-checks:
|
||||
@@ -374,6 +351,16 @@ jobs:
|
||||
else
|
||||
$BASE :exclude-tags '[:mb/driver-tests ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]' ${{ matrix.job.test-args }}
|
||||
fi
|
||||
env:
|
||||
# Both derived from test.profile-backend.yml, the sole dispatcher; its runs always profile. On a
|
||||
# normal CI run there are no dispatch inputs, so these render `false` and empty and the
|
||||
# leg's own selection stands.
|
||||
#
|
||||
# ONLY_TESTS replaces the leg's own test selection, so every matrix leg runs the whole
|
||||
# list: the partition args no longer apply, and legs that hold none of the named tests run
|
||||
# nothing.
|
||||
PROFILE_TESTS: ${{ github.event.inputs.suite != '' }}
|
||||
ONLY_TESTS: ${{ github.event.inputs.tests }}
|
||||
# Keep this step continue-on-error because the quarantine logic controls whether it ultimately fails.
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
53
.github/workflows/drivers.yml
vendored
@@ -10,45 +10,6 @@ on:
|
||||
description: Run the full suite regardless of the diff (master / release branches, ci:run-all label).
|
||||
type: boolean
|
||||
default: false
|
||||
# Run one driver job on demand, against the ref you dispatch on, optionally under a profiler.
|
||||
# Everything else about the job -- credentials, services, test args, quarantine gating -- is
|
||||
# whatever the normal CI run uses, because it is the same job.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
driver:
|
||||
description: 'Which driver job to run. Only this one runs; the usual decision rules are bypassed.'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- athena
|
||||
- bigquery
|
||||
- clickhouse
|
||||
- databricks
|
||||
- druid-jdbc
|
||||
- h2
|
||||
- mongo
|
||||
- mongo-sharded-cluster
|
||||
- mongo-ssl
|
||||
- mysql-mariadb
|
||||
- oracle
|
||||
- postgres
|
||||
- presto-jdbc
|
||||
- redshift
|
||||
- snowflake
|
||||
- sparksql
|
||||
- sqlite
|
||||
- sqlserver
|
||||
- vertica
|
||||
tests:
|
||||
description: 'Optional: run only these tests, comma- or space-separated, e.g. metabase.driver.bigquery-cloud-sdk-test/sync-test. Bare namespaces work too. Blank runs the job''s usual set.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
profile:
|
||||
description: 'Profile the test run and attach a flamegraph to the job. Adds sampling overhead.'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-drivers
|
||||
@@ -60,14 +21,6 @@ concurrency:
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ vars.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
# Both read by the test-driver action, which is what actually runs the tests. Set here rather than
|
||||
# passed as inputs to each job so that turning either on is one line, not one line per driver.
|
||||
# Empty (and so off) on workflow_call, where neither input exists.
|
||||
#
|
||||
# ONLY_TESTS replaces the job's own test selection, so every matrix leg of the chosen driver runs
|
||||
# the whole list -- BigQuery, for instance, has three legs.
|
||||
PROFILE_TESTS: ${{ inputs.profile }}
|
||||
ONLY_TESTS: ${{ inputs.tests }}
|
||||
|
||||
jobs:
|
||||
# This job determines which driver tests should run based on PR context.
|
||||
@@ -117,7 +70,7 @@ jobs:
|
||||
--force-run="${FORCE_RUN}"
|
||||
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
||||
--skip="${{ inputs.skip }}"
|
||||
--only-driver="${ONLY_DRIVER}"
|
||||
--only-driver="${PROFILE_DRIVER}"
|
||||
)
|
||||
echo "Running:"
|
||||
echo "./bin/mage -driver-decisions ${MAGE_ARGS[*]}"
|
||||
@@ -132,7 +85,9 @@ jobs:
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }}
|
||||
FORCE_RUN: ${{ inputs.force-run }}
|
||||
ONLY_DRIVER: ${{ inputs.driver }}
|
||||
# Set only by test.profile-backend.yml. Absent on a normal CI run, which leaves the decision rules
|
||||
# alone; mage rejects a name that is not a driver.
|
||||
PROFILE_DRIVER: ${{ github.event.inputs.db }}
|
||||
shell: bash
|
||||
|
||||
be-tests-athena-ee:
|
||||
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
run: NODE_ENV=development bun run build-pure:cljs
|
||||
|
||||
- name: Build Storybook
|
||||
run: bun run build-storybook
|
||||
run: bun run build-storybook:test
|
||||
env:
|
||||
STORYBOOK_STORIES_FILTER: ${{ needs.detect-changed-stories.outputs.story_files }}
|
||||
|
||||
|
||||
2
.github/workflows/loki.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
- name: Build Storybook
|
||||
env:
|
||||
STORYBOOK_STORY_PATHS_FILE: ${{ steps.loki-selection.outputs.paths-file }}
|
||||
run: bun run build-storybook
|
||||
run: bun run build-storybook:test
|
||||
|
||||
- name: Run Loki Visual Tests
|
||||
# Loki drops stories with parameters.loki.skip, and exits non-zero when that leaves the selection empty.
|
||||
|
||||
11
.github/workflows/schedule-patch.yml
vendored
@@ -14,14 +14,17 @@ jobs:
|
||||
runs-on: ${{ vars.SLIM_RUNNER_KEY }}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch patch release for the current and previous major
|
||||
- name: Dispatch patch release for the current and recent majors
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: | # js
|
||||
// Nightly patches cover the current supported major and the one
|
||||
// before it. release-auto.yml releases one major per dispatch.
|
||||
const autoPatchVersionsCount = Number('${{ vars.AUTO_PATCH_VERSIONS_COUNT }}') || 0;
|
||||
const currentRelease = Number('${{ vars.CURRENT_VERSION }}');
|
||||
const majors = [currentRelease];
|
||||
// [currentRelease, currentRelease - 1, ..., currentRelease - autoPatchVersionsCount]
|
||||
const majors = Array.from(
|
||||
{ length: autoPatchVersionsCount + 1 },
|
||||
(_, i) => currentRelease - i,
|
||||
);
|
||||
|
||||
await Promise.all(majors.map(version =>
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
|
||||
44
.github/workflows/semantic-search.yml
vendored
@@ -11,32 +11,6 @@ on:
|
||||
default: false
|
||||
description: Only run Postgres Latest to do a quick resource-efficient sanity check, intended to be set for PRs.
|
||||
|
||||
# Run one semantic-search job on demand, against the ref you dispatch on, optionally under a
|
||||
# profiler. Everything else about the job -- services, matrix legs, test args, quarantine gating --
|
||||
# is whatever the normal CI run uses, because it is the same job.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
suite:
|
||||
description: 'Which semantic-search job to run. Only this one runs; the others are skipped.'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- h2
|
||||
- mariadb
|
||||
- mysql
|
||||
- postgres
|
||||
- appdb-mode
|
||||
tests:
|
||||
description: 'Optional: run only these tests, comma- or space-separated, e.g. metabase-enterprise.semantic-search.index-test/some-test. Bare namespaces work too. Blank runs the job''s usual set.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
profile:
|
||||
description: 'Profile the test run and attach a flamegraph to the job. Adds sampling overhead.'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-semantic-search
|
||||
cancel-in-progress: true
|
||||
@@ -47,18 +21,10 @@ concurrency:
|
||||
env:
|
||||
CI_CONDUCTOR_BASE_URL: ${{ vars.CI_CONDUCTOR_BASE_URL }}
|
||||
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
|
||||
# Both read by the test-driver action, which is what actually runs the tests. Set here rather than
|
||||
# passed as inputs to each job so that turning either on is one line, not one line per job.
|
||||
# Empty (and so off) on workflow_call, where neither input exists.
|
||||
#
|
||||
# ONLY_TESTS replaces the job's own test selection, so every matrix leg of the chosen job runs the
|
||||
# whole list.
|
||||
PROFILE_TESTS: ${{ inputs.profile }}
|
||||
ONLY_TESTS: ${{ inputs.tests }}
|
||||
|
||||
jobs:
|
||||
semantic-search-tests-h2:
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (inputs.suite == '' || inputs.suite == 'h2') }}
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (github.event.inputs.db == '' || github.event.inputs.db == 'h2') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
@@ -102,7 +68,7 @@ jobs:
|
||||
:exclude-tags '[${{ matrix.job.exclude-tag }}]'
|
||||
|
||||
semantic-search-tests-mariadb:
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (inputs.suite == '' || inputs.suite == 'mariadb') }}
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (github.event.inputs.db == '' || github.event.inputs.db == 'mariadb') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
@@ -167,7 +133,7 @@ jobs:
|
||||
:exclude-tags '[${{ matrix.job.exclude-tag }}]'
|
||||
|
||||
semantic-search-tests-mysql:
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (inputs.suite == '' || inputs.suite == 'mysql') }}
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (github.event.inputs.db == '' || github.event.inputs.db == 'mysql') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
@@ -239,7 +205,7 @@ jobs:
|
||||
:exclude-tags '[${{ matrix.job.exclude-tag }}]'
|
||||
|
||||
semantic-search-tests-postgres:
|
||||
if: ${{ !inputs.skip && (inputs.suite == '' || inputs.suite == 'postgres') }}
|
||||
if: ${{ !inputs.skip && (github.event.inputs.db == '' || github.event.inputs.db == 'postgres') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
@@ -310,7 +276,7 @@ jobs:
|
||||
# MB_PGVECTOR_DB_URL is deliberately unset. The dedicated-harness tests gate themselves off; the
|
||||
# appdb-pgvector-mode round trip (and the URL-independent unit tests) run for real.
|
||||
semantic-search-tests-appdb-mode:
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (inputs.suite == '' || inputs.suite == 'appdb-mode') }}
|
||||
if: ${{ !inputs.skip && !inputs.smoke-only && (github.event.inputs.db == '' || github.event.inputs.db == 'appdb-mode') }}
|
||||
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
|
||||
timeout-minutes: 40
|
||||
strategy:
|
||||
|
||||
65
.github/workflows/test.profile-backend.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Profile Backend
|
||||
|
||||
# The only on-demand entry point for the backend test suites. Dispatching this workflow runs one
|
||||
# job of one suite against the ref you dispatch on, under clj-async-profiler, and attaches a
|
||||
# flamegraph to the job.
|
||||
#
|
||||
# The suite workflows below are called unchanged: services, matrix legs, test args and quarantine
|
||||
# gating are whatever the normal CI run uses, because they are the same jobs. They declare nothing
|
||||
# for this feature -- they read `suite`, `db` and `tests` off `github.event.inputs`, which inside a
|
||||
# called workflow is this dispatch's payload.
|
||||
#
|
||||
# `suite` picks the workflow, `db` picks the leg within it. `db` is free text so that adding a
|
||||
# driver or an app-DB leg needs no edit here; an unknown driver fails the drivers run through
|
||||
# mage's `--only-driver`, and an unknown app-DB or semantic-search leg matches nothing and runs no
|
||||
# jobs.
|
||||
#
|
||||
# Renaming an input here means updating every reader -- `grep -rn github.event.inputs .github/`.
|
||||
# Nothing enforces that, deliberately: a stale name reads as empty, which runs every leg of the
|
||||
# suite unprofiled, on the run you are watching. It cannot affect a normal CI run, where those
|
||||
# references are empty either way.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
suite:
|
||||
description: 'Which suite to profile.'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- backend
|
||||
- app-db
|
||||
- drivers
|
||||
- semantic-search
|
||||
db:
|
||||
description: 'Which leg of the suite: app-db takes mariadb, mysql or postgres; drivers takes a driver name, e.g. bigquery or mongo-ssl; semantic-search takes h2, mariadb, mysql, postgres or appdb-mode. Ignored by backend. Blank runs the whole suite -- every leg for app-db and semantic-search, mage''s usual decisions for drivers.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
tests:
|
||||
description: 'Optional: profile only these tests, comma- or space-separated, e.g. metabase.query-processor-test/some-test. Bare namespaces work too. Blank profiles the leg''s usual set.'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
# These jobs gate on `inputs.suite` -- this workflow's own input. Only the called workflows reach
|
||||
# for `github.event.inputs.*`, because `inputs` there is their own `workflow_call` signature.
|
||||
jobs:
|
||||
backend:
|
||||
if: ${{ inputs.suite == 'backend' }}
|
||||
uses: ./.github/workflows/backend.yml
|
||||
secrets: inherit
|
||||
|
||||
app-db:
|
||||
if: ${{ inputs.suite == 'app-db' }}
|
||||
uses: ./.github/workflows/app-db.yml
|
||||
secrets: inherit
|
||||
|
||||
drivers:
|
||||
if: ${{ inputs.suite == 'drivers' }}
|
||||
uses: ./.github/workflows/drivers.yml
|
||||
secrets: inherit
|
||||
|
||||
semantic-search:
|
||||
if: ${{ inputs.suite == 'semantic-search' }}
|
||||
uses: ./.github/workflows/semantic-search.yml
|
||||
secrets: inherit
|
||||
18
.github/workflows/translation-update.yml
vendored
@@ -144,12 +144,20 @@ jobs:
|
||||
EOF
|
||||
)"
|
||||
|
||||
- name: Approve and Merge the PR
|
||||
run: |
|
||||
gh pr review --approve "$PR_NUMBER"
|
||||
gh pr merge --squash --auto "$PR_NUMBER"
|
||||
# Approving takes a different token than the one that opened the PR (above, the automation
|
||||
# user's): GitHub won't let a token approve its own pull request.
|
||||
- name: Approve the PR
|
||||
run: gh pr review --approve "$PR_NUMBER"
|
||||
shell: bash
|
||||
env:
|
||||
# uses a different token than the token that created the PR so we can approve and merge it
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ steps.create_pr.outputs.pr_number }}
|
||||
|
||||
# Merging goes back through the automation user: GitHub starts no workflow run for a push made
|
||||
# with `GITHUB_TOKEN`, so an auto-merge enabled with it lands on master with no CI.
|
||||
- name: Merge the PR
|
||||
run: gh pr merge --squash --auto "$PR_NUMBER"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.create_pr.outputs.pr_number }}
|
||||
|
||||
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 573 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 518 KiB |
@@ -58,6 +58,11 @@ const config: StorybookConfig = {
|
||||
name: "@storybook/react-webpack5",
|
||||
options: {},
|
||||
},
|
||||
build: {
|
||||
test: {
|
||||
disableTreeShaking: false,
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
reactDocgen: "react-docgen-typescript",
|
||||
reactDocgenTypescriptOptions: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard", "stylelint-config-css-modules"],
|
||||
"plugins": ["./frontend/lint/stylelint-rules/no-base-colors.js"],
|
||||
"plugins": ["./frontend/lint/stylelint-rules/no-base-colors.mjs"],
|
||||
"rules": {
|
||||
"alpha-value-notation": null,
|
||||
"color-function-notation": null,
|
||||
|
||||
106
bun.lock
@@ -20,6 +20,8 @@
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.39.11",
|
||||
"@dagrejs/dagre": "^1.1.5",
|
||||
"@dicebear/core": "^10.7.0",
|
||||
"@dicebear/styles": "^10.6.0",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/modifiers": "^6.0.1",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
@@ -42,7 +44,7 @@
|
||||
"@modelcontextprotocol/ext-apps": "^1.3.2",
|
||||
"@react-oauth/google": "^0.11.1",
|
||||
"@reduxjs/toolkit": "^2.12.0",
|
||||
"@snowplow/browser-tracker": "3.2.0",
|
||||
"@snowplow/browser-tracker": "4.10.2",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.2",
|
||||
"@tiptap/core": "^3.31.3",
|
||||
@@ -809,6 +811,10 @@
|
||||
|
||||
"@dependents/detective-less": ["@dependents/detective-less@5.0.1", "", { "dependencies": { "gonzales-pe": "^4.3.0", "node-source-walk": "^7.0.1" } }, "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ=="],
|
||||
|
||||
"@dicebear/core": ["@dicebear/core@10.7.0", "", {}, "sha512-JUOzKOd8RUByJJo8ZL+C7YE/pvEsWewfpbWUmR8OIQZWv/PvUQHnohdevySC3icyyxe6EIU/8B/rQgA42us1QA=="],
|
||||
|
||||
"@dicebear/styles": ["@dicebear/styles@10.6.0", "", {}, "sha512-KhxbEVI+z8TaenX58+ZhIRFBt7adggu/F+8g6ZYLu43H2/lgLuWA3Kg8vHBrCVWdfTTTt1p2wUhQomIkkypTuQ=="],
|
||||
|
||||
"@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.5.7", "", {}, "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="],
|
||||
|
||||
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.0", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ=="],
|
||||
@@ -1461,11 +1467,11 @@
|
||||
|
||||
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@13.0.5", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw=="],
|
||||
|
||||
"@snowplow/browser-tracker": ["@snowplow/browser-tracker@3.2.0", "", { "dependencies": { "@snowplow/browser-tracker-core": "3.2.0", "@snowplow/tracker-core": "3.2.0", "tslib": "^2.3.0" } }, "sha512-k9oabjXpM7qIYM5CnMu1EZ/CbbffBKBK1TJTMJaf2k1F3Xplbyqx9DssN1NzgX+lH9Gc22n+yow+czRyppZOCg=="],
|
||||
"@snowplow/browser-tracker": ["@snowplow/browser-tracker@4.10.2", "", { "dependencies": { "@snowplow/browser-tracker-core": "4.10.2", "@snowplow/tracker-core": "4.10.2", "tslib": "^2.3.1" } }, "sha512-nDX0I2n7AUeTL9H1aI3FDBUkFo8DZIJ7Bp1g8ne7KBCcstrkxADpo7COz9iBOIXNDnr2scEy0ia/eKmRkaZFsA=="],
|
||||
|
||||
"@snowplow/browser-tracker-core": ["@snowplow/browser-tracker-core@3.2.0", "", { "dependencies": { "@snowplow/tracker-core": "3.2.0", "sha1": "^1.1.1", "tslib": "^2.3.0", "uuid": "^3.4.0" } }, "sha512-20mrp4OtyzXJMrvDjDmiddKnDUjccg3jOzmK/sNIv8jwPNleHct/MTV1dBo8aTkzxR2jcwL55AqnrD4XIQYDVg=="],
|
||||
"@snowplow/browser-tracker-core": ["@snowplow/browser-tracker-core@4.10.2", "", { "dependencies": { "@snowplow/tracker-core": "4.10.2", "tslib": "^2.3.1", "uuid": "^11.1.1" } }, "sha512-KX5Zu+IzslpQW7qVFKzTXupHoHgiz4UWA8A5KujcULrSm2vmDhYJGOtN0SVAOqc0TAB+xLmT9eoeDhSd+oSkKg=="],
|
||||
|
||||
"@snowplow/tracker-core": ["@snowplow/tracker-core@3.2.0", "", { "dependencies": { "tslib": "^2.3.0", "uuid": "^3.4.0" } }, "sha512-EsOs8HlWPp3SIFn3TSG5Eo8l/QEU0ZyHVIlvmoQT3fOdsWZVrTK9IlrWi/sA0S43tVxpHKLuiEKgmQIrQXuVyw=="],
|
||||
"@snowplow/tracker-core": ["@snowplow/tracker-core@4.10.2", "", { "dependencies": { "tslib": "^2.3.1", "uuid": "^11.1.1" } }, "sha512-L49i7OGAC2qQydnWXmKJnypTyVMqCWwfhKjC8Dk9mDsQn1+yzcHs+B4BxZB+vF0pCOrjPfGMWFmQkND4cWsrGA=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
@@ -1845,8 +1851,6 @@
|
||||
|
||||
"@types/node": ["@types/node@22.1.0", "", { "dependencies": { "undici-types": "~6.13.0" } }, "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw=="],
|
||||
|
||||
"@types/node-forge": ["@types/node-forge@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ=="],
|
||||
|
||||
"@types/pako": ["@types/pako@2.0.4", "", {}, "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.0", "", {}, "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="],
|
||||
@@ -1925,7 +1929,7 @@
|
||||
|
||||
"@types/yoga-layout": ["@types/yoga-layout@1.9.2", "", {}, "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg=="],
|
||||
|
||||
@@ -1935,7 +1939,7 @@
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="],
|
||||
|
||||
@@ -1943,7 +1947,7 @@
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
||||
|
||||
@@ -4023,8 +4027,6 @@
|
||||
|
||||
"node-fetch-h2": ["node-fetch-h2@2.3.0", "", { "dependencies": { "http2-client": "^1.2.5" } }, "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg=="],
|
||||
|
||||
"node-forge": ["node-forge@1.3.0", "", {}, "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA=="],
|
||||
|
||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs="],
|
||||
|
||||
"node-notifier": ["node-notifier@9.0.1", "", { "dependencies": { "growly": "^1.3.0", "is-wsl": "^2.2.0", "semver": "^7.3.2", "shellwords": "^0.1.1", "uuid": "^8.3.0", "which": "^2.0.2" } }, "sha512-fPNFIp2hF/Dq7qLDzSg4vZ0J4e9v60gJR+Qx7RbjbWqzPDdEqeVpEx5CFeDAELIl+A/woaaNn1fQ5nEVerMxJg=="],
|
||||
@@ -4713,8 +4715,6 @@
|
||||
|
||||
"secure-keys": ["secure-keys@1.0.0", "", {}, "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o="],
|
||||
|
||||
"selfsigned": ["selfsigned@2.4.1", "", { "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" } }, "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q=="],
|
||||
|
||||
"semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
|
||||
|
||||
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
@@ -4745,8 +4745,6 @@
|
||||
|
||||
"sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="],
|
||||
|
||||
"sha1": ["sha1@1.1.1", "", { "dependencies": { "charenc": ">= 0.0.1", "crypt": ">= 0.0.1" } }, "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg="],
|
||||
|
||||
"shadow-cljs": ["shadow-cljs@3.3.6", "", { "dependencies": { "buffer": "^6.0.3", "process": "^0.11.10", "readline-sync": "^1.4.10", "shadow-cljs-jar": "1.3.4", "source-map-support": "^0.5.21", "which": "^5.0.0", "ws": "^8.18.1" }, "bin": { "shadow-cljs": "cli/runner.js" } }, "sha512-t5TBJmy77abwlkAY7UOXa1s9H/28nTHeDVckgzEVS8ONYtDvbNCHEuBYfS7wwV9wVZVybRNb7dILWNXISJL1Nw=="],
|
||||
|
||||
"shadow-cljs-jar": ["shadow-cljs-jar@1.3.4", "", {}, "sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA=="],
|
||||
@@ -5065,7 +5063,7 @@
|
||||
|
||||
"ts-algebra": ["ts-algebra@1.2.2", "", {}, "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
|
||||
|
||||
@@ -5595,11 +5593,11 @@
|
||||
|
||||
"@snowplow/browser-tracker-core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@snowplow/browser-tracker-core/uuid": ["uuid@3.4.0", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="],
|
||||
"@snowplow/browser-tracker-core/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
|
||||
|
||||
"@snowplow/tracker-core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@snowplow/tracker-core/uuid": ["uuid@3.4.0", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="],
|
||||
"@snowplow/tracker-core/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
|
||||
|
||||
"@storybook/addon-docs/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||
|
||||
@@ -5683,38 +5681,32 @@
|
||||
|
||||
"@types/leaflet/@types/geojson": ["@types/geojson@7946.0.8", "", {}, "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="],
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@visx/axis/classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
|
||||
@@ -6803,8 +6795,6 @@
|
||||
|
||||
"typedoc/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="],
|
||||
|
||||
"unbox-primitive/which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
@@ -7101,28 +7091,32 @@
|
||||
|
||||
"@types/jest/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="],
|
||||
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="],
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="],
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||
|
||||
"@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
|
||||
"@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||
@@ -7301,8 +7295,6 @@
|
||||
|
||||
"detective-typescript/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"detective-typescript/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"es-get-iterator/get-intrinsic/has-proto": ["has-proto@1.0.1", "", {}, "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="],
|
||||
|
||||
"es-get-iterator/get-intrinsic/hasown": ["hasown@2.0.1", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA=="],
|
||||
@@ -7809,14 +7801,6 @@
|
||||
|
||||
"typed-array-length/is-typed-array/which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
@@ -7993,11 +7977,17 @@
|
||||
|
||||
"@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
@@ -8395,9 +8385,7 @@
|
||||
|
||||
"test-exclude/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"webpack-dev-middleware/schema-utils/ajv-formats/ajv": ["ajv@8.10.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw=="],
|
||||
|
||||
@@ -8495,7 +8483,9 @@
|
||||
|
||||
"@storybook/builder-webpack5/terser-webpack-plugin/schema-utils/ajv-formats/ajv": ["ajv@8.10.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
@@ -8677,9 +8667,9 @@
|
||||
|
||||
"test-exclude/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
|
||||
|
||||
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"webpack-dev-middleware/schema-utils/ajv-formats/ajv/uri-js": ["uri-js@4.4.0", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g=="],
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ services:
|
||||
metabase:
|
||||
image: ${METABASE_IMAGE:-metabase/metabase:latest}
|
||||
ports:
|
||||
- "${METABASE_PORT:-3000}:3000"
|
||||
- "127.0.0.1:${METABASE_PORT:-3000}:3000"
|
||||
environment:
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_DBNAME: metabase
|
||||
|
||||
6
deps.edn
@@ -941,8 +941,10 @@
|
||||
{:extra-deps {nrepl/nrepl {:mvn/version "1.5.1"}}
|
||||
:main-opts ["-m" "nrepl.cmdline" "-p" "50605"]}
|
||||
|
||||
;; - start a Socket REPL on port 50505 that you can connect your editor to:
|
||||
:socket {:jvm-opts ["-Dclojure.server.repl={:address,\"0.0.0.0\",:port,50505,:accept,clojure.core.server/repl}"]}
|
||||
;; - start a Socket REPL on port 50505 that you can connect your editor to. Bound to loopback: it has no
|
||||
;; authentication, so exposing it on a network interface is remote code execution. dev/docker-compose.yml
|
||||
;; overrides the address for the containerized dev app, where the published port is the boundary.
|
||||
:socket {:jvm-opts ["-Dclojure.server.repl={:address,\"127.0.0.1\",:port,50505,:accept,clojure.core.server/repl}"]}
|
||||
|
||||
;; Liquibase CLI:
|
||||
;;
|
||||
|
||||
@@ -39,8 +39,8 @@ services:
|
||||
image: clojure
|
||||
container_name: metabase-app
|
||||
ports:
|
||||
- "3001:3000"
|
||||
- "50505:50505"
|
||||
- "127.0.0.1:3001:3000"
|
||||
- "127.0.0.1:50505:50505"
|
||||
working_dir: /dev/metabase
|
||||
volumes:
|
||||
- ../:/dev/metabase
|
||||
@@ -58,7 +58,10 @@ services:
|
||||
MB_POSTGRESQL_TEST_PASSWORD: password
|
||||
MB_POSTGRESQL_TEST_PORT: 5432
|
||||
MB_POSTGRESQL_TEST_USER: user
|
||||
command: ["clojure", "-M:run:dev:drivers:drivers-dev:ee:ee-dev:socket"]
|
||||
# The :socket alias binds the REPL to loopback inside the container, which docker's port
|
||||
# forwarding can't reach, so bind it to all container interfaces here instead. The host
|
||||
# side stays loopback-only via the 127.0.0.1: prefix in `ports` above.
|
||||
command: ["clojure", "-J-Dclojure.server.repl={:address,\"0.0.0.0\",:port,50505,:accept,clojure.core.server/repl}", "-M:run:dev:drivers:drivers-dev:ee:ee-dev"]
|
||||
profiles:
|
||||
- dev-app
|
||||
networks:
|
||||
@@ -79,10 +82,14 @@ services:
|
||||
# the browser loads the bundle from the host's :8080 — it must be
|
||||
# published, not just exposed on the docker network.
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "127.0.0.1:8080:8080"
|
||||
volumes:
|
||||
- ../:/dev/metabase
|
||||
- ~/.bun/install/cache:/root/.bun/install/cache
|
||||
environment:
|
||||
# rspack binds to loopback by default; inside the container it must listen on all
|
||||
# interfaces for docker's port forwarding to reach it.
|
||||
MB_FRONTEND_DEV_HOST: 0.0.0.0
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
@@ -99,7 +106,7 @@ services:
|
||||
container_name: metabase-postgres-app-db
|
||||
restart: always
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- "127.0.0.1:5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: mbuser
|
||||
POSTGRES_PASSWORD: password
|
||||
@@ -116,7 +123,7 @@ services:
|
||||
container_name: metabase-mysql57-app-db
|
||||
restart: always
|
||||
ports:
|
||||
- "3306:3306"
|
||||
- "127.0.0.1:3306:3306"
|
||||
environment:
|
||||
MYSQL_USER: mbuser
|
||||
MYSQL_PASSWORD: password
|
||||
@@ -138,7 +145,7 @@ services:
|
||||
image: metabase/qa-databases:postgres-sample-14
|
||||
container_name: metabasedb_postgres_14_data
|
||||
ports:
|
||||
- "15432:5432"
|
||||
- "127.0.0.1:15432:5432"
|
||||
restart: always
|
||||
profiles:
|
||||
- postgres14data
|
||||
@@ -149,7 +156,7 @@ services:
|
||||
image: metabase/qa-databases:mysql-sample-5.7
|
||||
container_name: metabasedb_mysql_5_7_data
|
||||
ports:
|
||||
- "23306:3306"
|
||||
- "127.0.0.1:23306:3306"
|
||||
restart: always
|
||||
profiles:
|
||||
- mysql57data
|
||||
@@ -160,7 +167,7 @@ services:
|
||||
image: metabase/qa-databases:mysql-sample-8
|
||||
container_name: metabasedb_mysql_8_data
|
||||
ports:
|
||||
- "13306:3306"
|
||||
- "127.0.0.1:13306:3306"
|
||||
restart: always
|
||||
profiles:
|
||||
- mysql8data
|
||||
@@ -172,7 +179,7 @@ services:
|
||||
container_name: metabasedb_mongo_6_data
|
||||
restart: always
|
||||
ports:
|
||||
- "27017:27017"
|
||||
- "127.0.0.1:27017:27017"
|
||||
profiles:
|
||||
- mongodata
|
||||
networks:
|
||||
@@ -183,7 +190,7 @@ services:
|
||||
container_name: vertica_data
|
||||
restart: always
|
||||
ports:
|
||||
- "5433:5433"
|
||||
- "127.0.0.1:5433:5433"
|
||||
profiles:
|
||||
- verticadata
|
||||
networks:
|
||||
@@ -195,8 +202,8 @@ services:
|
||||
image: maildev/maildev:latest
|
||||
container_name: maildev
|
||||
ports:
|
||||
- "1080:1080"
|
||||
- "1025:1025"
|
||||
- "127.0.0.1:1080:1080"
|
||||
- "127.0.0.1:1025:1025"
|
||||
profiles:
|
||||
- pulse
|
||||
networks:
|
||||
@@ -209,7 +216,7 @@ services:
|
||||
- ./idp-private-key.pem:/idp-private-key.pem
|
||||
- ./idp-public-cert.pem:/idp-public-cert.pem
|
||||
ports:
|
||||
- "7000:7000"
|
||||
- "127.0.0.1:7000:7000"
|
||||
entrypoint:
|
||||
- node
|
||||
- app.js
|
||||
|
||||
@@ -32,7 +32,7 @@ Upload a custom icon. SVG or PNG with a transparent background gives the cleanes
|
||||
|
||||

|
||||
|
||||
Once you've uploaded a custom icon, this toggle appears and controls whether the default decorative illustrations show in the Metabot chat sidebar and on the AI exploration page.
|
||||
This toggle controls whether the default decorative illustrations show in the Metabot chat sidebar.
|
||||
|
||||
## Further reading
|
||||
|
||||
|
||||
@@ -257,6 +257,38 @@ Credentials:
|
||||
- **Region**. Pick one from the dropdown in **Admin > AI**. Defaults to `us-east-1`. You can also set it with the environment variable `MB_LLM_BEDROCK_REGION`.
|
||||
- **Session token** (advanced). Only together with **Access key ID** and **Secret access key**. Only needed for temporary credentials. You can also set it with the environment variable `MB_LLM_BEDROCK_SESSION_TOKEN`.
|
||||
|
||||
### IAM permissions for Bedrock
|
||||
|
||||
Metabase talks to Bedrock through the mantle endpoint, `https://bedrock-mantle.{region}.api.aws`, not through `bedrock-runtime`. Mantle is a separate IAM namespace with its own actions, so a policy written against the `bedrock` prefix won't grant access. Metabase lists models and runs conversations, so it needs `bedrock-mantle:ListModels` and `bedrock-mantle:CreateInference`.
|
||||
|
||||
Here's a least-privilege policy that grants both:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["bedrock-mantle:ListModels", "bedrock-mantle:CreateInference"],
|
||||
"Resource": "arn:aws:bedrock-mantle:*:*:project/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The AWS managed policy [AmazonBedrockMantleInferenceAccess](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonBedrockMantleInferenceAccess.html) also covers both actions (along with permissions Metabase doesn't use).
|
||||
|
||||
If Metabase reports "AWS Bedrock credentials lack permission for this model or action", check that your policy uses the `bedrock-mantle` prefix.
|
||||
|
||||
### The Bedrock models you can pick depend on the region
|
||||
|
||||
The table above lists the models Metabase can use. The **Models** card only offers the ones Bedrock serves in the connection's region, so what you can pick depends on the region. The mantle catalog has no cross-region inference profiles, so a model that isn't served in your region can't be reached from that region at all.
|
||||
|
||||
If the model list is empty or shorter than you expect after connecting:
|
||||
|
||||
- **Check the region**: the **Region** dropdown lists every AWS region, including regions where Bedrock serves none of these models. The [AWS model cards](https://docs.aws.amazon.com/bedrock/latest/userguide/model-cards.html) show where each model is available. For example, the GPT models are only served in US regions.
|
||||
- **Check your account's data retention setting**: Bedrock marks a model unavailable when your account's data retention mode doesn't meet what that model requires. For example, [Claude Fable 5](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-fable-5.html) requires the `aws_review` data retention mode.
|
||||
|
||||
## vLLM
|
||||
|
||||
- Provider key: `vllm`
|
||||
|
||||
@@ -58,7 +58,7 @@ To connect a provider with your own credentials:
|
||||
1. Go to **Admin > AI**.
|
||||
2. Click **Add a provider**.
|
||||
3. Pick your provider.
|
||||
4. Fill in its credentials. The **Where do I find this?** links open the provider's key management pages in a new tab.
|
||||
4. Enter your credentials. Most providers only need an **API key**. The **Where do I find this?** link opens your provider's key management page in a new tab. For what each provider needs, see [AI providers](./providers.md).
|
||||
5. Click **Connect**.
|
||||
|
||||
If you've already copied a key, one neat thing: just paste the key anywhere on the provider grid, and Metabase selects the matching provider and fills in the key for you. Check to make sure the provider matches.
|
||||
|
||||
3017
docs/api.json
generated
57
docs/cloud/ip-allowlist.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "IP allowlist"
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
---
|
||||
|
||||
# IP allowlist
|
||||
|
||||
Use an IP allowlist to limit access to your Metabase instance to approved IP addresses. The allowlist applies to all access to your instance, so people outside the allowlist can't reach the API, embedded dashboards and questions, or public links.
|
||||
|
||||
IP allowlists are only available on Metabase Cloud [Pro or Enterprise](https://www.metabase.com/pricing) plans. To restrict access to a self-hosted Metabase, use your own firewall or load balancer, or consider [air-gapping](https://www.metabase.com/product/air-gapping).
|
||||
|
||||
The allowlist accepts IPv4 addresses and CIDR ranges. Metabase treats a bare IPv4 address as a `/32` range.
|
||||
|
||||
By default, restricting access by IP address is turned off.
|
||||
|
||||
To allow Metabase to reach your databases, see [IP addresses to whitelist](./ip-addresses-to-whitelist.md).
|
||||
|
||||
## Set up an IP allowlist
|
||||
|
||||
When you save an allowlist, your instance restarts. Metabase blocks requests from addresses outside the allowlist, so include the address or range you're connecting from before you save.
|
||||
|
||||
The allowlist doesn't apply to the Metabase Store. If you leave out your own address, you can still log in to the Store and update the allowlist.
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Navigate to **Instances**.
|
||||
3. In the instance you want to restrict access to, click **Settings**.
|
||||
4. Scroll to **IP allowlist**.
|
||||
5. Enable the **Restrict access by IP address** toggle.
|
||||
6. In the **IP/CIDR ranges** field, enter an IPv4 address or a CIDR range.
|
||||
7. To add more addresses, enter them in the empty field. To add several at once, paste a list with one address or range per line.
|
||||
8. Click **Save**.
|
||||
9. Review the warning, then confirm. Your instance restarts.
|
||||
|
||||
Metabase validates each entry as you type. To save the allowlist, you must first correct or remove invalid entries.
|
||||
|
||||
To remove an address, click the trash icon next to the address or range, then click **Save**.
|
||||
|
||||
## Turn off the IP allowlist
|
||||
|
||||
When you turn off the IP allowlist and save, Metabase clears the saved addresses. To restrict access again, you must re-enter the addresses.
|
||||
|
||||
Turning off the allowlist doesn't remove authentication. People still need to sign in to your Metabase.
|
||||
|
||||
In your instance's **IP allowlist** settings:
|
||||
|
||||
1. Disable the **Restrict access by IP address** toggle.
|
||||
2. Click **Save**.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [IP addresses to whitelist](./ip-addresses-to-whitelist.md)
|
||||
- [Changing your domain name](./custom-domain.md)
|
||||
@@ -29,6 +29,7 @@ Manage your Metabase Cloud instance, subscription, and billing via the [Metabase
|
||||
|
||||
- [Changing which region your Metabase is hosted in](./change-region.md)
|
||||
- [IP addresses to whitelist](ip-addresses-to-whitelist.md)
|
||||
- [IP allowlist](./ip-allowlist.md)
|
||||
- [Changing your domain name](./custom-domain.md)
|
||||
|
||||
### Migrations
|
||||
|
||||
@@ -147,7 +147,9 @@ config:
|
||||
embedding-homepage: hidden
|
||||
embedding-secret-key: null
|
||||
enable-embedding-interactive: false
|
||||
enable-embedding-modular: false
|
||||
enable-embedding-sdk: false
|
||||
enable-embedding-simple: false
|
||||
enable-embedding-static: false
|
||||
enable-password-login: true
|
||||
enable-pivoted-exports: true
|
||||
enable-public-sharing: true
|
||||
@@ -163,7 +165,6 @@ config:
|
||||
help-link: metabase
|
||||
help-link-custom-destination: https://www.metabase.com/help/premium
|
||||
hide-stacktraces: false
|
||||
http-channel-allowed-networks: external-only
|
||||
humanization-strategy: simple
|
||||
index-update-thread-count: 2
|
||||
jdbc-data-warehouse-connection-pool-checkout-timeout-ms: 0
|
||||
@@ -251,7 +252,6 @@ config:
|
||||
loading-message: doing-science
|
||||
login-page-illustration: default
|
||||
login-page-illustration-custom: null
|
||||
map-tile-server-allowed-networks: null
|
||||
map-tile-server-url: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
mcp-apps-cors-custom-origins: ''
|
||||
mcp-apps-cors-enabled-clients: []
|
||||
@@ -282,7 +282,6 @@ config:
|
||||
notification-link-base-url: null
|
||||
notification-system-event-thread-pool-size: 5
|
||||
notification-thread-pool-size: 3
|
||||
oidc-allowed-networks: allow-all
|
||||
oidc-providers: []
|
||||
oidc-user-provisioning-enabled: true
|
||||
persisted-model-refresh-cron-schedule: 0 0 0/6 * * ? *
|
||||
@@ -382,5 +381,4 @@ config:
|
||||
use-native-pivot-tables: false
|
||||
use-tenants: false
|
||||
user-visibility: all
|
||||
warehouse-allowed-networks: null
|
||||
```
|
||||
|
||||
@@ -731,17 +731,7 @@ Allow admins to securely embed questions and dashboards within other application
|
||||
|
||||
Allow admins to embed Metabase via interactive embedding?
|
||||
|
||||
### `MB_ENABLE_EMBEDDING_MODULAR`
|
||||
|
||||
- Type: boolean
|
||||
- Default: `false`
|
||||
- [Configuration file name](./config-file.md): `enable-embedding-modular`
|
||||
|
||||
Allow admins to embed Metabase via modular embedding, the SDK for React, and guest embeds?
|
||||
|
||||
### `MB_ENABLE_EMBEDDING_SDK [DEPRECATED]`
|
||||
|
||||
> DEPRECATED: 0.65.0
|
||||
### `MB_ENABLE_EMBEDDING_SDK`
|
||||
|
||||
- Type: boolean
|
||||
- Default: `false`
|
||||
@@ -749,9 +739,7 @@ Allow admins to embed Metabase via modular embedding, the SDK for React, and gue
|
||||
|
||||
Allow admins to embed Metabase via the SDK?
|
||||
|
||||
### `MB_ENABLE_EMBEDDING_SIMPLE [DEPRECATED]`
|
||||
|
||||
> DEPRECATED: 0.65.0
|
||||
### `MB_ENABLE_EMBEDDING_SIMPLE`
|
||||
|
||||
- Type: boolean
|
||||
- Default: `false`
|
||||
@@ -759,9 +747,7 @@ Allow admins to embed Metabase via the SDK?
|
||||
|
||||
Allow admins to embed Metabase via modular embedding?
|
||||
|
||||
### `MB_ENABLE_EMBEDDING_STATIC [DEPRECATED]`
|
||||
|
||||
> DEPRECATED: 0.65.0
|
||||
### `MB_ENABLE_EMBEDDING_STATIC`
|
||||
|
||||
- Type: boolean
|
||||
- Default: `false`
|
||||
@@ -901,6 +887,7 @@ Prevent the exception middleware from including stacktraces in responses.
|
||||
|
||||
- Type: keyword
|
||||
- Default: `external-only`
|
||||
- Environment variable only: you can't set this in the Admin settings or in a [configuration file](./config-file.md).
|
||||
|
||||
Controls which types of hosts are allowed as HTTP channel destinations.
|
||||
Options:
|
||||
@@ -909,6 +896,8 @@ Options:
|
||||
- allow-all (no restrictions including localhost).
|
||||
.
|
||||
|
||||
Set this when a notification webhook must reach a host on your private network (`allow-private`) or on this machine (`allow-all`). Default is `external-only`
|
||||
|
||||
### `MB_HUMANIZATION_STRATEGY`
|
||||
|
||||
- Type: keyword
|
||||
@@ -1433,7 +1422,7 @@ Backed by the bedrock connection in the admin AI settings provider list: reads a
|
||||
|
||||
The AWS region for Amazon Bedrock (e.g. us-east-1).
|
||||
|
||||
Backed by the bedrock connection in the admin AI settings provider list: reads and writes go through the llm-providers connection list, and a value set by this environment variable shadows this one field of that connection. On a self-hosted Metabase, setting only the region enables Bedrock with the AWS default credentials chain, with no access keys configured.
|
||||
Backed by the bedrock connection in the admin AI settings provider list: reads and writes go through the llm-providers connection list, and a value set by this environment variable shadows this one field of that connection. On a self-hosted Metabase, setting only the region enables Bedrock with the AWS default credentials chain, with no access keys configured. [The Bedrock models you can pick depend on the region](../ai/providers.md#the-bedrock-models-you-can-pick-depend-on-the-region).
|
||||
|
||||
### `MB_LLM_BEDROCK_SECRET_ACCESS_KEY`
|
||||
|
||||
@@ -1784,6 +1773,7 @@ The custom illustration for the login page.
|
||||
|
||||
- Type: keyword
|
||||
- Default: `null`
|
||||
- Environment variable only: you can't set this in the Admin settings or in a [configuration file](./config-file.md).
|
||||
|
||||
Controls which networks Metabase may connect to for map tile servers.
|
||||
Options:
|
||||
@@ -1792,6 +1782,8 @@ Controls which networks Metabase may connect to for map tile servers.
|
||||
- allow-all (no restrictions).
|
||||
Defaults to external-only on Metabase Cloud and allow-private when self-hosted.
|
||||
|
||||
Allowed network for the map tile server.
|
||||
|
||||
### `MB_MAP_TILE_SERVER_URL`
|
||||
|
||||
- Type: string
|
||||
@@ -1800,6 +1792,16 @@ Controls which networks Metabase may connect to for map tile servers.
|
||||
|
||||
The map tile server URL template used in map visualizations, for example from OpenStreetMaps or MapBox. This URL is visible to clients, so do not include private keys.
|
||||
|
||||
### `MB_MAX_UNAUTHENTICATED_REQUEST_BODY_BYTES`
|
||||
|
||||
- Type: integer
|
||||
- Default: `4194304`
|
||||
- Environment variable only: you can't set this in the Admin settings or in a [configuration file](./config-file.md).
|
||||
|
||||
Maximum size in bytes of an HTTP request body from a caller who is not authenticated. Larger requests are rejected with HTTP 413 before the body is read.
|
||||
|
||||
Applies to requests without a valid session, API key or token. The body is bounded before authentication is checked, so this is the most memory an anonymous request can make the server buffer. It covers login, setup, password reset, SSO callbacks, and public or embedded queries; none of those legitimately send more than a few hundred kilobytes. Authenticated requests are not limited.
|
||||
|
||||
### `MB_MCP_APPS_CORS_CUSTOM_ORIGINS`
|
||||
|
||||
- Type: string
|
||||
@@ -2028,10 +2030,12 @@ If Metabase stops sending notifications like alerts, it may be because long-runn
|
||||
|
||||
- Type: keyword
|
||||
- Default: `allow-all`
|
||||
- [Configuration file name](./config-file.md): `oidc-allowed-networks`
|
||||
- Environment variable only: you can't set this in the Admin settings or in a [configuration file](./config-file.md).
|
||||
|
||||
What networks are OIDC requests allowed to? Possible values: 'allow-all' (default), 'allow-private', or 'external-only'.
|
||||
|
||||
Set this to tighten which networks OIDC discovery and token requests may reach; it defaults to allow-all. Other values: external-only and allow-private
|
||||
|
||||
### `MB_OIDC_PROVIDERS`
|
||||
|
||||
> Only available on Metabase [Pro](https://www.metabase.com/product/pro) and [Enterprise](https://www.metabase.com/product/enterprise) plans.
|
||||
@@ -2178,7 +2182,7 @@ Network timeout (in seconds) for remote git operations such as fetch, push, clon
|
||||
- Default: `300000`
|
||||
- [Configuration file name](./config-file.md): `remote-sync-task-time-limit-ms`
|
||||
|
||||
The maximum amount of time a remote sync task will be given to complete.
|
||||
How long a remote sync task may go without proving its process is alive (via heartbeat, or progress on rows without one) before it is treated as dead and superseded. A slow but live task is never affected. The task itself is aborted after ten times this limit.
|
||||
|
||||
### `MB_REMOTE_SYNC_TOKEN`
|
||||
|
||||
@@ -2997,6 +3001,7 @@ Note: Users with row or column security restrictions will never see suggestions.
|
||||
|
||||
- Type: keyword
|
||||
- Default: `null`
|
||||
- Environment variable only: you can't set this in the Admin settings or in a [configuration file](./config-file.md).
|
||||
|
||||
Controls which networks Metabase may connect to for warehouse connections.
|
||||
Options:
|
||||
@@ -3006,6 +3011,8 @@ Options:
|
||||
Defaults to external-only on Metabase Cloud and allow-all when self-hosted.
|
||||
Also covers the SSH tunnel host and the database auth-provider URLs.
|
||||
|
||||
Set this when Metabase must reach a warehouse on a private network (allow-private) or on this machine (allow-all). There is no admin UI for it, and a value stored in the application database is ignored. Defaults to external-only on Metabase Cloud and allow-all when self-hosted. Metabase refuses to start if this is set to anything but one of the three policies, rather than run on a policy nobody chose.
|
||||
|
||||
## Other environment variables
|
||||
|
||||
The following environment variables can only be set via the environment. They cannot be set by the configuration file.
|
||||
|
||||
@@ -51,7 +51,7 @@ On self-hosted Metabases, this option determines whether or not you allow [anony
|
||||
|
||||
{% include plans-blockquote.html feature="Collecting user data" %}
|
||||
|
||||
You can switch on logging of IP addresses, user agents, embed path, query parameters, and Metabot conversation metadata for people using your Metabase, both for people logging in directly to Metabase, and for people who view an embedded Metabase component in your app. If enabled, you can find this information in your [usage analytics](../usage-and-performance-tools/usage-analytics.md).
|
||||
You can switch on logging of IP addresses, user agents, embed path, query parameters, the values people submit when they run an action, and Metabot conversation metadata for people using your Metabase, both for people logging in directly to Metabase, and for people who view an embedded Metabase component in your app. If enabled, you can find this information in your [usage analytics](../usage-and-performance-tools/usage-analytics.md).
|
||||
|
||||
By default, collection of user data is turned **off**.
|
||||
|
||||
|
||||
@@ -343,11 +343,12 @@ To configure a dashboard using its URL, you can add the following optional keywo
|
||||
|
||||
- `fullscreen`
|
||||
- `refresh`
|
||||
- `theme`
|
||||
|
||||
Here's an example URL:
|
||||
|
||||
```
|
||||
https://metabase.mydomain.com/dash/2#refresh=60&fullscreen&night
|
||||
https://metabase.mydomain.com/dash/2#refresh=60&fullscreen&theme=night
|
||||
```
|
||||
|
||||
The part that says `refresh=60` sets the dashboard to automatically refresh every 60 seconds, `fullscreen` sets it to fullscreen mode. Use an ampersand, `&`, in between keywords, and make sure there's a hash, `#`, after the dashboard's ID number.
|
||||
|
||||
@@ -53,6 +53,12 @@ There are multiple ways to run actions:
|
||||
- From a [public form](./custom.md#make-public) of an action.
|
||||
- From a [button on dashboard](../../../dashboards/actions.md).
|
||||
|
||||
## Auditing actions
|
||||
|
||||
{% include plans-blockquote.html feature="Usage analytics" %}
|
||||
|
||||
Metabase records every action run in the [Query log](../../../usage-and-performance-tools/usage-analytics-reference.md#query-log) model in your Usage analytics collection.
|
||||
|
||||
## Actions change data in tables, which affect models
|
||||
|
||||
Just something to clarify here: actions, even though they are added to models, make their changes to the underlying table that a model queries. Which means that anyone who has access to the underlying table, or to questions or other models based on that table, will be able to see the effects of an action. Tools other than Metabase that are connected to that database will also pick up these changes.
|
||||
|
||||
@@ -33,7 +33,7 @@ Once Docker is installed, you’re ready to go.
|
||||
2. Copy and paste this command, switching out `<branch-name>` for the name of the branch you’d like to test:
|
||||
|
||||
```bash
|
||||
docker run --platform linux/amd64 -d -p 3000:3000 --name metabase-dev metabase/metabase-dev:<branch-name>
|
||||
docker run --platform linux/amd64 -d -p 127.0.0.1:3000:3000 --name metabase-dev metabase/metabase-dev:<branch-name>
|
||||
```
|
||||
|
||||
3. In your browser, navigate to `http://localhost:3000`, where you should see Metabase. It may take a minute or two to start up depending on your computer.
|
||||
@@ -51,7 +51,7 @@ docker pull metabase/metabase-enterprise-head:latest
|
||||
Then:
|
||||
|
||||
```
|
||||
docker run --platform linux/amd64 -d -p 3000:3000 --name metabase metabase/metabase-enterprise-head:latest
|
||||
docker run --platform linux/amd64 -d -p 127.0.0.1:3000:3000 --name metabase metabase/metabase-enterprise-head:latest
|
||||
```
|
||||
|
||||
The “latest” tag is not automatically upgraded on your local machine, so the above commands ensure that you’re pulling the latest changes.
|
||||
|
||||
@@ -4,8 +4,28 @@ title: Driver interface changelog
|
||||
|
||||
# Driver Interface Changelog
|
||||
|
||||
## Metabase 0.65.0
|
||||
|
||||
- `sql.qp/use-ctes-for-stages?` is a multi-method for drivers to opt-in to compiling multi-stage queries
|
||||
with CTEs instead of nested subselects. Drivers should only do this if they satisfy all of the criteria
|
||||
in the docstring of this method.
|
||||
|
||||
## Metabase 0.64.0
|
||||
|
||||
- `metabase.driver.sql.normalize/default-schema` now takes the database as well as the driver:
|
||||
`[driver database]`. The schema an unqualified table reference resolves to is a property of the connection for a
|
||||
driver that opens a database where others have a default schema — ClickHouse now answers with the database its
|
||||
connection opened, where before it inherited `"public"` from `:sql`. `database` may be nil when the caller has none
|
||||
in hand, and such a driver answers nil for it.
|
||||
|
||||
- New feature `:transforms/testing` -- whether the driver can run transform test suites against temp tables. Drivers
|
||||
with this feature implement the new multimethods `metabase.driver/temp-table-name` `[driver]`,
|
||||
`metabase.driver/compile-create-temp-table` `[driver {:keys [table query]}]`,
|
||||
`metabase.driver/compile-drop-temp-table` `[driver table]`,
|
||||
`metabase.driver/do-with-test-connection` `[driver database f]`,
|
||||
`metabase.driver/execute-on-connection!` `[driver conn query]` and
|
||||
`metabase.driver/query-on-connection` `[driver conn query {:keys [max-rows]}]`.
|
||||
|
||||
- `metabase.driver.sql-jdbc.execute/cancelation-poisons-connection?` `[driver]` -- whether canceling a `Statement`
|
||||
leaves the `Connection` unfit for the next query, so that it must be discarded rather than returned to the
|
||||
connection pool. The query processor cancels a `Statement` whenever reduction stops before the `ResultSet` runs out
|
||||
@@ -70,6 +90,10 @@ title: Driver interface changelog
|
||||
- `metabase.driver/compile-create-index` `[driver schema table structured]` -- compiles a `:standalone` index into
|
||||
the DDL statement(s) that create it.
|
||||
|
||||
- `metabase.driver/humanize-index-error-message` `[driver message]` -- trims the message of an exception raised by
|
||||
`fetch-table-indexes` or by index DDL before it is shown to the user. Defaults to returning it unchanged;
|
||||
ClickHouse drops the `(version ...) (queryId=...)` tail its server appends.
|
||||
|
||||
- `metabase.driver.sql-jdbc.sync.interface/db-tables` is now a multimethod for retrieving JDBC metadata
|
||||
tables. SQL JDBC drivers can override this method to customize which database objects are discovered during sync.
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ We have a few helpers for dealing with tests involving snowplow
|
||||
Some of our tests depend on the email being set up, and require a local SMTP server. We use `maildev` Docker image for that purpose. At the time of this writing the image we use is `maildev/maildev:2.1.0`. The default cypress configuration for local development will handle this for you. If you want to set it up manually, you can use this command:
|
||||
|
||||
```sh
|
||||
docker run -d -p 1180:1080 -p 1125:1025 maildev/maildev:2.2.1
|
||||
docker run -d -p 127.0.0.1:1180:1080 -p 127.0.0.1:1125:1025 maildev/maildev:2.2.1
|
||||
```
|
||||
|
||||
The e2e maildev is published on host ports 1180 (web UI) and 1125 (SMTP) on purpose. These differ, on purpose, from the 1080/1025 used for development.
|
||||
|
||||
@@ -648,6 +648,11 @@ The Topic column on the [Activity log](#activity-log) model takes one of:
|
||||
- transform-run-canceled
|
||||
- transform-run-start
|
||||
- transform-run-timeout
|
||||
- transform-test-create
|
||||
- transform-test-delete
|
||||
- transform-test-run-start
|
||||
- transform-test-run-timeout
|
||||
- transform-test-update
|
||||
- update-permission-failure
|
||||
- update-transform
|
||||
- upload-append
|
||||
|
||||