Compare commits
269 Commits
single-dev
...
sdk-web-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30e69e1c1c | ||
|
|
5abd270655 | ||
|
|
a5880551f8 | ||
|
|
0614a03ea2 | ||
|
|
dccd4f970e | ||
|
|
b5c789acf0 | ||
|
|
e587fa357d | ||
|
|
bdeca87a47 | ||
|
|
efef12dbb0 | ||
|
|
02da4ebccc | ||
|
|
0ef0f93644 | ||
|
|
76fd1bfb52 | ||
|
|
52fd7f457c | ||
|
|
b1401f1a5a | ||
|
|
de37711918 | ||
|
|
5e40b0d589 | ||
|
|
07267954f1 | ||
|
|
4cd5ac3743 | ||
|
|
bf06c662eb | ||
|
|
9ab71b7283 | ||
|
|
6acc4b0e86 | ||
|
|
c8e9ccae66 | ||
|
|
89dd4a6db2 | ||
|
|
98e2dcc7c8 | ||
|
|
148bd23161 | ||
|
|
68549e8664 | ||
|
|
8adc64a632 | ||
|
|
1ea95981bf | ||
|
|
00a24143f0 | ||
|
|
5feeb16ab7 | ||
|
|
34965e0fcb | ||
|
|
7c39ff2fbe | ||
|
|
7f724d65b0 | ||
|
|
4c6ebf7d58 | ||
|
|
63466c8a41 | ||
|
|
997806cad5 | ||
|
|
a5c3bec1fa | ||
|
|
ababaeaa40 | ||
|
|
9e2c462c09 | ||
|
|
d80b0f7cb5 | ||
|
|
03a090e3fa | ||
|
|
1aecbe45ea | ||
|
|
dd8d9aaee6 | ||
|
|
0ff434acce | ||
|
|
c0da9fe3a1 | ||
|
|
42aa1cc95f | ||
|
|
5a07192176 | ||
|
|
5477bf1a8c | ||
|
|
91fc911887 | ||
|
|
1e4efe0fb8 | ||
|
|
5372f7a813 | ||
|
|
7b7a81ec37 | ||
|
|
f0bffec1ef | ||
|
|
dae160df0b | ||
|
|
f79a5a3b68 | ||
|
|
51b6f7e41c | ||
|
|
9e75dcffa6 | ||
|
|
93c0afb069 | ||
|
|
6646c46d90 | ||
|
|
4b39657797 | ||
|
|
d9eec6d01f | ||
|
|
131c127eb9 | ||
|
|
4b527d16cb | ||
|
|
50ba7910a0 | ||
|
|
1de9f60d69 | ||
|
|
427c8df3e3 | ||
|
|
8321dcb44d | ||
|
|
feb1037758 | ||
|
|
b7a556a2b7 | ||
|
|
cc7564e1f8 | ||
|
|
4dad9fd40e | ||
|
|
9a3a5e8d52 | ||
|
|
96e51b83b1 | ||
|
|
835792147a | ||
|
|
cc112327ab | ||
|
|
3204acc748 | ||
|
|
3f2f202b28 | ||
|
|
1324a86e65 | ||
|
|
e59956db6a | ||
|
|
ad8f5d6944 | ||
|
|
59cd8376fe | ||
|
|
3584d9410d | ||
|
|
ab17acf66b | ||
|
|
5d969b8913 | ||
|
|
1ba3a2259e | ||
|
|
68c03d37c9 | ||
|
|
1169f3f043 | ||
|
|
2ab98edd41 | ||
|
|
ebcf446ad9 | ||
|
|
dd02ac1df1 | ||
|
|
4504595645 | ||
|
|
fbf6038a83 | ||
|
|
11cfa6679a | ||
|
|
e3688062aa | ||
|
|
ecbb96c04d | ||
|
|
e121e258ed | ||
|
|
7c25b5d622 | ||
|
|
542998e964 | ||
|
|
70c565cc2a | ||
|
|
fcb5200ad9 | ||
|
|
f854fa1b6c | ||
|
|
a15838f7af | ||
|
|
7405037f12 | ||
|
|
4299f31087 | ||
|
|
2a1665066e | ||
|
|
2da2dc3a03 | ||
|
|
4406c4464d | ||
|
|
838ae27f16 | ||
|
|
01ef763723 | ||
|
|
974684b58d | ||
|
|
7ed7423614 | ||
|
|
97d1a1e82a | ||
|
|
e872767998 | ||
|
|
b89726a15d | ||
|
|
bcf6e46585 | ||
|
|
a3aed2ab66 | ||
|
|
57fcb1a6d1 | ||
|
|
19b4b29ce7 | ||
|
|
547028bafd | ||
|
|
343dd2c518 | ||
|
|
d834221200 | ||
|
|
0fb5606341 | ||
|
|
0452a7403b | ||
|
|
90bf2749d3 | ||
|
|
3219571e48 | ||
|
|
b39dfa529a | ||
|
|
2fd83dba03 | ||
|
|
223e79b9c2 | ||
|
|
7faeda11f2 | ||
|
|
702230d6f4 | ||
|
|
3d9ec072e4 | ||
|
|
08cfa3d14b | ||
|
|
ab57a144aa | ||
|
|
22a045996c | ||
|
|
5c468e8170 | ||
|
|
3aed5bfd73 | ||
|
|
50554f3001 | ||
|
|
8464213a96 | ||
|
|
67397aeadf | ||
|
|
88bdcfe6ae | ||
|
|
8ca1361d47 | ||
|
|
af55313e5d | ||
|
|
a41e383e0e | ||
|
|
37f6d88370 | ||
|
|
e4003975d0 | ||
|
|
a36337cb42 | ||
|
|
90f2f3b309 | ||
|
|
7aba2a5695 | ||
|
|
271459ead8 | ||
|
|
b7c40b13a4 | ||
|
|
a024d632f4 | ||
|
|
14cd563b6e | ||
|
|
01bf8c50fa | ||
|
|
6da4c6ebe8 | ||
|
|
2d4d1dfc47 | ||
|
|
6c0cd33105 | ||
|
|
4371c14114 | ||
|
|
2cd7b7abcd | ||
|
|
cc84b518f2 | ||
|
|
92f2dc76e9 | ||
|
|
d8e8b17d23 | ||
|
|
60fe4437ba | ||
|
|
d149e62a5c | ||
|
|
3f85969803 | ||
|
|
6ccd85ae4a | ||
|
|
ef109213a2 | ||
|
|
7bd58dd39b | ||
|
|
f23bca761c | ||
|
|
b7e62695f1 | ||
|
|
af696da68c | ||
|
|
a23753148e | ||
|
|
4dcc7e5e1e | ||
|
|
f5aa70d869 | ||
|
|
b039849b2b | ||
|
|
fb2691764f | ||
|
|
5b3f0b8ada | ||
|
|
93d44f7c4d | ||
|
|
723ca9e4a8 | ||
|
|
f96d0a7e53 | ||
|
|
625145686a | ||
|
|
343445301b | ||
|
|
363af94d50 | ||
|
|
e7900c56c0 | ||
|
|
99eddd4aa4 | ||
|
|
60830ca5fb | ||
|
|
4ae6739cab | ||
|
|
df8f88c0d9 | ||
|
|
a1ec32c4c1 | ||
|
|
db76991217 | ||
|
|
1aef88e1a0 | ||
|
|
cfa1cac67a | ||
|
|
991a536af1 | ||
|
|
02cd081cf3 | ||
|
|
027dcf9071 | ||
|
|
f234621449 | ||
|
|
b04b0baa1c | ||
|
|
7a2a99379e | ||
|
|
7c676d4f30 | ||
|
|
756ba5c593 | ||
|
|
ca79b062e7 | ||
|
|
bd45dd3ceb | ||
|
|
70c663d859 | ||
|
|
a489f8cf49 | ||
|
|
e8056cd996 | ||
|
|
dd46aa0686 | ||
|
|
64d36c81b0 | ||
|
|
7d860ce9d0 | ||
|
|
d4ddb685b5 | ||
|
|
acd034425a | ||
|
|
c3c2469f6a | ||
|
|
14a0913f13 | ||
|
|
e4f959e109 | ||
|
|
3bf9bff940 | ||
|
|
2943d49d22 | ||
|
|
7ff73d789a | ||
|
|
2eb3558399 | ||
|
|
00b07b20f6 | ||
|
|
b56244edde | ||
|
|
31cd80ece1 | ||
|
|
da97a51e95 | ||
|
|
efcc142db0 | ||
|
|
f7e83b35b1 | ||
|
|
9e5b064bc0 | ||
|
|
2b82fc5785 | ||
|
|
e6da8a0bf0 | ||
|
|
a158877776 | ||
|
|
519a7fef26 | ||
|
|
dc657da1cb | ||
|
|
1825d9ea72 | ||
|
|
6126a40361 | ||
|
|
24f8cefc56 | ||
|
|
9213639b44 | ||
|
|
0fa32c79f1 | ||
|
|
44265e9d25 | ||
|
|
f475c78129 | ||
|
|
46588a1ef6 | ||
|
|
e32252fea4 | ||
|
|
ccf1f976ca | ||
|
|
0b5707fc97 | ||
|
|
d1d137319a | ||
|
|
0831ff0d70 | ||
|
|
8081a68505 | ||
|
|
ff08209ceb | ||
|
|
f43e43f8ad | ||
|
|
e00ef81d03 | ||
|
|
2a55b86213 | ||
|
|
29f9071f99 | ||
|
|
4bf5f38a96 | ||
|
|
4cb8315580 | ||
|
|
cb9ac33d3b | ||
|
|
478cd9f933 | ||
|
|
7bf69d33e0 | ||
|
|
e89dc0b721 | ||
|
|
c1badf210c | ||
|
|
0e7a277d82 | ||
|
|
7f43a53cf0 | ||
|
|
fb1c041353 | ||
|
|
030684868f | ||
|
|
38715c57cc | ||
|
|
c43bcc46d1 | ||
|
|
83f7cf0fd9 | ||
|
|
677473f773 | ||
|
|
a3fb318575 | ||
|
|
e71dba060f | ||
|
|
411f2ac8ea | ||
|
|
58b2ac5b0d | ||
|
|
9e7943aeb5 | ||
|
|
f8c015777c | ||
|
|
2b1efe414a |
@@ -76,8 +76,13 @@
|
||||
;;
|
||||
;; TODO -- these should all be made thread safe if possible or renamed so they end in `!`. Then we can remove them
|
||||
;; from this list because by default anything ending in `!` is considered to be thread-unsafe
|
||||
;;
|
||||
;; TODO (Cam 6/19/25) -- move this into the `test-helpers/config.edn` file so they can live with their friends.
|
||||
:parallel/unsafe
|
||||
#{clojure.core/alter-var-root
|
||||
#{java.util.Locale/setDefault
|
||||
java.lang.System/setProperty
|
||||
java.lang.System/setProperties
|
||||
clojure.core/alter-var-root
|
||||
clojure.core/with-redefs
|
||||
clojure.core/with-redefs-fn
|
||||
metabase.actions.test-util/with-actions
|
||||
@@ -96,7 +101,6 @@
|
||||
metabase.test.util/with-all-users-permission
|
||||
metabase.test.util/with-discarded-collections-perms-changes
|
||||
metabase.test.util/with-env-keys-renamed-by
|
||||
metabase.test.util/with-locale
|
||||
metabase.test.util/with-model-cleanup
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
metabase.test.util/with-non-admin-groups-no-root-collection-perms
|
||||
@@ -119,7 +123,6 @@
|
||||
metabase.test/with-fake-inbox
|
||||
metabase.test/with-group
|
||||
metabase.test/with-group-for-user
|
||||
metabase.test/with-locale
|
||||
metabase.test/with-log-level
|
||||
metabase.test/with-model-cleanup
|
||||
metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms
|
||||
@@ -232,6 +235,8 @@
|
||||
(metabase.driver.druid.query-processor-test/druid-query-returning-rows)
|
||||
(metabase.lib.util.match/match)
|
||||
(metabase.lib.util.match/match-one)
|
||||
(metabase.lib.util.match/match-lite)
|
||||
(metabase.lib.util.match/match-lite-recursive)
|
||||
(metabase.lib.util.match/replace)
|
||||
(metabase.lib.util.match/replace-in)
|
||||
(metabase.driver-api.core/match)
|
||||
@@ -287,7 +292,9 @@
|
||||
medley.core/random-uuid {:message "Use clojure.core/random-uuid instead of medley.core/random-uuid"}
|
||||
metabase.driver/database-supports? {:message "Use metabase.driver.util/supports? instead of metabase.driver/database-supports?"}
|
||||
metabase.lib.equality/find-column-indexes-for-refs {:message "Use lib.equality/closest-matches-in-metadata or lib.equality/find-closest-matches-for-refs instead of lib.equality/find-column-indexes-for-refs"}
|
||||
next.jdbc/active-tx? {:message "Use metabase.app-db.core/in-transaction? to see if we are in a transaction"}}
|
||||
next.jdbc/active-tx? {:message "Use metabase.app-db.core/in-transaction? to see if we are in a transaction"}
|
||||
toucan2.core/debug {:message "Don't commit code using t2/debug"}
|
||||
toucan2.tools.debug/debug {:message "Don't commit code using t2/debug"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{camel-snake-kebab.core {:message "CSK is not Turkish-safe, use the versions in metabase.util instead."}
|
||||
@@ -690,6 +697,8 @@ metabase-enterprise.content-translation.routes-test/with-new-secret-key!
|
||||
:name printable-namespaces}
|
||||
{:pattern "^metabase\\.(?:(?:lib)|(?:legacy-mbql))\\..*"
|
||||
:name metabase-lib}
|
||||
{:pattern "^metabase\\.lib\\..*"
|
||||
:name metabase-lib-excluding-legacy-mbql}
|
||||
;;
|
||||
;; QP source namespaces: `metabase.query-processor.*`, `metabase-enterprise.sandbox.query-processor.*`, and
|
||||
;; `metabase.driver.*`, excluding `-test` namespaces.
|
||||
@@ -770,6 +779,11 @@ metabase-enterprise.content-translation.routes-test/with-new-secret-key!
|
||||
toucan.util.test {:message "Don't use application database inside MLv2 code"}
|
||||
toucan2.core {:message "Don't use application database inside MLv2 code"}}}}
|
||||
|
||||
metabase-lib-excluding-legacy-mbql
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
{metabase.legacy-mbql.util/unique-name-generator {:message "Use metabase.lib.util/unique-name-generator or non-truncating-unique-name-generator"}}}}
|
||||
|
||||
driver-namespaces
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
|
||||
@@ -573,15 +573,16 @@
|
||||
metabase.lib.core
|
||||
metabase.lib.equality
|
||||
metabase.lib.field
|
||||
metabase.lib.field.resolution
|
||||
metabase.lib.filter
|
||||
metabase.lib.ident
|
||||
metabase.lib.init
|
||||
metabase.lib.join.util
|
||||
metabase.lib.metadata
|
||||
metabase.lib.metadata.cached-provider
|
||||
metabase.lib.metadata.calculation
|
||||
metabase.lib.metadata.invocation-tracker
|
||||
metabase.lib.metadata.protocols
|
||||
metabase.lib.metadata.result-metadata
|
||||
metabase.lib.normalize
|
||||
metabase.lib.options
|
||||
metabase.lib.query
|
||||
@@ -594,6 +595,7 @@
|
||||
metabase.lib.schema.expression.window
|
||||
metabase.lib.schema.id
|
||||
metabase.lib.schema.info
|
||||
metabase.lib.schema.join
|
||||
metabase.lib.schema.literal
|
||||
metabase.lib.schema.metadata
|
||||
metabase.lib.schema.parameter
|
||||
@@ -755,17 +757,7 @@
|
||||
{:team "Admin Webapp"
|
||||
:api #{metabase.permissions.api
|
||||
metabase.permissions.core
|
||||
metabase.permissions.init
|
||||
;; TODO -- move these into the API namespace.
|
||||
metabase.permissions.models.application-permissions-revision
|
||||
metabase.permissions.models.collection-permission-graph-revision
|
||||
metabase.permissions.models.collection.graph
|
||||
metabase.permissions.models.data-permissions
|
||||
metabase.permissions.models.permissions
|
||||
metabase.permissions.models.permissions-group
|
||||
metabase.permissions.path
|
||||
metabase.permissions.util
|
||||
metabase.permissions.validation}
|
||||
metabase.permissions.init}
|
||||
:uses #{api
|
||||
app-db
|
||||
audit-app
|
||||
@@ -1355,6 +1347,7 @@
|
||||
metabase.warehouse-schema.metadata-from-qp
|
||||
metabase.warehouse-schema.metadata-queries
|
||||
metabase.warehouse-schema.models.field
|
||||
metabase.warehouse-schema.models.field-user-settings
|
||||
metabase.warehouse-schema.models.field-values
|
||||
metabase.warehouse-schema.models.table
|
||||
metabase.warehouse-schema.table}
|
||||
@@ -1593,6 +1586,7 @@
|
||||
:uses #{api
|
||||
config
|
||||
database-routing
|
||||
driver
|
||||
events
|
||||
lib
|
||||
models
|
||||
@@ -1663,6 +1657,7 @@
|
||||
app-db
|
||||
collections
|
||||
config
|
||||
driver
|
||||
legacy-mbql
|
||||
lib
|
||||
lib-be
|
||||
@@ -1672,12 +1667,23 @@
|
||||
pulse
|
||||
query-processor
|
||||
request
|
||||
server
|
||||
settings
|
||||
system
|
||||
users
|
||||
util
|
||||
warehouse-schema}}
|
||||
|
||||
enterprise/permission-debug
|
||||
{:team "Admin Webapp"
|
||||
:api #{metabase-enterprise.permission-debug.api}
|
||||
:uses #{api
|
||||
lib
|
||||
models
|
||||
permissions
|
||||
query-processor
|
||||
util}}
|
||||
|
||||
enterprise/premium-features
|
||||
{:team "Admin Webapp"
|
||||
:api #{}
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
metabase.collections.models.collection-test/with-personal-and-impersonal-collections [[:block 1]]
|
||||
metabase.config/build-type-case [[:block 0]]
|
||||
metabase.dashboards.models.dashboard-test/with-dash-in-collection [[:inner 0]]
|
||||
metabase.driver-api.core/match [[:inner 0]]
|
||||
metabase.driver-api.core/match-one [[:inner 0]]
|
||||
metabase.driver-api.core/replace [[:inner 0]]
|
||||
metabase.driver-api.core/replace-in [[:inner 0]]
|
||||
metabase.driver.bigquery-cloud-sdk.query-processor/with-temporal-type [[:default]]
|
||||
metabase.driver.druid.client/DELETE [[:default]]
|
||||
metabase.driver.druid.client/GET [[:default]]
|
||||
@@ -151,6 +155,8 @@
|
||||
metabase.lib.test-util.macros/mbql-query [[:inner 0]]
|
||||
metabase.lib.util.match/match [[:inner 0]]
|
||||
metabase.lib.util.match/match-one [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite [[:inner 0]]
|
||||
metabase.lib.util.match/match-lite-recursive [[:inner 0]]
|
||||
metabase.lib.util.match/replace [[:inner 0]]
|
||||
metabase.lib.util.match/replace-in [[:inner 0]]
|
||||
metabase.models.interface/define-batched-hydration-method [[:inner 0]]
|
||||
|
||||
@@ -2,3 +2,6 @@ frontend/src/cljs
|
||||
frontend/src/cljs_release
|
||||
e2e/support/cypress_sample_database.js
|
||||
e2e/support/cypress_sample_instance_data.js
|
||||
dist
|
||||
.next
|
||||
.angular
|
||||
|
||||
27
.eslintrc.js
@@ -13,6 +13,7 @@ if (shouldLintCssModules) {
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"no-direct-document-references": "error",
|
||||
strict: [2, "never"],
|
||||
"no-undef": 2,
|
||||
"no-var": 1,
|
||||
@@ -228,6 +229,32 @@ module.exports = {
|
||||
"i18next/no-literal-string": "off",
|
||||
},
|
||||
},
|
||||
// Disable the `no-direct-document-references` rule code that is not related to the Embedding SDK
|
||||
{
|
||||
files: [
|
||||
"frontend/src/metabase/lib/loki-utils.ts",
|
||||
"frontend/src/metabase/app.js",
|
||||
"frontend/src/metabase/admin/**/*",
|
||||
"frontend/src/metabase/setup/**/*",
|
||||
"enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/**/*",
|
||||
"frontend/src/metabase/public/components/EmbedFrame/**/*",
|
||||
"frontend/src/metabase/app-embed*",
|
||||
"frontend/src/metabase/lib/embed.js",
|
||||
"*.unit.spec.*",
|
||||
"frontend/lint/**/*",
|
||||
"*.stories.*",
|
||||
"stories-data.*",
|
||||
"e2e/**/*",
|
||||
"**/tests/*",
|
||||
"**/__support__/*",
|
||||
"release/**/*",
|
||||
"webpack.config.js",
|
||||
"rspack.config.js",
|
||||
],
|
||||
rules: {
|
||||
"no-direct-document-references": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
name: Cross-branch migration test
|
||||
|
||||
inputs:
|
||||
base-ref:
|
||||
base-ref: ## Base branch PR came from
|
||||
required: true
|
||||
head-ref: ## Source branch of the pull request
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
## First we deploy the base branch
|
||||
- name: Check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -39,10 +42,11 @@ runs:
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
## Then we deploy the PR branch to ensure it migrates up successfully
|
||||
- name: Check out pull request branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
ref: ${{ inputs.head-ref }}
|
||||
|
||||
- name: Run backend on pull request branch
|
||||
run: |
|
||||
@@ -65,3 +69,38 @@ runs:
|
||||
- name: Stop Metabase on pull request branch
|
||||
run: kill $(cat metabase_pid)
|
||||
shell: bash
|
||||
|
||||
## Testing that originally deploying this PR allows you to roll back to a previous release
|
||||
## Reset the checksums so when we go back to the base branch, it's as if the PR branch was what was originally deployed
|
||||
- name: Reset the checksums as if the PR branch was what was originally deployed
|
||||
run: |
|
||||
clojure -M:dev:ee:migrate reset-checksums
|
||||
shell: bash
|
||||
|
||||
- name: Rollback the last deployment
|
||||
run: |
|
||||
clojure -M:dev:ee:migrate rollback last-deployment
|
||||
shell: bash
|
||||
|
||||
- name: Re-check out base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.base-ref }}
|
||||
|
||||
- name: Re-run backend on base branch
|
||||
run: |
|
||||
clojure -M:run:ee > metabase.log 2>&1 & echo $! > metabase_pid
|
||||
tail -F metabase.log &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Metabase to start
|
||||
run: |
|
||||
while ! curl -s 'http://localhost:3000/api/health' | grep '{"status":"ok"}' > /dev/null; do
|
||||
if ! kill -0 $(cat metabase_pid) 2>/dev/null; then
|
||||
cat metabase.log
|
||||
echo "Metabase process has exited."
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
shell: bash
|
||||
|
||||
@@ -31,6 +31,10 @@ inputs:
|
||||
description: Webhook tester container
|
||||
required: false
|
||||
default: false
|
||||
snowplow:
|
||||
description: Snowplow micro container
|
||||
required: false
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -42,56 +46,62 @@ runs:
|
||||
password: ${{ inputs.password }}
|
||||
|
||||
- name: Start Containers
|
||||
run: |
|
||||
shell: bash
|
||||
run: | # bash
|
||||
Y='\033[33m'
|
||||
G='\033[1;32m'
|
||||
|
||||
cd e2e/test/scenarios/
|
||||
|
||||
docker compose up \
|
||||
$([ ${{ inputs.postgres }} = true ] && echo "postgres-sample") \
|
||||
$([ ${{ inputs.mysql }} = true ] && echo "mysql-sample") \
|
||||
$([ ${{ inputs.mongo }} = true ] && echo "mongo-sample") \
|
||||
$([ ${{ inputs.maildev }} = true ] && echo "maildev") \
|
||||
$([ ${{ inputs.openldap }} = true ] && echo "ldap") \
|
||||
$([ ${{ inputs.webhook }} = true ] && echo "webhook-tester") \
|
||||
$([ ${{ inputs.snowplow }} = true ] && echo "snowplow-micro") \
|
||||
-d
|
||||
|
||||
if ${{ inputs.maildev }}; then
|
||||
echo -e "${Y}Starting maildev container..." &&
|
||||
docker run -d -p 1080:1080 -p 1025:1025 maildev/maildev:2.0.5 &&
|
||||
echo -e "${Y}checking maildev container..." &&
|
||||
while ! nc -z localhost 1080; do sleep 1; done &&
|
||||
while ! nc -z localhost 1025; do sleep 1; done &&
|
||||
echo -e "${G}Maildev is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.openldap }}; then
|
||||
echo -e "${Y}Starting openldap container..." &&
|
||||
docker run -d -p 389:389 \
|
||||
--env LDAP_ADMIN_PASSWORD=adminpass \
|
||||
--env LDAP_USERS=user01@example.org,user02@example.org \
|
||||
--env LDAP_PASSWORDS=123456,123465 \
|
||||
--env LDAP_ROOT=dc=example,dc=org \
|
||||
--env LDAP_PORT_NUMBER=389 \
|
||||
bitnami/openldap:2.6.4 &&
|
||||
echo -e "${Y}checking openldap container..." &&
|
||||
while ! nc -z localhost 389; do sleep 1; done &&
|
||||
echo -e "${G}Openldap is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.postgres }}; then
|
||||
echo -e "${Y}Starting postgres container..." &&
|
||||
docker run -d -p 5404:5432 -e TZ='US/Pacific' metabase/qa-databases:postgres-sample-12 &&
|
||||
echo -e "${Y}checking postgres container..." &&
|
||||
while ! nc -z localhost 5404; do sleep 1; done &&
|
||||
echo -e "${G}Postgres is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.mysql }}; then
|
||||
echo -e "${Y}Starting mysql container..." &&
|
||||
docker run -d -p 3304:3306 -e TZ='US/Pacific' metabase/qa-databases:mysql-sample-8 &&
|
||||
echo -e "${Y}checking mysql container..." &&
|
||||
while ! nc -z localhost 3304; do sleep 1; done &&
|
||||
echo -e "${G}MySQL is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.mongo }}; then
|
||||
echo -e "${Y}Starting mongo container..." &&
|
||||
docker run -d -p 27004:27017 metabase/qa-databases:mongo-sample-4.4 &&
|
||||
echo -e "${Y}checking mongo container..." &&
|
||||
while ! nc -z localhost 27004; do sleep 1; done &&
|
||||
echo -e "${G}Mongo is up and running!"
|
||||
fi
|
||||
|
||||
if ${{ inputs.webhook }}; then
|
||||
echo -e "${Y}Starting webhook test container..." &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester:1.1.0 serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
echo -e "${Y}checking webhook test container..." &&
|
||||
while ! nc -z localhost 9080; do sleep 1; done &&
|
||||
echo -e "${G}Webhook tester is up and running!"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
if ${{ inputs.snowplow }}; then
|
||||
echo -e "${Y}checking snowplow micro container..." &&
|
||||
while ! nc -z localhost 9090; do sleep 1; done &&
|
||||
echo -e "${G}Snowplow micro is up and running!"
|
||||
fi
|
||||
|
||||
13
.github/actions/run-snowplow-micro/action.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Run Snowplow micro
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Download Snowplow micro JAR
|
||||
run: curl -L -o snowplow-micro.jar https://github.com/snowplow-incubator/snowplow-micro/releases/download/micro-1.2.1/snowplow-micro-1.2.1.jar
|
||||
shell: bash
|
||||
- name: Launch Snowplow micro
|
||||
run: java -cp snowplow-micro.jar:snowplow com.snowplowanalytics.snowplow.micro.Main --collector-config snowplow/micro.conf --iglu snowplow/iglu.json &
|
||||
shell: bash
|
||||
- name: Wait until Snowplow micro is ready
|
||||
run: while ! curl -s 'http://localhost:9090' | grep '404'; do sleep 1; done
|
||||
shell: bash
|
||||
38
.github/file-paths.yaml
vendored
@@ -16,10 +16,12 @@ frontend_ci: &frontend_ci
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/frontend.yml"
|
||||
|
||||
frontend_sources: &frontend_sources
|
||||
- *shared_sources
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
frontend_embedding_sdk_ci: &frontend_embedding_sdk_ci
|
||||
- ".github/actions/prepare-frontend/**"
|
||||
- ".github/actions/prepare-backend/**"
|
||||
- ".github/workflows/*embedding-sdk*.yml"
|
||||
|
||||
frontend_configs: &frontend_configs
|
||||
- "yarn.lock"
|
||||
- "**/tsconfig*.json"
|
||||
- "package.json"
|
||||
@@ -32,8 +34,15 @@ frontend_sources: &frontend_sources
|
||||
- "webpack.static-viz.config.js"
|
||||
- ".nvmrc"
|
||||
|
||||
frontend_sources: &frontend_sources
|
||||
- *shared_sources
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_sources:
|
||||
- *frontend_sources
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_specs: &frontend_specs
|
||||
- *shared_specs
|
||||
@@ -105,25 +114,27 @@ e2e_ci: &e2e_ci
|
||||
- ".github/actions/prepare-uberjar-artifact/**"
|
||||
- ".github/actions/e2e-prepare-containers/**"
|
||||
- ".github/actions/prepare-cypress/**"
|
||||
- ".github/actions/run-snowplow-micro/**"
|
||||
- ".github/workflows/e2e-*"
|
||||
|
||||
e2e_specs: &e2e_specs
|
||||
- "**/*.cy.*.js"
|
||||
- "**/*.cy.*.ts"
|
||||
- e2e/!(test-host-app)**/*.cy.*.{js,ts}
|
||||
|
||||
e2e_cross_version: &e2e_cross_version
|
||||
- ".github/workflows/e2e-cross-version.yml"
|
||||
- "e2e/test/scenarios/cross-version/**"
|
||||
|
||||
e2e_embedding_sdk:
|
||||
- *frontend_embedding_sdk_ci
|
||||
- *sources
|
||||
- "e2e/component/**/*.cy.*.{js,ts}"
|
||||
|
||||
e2e_embedding_sdk_compatibility:
|
||||
- *default
|
||||
- *e2e_ci
|
||||
- *frontend_embedding_sdk_ci
|
||||
- *frontend_sources
|
||||
- "e2e/embedding-sdk-host-apps/**"
|
||||
- "e2e/runner/embedding-sdk/**"
|
||||
- "e2e/runner/test-host-app/**/*.cy.*.js"
|
||||
- "e2e/runner/test-host-app/**/*.cy.*.ts"
|
||||
- "e2e/test-host-app/**/*.cy.*.{js,ts}"
|
||||
|
||||
e2e_all:
|
||||
- *default
|
||||
@@ -137,10 +148,13 @@ snowplow:
|
||||
- "snowplow/**"
|
||||
|
||||
documentation:
|
||||
- ".typedoc/**"
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
|
||||
embedding_documentation:
|
||||
- ".typedoc/**"
|
||||
- "docs/embedding/**"
|
||||
|
||||
yaml:
|
||||
- "**/*.yml"
|
||||
- "**/*.yaml"
|
||||
|
||||
405
.github/workflows/app-db.yml
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
name: App DB Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
skip:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-app-db
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
be-tests-mariadb:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: MariaDB 10.2
|
||||
junit-name: be-tests-mariadb-10-2-ee
|
||||
image: circleci/mariadb:10.2.23
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MariaDB Latest
|
||||
junit-name: be-tests-mariadb-latest-ee
|
||||
image: circleci/mariadb:latest
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- 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'
|
||||
- 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'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ matrix.version.image }}
|
||||
ports:
|
||||
- "3306:3306"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
#
|
||||
# SSL config: only for mysql latest tests.
|
||||
#
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mysql:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: MySQL 8.0
|
||||
junit-name: be-tests-mysql-8-0-ee
|
||||
image: cimg/mysql:8.0
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL Latest
|
||||
junit-name: be-tests-mysql-latest-ee
|
||||
image: mysql:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- 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'
|
||||
- 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'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ matrix.version.image }}
|
||||
ports:
|
||||
- "3306:3306"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
#
|
||||
# SSL config: only for mysql latest tests.
|
||||
#
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-postgres:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: Postgres 12.x
|
||||
junit-name: postgres-ee
|
||||
docker-image: postgres:12-alpine
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: Postgres Latest
|
||||
junit-name: postgres-latest-ee
|
||||
docker-image: postgres:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- 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'
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
# with the tag `mb/old-migrations-test`. `__ADDITIONAL_EXCLUDED_TAG__` is not used anywhere outside of splicing
|
||||
# it in to the command below.
|
||||
__ADDITIONAL_EXCLUDED_TAG__: >-
|
||||
${{
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
(
|
||||
github.ref == 'master' ||
|
||||
startsWith(github.ref, 'release-')
|
||||
) &&
|
||||
''
|
||||
) ||
|
||||
':mb/old-migrations-test'
|
||||
}}
|
||||
# actual serious env vars below
|
||||
CI: 'true'
|
||||
DRIVERS: postgres
|
||||
MB_DB_TYPE: postgres
|
||||
MB_DB_PORT: 5432
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_DBNAME: mb_test
|
||||
MB_DB_USER: mb_test
|
||||
MB_POSTGRESQL_TEST_USER: mb_test
|
||||
# SSL tests are only enabled for the postgres-latest job.
|
||||
MB_POSTGRES_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_POSTGRES_SSL_TEST_SSL_MODE: verify-full
|
||||
MB_POSTGRES_SSL_TEST_SSL_ROOT_CERT_PATH: 'test-resources/certificates/us-east-2-bundle.pem'
|
||||
services:
|
||||
postgres:
|
||||
image: ${{ matrix.version.docker-image }}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
env:
|
||||
POSTGRES_USER: mb_test
|
||||
POSTGRES_DB: mb_test
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
app-db-tests-result:
|
||||
needs:
|
||||
- be-tests-mariadb
|
||||
- be-tests-mysql
|
||||
- be-tests-postgres
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
name: app-db-tests-result
|
||||
if: always() && !cancelled()
|
||||
steps:
|
||||
- name: Check job status
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
needs: ${{ toJson(needs) }}
|
||||
with:
|
||||
script: | # js
|
||||
const needs = JSON.parse(process.env.needs);
|
||||
|
||||
const jobs = Object.entries(needs).map(
|
||||
([jobName, jobValues]) => ({
|
||||
name: jobName,
|
||||
result: jobValues.result
|
||||
}));
|
||||
|
||||
// are all jobs skipped or successful?
|
||||
if (jobs.every(job => (job.result === 'skipped' || job.result === 'success'))) {
|
||||
console.log("");
|
||||
console.log(" _------. ");
|
||||
console.log(" / , \_ ");
|
||||
console.log(" / / /{}\ |o\_ ");
|
||||
console.log(" / \ `--' /-' \ ");
|
||||
console.log(" | \ \ | ");
|
||||
console.log(" | |`-, | ");
|
||||
console.log(" / /__/)/ ");
|
||||
console.log(" | | ");
|
||||
console.log("");
|
||||
console.log("All app db tests have passed (or have been skipped). Cam is very proud of you.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// otherwise, something failed
|
||||
console.log("");
|
||||
console.log(" .::::::::::. .::::::::::. ");
|
||||
console.log(" .::::''''''::::. .::::''''''::::. ");
|
||||
console.log(" .:::' `::::.... ....::::' `:::. ");
|
||||
console.log(" .::' `:::::::| |:::::::' `::. ");
|
||||
console.log(" .::| |::::::|_ ___ __|::::::| |::. ");
|
||||
console.log(" `--' |::::::|_()__()_|::::::| `--' ");
|
||||
console.log(" ::: |::-o::| |::o-::| ::: ");
|
||||
console.log(" `::. .|::::::| |::::::|. .::' ");
|
||||
console.log(" `:::. .::\-----' `-----/::. .:::' ");
|
||||
console.log(" `::::......::::' `::::......::::' ");
|
||||
console.log(" `::::::::::' `::::::::::' ");
|
||||
console.log("");
|
||||
console.log("App db tests have failed. You have been sentenced to MetaJail.");
|
||||
|
||||
jobs.forEach((job) => {
|
||||
if (job.result !== 'success') {
|
||||
console.log(`${job.name} - ${job.result}`);
|
||||
}
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
49
.github/workflows/cross-branch-migrations.yml
vendored
@@ -5,6 +5,19 @@ on:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "resources/migrations/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base-ref:
|
||||
description: 'Base reference for the migration test'
|
||||
required: true
|
||||
type: string
|
||||
head-ref:
|
||||
description: 'Head reference for the migration test'
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
BASE_REF: ${{ inputs.base-ref || github.base_ref }}
|
||||
HEAD_REF: ${{ inputs.head-ref || github.head_ref }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||
@@ -13,19 +26,21 @@ concurrency:
|
||||
jobs:
|
||||
migrations-h2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: h2
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-postgres-12-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
@@ -46,11 +61,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-postgres-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: postgres
|
||||
@@ -71,11 +87,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mysql-8-0-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -92,11 +109,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mysql-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -117,11 +135,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mariadb-10-2-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -138,11 +157,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
migrations-mariadb-latest-ee:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
CI: 'true'
|
||||
MB_DB_TYPE: mysql
|
||||
@@ -159,4 +179,5 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/cross-branch-migration-test
|
||||
with:
|
||||
base-ref: ${{ github.base_ref }}
|
||||
base-ref: ${{ env.BASE_REF }}
|
||||
head-ref: ${{ env.HEAD_REF }}
|
||||
|
||||
11
.github/workflows/docs_bump_detected.yml
vendored
@@ -18,10 +18,15 @@ jobs:
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A change in the docs directory was detected."
|
||||
echo "A PR with changes in the docs directory was detected."
|
||||
echo "Sending workflow trigger to create/update the corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
@@ -33,5 +38,7 @@ jobs:
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_update",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_update",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH}});
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
|
||||
43
.github/workflows/docs_close_detected.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Docs Close Detected
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-x.[0-9]+.x'
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
push_and_merge_workflow_to_docs_metabase_github_io_job:
|
||||
if: github.event.pull_request.merged == false
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes to the docs directory was closed."
|
||||
echo "Sending workflow trigger to close corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | #js
|
||||
github.rest.repos.createDispatchEvent({
|
||||
owner: "${{ github.repository_owner }}",
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_close",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_close",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
9
.github/workflows/docs_merge_detected.yml
vendored
@@ -17,10 +17,15 @@ jobs:
|
||||
env:
|
||||
SOURCE_BRANCH: ${{ github.head_ref || github.ref }}
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
steps:
|
||||
- name: Report changed docs files exist
|
||||
run: |
|
||||
echo "A PR with changes to the docs directory was merged."
|
||||
echo "Sending workflow trigger to merge corresponding PR in docs repo."
|
||||
echo "source branch: $SOURCE_BRANCH"
|
||||
echo "target branch: $TARGET_BRANCH"
|
||||
echo "pr number: $PR_NUMBER"
|
||||
|
||||
- name: Trigger repo dispatch
|
||||
uses: actions/github-script@v7
|
||||
@@ -32,5 +37,7 @@ jobs:
|
||||
repo: "docs.metabase.github.io",
|
||||
event_type: "docs_merge",
|
||||
client_payload: {
|
||||
dispatch_type: "docs_merge",
|
||||
source_branch: process.env.SOURCE_BRANCH,
|
||||
target_branch: process.env.TARGET_BRANCH}});
|
||||
target_branch: process.env.TARGET_BRANCH,
|
||||
pr_number: process.env.PR_NUMBER}});
|
||||
|
||||
119
.github/workflows/drivers.yml
vendored
@@ -12,8 +12,38 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# This job will determine if the tier 3 driver tests should be skipped.
|
||||
# It will always run first.
|
||||
determine-driver-skips:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip: ${{ steps.check_team_and_branch.outputs.skip }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Check if tier3 drivers should run
|
||||
id: check_team_and_branch
|
||||
run: |
|
||||
IS_MASTER_OR_RELEASE="${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') }}"
|
||||
HAS_TEAM_LABEL="${{ contains(github.event.pull_request.labels.*.name, '.Team/Drivers') || contains(github.event.pull_request.labels.*.name, '.Team/Querying') || contains(github.event.pull_request.labels.*.name, '.Team/SemanticLayer') }}"
|
||||
|
||||
|
||||
echo "Is master or release: ${IS_MASTER_OR_RELEASE}"
|
||||
echo "Has '.Team/Drivers' or '.Team/Querying' or '.Team/SemanticLayer' label: ${HAS_TEAM_LABEL}"
|
||||
if [[ "${IS_MASTER_OR_RELEASE}" == "true" ]]; then
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
echo "Merging into master or a release branch. Tier 3 driver tests will run."
|
||||
elif [[ "${HAS_TEAM_LABEL}" == "true" ]]; then
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
echo "A relevant team label is present. Tier 3 driver tests will run."
|
||||
else
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "Not labeled with required team and not merging into master/release. Tier 3 driver tests will be skipped."
|
||||
fi
|
||||
shell: bash
|
||||
be-tests-athena-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -129,7 +159,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-druid-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -164,7 +195,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-druid-jdbc-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -199,7 +231,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-databricks-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -233,7 +266,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-databricks-multi-catalog-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -314,7 +348,7 @@ jobs:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL 8.0
|
||||
junit-name: be-tests-mysql-8-0-ee
|
||||
image: cimg/mysql:8.0
|
||||
image: mysql:8.0
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL Latest
|
||||
@@ -324,28 +358,8 @@ jobs:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- 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'
|
||||
- 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'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -403,7 +417,6 @@ jobs:
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
@@ -421,7 +434,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mongo:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -470,7 +484,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mongo-ssl:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -534,7 +549,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mongo-sharded-cluster-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -567,7 +583,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-oracle:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -657,29 +674,8 @@ jobs:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
- 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'
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
@@ -724,7 +720,6 @@ jobs:
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
@@ -742,7 +737,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-presto-jdbc-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -898,7 +894,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-sparksql-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -931,7 +928,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-sqlite-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -959,7 +957,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-sqlserver:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
@@ -1009,7 +1008,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-starburst-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
@@ -1046,7 +1046,8 @@ jobs:
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-vertica-ee:
|
||||
if: ${{ !inputs.skip }}
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
|
||||
@@ -128,8 +128,10 @@ jobs:
|
||||
DISPLAY: ""
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
@@ -138,12 +140,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Needed to omit newly added tests for functionality that is not yet released
|
||||
- name: Sparse checkout tests from SDK release commit
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
e2e_all: ${{ steps.changes.outputs.e2e_all }}
|
||||
e2e_embedding_sdk: ${{ steps.changes.outputs.e2e_embedding_sdk }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
needs: [files-changed, get-build-requirements]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.files-changed.outputs.e2e_all == 'true'
|
||||
needs.files-changed.outputs.e2e_embedding_sdk == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
@@ -109,8 +109,10 @@ jobs:
|
||||
DISPLAY: ""
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
CYPRESS_IS_EMBEDDING_SDK: true
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
@@ -120,12 +122,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: E2E Cross-version Tests
|
||||
|
||||
# See `bin/backward-compatibility-test.js` for the documentation of this workflow.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -143,8 +145,10 @@ jobs:
|
||||
- name: Run the tests
|
||||
env:
|
||||
MB_EDITION: ee
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
CYPRESS_CI: true
|
||||
run: |
|
||||
node bin/backward-compatibility-test.js \
|
||||
|
||||
12
.github/workflows/e2e-cross-version.yml
vendored
@@ -66,8 +66,10 @@ jobs:
|
||||
docker run -d \
|
||||
-v $PWD/my-metabase:/metabase.db \
|
||||
-p 3000:3000 \
|
||||
-e CYPRESS_ALL_FEATURES_TOKEN=${{ secrets.ENTERPRISE_TOKEN }} \
|
||||
-e CYPRESS_NO_FEATURES_TOKEN=${{ secrets.E2E_STARTER_TOKEN }} \
|
||||
-e CYPRESS_MB_ALL_FEATURES_TOKEN=${{ secrets.MB_ALL_FEATURES_TOKEN }} \
|
||||
-e CYPRESS_MB_STARTER_CLOUD_TOKEN=${{ secrets.MB_STARTER_CLOUD_TOKEN }} \
|
||||
-e CYPRESS_MB_PRO_CLOUD_TOKEN=${{ secrets.MB_PRO_CLOUD_TOKEN }} \
|
||||
-e CYPRESS_MB_PRO_SELF_HOSTED_TOKEN=${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }} \
|
||||
--name metabase-${{ matrix.version.source }} \
|
||||
${{ env.DOCKER_SOURCE_IMAGE }}
|
||||
- name: Wait for Metabase to start on port 3000
|
||||
@@ -97,8 +99,10 @@ jobs:
|
||||
docker run -d \
|
||||
-v $PWD/my-metabase:/metabase.db \
|
||||
-p 3001:3000 \
|
||||
-e CYPRESS_ALL_FEATURES_TOKEN=${{ secrets.ENTERPRISE_TOKEN }} \
|
||||
-e CYPRESS_NO_FEATURES_TOKEN=${{ secrets.E2E_STARTER_TOKEN }} \
|
||||
-e CYPRESS_MB_ALL_FEATURES_TOKEN=${{ secrets.MB_ALL_FEATURES_TOKEN }} \
|
||||
-e CYPRESS_MB_STARTER_CLOUD_TOKEN=${{ secrets.MB_STARTER_CLOUD_TOKEN }} \
|
||||
-e CYPRESS_MB_PRO_CLOUD_TOKEN=${{ secrets.MB_PRO_CLOUD_TOKEN }} \
|
||||
-e CYPRESS_MB_PRO_SELF_HOSTED_TOKEN=${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }} \
|
||||
--name metabase-${{ matrix.version.target }} \
|
||||
${{ env.DOCKER_TARGET_IMAGE }}
|
||||
- name: Wait for Metabase to start on port 3001
|
||||
|
||||
@@ -191,6 +191,12 @@ jobs:
|
||||
name: embedding-sdk-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
path: resources/embedding-sdk
|
||||
|
||||
# In order to check compatibility between Sample Apps and Embedding SDK, the job for each sample app:
|
||||
# - pulls a Sample App
|
||||
# - starts it
|
||||
# - runs its Cypress tests against the local metabase.jar and local @metabase/embedding-sdk-react package.
|
||||
# The job is not required, though if it's failing better to be sure that it's not caused by a breaking change.
|
||||
# For more details see `Sample Apps compatibility with Embedding SDK tests` in `enterprise/frontend/src/embedding-sdk/dev.md`
|
||||
e2e-sample-apps-tests:
|
||||
needs: [get-build-requirements, get-sample-apps-data, build]
|
||||
strategy:
|
||||
@@ -208,7 +214,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
|
||||
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_IS_EMBEDDING_SDK: true
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
@@ -308,6 +314,13 @@ jobs:
|
||||
run: |
|
||||
exit 1
|
||||
|
||||
# The job checks integration of the Embedding SDK with consumer's apps that use different frameworks/bundlers,
|
||||
# or when we want to test some tricky integration cases like conflicting types.
|
||||
# The job
|
||||
# - starts an app
|
||||
# - runs its Cypress tests against the e2e test metabase instance and local @metabase/embedding-sdk-react package.
|
||||
# The job is not required, though if it's failing better to be sure that it's not caused by a breaking change.
|
||||
# For more details see `Embedding SDK integration tests with Host Apps` in `enterprise/frontend/src/embedding-sdk/dev.md`
|
||||
e2e-host-apps-tests:
|
||||
needs: [ get-build-requirements, build ]
|
||||
strategy:
|
||||
@@ -330,7 +343,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
|
||||
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_IS_EMBEDDING_SDK: true
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
|
||||
@@ -63,8 +63,10 @@ jobs:
|
||||
DISPLAY: ""
|
||||
QA_DB_ENABLED: ${{ contains(fromJSON('["sql", "mongo"]'), inputs.qa_db) }}
|
||||
CYPRESS_QA_DB_MONGO: ${{ inputs.qa_db == 'mongo' }}
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
MB_EDITION: ${{ inputs.mb_edition }}
|
||||
@@ -84,6 +86,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: true
|
||||
snowplow: true
|
||||
postgres: ${{ inputs.qa_db == 'sql' }}
|
||||
mysql: ${{ inputs.qa_db == 'sql' }}
|
||||
mongo: ${{ inputs.qa_db == 'mongo' }}
|
||||
@@ -103,8 +106,6 @@ jobs:
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
- name: Run Metabase
|
||||
run: node e2e/runner/run_cypress_ci.js start
|
||||
- name: Generate database snapshots
|
||||
|
||||
26
.github/workflows/e2e-test.yml
vendored
@@ -44,8 +44,10 @@ jobs:
|
||||
JOB_NAME: ${{ inputs.name }}
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
MB_EXPERIMENTAL_SEARCH_BLOCK_ON_QUEUE: true
|
||||
@@ -56,12 +58,6 @@ jobs:
|
||||
CYPRESS_CI: true
|
||||
CYPRESS_QA_DB_MONGO: ${{ inputs.name == 'mongo' }}
|
||||
CYPRESS_IS_EMBEDDING_SDK: ${{ inputs.name == 'embedding-sdk' }}
|
||||
mongo: ${{ inputs.name == 'mongo' }}
|
||||
postgres: ${{ inputs.name != 'mongo' }}
|
||||
mysql: ${{ inputs.name != 'mongo' }}
|
||||
openldap: true
|
||||
maildev: true
|
||||
webhook: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -71,11 +67,12 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: ${{ env.openldap }}
|
||||
postgres: ${{ env.postgres }}
|
||||
mysql: ${{ env.mysql }}
|
||||
mongo: ${{ env.mongo }}
|
||||
webhook: ${{ env.webhook }}
|
||||
openldap: true
|
||||
webhook: true
|
||||
snowplow: true
|
||||
postgres: ${{ inputs.name != 'mongo' }}
|
||||
mysql: ${{ inputs.name != 'mongo' }}
|
||||
mongo: ${{ inputs.name == 'mongo' }}
|
||||
|
||||
- name: Run Embedding SDK Storybook
|
||||
if: inputs.name == 'embedding-sdk'
|
||||
@@ -111,9 +108,6 @@ jobs:
|
||||
if: github.run_attempt != '1'
|
||||
run: echo "specs=$(cat failed-specs)" >> $GITHUB_ENV
|
||||
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
|
||||
- name: Run Metabase
|
||||
run: node e2e/runner/run_cypress_ci.js start
|
||||
|
||||
|
||||
14
.github/workflows/embedding-sdk.yml
vendored
@@ -18,8 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
documentation: ${{ steps.changes.outputs.documentation }}
|
||||
e2e_embedding_sdk: ${{ steps.changes.outputs.e2e_embedding_sdk }}
|
||||
frontend_embedding_sdk_sources: ${{ steps.changes.outputs.frontend_embedding_sdk_sources }}
|
||||
embedding_documentation: ${{ steps.changes.outputs.embedding_documentation }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
@@ -33,8 +34,9 @@ jobs:
|
||||
needs: [files-changed]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
(needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' ||
|
||||
needs.files-changed.outputs.documentation == 'true')
|
||||
(needs.files-changed.outputs.e2e_embedding_sdk == 'true' ||
|
||||
needs.files-changed.outputs.frontend_embedding_sdk_sources == 'true' ||
|
||||
needs.files-changed.outputs.embedding_documentation == 'true')
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
@@ -75,7 +77,7 @@ jobs:
|
||||
embedding-sdk-docs-snippets-type-check:
|
||||
needs: [files-changed, build]
|
||||
if: |
|
||||
needs.files-changed.outputs.documentation == 'true' &&
|
||||
needs.files-changed.outputs.embedding_documentation == 'true' &&
|
||||
needs.build.result == 'success'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 20
|
||||
@@ -140,6 +142,7 @@ jobs:
|
||||
if: ${{ steps.docs-generation.outcome == 'failure' }}
|
||||
run: exit 1
|
||||
|
||||
# See `Component e2e tests` in `enterprise/frontend/src/embedding-sdk/dev.md`
|
||||
sdk-e2e-tests:
|
||||
uses: ./.github/workflows/e2e-component-tests-embedding-sdk.yml
|
||||
needs: [build]
|
||||
@@ -153,8 +156,9 @@ jobs:
|
||||
uses: ./.github/workflows/e2e-component-tests-embedding-sdk-cross-version.yml
|
||||
secrets: inherit
|
||||
|
||||
# See `bin/backward-compatibility-test.js` for the documentation of this workflow.
|
||||
cross-version-for-breaking-changes:
|
||||
# run on: every pr that targets a release branch
|
||||
# run on: every pr that targets a release branch, it does NOT run for pr targeting master
|
||||
if: ${{ github.event_name == 'pull_request' && startsWith(github.base_ref, 'release-x.') }}
|
||||
uses: ./.github/workflows/e2e-cross-version-for-breaking-changes.yml
|
||||
secrets: inherit
|
||||
|
||||
13
.github/workflows/pre-release.yml
vendored
@@ -103,8 +103,14 @@ jobs:
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.edition }}
|
||||
DISPLAY: ""
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.MB_PRO_CLOUD_TOKEN }}
|
||||
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.MB_PRO_SELF_HOSTED_TOKEN }}
|
||||
# these are needed for backwards compatibility with release branches earlier than v55, since
|
||||
# this workflow always runs from master, but we run tests from the release branch
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.MB_ALL_FEATURES_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.MB_STARTER_CLOUD_TOKEN }}
|
||||
# disabled because of out of memory issues
|
||||
# probably related to https://github.com/cypress-io/cypress/issues/27415
|
||||
CYPRESS_NO_COMMAND_LOG: 1
|
||||
@@ -122,6 +128,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
snowplow: true
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare JDK 21
|
||||
@@ -132,8 +139,6 @@ jobs:
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
with:
|
||||
|
||||
16
.github/workflows/release-tag.yml
vendored
@@ -250,19 +250,9 @@ jobs:
|
||||
|
||||
- name: Tag the docker image
|
||||
run: | # sh
|
||||
docker pull $DOCKERHUB_REPO\:$DOCKERHUB_VERSION
|
||||
docker tag $DOCKERHUB_REPO\:$DOCKERHUB_VERSION $DOCKERHUB_REPO\:$TAG_NAME
|
||||
docker image ls $DOCKERHUB_REPO
|
||||
|
||||
SOURCE_ID=$(docker image ls $DOCKERHUB_REPO\:$DOCKERHUB_VERSION -q)
|
||||
TAGGED_ID=$(docker image ls $DOCKERHUB_REPO\:$TAG_NAME -q)
|
||||
|
||||
if [[ $SOURCE_ID != $TAGGED_ID ]]; then
|
||||
echo "Error tagging docker image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker push $DOCKERHUB_REPO\:$TAG_NAME
|
||||
docker buildx imagetools create --tag \
|
||||
$DOCKERHUB_REPO\:$TAG_NAME \
|
||||
$DOCKERHUB_REPO\:$DOCKERHUB_VERSION
|
||||
|
||||
push-git-tags:
|
||||
permissions: write-all
|
||||
|
||||
7
.github/workflows/run-tests.yml
vendored
@@ -71,6 +71,13 @@ jobs:
|
||||
with:
|
||||
skip: ${{ needs.files-changed.outputs.backend_all != 'true' && needs.static-viz-files-changed.outputs.static_viz != 'true' }}
|
||||
|
||||
app-db-tests:
|
||||
needs: files-changed
|
||||
uses: ./.github/workflows/app-db.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
skip: ${{ needs.files-changed.outputs.backend_all != 'true' }}
|
||||
|
||||
driver-tests:
|
||||
needs: files-changed
|
||||
uses: ./.github/workflows/drivers.yml
|
||||
|
||||
1
.github/workflows/translation-update.yml
vendored
@@ -43,6 +43,7 @@ jobs:
|
||||
command_args: |
|
||||
--config locales/crowdin.yml
|
||||
-e=el
|
||||
-e=et
|
||||
-e=es-MX
|
||||
-e=gu-IN
|
||||
-e=hr
|
||||
|
||||
2
.gitignore
vendored
@@ -36,6 +36,7 @@
|
||||
/coverage
|
||||
/crate-*
|
||||
/cypress
|
||||
cypress.env.json
|
||||
/deploy/artifacts/*
|
||||
/docs/**/node_modules
|
||||
/docs/**/package.json
|
||||
@@ -146,6 +147,7 @@ llm-payloads
|
||||
.aider*
|
||||
/config.yml
|
||||
CLAUDE.local.md
|
||||
.claude/settings.local.json
|
||||
|
||||
.eslintcache
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -10,7 +10,7 @@ const mainAppStories = [
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: mainAppStories,
|
||||
staticDirs: ["../resources/frontend_client"],
|
||||
staticDirs: ["../resources/frontend_client", "./msw-public"],
|
||||
addons: [
|
||||
"@storybook/addon-webpack5-compiler-babel",
|
||||
"@storybook/addon-essentials",
|
||||
|
||||
307
.storybook/msw-public/mockServiceWorker.js
Normal file
@@ -0,0 +1,307 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker.
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
* - Please do NOT serve this file on production.
|
||||
*/
|
||||
|
||||
const PACKAGE_VERSION = "2.7.5";
|
||||
const INTEGRITY_CHECKSUM = "00729d72e3b82faf54ca8b9621dbb96f";
|
||||
const IS_MOCKED_RESPONSE = Symbol("isMockedResponse");
|
||||
const activeClientIds = new Set();
|
||||
|
||||
self.addEventListener("install", function () {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", function (event) {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener("message", async function (event) {
|
||||
const clientId = event.source.id;
|
||||
|
||||
if (!clientId || !self.clients) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await self.clients.get(clientId);
|
||||
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: "window",
|
||||
});
|
||||
|
||||
switch (event.data) {
|
||||
case "KEEPALIVE_REQUEST": {
|
||||
sendToClient(client, {
|
||||
type: "KEEPALIVE_RESPONSE",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "INTEGRITY_CHECK_REQUEST": {
|
||||
sendToClient(client, {
|
||||
type: "INTEGRITY_CHECK_RESPONSE",
|
||||
payload: {
|
||||
packageVersion: PACKAGE_VERSION,
|
||||
checksum: INTEGRITY_CHECKSUM,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "MOCK_ACTIVATE": {
|
||||
activeClientIds.add(clientId);
|
||||
|
||||
sendToClient(client, {
|
||||
type: "MOCKING_ENABLED",
|
||||
payload: {
|
||||
client: {
|
||||
id: client.id,
|
||||
frameType: client.frameType,
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "MOCK_DEACTIVATE": {
|
||||
activeClientIds.delete(clientId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "CLIENT_CLOSED": {
|
||||
activeClientIds.delete(clientId);
|
||||
|
||||
const remainingClients = allClients.filter((client) => {
|
||||
return client.id !== clientId;
|
||||
});
|
||||
|
||||
// Unregister itself when there are no more clients
|
||||
if (remainingClients.length === 0) {
|
||||
self.registration.unregister();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", function (event) {
|
||||
const { request } = event;
|
||||
|
||||
// Bypass navigation requests.
|
||||
if (request.mode === "navigate") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Opening the DevTools triggers the "only-if-cached" request
|
||||
// that cannot be handled by the worker. Bypass such requests.
|
||||
if (request.cache === "only-if-cached" && request.mode !== "same-origin") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass all requests when there are no active clients.
|
||||
// Prevents the self-unregistered worked from handling requests
|
||||
// after it's been deleted (still remains active until the next reload).
|
||||
if (activeClientIds.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate unique request ID.
|
||||
const requestId = crypto.randomUUID();
|
||||
event.respondWith(handleRequest(event, requestId));
|
||||
});
|
||||
|
||||
async function handleRequest(event, requestId) {
|
||||
const client = await resolveMainClient(event);
|
||||
const response = await getResponse(event, client, requestId);
|
||||
|
||||
// Send back the response clone for the "response:*" life-cycle events.
|
||||
// Ensure MSW is active and ready to handle the message, otherwise
|
||||
// this message will pend indefinitely.
|
||||
if (client && activeClientIds.has(client.id)) {
|
||||
(async function () {
|
||||
const responseClone = response.clone();
|
||||
|
||||
sendToClient(
|
||||
client,
|
||||
{
|
||||
type: "RESPONSE",
|
||||
payload: {
|
||||
requestId,
|
||||
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||
type: responseClone.type,
|
||||
status: responseClone.status,
|
||||
statusText: responseClone.statusText,
|
||||
body: responseClone.body,
|
||||
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||
},
|
||||
},
|
||||
[responseClone.body],
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Resolve the main client for the given event.
|
||||
// Client that issues a request doesn't necessarily equal the client
|
||||
// that registered the worker. It's with the latter the worker should
|
||||
// communicate with during the response resolving phase.
|
||||
async function resolveMainClient(event) {
|
||||
const client = await self.clients.get(event.clientId);
|
||||
|
||||
if (activeClientIds.has(event.clientId)) {
|
||||
return client;
|
||||
}
|
||||
|
||||
if (client?.frameType === "top-level") {
|
||||
return client;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: "window",
|
||||
});
|
||||
|
||||
return allClients
|
||||
.filter((client) => {
|
||||
// Get only those clients that are currently visible.
|
||||
return client.visibilityState === "visible";
|
||||
})
|
||||
.find((client) => {
|
||||
// Find the client ID that's recorded in the
|
||||
// set of clients that have registered the worker.
|
||||
return activeClientIds.has(client.id);
|
||||
});
|
||||
}
|
||||
|
||||
async function getResponse(event, client, requestId) {
|
||||
const { request } = event;
|
||||
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const requestClone = request.clone();
|
||||
|
||||
function passthrough() {
|
||||
// Cast the request headers to a new Headers instance
|
||||
// so the headers can be manipulated with.
|
||||
const headers = new Headers(requestClone.headers);
|
||||
|
||||
// Remove the "accept" header value that marked this request as passthrough.
|
||||
// This prevents request alteration and also keeps it compliant with the
|
||||
// user-defined CORS policies.
|
||||
const acceptHeader = headers.get("accept");
|
||||
if (acceptHeader) {
|
||||
const values = acceptHeader.split(",").map((value) => value.trim());
|
||||
const filteredValues = values.filter(
|
||||
(value) => value !== "msw/passthrough",
|
||||
);
|
||||
|
||||
if (filteredValues.length > 0) {
|
||||
headers.set("accept", filteredValues.join(", "));
|
||||
} else {
|
||||
headers.delete("accept");
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(requestClone, { headers });
|
||||
}
|
||||
|
||||
// Bypass mocking when the client is not active.
|
||||
if (!client) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Bypass initial page load requests (i.e. static assets).
|
||||
// The absence of the immediate/parent client in the map of the active clients
|
||||
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||
// and is not ready to handle requests.
|
||||
if (!activeClientIds.has(client.id)) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Notify the client that a request has been intercepted.
|
||||
const requestBuffer = await request.arrayBuffer();
|
||||
const clientMessage = await sendToClient(
|
||||
client,
|
||||
{
|
||||
type: "REQUEST",
|
||||
payload: {
|
||||
id: requestId,
|
||||
url: request.url,
|
||||
mode: request.mode,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: requestBuffer,
|
||||
keepalive: request.keepalive,
|
||||
},
|
||||
},
|
||||
[requestBuffer],
|
||||
);
|
||||
|
||||
switch (clientMessage.type) {
|
||||
case "MOCK_RESPONSE": {
|
||||
return respondWithMock(clientMessage.data);
|
||||
}
|
||||
|
||||
case "PASSTHROUGH": {
|
||||
return passthrough();
|
||||
}
|
||||
}
|
||||
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
function sendToClient(client, message, transferrables = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
if (event.data && event.data.error) {
|
||||
return reject(event.data.error);
|
||||
}
|
||||
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
client.postMessage(
|
||||
message,
|
||||
[channel.port2].concat(transferrables.filter(Boolean)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function respondWithMock(response) {
|
||||
// Setting response status code to 0 is a no-op.
|
||||
// However, when responding with a "Response.error()", the produced Response
|
||||
// instance will have status code set to 0. Since it's not possible to create
|
||||
// a Response instance with status code 0, handle that use-case separately.
|
||||
if (response.status === 0) {
|
||||
return Response.error();
|
||||
}
|
||||
|
||||
const mockedResponse = new Response(response.body, response);
|
||||
|
||||
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||
value: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
return mockedResponse;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { css, Global, useTheme } from "@emotion/react";
|
||||
import { baseStyle, rootStyle } from "metabase/css/core/base.styled";
|
||||
import { defaultFontFiles } from "metabase/css/core/fonts.styled";
|
||||
import { saveDomImageStyles } from "metabase/visualizations/lib/image-exports";
|
||||
import { initialize, mswLoader } from "msw-storybook-addon";
|
||||
|
||||
// Note: Changing the names of the stories may impact loki visual testing. Please ensure that
|
||||
// Any story name changes are also reflected in the loki.config.js storiesFilter array.
|
||||
@@ -124,6 +125,15 @@ function CssVariables() {
|
||||
return <Global styles={styles} />;
|
||||
}
|
||||
|
||||
const preview = { parameters, decorators };
|
||||
/*
|
||||
* Initializes MSW
|
||||
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
|
||||
* to learn how to customize it
|
||||
*/
|
||||
|
||||
initialize({
|
||||
onUnhandledRequest: "bypass",
|
||||
});
|
||||
const preview = { parameters, decorators, loaders: [mswLoader] };
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
/* eslint-disable no-console */
|
||||
/* global process */
|
||||
|
||||
// This workflow is experimental POC, non-required, and is owned by team
|
||||
// Embedding. For the time being, unless you think you did a breaking change on
|
||||
// the backend, you can safely ignore this if it fails. If you're working on CI or
|
||||
// related work and can't get this to pass, please reach out to team Embedding.
|
||||
|
||||
// This implements a POC of a way of testing for breaking changes explained in
|
||||
// https://www.notion.so/metabase/Tech-Run-e2e-with-different-versions-of-FE-and-BE-to-detect-breaking-changes-1b669354c9018068b55bc7f2ad4ca83a
|
||||
// It builds a uberjar with the frontend and a backend code from two different
|
||||
// commits, and runs the e2e tests from the frontend commit on the uberjar. The
|
||||
// goal is to try and detect breaking changes between the frontend and backend
|
||||
// code.
|
||||
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@@ -27,6 +39,12 @@ const BE_FOLDER = path.join(TMP_FOLDER, "metabase-be");
|
||||
const JAR_PATH = path.join(BE_FOLDER, "target/uberjar/metabase.jar");
|
||||
|
||||
const getTestFiles = () => {
|
||||
// This is not ideal as we're "hardcoding" the path to the files. The
|
||||
// alternative would have been to add a specific @tag to the tests we wanted
|
||||
// to run, but because we're running the test files from the frontend commit,
|
||||
// which is an "older" commit, that commit wouldn't have these tags. We went
|
||||
// with this in this POC, but we should revisit this if we graduate this to
|
||||
// "official" testing.
|
||||
const questionFiles = getFilesInsideFolder(
|
||||
path.join(FE_FOLDER, "e2e/test/scenarios/question"),
|
||||
).map((file) => path.join("e2e/test/scenarios/question", file)); // make sure the path is how we expect, starting with "e2e/test..."
|
||||
@@ -40,7 +58,7 @@ const getTestFiles = () => {
|
||||
// place to put files if we want to skip them
|
||||
const SKIPPED_FILES = [];
|
||||
|
||||
console.log(`Using frontend from ${FE_GIT_REF}`);
|
||||
console.log(`Using frontend (and test files and helpers) from ${FE_GIT_REF}`);
|
||||
console.log(`Using backend from ${BE_GIT_REF}`);
|
||||
console.log("---");
|
||||
console.log("To test locally run:");
|
||||
@@ -132,10 +150,10 @@ function start() {
|
||||
|
||||
const dbFile = path.join(os.tmpdir(), `metabase-test-${process.pid}.db`);
|
||||
|
||||
const cypressToken = process.env.CYPRESS_ALL_FEATURES_TOKEN;
|
||||
const cypressToken = process.env.CYPRESS_MB_ALL_FEATURES_TOKEN;
|
||||
if (typeof cypressToken === "undefined") {
|
||||
console.error(
|
||||
"Error: CYPRESS_ALL_FEATURES_TOKEN environment variable is not set. This is required for the 'start' command.",
|
||||
"Error: CYPRESS_MB_ALL_FEATURES_TOKEN environment variable is not set. This is required for the 'start' command.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,22 @@ const path = require("path");
|
||||
const glob = require("glob");
|
||||
const { Extractor, ExtractorConfig } = require("@microsoft/api-extractor");
|
||||
|
||||
const SDK_DIST_SRC_PATH = path.resolve(
|
||||
"./enterprise/frontend/src/embedding-sdk",
|
||||
);
|
||||
const SDK_DIST_DIR_PATH = path.resolve("./resources/embedding-sdk/dist");
|
||||
|
||||
const DTS_ROLLUP_INPUT_FILE_PATH = path.resolve(
|
||||
path.join(
|
||||
__dirname,
|
||||
"../../resources/embedding-sdk/dist/enterprise/frontend/src/embedding-sdk/index.d.ts",
|
||||
),
|
||||
);
|
||||
|
||||
const DTS_ROLLUP_OUTPUT_FILE_PATH = path.resolve(
|
||||
path.join(__dirname, "../../resources/embedding-sdk/dist/index.d.ts"),
|
||||
);
|
||||
|
||||
/*
|
||||
* This script replaces all custom aliases in Embedding SDK generated ".d.ts" files so that this imports could be resolved
|
||||
* in the host app
|
||||
@@ -128,15 +142,9 @@ const generateDtsRollup = () => {
|
||||
|
||||
log("Generate dts rollup...");
|
||||
|
||||
const dtsRollupEntryPointPath = path.resolve(
|
||||
path.join(
|
||||
__dirname,
|
||||
"../../resources/embedding-sdk/dist/enterprise/frontend/src/embedding-sdk/index.d.ts",
|
||||
),
|
||||
);
|
||||
const dtsRollupEntryPointPathExist = fs.existsSync(dtsRollupEntryPointPath);
|
||||
const dtsRollupInputFileExist = fs.existsSync(DTS_ROLLUP_INPUT_FILE_PATH);
|
||||
|
||||
if (!dtsRollupEntryPointPathExist) {
|
||||
if (!dtsRollupInputFileExist) {
|
||||
log("It looks like dts rollup is already generated, skipping...");
|
||||
|
||||
return;
|
||||
@@ -159,6 +167,8 @@ const generateDtsRollup = () => {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
appendGlobalDtsDeclarations();
|
||||
|
||||
log("API Extractor completed successfully");
|
||||
|
||||
log("Removing intermediate dts files...");
|
||||
@@ -177,6 +187,16 @@ const generateDtsRollup = () => {
|
||||
log("Dts rollup done!");
|
||||
};
|
||||
|
||||
// API extractor can't handle global augmented declarations, so we add them manually
|
||||
// https://github.com/microsoft/rushstack/issues/1709
|
||||
const appendGlobalDtsDeclarations = () => {
|
||||
const content = fs.readFileSync(`${SDK_DIST_SRC_PATH}/global.d.ts`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
fs.appendFileSync(DTS_ROLLUP_OUTPUT_FILE_PATH, content);
|
||||
};
|
||||
|
||||
const watchFilesAndFixThem = () => {
|
||||
log("Watching for changes in the SDK d.ts files...");
|
||||
// we need to keep track of the files that just edited
|
||||
|
||||
14
cypress.env.json.example
Normal file
@@ -0,0 +1,14 @@
|
||||
# Use this file to create a `cypress.env.json` in the root of this project.
|
||||
# Documentation: https://docs.cypress.io/app/references/environment-variables#Option-2-cypressenvjson
|
||||
|
||||
# Remove all lines starting with "#" (including this one)
|
||||
{
|
||||
"MB_SNOWPLOW_URL": "http://localhost:9090",
|
||||
"MB_SNOWPLOW_AVAILABLE": "true",
|
||||
"MB_ALL_FEATURES_TOKEN": "get it from the password manager",
|
||||
"MB_STARTER_CLOUD_TOKEN": "get it from the password manager",
|
||||
"MB_PRO_CLOUD_TOKEN": "get it from the password manager",
|
||||
"MB_PRO_SELF_HOSTED_TOKEN": "get it from the password manager",
|
||||
"MB_EDITION": "ee",
|
||||
"QA_DB_ENABLED": "true"
|
||||
}
|
||||
20
deps.edn
@@ -135,14 +135,14 @@
|
||||
org.slf4j/jcl-over-slf4j]}
|
||||
org.apache.tika/tika-core {:mvn/version "3.1.0"}
|
||||
org.apache.xmlgraphics/batik-all {:mvn/version "1.19"} ; SVG -> image
|
||||
org.bouncycastle/bcpkix-jdk18on {:mvn/version "1.80"} ; Bouncy Castle crypto library -- explicit version of BC specified to resolve illegal reflective access errors
|
||||
org.bouncycastle/bcprov-jdk18on {:mvn/version "1.80"}
|
||||
org.bouncycastle/bcpkix-jdk18on {:mvn/version "1.81"} ; Bouncy Castle crypto library -- explicit version of BC specified to resolve illegal reflective access errors
|
||||
org.bouncycastle/bcprov-jdk18on {:mvn/version "1.81"}
|
||||
org.clj-commons/claypoole {:mvn/version "1.2.2"} ; Threadpool tools for Clojure
|
||||
org.clj-commons/dirigiste {:mvn/version "1.0.4"} ; Instrumented object pooling
|
||||
org.clj-commons/hickory {:mvn/version "0.7.7" ; Parse HTML into Clojure data structures
|
||||
:exclusions [org.jsoup/jsoup]}
|
||||
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} ; LDAP client
|
||||
org.clojure/clojure {:mvn/version "1.12.0"}
|
||||
org.clojure/clojure {:mvn/version "1.12.1"}
|
||||
org.clojure/core.async {:mvn/version "1.7.701"
|
||||
:exclusions [org.clojure/tools.reader]}
|
||||
org.clojure/core.logic {:mvn/version "1.1.0"} ; optimized pattern matching library for Clojure
|
||||
@@ -161,9 +161,9 @@
|
||||
org.clojure/tools.namespace {:mvn/version "1.5.0"}
|
||||
org.clojure/tools.reader {:mvn/version "1.5.2"}
|
||||
org.clojure/tools.trace {:mvn/version "0.8.0"} ; function tracing
|
||||
org.eclipse.jetty/jetty-server {:mvn/version "12.0.21"}
|
||||
org.eclipse.jetty/jetty-unixdomain-server {:mvn/version "12.0.21"}
|
||||
org.eclipse.jetty.ee9/jetty-ee9-servlet {:mvn/version "12.0.21"}
|
||||
org.eclipse.jetty/jetty-server {:mvn/version "12.0.22"}
|
||||
org.eclipse.jetty/jetty-unixdomain-server {:mvn/version "12.0.22"}
|
||||
org.eclipse.jetty.ee9/jetty-ee9-servlet {:mvn/version "12.0.22"}
|
||||
org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server {:mvn/version "12.0.21"}
|
||||
org.flatland/ordered {:mvn/version "1.15.12"} ; ordered maps & sets
|
||||
org.graalvm.polyglot/js-community {:mvn/version "24.2.1" :extension "pom"} ; JavaScript engine
|
||||
@@ -188,8 +188,8 @@
|
||||
redux/redux {:git/url "https://github.com/metabase/redux"
|
||||
:sha "4a37feaf817a2a6b5ef688c927f6fd4375964433"}
|
||||
riddley/riddley {:mvn/version "0.2.0"} ; code walking lib -- used interally by Potemkin, manifold, etc.
|
||||
ring/ring-core {:mvn/version "1.14.1"} ; HTTP abstraction
|
||||
ring/ring-jetty-adapter {:mvn/version "1.14.1" ; Ring adapter for Jetty
|
||||
ring/ring-core {:mvn/version "1.14.2"} ; HTTP abstraction
|
||||
ring/ring-jetty-adapter {:mvn/version "1.14.2" ; Ring adapter for Jetty
|
||||
:exclusions [org.eclipse.jetty/jetty-server
|
||||
org.eclipse.jetty/jetty-unixdomain-server
|
||||
org.eclipse.jetty.ee9/jetty-ee9-servlet
|
||||
@@ -266,7 +266,7 @@
|
||||
:exclusions [org.clojure/clojure
|
||||
org.clojure/clojurescript]} ; lets you write test fixtures that are clear, concise, and easy to maintain (clojure.spec)
|
||||
rewrite-clj/rewrite-clj {:mvn/version "1.2.50"} ; clojure source parsing and rewriting
|
||||
ring/ring-devel {:mvn/version "1.14.1"} ; reload clojure files on the fly
|
||||
ring/ring-devel {:mvn/version "1.14.2"} ; reload clojure files on the fly
|
||||
ring/ring-mock {:mvn/version "0.6.1"} ; creating Ring request maps for testing purposes
|
||||
talltale/talltale {:mvn/version "0.5.14"}} ; generates fake data, useful for prototyping or load testing
|
||||
:extra-paths ["dev/src" "local/src" "test" "test_resources" ".clj-kondo/src" ".clj-kondo/test"]
|
||||
@@ -785,7 +785,7 @@
|
||||
;; get the H2 shell with clojure -X:dev:h2
|
||||
:h2
|
||||
{:extra-paths ["dev/src"]
|
||||
:exec-fn dev.h2-shell/shell
|
||||
:exec-fn dev.h2/shell
|
||||
:jvm-opts ["-Dfile.encoding=UTF-8"]}
|
||||
|
||||
:generate-automagic-dashboards-pot
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
[clojure.test]
|
||||
[dev.debug-qp :as debug-qp]
|
||||
[dev.explain :as dev.explain]
|
||||
[dev.h2 :as dev.h2]
|
||||
[dev.memory :as dev.memory]
|
||||
[dev.migrate :as dev.migrate]
|
||||
[dev.model-tracking :as model-tracking]
|
||||
@@ -95,7 +96,8 @@
|
||||
|
||||
(comment
|
||||
debug-qp/keep-me
|
||||
model-tracking/keep-me)
|
||||
model-tracking/keep-me
|
||||
dev.h2/keep-me)
|
||||
|
||||
#_:clj-kondo/ignore
|
||||
(defn tap>-spy [x]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
(ns dev.h2-shell
|
||||
(ns dev.h2
|
||||
(:require
|
||||
[environ.core :as env]
|
||||
[metabase.app-db.data-source :as mdb.data-source]
|
||||
[metabase.app-db.env :as mdb.env]))
|
||||
[metabase.app-db.env :as mdb.env])
|
||||
(:import (org.h2.tools Server)))
|
||||
|
||||
(comment mdb.data-source/keep-me)
|
||||
|
||||
@@ -19,3 +20,13 @@
|
||||
url (.url data-source)]
|
||||
(println "Connecting to database at URL" url)
|
||||
url)])))
|
||||
|
||||
(def ^:private tcp-listener (atom nil))
|
||||
|
||||
(defn tcp-listen
|
||||
"Starts a TCP server for the H2 app-db database on port 9092."
|
||||
[]
|
||||
(when @tcp-listener
|
||||
(throw (ex-info "TCP listener is already running" {:port 9092})))
|
||||
(reset! tcp-listener (.start (Server/createTcpServer (into-array ["-tcp" "-tcpAllowOthers" "-tcpPort" "9092"]))))
|
||||
(println (str "H2 TCP server started (no username or password): jdbc:h2:tcp://localhost:9092/" (#'metabase.app-db.env/env->db-file metabase.app-db.env/env))))
|
||||
@@ -5,15 +5,16 @@
|
||||
[metabase.app-db.core :as mdb]
|
||||
[metabase.app-db.liquibase :as liquibase]
|
||||
[metabase.util.malli :as mu]
|
||||
[toucan2.core :as t2])
|
||||
[toucan2.core :as t2]
|
||||
[toucan2.honeysql2 :as t2.honeysql])
|
||||
(:import
|
||||
(liquibase Contexts Liquibase RuntimeEnvironment)
|
||||
(liquibase Contexts LabelExpression Liquibase RuntimeEnvironment)
|
||||
(liquibase.change Change)
|
||||
(liquibase.changelog ChangeLogIterator ChangeSet DatabaseChangeLog)
|
||||
(liquibase.changelog.filter ChangeSetFilter)
|
||||
(liquibase.changelog.visitor ListVisitor)
|
||||
(liquibase.database Database)
|
||||
(liquibase.database.core H2Database MySQLDatabase PostgresDatabase MariaDBDatabase)
|
||||
(liquibase.database.core H2Database MariaDBDatabase MySQLDatabase PostgresDatabase)
|
||||
(liquibase.exception RollbackImpossibleException)
|
||||
(liquibase.sql Sql)
|
||||
(liquibase.sqlgenerator SqlGeneratorFactory)
|
||||
@@ -21,6 +22,11 @@
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(def ^:private databasechangelog-name
|
||||
(if (#{:mysql} (mdb/db-type))
|
||||
:DATABASECHANGELOG
|
||||
:databasechangelog))
|
||||
|
||||
(defn- latest-migration
|
||||
[]
|
||||
((juxt :id :comments)
|
||||
@@ -28,6 +34,7 @@
|
||||
:from [(keyword (liquibase/changelog-table-name (mdb/data-source)))]
|
||||
:order-by [[:orderexecuted :desc]]
|
||||
:limit 1})))
|
||||
|
||||
(defn migrate!
|
||||
"Run migrations for the Metabase application database. Possible directions are `:up` (default), `:force`, `:down`, and
|
||||
`:release-locks`. When migrating `:down` pass along a version to migrate to (44+)."
|
||||
@@ -49,9 +56,9 @@
|
||||
(defn- migration-since
|
||||
[id]
|
||||
(->> (t2/query-one {:select [[:%count.* :count]]
|
||||
:from [:databasechangelog]
|
||||
:from [databasechangelog-name]
|
||||
:where [:> :orderexecuted {:select [:orderexecuted]
|
||||
:from [:databasechangelog]
|
||||
:from [databasechangelog-name]
|
||||
:where [:like :id (format "%s%%" id)]
|
||||
:order-by [[:orderexecuted :desc]]
|
||||
:limit 1}]
|
||||
@@ -66,22 +73,59 @@
|
||||
(string? x)
|
||||
parse-long))
|
||||
|
||||
(defn- last-deployment
|
||||
[]
|
||||
(binding [t2.honeysql/*options* (assoc t2.honeysql/*options*
|
||||
:quoted false)]
|
||||
(if (= 1 (:count (t2/query-one {:select [[[:count [:distinct :deployment_id]] :count]]
|
||||
:from [databasechangelog-name]
|
||||
:limit 1})))
|
||||
0 ;; don't rollback if there was just one deployment of everything
|
||||
(:count (t2/query-one {:select [[:%count.* :count]]
|
||||
:from [databasechangelog-name]
|
||||
:where [:= :deployment_id {:select [:deployment_id]
|
||||
:from [databasechangelog-name]
|
||||
:order-by [[:orderexecuted :desc]]
|
||||
:limit 1}]})))))
|
||||
|
||||
(defn reset-checksums!
|
||||
[]
|
||||
(with-open [conn (.getConnection ^javax.sql.DataSource (mdb/data-source))]
|
||||
(let [changelog-table (keyword (liquibase/changelog-table-name (mdb/data-source)))]
|
||||
(t2/query {:update changelog-table
|
||||
:set {:md5sum nil}}))
|
||||
(.setAutoCommit conn false)
|
||||
(liquibase/with-liquibase [liquibase conn]
|
||||
(liquibase/with-scope-locked liquibase
|
||||
(.changeLogSync liquibase (Contexts.) (LabelExpression.)))))
|
||||
(println "Reset checksums"))
|
||||
|
||||
(mu/defn rollback!
|
||||
"Rollback helper, can take a number of migrations to rollback or a specific migration ID(inclusive).
|
||||
"Rollback helper, can take a number of migrations to rollback or a specific migration ID(inclusive) or last-deployment.
|
||||
|
||||
;; Rollback 2 migrations:
|
||||
(rollback! :count 2)
|
||||
|
||||
;; Rollback last migration run:
|
||||
;; (rollback! :last-deployment)
|
||||
|
||||
;; rollback to \"v50.2024-03-18T16:00:00\" (inclusive)
|
||||
(rollback! :id \"v50.2024-03-18T16:00:00\")"
|
||||
[k :- [:enum :id :count "id" "count"]
|
||||
target]
|
||||
(let [n (case (keyword k)
|
||||
:id (migration-since target)
|
||||
:count (maybe-parse-long target))]
|
||||
(rollback-n-migrations! n)
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(println (format "Rollbacked %d migrations. Latest migration: %s" n (latest-migration)))))
|
||||
([k :- [:enum :last-deployment "last-deployment"]]
|
||||
(let [n (case (keyword k)
|
||||
:last-deployment (last-deployment))]
|
||||
(rollback-n-migrations! n)
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(println (format "Rollbacked %d migrations. Latest migration: %s" n (latest-migration)))))
|
||||
|
||||
([k :- [:enum :id :count "id" "count"]
|
||||
target]
|
||||
(let [n (case (keyword k)
|
||||
:id (migration-since target)
|
||||
:count (maybe-parse-long target))]
|
||||
(rollback-n-migrations! n)
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(println (format "Rollbacked %d migrations. Latest migration: %s" n (latest-migration))))))
|
||||
|
||||
(defn migration-status
|
||||
"Print the latest migration ID."
|
||||
@@ -96,7 +140,9 @@
|
||||
clojure -M:migrate up ;; migrate up to the latest
|
||||
clojure -M:migrate rollback count 2 ;; rollback 2 migrations
|
||||
clojure -M:migrate rollback id \"v40.00.001\" ;; rollback to a specific migration with id
|
||||
clojure -M:migrate status ;; print the latest migration id"
|
||||
clojure -M:migrate rollback last-deployment ;; rollback the last deployment
|
||||
clojure -M:migrate status ;; print the latest migration id
|
||||
clojure -M:migrate reset-checksums. ;; sets the checkums to what they would be if migrated from the current changelog"
|
||||
|
||||
[& args]
|
||||
(let [[cmd & migration-args] args]
|
||||
@@ -110,6 +156,9 @@
|
||||
"status"
|
||||
(migration-status)
|
||||
|
||||
"reset-checksums"
|
||||
(reset-checksums!)
|
||||
|
||||
(throw (ex-info "Invalid command" {:command cmd
|
||||
:args args})))))
|
||||
|
||||
@@ -148,10 +197,10 @@
|
||||
(let [database (liquibase-database db-type)
|
||||
change-log-iterator (ChangeLogIterator. ^DatabaseChangeLog (.getDatabaseChangeLog liquibase)
|
||||
^"[Lliquibase.changelog.filter.ChangeSetFilter;" (into-array ChangeSetFilter []))
|
||||
list-visistor (ListVisitor.)
|
||||
list-visitor (ListVisitor.)
|
||||
runtime-env (RuntimeEnvironment. database (Contexts.) nil)
|
||||
_ (.run change-log-iterator list-visistor runtime-env)
|
||||
^ChangeSet change-set (first (filter #(= id (.getId ^ChangeSet %)) (.getSeenChangeSets list-visistor)))
|
||||
_ (.run change-log-iterator list-visitor runtime-env)
|
||||
^ChangeSet change-set (first (filter #(= id (.getId ^ChangeSet %)) (.getSeenChangeSets list-visitor)))
|
||||
sql-generator-factory (SqlGeneratorFactory/getInstance)]
|
||||
(reduce (fn [acc data]
|
||||
;; merge all changes in one change set into one single :forward and :rollback
|
||||
@@ -160,6 +209,52 @@
|
||||
{}
|
||||
(map #(change->sql % sql-generator-factory database) (.getChanges change-set))))))))
|
||||
|
||||
(defn known-changesets
|
||||
"Gets a list of all changesets applicable to current db"
|
||||
[]
|
||||
(t2/with-connection [conn]
|
||||
(liquibase/with-liquibase [^Liquibase liquibase conn]
|
||||
(let [database (liquibase-database (mdb/db-type))
|
||||
change-log-iterator (ChangeLogIterator. ^DatabaseChangeLog (.getDatabaseChangeLog liquibase)
|
||||
^"[Lliquibase.changelog.filter.ChangeSetFilter;" (into-array ChangeSetFilter []))
|
||||
list-visitor (ListVisitor.)
|
||||
runtime-env (RuntimeEnvironment. database (Contexts.) nil)]
|
||||
(.run change-log-iterator list-visitor runtime-env)
|
||||
(->> (.getSeenChangeSets list-visitor)
|
||||
(map (fn [^ChangeSet c]
|
||||
{:id (.getId c)
|
||||
:author (.getAuthor c)
|
||||
:filename (.getFilePath c)
|
||||
:description (.getDescription c)
|
||||
:changes (vec (.getChanges c))
|
||||
:comments (.getComments c)})))))))
|
||||
|
||||
(defn orphaned-changesets
|
||||
"List migrations that are applied to the database but not present in the current changelog.
|
||||
|
||||
They either have different id or are not present at all."
|
||||
[]
|
||||
(let [applied (->> (t2/query {:from [(keyword (liquibase/changelog-table-name (mdb/data-source)))]
|
||||
:select [:id :comments :author]
|
||||
;; ignore ancient history
|
||||
:where [:< [:age :dateexecuted] [:raw "INTERVAL '2 year'"]]})
|
||||
(map t2/current))
|
||||
known (mapv #(select-keys % [:id :comments :author]) (known-changesets))
|
||||
by-id (group-by :id known)
|
||||
by-comments (group-by :comments known)
|
||||
;; we cannot use `set/diff` here because long `comments` can be truncated in the db
|
||||
orphans (remove #(contains? by-id (:id %)) applied)
|
||||
;; obviously prev comment is valid here as well but we work with what we have :)
|
||||
mismatches (for [c orphans
|
||||
:let [known-as (get by-comments (:comments c))]
|
||||
:when known-as]
|
||||
(assoc c :knownas (:id known-as)))
|
||||
mismatch-ids (set (map :id mismatches))
|
||||
unknown (vec (remove #(contains? mismatch-ids (:id %)) orphans))]
|
||||
{:mismatches (sort-by :id mismatches) ; identical comments, but different ids
|
||||
:unknown (sort-by :id unknown) ; those are not present in current branch
|
||||
:total (+ (count mismatches) (count unknown))}))
|
||||
|
||||
(comment
|
||||
(rollback! :count 1)
|
||||
(rollback! :id "v51.2024-08-30T08:00:03")
|
||||
|
||||
@@ -18,7 +18,7 @@ Metabase is an open-source business intelligence platform. You can use Metabase
|
||||
|
||||
### Metabase Cloud
|
||||
|
||||
The easiest way to get started with Metabase is to sign up for a free trial of [Metabase Cloud](https://store.metabase.com/checkout). You get support, backups, upgrades, an SMTP server, SSL certificate, SoC2 Type 2 security auditing, and more (plus your money goes toward improving Metabase). Check out our quick overview of [cloud vs self-hosting](https://www.metabase.com/docs/latest/cloud/cloud-vs-self-hosting). If you need to, you can always switch to [self-hosting](./installation-and-operation/installing-metabase.md) Metabase at any time (or vice versa).
|
||||
The easiest way to get started with Metabase is to sign up for a free trial of [Metabase Cloud](https://store.metabase.com/checkout). You get support, backups, upgrades, an SMTP server, SSL certificate, SoC2 Type 2 security auditing, and more (plus your money goes toward improving Metabase). Check out our quick overview of [cloud vs self-hosting](./cloud/cloud-vs-self-hosting.md). If you need to, you can always switch to [self-hosting](./installation-and-operation/installing-metabase.md) Metabase at any time (or vice versa).
|
||||
|
||||
### [Installing Metabase](./installation-and-operation/installing-metabase.md)
|
||||
|
||||
@@ -187,6 +187,7 @@ Metabase's reference documentation.
|
||||
- [Database routing](./permissions/database-routing.md)
|
||||
- [Snippets folder permissions](./permissions/snippets.md)
|
||||
- [Notification permissions](./permissions/notifications.md)
|
||||
- [Configuring permissions for embedding](./permissions/embedding.md)
|
||||
|
||||
### Embedding
|
||||
|
||||
@@ -221,9 +222,9 @@ Metabase's reference documentation.
|
||||
- [Usage analytics](./usage-and-performance-tools/usage-analytics.md)
|
||||
- [Admin tools](./usage-and-performance-tools/tools.md)
|
||||
|
||||
### Cloud
|
||||
### Metabase Cloud
|
||||
|
||||
- [Documentation for Metabase Cloud and Store](https://www.metabase.com/docs/latest/cloud/start)
|
||||
- [Documentation for Metabase Cloud and Store](./cloud/start.md)
|
||||
|
||||
### Metabase API
|
||||
|
||||
@@ -256,10 +257,6 @@ Metabase's reference documentation.
|
||||
|
||||
Share and connect with other Metabasers.
|
||||
|
||||
### [Metabase Cloud](https://www.metabase.com/docs/latest/cloud/start)
|
||||
|
||||
For docs specific to Metabase Cloud plans.
|
||||
|
||||
### [Community stories](https://www.metabase.com/community)
|
||||
|
||||
Practical advice from our community.
|
||||
@@ -295,3 +292,4 @@ Data jargon explained.
|
||||
### [Metabase Experts](https://www.metabase.com/partners/)
|
||||
|
||||
If you’d like more technical resources to set up your data stack with Metabase, connect with a [Metabase Expert](https://www.metabase.com/partners/).
|
||||
<!-- bump 2 -->
|
||||
|
||||
@@ -10,8 +10,6 @@ title: Introduction to actions
|
||||
|
||||
## What are actions?
|
||||
|
||||
> Want to edit data directly in your database through Metabase and build data apps? Join our [Early Access Program](https://form.fillout.com/t/j9cymCcLGVus) to explore this new feature, try it with your team, and help shape what we build next.
|
||||
|
||||
**Actions** are entities in Metabase that let you build custom forms and business logic.
|
||||
|
||||
Actions let you write parameterized SQL that writes back to your database. Actions can be attached to [buttons on dashboards](../dashboards/actions.md) to create custom workflows. You can even publicly share the parameterized forms that actions generate to collect data.
|
||||
|
||||
149
docs/cloud/accounts-and-billing.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "Accounts and billing"
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
---
|
||||
|
||||
# Accounts and billing
|
||||
|
||||
Managing billing and accounts in the Metabase Store.
|
||||
|
||||
## Try out a free 14-day trial of Pro
|
||||
|
||||
If you're on a [Starter](/product/starter) cloud plan, you can try out [all the features of Metabase Pro](/product/pro) for free for 14 days with no commitment.
|
||||
|
||||
If you want to stick with Pro, you'll have the chance to upgrade. Otherwise, your plan will automatically switch back to Starter.
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. In the **Instances** section, select your instance.
|
||||
3. Click on **Try a free 14-day trial of Pro**.
|
||||
|
||||
Once selected, the Store will display the status of your trial, along with a trial end date. We'll also send you an email with details about the trial.
|
||||
|
||||
If you like the Pro features and want to upgrade, you'll need to upgrade your instance, either by clicking on the **Upgrade to Pro** button, or by managing your plan (see the next section).
|
||||
|
||||
## Upgrade to Pro (or downgrade to Starter)
|
||||
|
||||
If you'd like to switch plans:
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. In the **Instances** section, select the instance you want to change.
|
||||
3. Click on **Manage plan**.
|
||||
4. Select the plan you want.
|
||||
5. Click **Change plan**.
|
||||
|
||||
## Add people to manage your Metabase Store account
|
||||
|
||||
You can add people to your account to allow them to manage your Metabase Store account (not your Metabase instance).
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to **Settings**.
|
||||
3. Scroll to **Managers**.
|
||||
4. Enter email address of the person(s) you'd like to invite.
|
||||
5. The Store will send them an email prompting them to create a Metabase Store account.
|
||||
|
||||
## Add people to receive billing and/or technical notifications
|
||||
|
||||
You can include contacts to receive billing notifications, as well as technical notifications, like info about new Metabase releases.
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to **Settings**.
|
||||
3. Scroll to **Contacts**.
|
||||
4. Enter email address of the person(s) you'd like to invite.
|
||||
5. The Store will send them an email notifying them that they've been added to Metabase's contact list.
|
||||
|
||||
## Cancel a subscription or trial
|
||||
|
||||
To cancel a Starter or Pro plan directly in the Metabase Store:
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to **Instances**.
|
||||
3. Under the instance with the subscription that you want to cancel, click **Cancel subscription and delete instance**. If you're on an Enterprise plan, you'll need to [contact support](/help-premium).
|
||||
|
||||
## Update payment information
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Navigate to **Billing**.
|
||||
3. Click on the payment method that you want to update.
|
||||
4. Enter new credit card information.
|
||||
5. Click on **Update**.
|
||||
|
||||
If your Metabase was suspended due to a failed payment, the Store will automatically restore your Metabase upon a successful payment.
|
||||
|
||||
## Switch to annual billing
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to **Instances**.
|
||||
3. Navigate to the instance you want to switch to annual billing.
|
||||
4. Click on **Manage plan** next to the plan name.
|
||||
5. Confirm the plan selection and toggle on **Switch to annual billing**.
|
||||
6. Click on **Switch plan**.
|
||||
|
||||
Note that you cannot switch back to monthly billing until your annual cycle has completed.
|
||||
|
||||
## Download past invoices
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to **Billing**.
|
||||
3. Scroll down to **Invoices** to view past or open invoices.
|
||||
|
||||
## W-9 form requests
|
||||
|
||||
See Metabase's updated [W9 form](/files/2024_W9_signed.pdf).
|
||||
|
||||
## Update your Tax ID
|
||||
|
||||
1. Log in to your Metabase [Store account](https://store.metabase.com).
|
||||
2. Go to on **Billing**.
|
||||
3. Click on **Invoice Tax IDs**.
|
||||
4. Enter your country and Tax ID.
|
||||
5. Click **Save**.
|
||||
|
||||
## Change the company name on invoices/receipts
|
||||
|
||||
The primary account holder should request a name change by emailing [billing@metabase.com](mailto:billing@metabase.com).
|
||||
|
||||
## Reset your Admin password
|
||||
|
||||
If you need to reset your Admin password, [contact support](/help-premium).
|
||||
|
||||
## Change your company name
|
||||
|
||||
1. Navigate to your Metabase Store.
|
||||
2. Go to **Settings**.
|
||||
3. Click **Change company name**.
|
||||
|
||||
## Change your billing address
|
||||
|
||||
1. Navigate to your Metabase Store.
|
||||
2. Go to **Billing**.
|
||||
3. Click on **Billing address**.
|
||||
4. Update your billing address.
|
||||
5. Click **Save**.
|
||||
|
||||
### Add or change your shipping address
|
||||
|
||||
1. Navigate to your Metabase Store.
|
||||
2. Go to **Billing**.
|
||||
3. Click on **Shipping address**.
|
||||
4. Add or update your shipping address.
|
||||
5. Click **Save**.
|
||||
|
||||
## Change the primary account holder
|
||||
|
||||
If you need to change your primary account holder, for example if your primary account holder left your company:
|
||||
|
||||
- If another person at your company holds a Metabase Store account, work with them to add you to your company's Metabase Store account.
|
||||
- If there is no other account holder, email [billing@metabase.com](mailto:billing@metabase.com) to request access to your company's Metabase Store account. Please note that if the primary account holder is unavailable, there is a 48-hour waiting period to process changes to Metabase Store accounts.
|
||||
|
||||
## Move from Metabase Cloud to self-hosting Metabase
|
||||
|
||||
If you're on Metabase Cloud but need to switch to hosting Metabase yourself, [contact us](/help-premium) and we'll give you a backup of your application database that you can use to restore Metabase on your own infrastructure.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [How billing works](./how-billing-works.md)
|
||||
47
docs/cloud/change-region.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "Changing which region your Metabase is hosted in"
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
---
|
||||
|
||||
# Changing which region your Metabase is hosted in
|
||||
|
||||
You may want to change which region your Metabase is hosted in to bring it closer to the people using it
|
||||
|
||||
- [Plan for some down time](#plan-for-some-down-time)
|
||||
- [Select a region](#select-a-region)
|
||||
- [Add the relevant IP addresses to your whitelist](#add-the-relevant-ip-addresses-to-your-whitelist)
|
||||
- [If your Metabase uses a custom domain, update your CNAMES](#if-your-metabase-uses-a-custom-domain-update-your-cnames)
|
||||
|
||||
## Plan for some down time
|
||||
|
||||
Most region switches should take less than 30 minutes. Your Metabase will be offline and unavailable while it changes regions, so plan for some down time.
|
||||
|
||||
If you're using a [custom domain](./custom-domain.md), that region change could take longer, maybe up to 24 hours. That delay is due to DNS propagation (which is out of our hands).
|
||||
|
||||
## Select a region
|
||||
|
||||
To change regions, go to your [Metabase Store page](https://store.metabase.com/) and click on **Instances**. In the **Hosting regions** section, select the region you want to choose from:
|
||||
|
||||
- US East (North Virginia)
|
||||
- Europe (Frankfurt)
|
||||
- Asia Pacific (Singapore)
|
||||
- South America (São Paulo)
|
||||
|
||||
## Add the relevant IP addresses to your whitelist
|
||||
|
||||
If you're using a security group or firewall rules, you should add the relevant [IP addresses](./ip-addresses-to-whitelist.md) to your whitelist.
|
||||
|
||||
## If your Metabase uses a custom domain, update your CNAMES
|
||||
|
||||
If your Metabase uses a [custom domain](./custom-domain.md), you'll need to update your CNAMES. DNS switches are generally fast these days, but it'll take some time to sign a new certificate for that domain once it's updated (up to 24 hours).
|
||||
|
||||
We recommend switching regions during a time when people are less likely to use your Metabase.
|
||||
|
||||
## Need help?
|
||||
|
||||
No need to worry about backups (we have you covered), but if you run into any issues, contact support at [help@metabase.com](mailto:help@metabase.com).
|
||||
52
docs/cloud/cloud-vs-self-hosting.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "Metabase Cloud versus self-hosting"
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
---
|
||||
|
||||
# Metabase Cloud versus self-hosting
|
||||
|
||||
We have a post that goes into more depth on [why Metabase Cloud might be right for you](/blog/why-metabase-cloud), but here's a (nonexhaustive) table listing some of the differences between Metabase Cloud and self-hosting Metabase.
|
||||
|
||||
| | Self-hosted OSS | Metabase Cloud |
|
||||
| ------------------------------ | ----------------------------------- | ------------------ |
|
||||
| High availability servers | ~\$48+/month | ✅ |
|
||||
| Load balancer | ~\$12+/month | ✅ |
|
||||
| Managed database | ~\$40-60+/month | ✅ |
|
||||
| SMTP server | ~\$12+/month | ✅ |
|
||||
| Support | Forum only (paid plans get support) | Email with SLA |
|
||||
| Up and running in minutes | Manual | ✅ |
|
||||
| Upgrades | Manual | ✅ |
|
||||
| Multi-zone availability | Manual | ✅ |
|
||||
| Backups | Manual | ✅ |
|
||||
| Monitoring | Manual | ✅ |
|
||||
| Keep your own fork of the code | ✅ | ❌ |
|
||||
| SoC2 Type 2 security auditing | Manual | ✅ |
|
||||
| SSL certificate | Manual | ✅ |
|
||||
| Where your money goes | 3rd-party companies | Improving Metabase |
|
||||
|
||||
Infrastructure costs are tricky to estimate, so keep in mind that the above self-hosting prices are ballpark estimates for a comparable setup with other cloud providers. If you _really_ know what you're doing, you can price out a cheaper setup, but then the services aren't managed and you don't get official support (which alone might make Cloud the better choice). We're just trying to provide an anchor to compare against for a similar production-grade setup, and anyway, you can always start out with Cloud and move to self-hosting whenever.
|
||||
|
||||
## When _should_ you self-host?
|
||||
|
||||
There are some cases where you _should_ self-host.
|
||||
|
||||
- You have some regulations that stipulate how you run your software (like HIPAA).
|
||||
- You want to run a custom build of Metabase. That is, you’re running a fork of Metabase (OSS or the Enterprise/Pro version) so you can add your own customizations to the software, and you have the engineering resources to tank that level of overhead.
|
||||
- You want to use a community or custom driver. We only support [official databases](../databases/connecting.md#connecting-to-supported-databases) on Metabase Cloud, because we need to be able to vouch for their quality and help you with problems in a production context.
|
||||
- You need an air-gapped environment, usually for regulatory compliance, or if you’re running your own three-letter-agency… If you’re not sure if you need an air-gapped environment, you don’t need an air-gapped environment. If you do need an air-gap, we have an [air-gap offering as well](/product/air-gapping) (just not for Cloud, obviously).
|
||||
|
||||
Otherwise, you're better off having us handle all of the overhead so you can focus on your business. The official support alone is probably worth it.
|
||||
|
||||
## Metabase Pro and Enterprise
|
||||
|
||||
Metabase also has a paid version that ships with all of the bells and whistles (SSO, row-level permissions, customization, and a lot more). Plans with this version include:
|
||||
|
||||
- [Metabase Pro](/product/pro)
|
||||
- [Metabase Enterprise](/product/enterprise)
|
||||
|
||||
You can self-host this paid version, or have have us host it for you on Metabase Cloud for no additional cost. We don't charge more because we believe you'll have such a better experience that it'll be easier for us to support you.
|
||||
61
docs/cloud/custom-domain.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: "Changing your domain name"
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
redirect_from:
|
||||
- /cloud/docs/custom-domain
|
||||
---
|
||||
|
||||
# Changing your domain name
|
||||
|
||||
By default, Metabase will automatically provision a domain name for your Metabase that ends in metabaseapp.com (e.g., yourcompany.metabaseapp.com).
|
||||
|
||||
[Pro and Enterprise](https://www.metabase.com/pricing/) plans allow you to add a custom domain. Setting a custom domain will update your instance's [Site URL](../configuring-metabase/settings.md#site-url). The Site URL is used for things like creating links in emails, auth redirects, and in some embedding scenarios.
|
||||
|
||||
## How to add a custom domain to your Metabase
|
||||
|
||||
1. Log in to your [Metabase Store account](https://store.metabase.com/).
|
||||
|
||||
2. Find the Metabase instance that you want to add the custom domain to, and select **Manage** on the bottom right of the instance's card. Then click on the **Domains** tab.
|
||||
|
||||
You'll see something like this:
|
||||
|
||||

|
||||
|
||||
3. In the custom domain field (the input prefixed with `https://` in the Metabase Store), enter your custom domain (e.g., your.custom.domain).
|
||||
4. Talk to whoever manages your Domain Name System (DNS) records, and have them add a CNAME to wherever you manage your DNS records. Point that CNAME to this URL:
|
||||
|
||||
```url
|
||||
us-1.cd.metabaseapp.com
|
||||
```
|
||||
|
||||
So your CNAME record would look something like:
|
||||
|
||||
```domain
|
||||
CNAME your.custom.domain us-1.cd.metabaseapp.com
|
||||
```
|
||||
|
||||
Just to be clear: you don't enter this record in the Metabase Store; you enter it in wherever you manage your DNS records.
|
||||
|
||||
5. If you've already set up SSO (like SAML or LDAP) for your Metabase, you'll need to update the return URL in your identity provider configuration to include this new custom domain.
|
||||
|
||||
That's it. You may need to wait a bit for the CNAME to update (this delay is specific to how DNS works, and not something Metabase has control over). Metabase will take care of the rest: we'll make sure that:
|
||||
|
||||
- the URL directs people to your Metabase,
|
||||
- your domain has SSL certificates set up to serve your instance over HTTPS to the new domain,
|
||||
- and that your [metabase.com/cloud/login](https://www.metabase.com/cloud/login) works as expected.
|
||||
|
||||
You'll still be able to access your original, automatically provisioned domain (e.g., yourcompany.metabaseapp.com), so existing embedded links should continue to work.
|
||||
|
||||
## Why you'd want to use a custom domain
|
||||
|
||||
- **White-labeling polish**: If you're white labeling Metabase, a custom domain adds another bit of polish that abstracts Metabase away from the experience you deliver to your customers.
|
||||
- **Consistency**: you may have several internal tools that follow a pattern, e.g., crm.yourcompany.com, ops.yourcompany.com, etc., and you want something like stats.yourcompany.com for your Metabase.
|
||||
|
||||
## Change the name of your Metabase (your DNS alias)
|
||||
|
||||
Log in to your Metabase Store account and go to **Instances** > **DNS Alias**, where you can update your alias.
|
||||
102
docs/cloud/google-sheets.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: Sync Google Sheets with Metabase
|
||||
version: latest
|
||||
has_magic_breadcrumbs: true
|
||||
show_category_breadcrumb: true
|
||||
show_title_breadcrumb: true
|
||||
category: "Cloud"
|
||||
layout: new-docs
|
||||
---
|
||||
|
||||
# Sync Google Sheets with Metabase
|
||||
|
||||
> Syncing Google Sheets is only available for Metabase Cloud instances with the [Metabase Cloud Storage](./storage.md) add-on.
|
||||
|
||||

|
||||
|
||||
If you've set up [Metabase Cloud Storage](./storage.md), an admin can set up Metabase to sync with Google Sheets in a Google Drive folder. Metabase will create tables in your Metabase Cloud Storage for the synced Google Sheets.
|
||||
|
||||
Currently, you can't sync Google Sheets to your own database.
|
||||
|
||||
## How to sync Google Sheets with Metabase
|
||||
|
||||
An admin can set up Metabase to sync with either:
|
||||
|
||||
- **A single folder in your Google Drive**. Metabase will sync all Google Sheets files saved in this folder, refreshing the data automatically every 15 minutes.
|
||||
- **A single Google Sheet**. Metabase will sync with the sheet (and all its tabs), refreshing every 15 minutes.
|
||||
|
||||
Here's how to set it up:
|
||||
|
||||
1. In the left nav sidebar, click **Add Data** > **Connect Google Sheets**.
|
||||
2. Select **Entire Folder** or **Single sheet**.
|
||||
3. Metabase will ask you to share the Google Drive folder or sheet. You can only share a single folder or sheet with Metabase at a time.
|
||||
4. In Google Drive, share the folder or sheet with the service account that Metabase provides.
|
||||
5. Give the service account **Viewer** permissions. Metabase will only have access to this folder or sheet; it won't have access to any other files in your Google Drive. If you select folder, Metabase will also sync any sheets it finds in any subfolders.
|
||||
6. Click **Send** to share the folder with the Metabase service account.
|
||||
7. Copy the sharing link for the folder or sheet.
|
||||
8. Return to Metabase and paste the sharing link into the field that asks for "the sharing link for this folder/file".
|
||||
|
||||
**If you synced a Google Drive folder**, Metabase will sync with the Google Drive folder (and its subfolders) and import all Google Sheets, creating a new table in your Metabase Cloud Storage database for each sheet. For sheets with multiple tabs, Metabase will create a table for each tab. Metabase will only sync Google Sheets; it'll ignore other file types in the folder. After the initial sync, Metabase will sync every 15 minutes.
|
||||
|
||||
**If you synced a Single sheet**, Metabase will create tables for all tabs in the sheet.
|
||||
|
||||
You can find your synced Google Sheets in Metabase by clicking on **Databases** in the left nav sidebar and navigating to the Metabase Cloud Storage database.
|
||||
|
||||
## Disconnecting from a Google Drive folder or sheet
|
||||
|
||||

|
||||
|
||||
To disconnect your Google Drive connection to a folder or sheet:
|
||||
|
||||
1. Go to **Databases** in the left nav sidebar.
|
||||
2. Click on Metabase Cloud Storage.
|
||||
3. Click on **Disconnect**.
|
||||
4. Confirm the disconnection.
|
||||
|
||||
Disconnecting won't delete your existing tables. An admin will need to manually delete tables in [Uploads settings](../exploration-and-organization/uploads.md#deleting-tables-created-by-uploads).
|
||||
|
||||
## Deleting sheets
|
||||
|
||||
Disconnecting from the Google Drive folder won't delete your imported sheets. Admins will need to delete these tables manually in [Uploads settings](../exploration-and-organization/uploads.md#deleting-tables-created-by-uploads).
|
||||
|
||||
## Changing the Google Drive folder or sheet
|
||||
|
||||
To change the Google Drive folder, you'll need to first [disconnect the current connection](#disconnecting-from-a-google-drive-folder-or-sheet), then [connect a new folder or sheet](#how-to-sync-google-sheets-with-metabase).
|
||||
|
||||
If you change the sync target, Metabase will:
|
||||
|
||||
- Keep the tables from the previous folder/sheet
|
||||
- Stop updating those tables
|
||||
- Start syncing with the new folder/sheet
|
||||
|
||||
If you want to delete the tables from the old folder, admins will need to delete them manually in [Uploads settings](../exploration-and-organization/uploads.md#deleting-tables-created-by-uploads).
|
||||
|
||||
## Limitations and gotchas
|
||||
|
||||
Here's what you need to know when syncing Google Sheets:
|
||||
|
||||
- **Only Google Sheets are synced**. We can only import Google Sheets format files — other file types like CSVs or Parquet files won't work, even if they're in your Google Drive folder.
|
||||
- **Column header handling**. If we run into any issues with column headers (like empty headers or duplicate names), we'll treat that row as data and use generic names like Col1, Col2 instead.
|
||||
- **Special character replacement**. Some characters just don't play nice with databases (like "?"). When we find these in column names, we'll replace them with "x" to keep things running smoothly.
|
||||
- **Renamed files will create new tables**. If you rename files in your folder or tabs in your sheets, we'll treat them as brand new tables and import them fresh.
|
||||
- **New columns sync automatically**. Adding new columns to your sheets? No problem — they'll show up in Metabase as expected.
|
||||
- **Empty sheets won't import**. We won't import completely empty sheets or sheets that only have column headers. There needs to be some actual data in there.
|
||||
- **Google Sheets must have unique names**. If files in the synced Google Drive folder (and its subfolders) have the same name (e.g., one sheet is in the root folder, another sheet in a subfolder), the sheets might not sync properly.
|
||||
|
||||
## Metabase Cloud Storage quota management
|
||||
|
||||
Just like uploaded CSV data, the data from your Google Sheets counts toward your Storage quota.
|
||||
|
||||
To check how much storage you're using:
|
||||
|
||||
1. Log in to your Metabase.
|
||||
2. Click on the **gear** icon in the upper right.
|
||||
3. Select **Admin settings**.
|
||||
4. In the **Settings** tab, click on **License and billing**.
|
||||
5. Check:
|
||||
- Currently stored rows
|
||||
- Maximum stored rows
|
||||
|
||||
The quota numbers update every 6 hours, so there might be a slight delay. Once you hit your quota limit, you won't be able to upload/sync more data until you either free up some space or increase your storage.
|
||||
|
||||
To store more rows, see [increasing Metabase Cloud storage](./storage.md#increasing-metabase-cloud-storage).
|
||||