Compare commits
391 Commits
wot/refact
...
docs-docum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa474e427 | ||
|
|
85049c1474 | ||
|
|
11f0cdfec3 | ||
|
|
521eed05c5 | ||
|
|
e88afceba9 | ||
|
|
ac2003368e | ||
|
|
1d2fc06b60 | ||
|
|
581c1e7a38 | ||
|
|
17a61a81c8 | ||
|
|
1d699a5fae | ||
|
|
277d1fdaff | ||
|
|
82b3ba1fcc | ||
|
|
dd7845cc83 | ||
|
|
7e7ed1ac9f | ||
|
|
19387d8d29 | ||
|
|
7ac8feb49b | ||
|
|
4447e2c6bc | ||
|
|
8434b23764 | ||
|
|
be72407fc8 | ||
|
|
e530911305 | ||
|
|
ca9ba3390d | ||
|
|
a947f3cb94 | ||
|
|
33de954fcf | ||
|
|
db79c64d3d | ||
|
|
3e646a69f3 | ||
|
|
d6c15aab01 | ||
|
|
34c224b5f2 | ||
|
|
568443a99c | ||
|
|
5fe1efe15b | ||
|
|
894176cf26 | ||
|
|
c28f66c961 | ||
|
|
85e6e7f779 | ||
|
|
d44a2cab66 | ||
|
|
e349faa741 | ||
|
|
817af7b71d | ||
|
|
e8cd58b376 | ||
|
|
62d7beec2b | ||
|
|
c2ec56beed | ||
|
|
5274867997 | ||
|
|
b5a9d7457f | ||
|
|
12f4387864 | ||
|
|
6b5e3bd646 | ||
|
|
b7c4063dd9 | ||
|
|
49fcc4a40f | ||
|
|
7fb62b782b | ||
|
|
f393237eb9 | ||
|
|
48376d3f63 | ||
|
|
a67c48a036 | ||
|
|
8c3d77473a | ||
|
|
9f029dbb2f | ||
|
|
1adaaae66e | ||
|
|
21956d6ab7 | ||
|
|
fd94a0b0cb | ||
|
|
7b6fbd6374 | ||
|
|
eeecd9a10a | ||
|
|
685424df74 | ||
|
|
c4af9c415b | ||
|
|
ca66414190 | ||
|
|
855e846d0a | ||
|
|
270086315f | ||
|
|
16ea0dc141 | ||
|
|
94b9737f0b | ||
|
|
4448933543 | ||
|
|
a9e0afd420 | ||
|
|
d018b4a1bc | ||
|
|
92011484de | ||
|
|
c10fd5cd5b | ||
|
|
564556d706 | ||
|
|
5e55ecd75c | ||
|
|
0a33f82baf | ||
|
|
7ecdc2f757 | ||
|
|
0bf1ac867f | ||
|
|
cbdc23a7bf | ||
|
|
c59e9de090 | ||
|
|
9668ebe98c | ||
|
|
3c2d46151b | ||
|
|
1cacd1f510 | ||
|
|
a8074ca04b | ||
|
|
28014961c3 | ||
|
|
ec5b20af0c | ||
|
|
ab68e21cf7 | ||
|
|
821c0f624b | ||
|
|
7423aaf572 | ||
|
|
03f68f0a2c | ||
|
|
e8f125c19b | ||
|
|
21c5271879 | ||
|
|
45d701fd6b | ||
|
|
7c1152e9d0 | ||
|
|
ab20852102 | ||
|
|
137bc61a42 | ||
|
|
f567aeece1 | ||
|
|
e60e38682b | ||
|
|
84efc79be4 | ||
|
|
8de8d13eb6 | ||
|
|
a52d58a3e2 | ||
|
|
f34cb3d5c3 | ||
|
|
d8571ccabe | ||
|
|
19f279610b | ||
|
|
5647233576 | ||
|
|
14481bcf2f | ||
|
|
bbccbb4842 | ||
|
|
4b99060f46 | ||
|
|
3903af487f | ||
|
|
0fb061b839 | ||
|
|
ab9e7bbcb8 | ||
|
|
a137631b7f | ||
|
|
e4a04ebc05 | ||
|
|
432287f763 | ||
|
|
d8be52f651 | ||
|
|
6516199e3f | ||
|
|
1bba35237e | ||
|
|
4337a1baf3 | ||
|
|
23cb8b11c0 | ||
|
|
c863f41c2a | ||
|
|
34e7a0e9f1 | ||
|
|
454a05e3c6 | ||
|
|
1ed050b03d | ||
|
|
9041dbb329 | ||
|
|
63cc5c3137 | ||
|
|
7551f766dd | ||
|
|
f4b675b3f2 | ||
|
|
123e0cef51 | ||
|
|
eed3bb1e19 | ||
|
|
dad9b5c709 | ||
|
|
166e20c944 | ||
|
|
e4d0318213 | ||
|
|
3913c6e850 | ||
|
|
f6384ac6f3 | ||
|
|
bd6d5e527b | ||
|
|
4dac7d9dcf | ||
|
|
5bda80e53f | ||
|
|
07a34dc556 | ||
|
|
3a4e2a311c | ||
|
|
f0427f0a34 | ||
|
|
ebcdcf1ce9 | ||
|
|
20afc08468 | ||
|
|
c60b139cf4 | ||
|
|
cb7e5a0437 | ||
|
|
1e4e4bcae5 | ||
|
|
f65c0fb194 | ||
|
|
fa187a027d | ||
|
|
d25a14aec8 | ||
|
|
4a59711053 | ||
|
|
c33489af35 | ||
|
|
d6c7ef8e63 | ||
|
|
6e7aeb6d71 | ||
|
|
6db1e7e2ad | ||
|
|
f901d08b1c | ||
|
|
5bc7d211ba | ||
|
|
d04ffc2925 | ||
|
|
c471c32f3f | ||
|
|
d867a09072 | ||
|
|
090fbc2d5a | ||
|
|
33fe7b3afe | ||
|
|
ea7eeeb15e | ||
|
|
b693b137f6 | ||
|
|
1a6044bbb3 | ||
|
|
e5706a4def | ||
|
|
0014bbc775 | ||
|
|
b96b8fbead | ||
|
|
7e09b87447 | ||
|
|
102025c69c | ||
|
|
80b31c4b1e | ||
|
|
d4adbb7ef0 | ||
|
|
04192e385d | ||
|
|
18b9ae17c7 | ||
|
|
b69218fe00 | ||
|
|
61a8141a44 | ||
|
|
2ed9e3e1d4 | ||
|
|
a1a9e71867 | ||
|
|
0ed177ddb1 | ||
|
|
814dd5c800 | ||
|
|
8285e38f9c | ||
|
|
8ad7ee944d | ||
|
|
975002a6f7 | ||
|
|
3fddbec54e | ||
|
|
c3461ab646 | ||
|
|
b5878d9bf5 | ||
|
|
6206b6f204 | ||
|
|
4ef96bd73a | ||
|
|
b336da1517 | ||
|
|
c98dca560d | ||
|
|
e370820778 | ||
|
|
69405a7dea | ||
|
|
c0ca9e0493 | ||
|
|
ced8d84def | ||
|
|
8c8279e570 | ||
|
|
6afe00b9f1 | ||
|
|
6e8cca3da7 | ||
|
|
a1582d07de | ||
|
|
40387cc586 | ||
|
|
911da7f851 | ||
|
|
4deefb87b3 | ||
|
|
c56432c2f7 | ||
|
|
f9e9a20d94 | ||
|
|
5d68e7363f | ||
|
|
0efa8eac5b | ||
|
|
6e3dfdb62b | ||
|
|
e915bf2d40 | ||
|
|
118613ab89 | ||
|
|
4f764b82ef | ||
|
|
ce5a554d4d | ||
|
|
eb5100abc6 | ||
|
|
64630276e3 | ||
|
|
6f660a3090 | ||
|
|
eab4bf076d | ||
|
|
2be60d80c3 | ||
|
|
3038880e46 | ||
|
|
b2b04e9206 | ||
|
|
ef8111537e | ||
|
|
7bce3ff38a | ||
|
|
066d3a233d | ||
|
|
095ad3c39c | ||
|
|
d50561227f | ||
|
|
63f9d193a9 | ||
|
|
0a18c13feb | ||
|
|
a58c63a74f | ||
|
|
96f1ef42a2 | ||
|
|
fd073fb8b1 | ||
|
|
a7971b43b1 | ||
|
|
0d1b4e253a | ||
|
|
9986021635 | ||
|
|
ae422678b2 | ||
|
|
8ecc5c6718 | ||
|
|
dec95aabe7 | ||
|
|
98e29f6c39 | ||
|
|
b110c02981 | ||
|
|
a5056280e8 | ||
|
|
33f5b24a0a | ||
|
|
323e6f3823 | ||
|
|
b5b9bad486 | ||
|
|
7f4cb813c2 | ||
|
|
068f460124 | ||
|
|
50e10ee9c2 | ||
|
|
4d54129a72 | ||
|
|
3d2789c2f1 | ||
|
|
2e4c39904e | ||
|
|
0911bbe3b3 | ||
|
|
e08969edd9 | ||
|
|
b5942d4f36 | ||
|
|
914d56b2c0 | ||
|
|
035be556eb | ||
|
|
1919a6cd62 | ||
|
|
1a16244f86 | ||
|
|
ea5b0d4a9e | ||
|
|
b84b11532e | ||
|
|
5e3968f517 | ||
|
|
1b2ebb4fd7 | ||
|
|
d888aa8ee1 | ||
|
|
7aa3f7fed1 | ||
|
|
be673c4771 | ||
|
|
41db01c5e2 | ||
|
|
b0299c474d | ||
|
|
3275cb9a44 | ||
|
|
e7b70ba03f | ||
|
|
25adbcfee8 | ||
|
|
94b90013cb | ||
|
|
065d6ce5aa | ||
|
|
2ce5dc4e46 | ||
|
|
5b450f29aa | ||
|
|
dbc6913076 | ||
|
|
c1a3971ae8 | ||
|
|
319fc114ab | ||
|
|
c456cb5a6f | ||
|
|
a743c86b25 | ||
|
|
0348e0b3c3 | ||
|
|
a32eeb88a2 | ||
|
|
7ad5cdac94 | ||
|
|
0aa42f596f | ||
|
|
dd68df30c9 | ||
|
|
af495f2ff3 | ||
|
|
c343eaa0a3 | ||
|
|
29c013a78a | ||
|
|
a85675ef2a | ||
|
|
8c1582e601 | ||
|
|
a7eacc1c96 | ||
|
|
6ab20070a4 | ||
|
|
7bc2093651 | ||
|
|
fa9d4ad2a4 | ||
|
|
ea74e4e1dc | ||
|
|
1920cc924c | ||
|
|
df7ba12468 | ||
|
|
35f9175eb0 | ||
|
|
eeab38518a | ||
|
|
bc7d55b0dd | ||
|
|
c5b5970b77 | ||
|
|
c8982e0d1d | ||
|
|
3477b876a2 | ||
|
|
63a60230f1 | ||
|
|
b9838f7cf7 | ||
|
|
c024186f79 | ||
|
|
acbff0c969 | ||
|
|
75d56ef281 | ||
|
|
d2b1aadf84 | ||
|
|
f07baad1f8 | ||
|
|
eecf97f077 | ||
|
|
4768c58a01 | ||
|
|
84530c5a58 | ||
|
|
0d1522742e | ||
|
|
2fcecd5de8 | ||
|
|
9164ad955e | ||
|
|
293120750e | ||
|
|
b8b61d0647 | ||
|
|
e4744ae67f | ||
|
|
aa59dca84e | ||
|
|
5f5b2f6d07 | ||
|
|
40420f8d44 | ||
|
|
34bddeb931 | ||
|
|
16daec0315 | ||
|
|
5caa919d59 | ||
|
|
69bda6feef | ||
|
|
cb1c25d58b | ||
|
|
d998df052e | ||
|
|
ce15cfeea9 | ||
|
|
a10afa8256 | ||
|
|
daca8d6c4e | ||
|
|
80d6389e6e | ||
|
|
7f723a20de | ||
|
|
179d170826 | ||
|
|
b263bd0bc0 | ||
|
|
4e851e0e19 | ||
|
|
8ac41c91d2 | ||
|
|
7a122eefd4 | ||
|
|
f74d718df9 | ||
|
|
8ac41c5b91 | ||
|
|
cffe9615c6 | ||
|
|
4ba3309c4c | ||
|
|
9c712173ce | ||
|
|
71f2b73e51 | ||
|
|
d5e806325e | ||
|
|
09b2346c6a | ||
|
|
cf5e3ed830 | ||
|
|
51140fce2e | ||
|
|
7b5a4436e7 | ||
|
|
f85364f530 | ||
|
|
63d6945498 | ||
|
|
e539ba98ce | ||
|
|
926b5ea393 | ||
|
|
00aca4b873 | ||
|
|
e4f11aba66 | ||
|
|
70e31de9a0 | ||
|
|
cd9ebca53a | ||
|
|
9dab171530 | ||
|
|
0e42be1914 | ||
|
|
e1f6606ba2 | ||
|
|
a0243ff3a4 | ||
|
|
f2d1f9f93d | ||
|
|
71f02e8684 | ||
|
|
7a90868ef9 | ||
|
|
48971b48eb | ||
|
|
169f3f79e3 | ||
|
|
908eda55b5 | ||
|
|
2365a169e4 | ||
|
|
aca75bee72 | ||
|
|
3eb696e22b | ||
|
|
60da15bf27 | ||
|
|
3b3e5f91d6 | ||
|
|
0fcff349d1 | ||
|
|
f8a66a7470 | ||
|
|
4672ea6c34 | ||
|
|
73e4ba5bd8 | ||
|
|
f09dbce65d | ||
|
|
dabaa3007e | ||
|
|
00fb1c3841 | ||
|
|
e7fd61a386 | ||
|
|
a17971e449 | ||
|
|
02390555ba | ||
|
|
20577becba | ||
|
|
4c3656a57b | ||
|
|
cca17f5f4d | ||
|
|
f74de7e31e | ||
|
|
cb946d095d | ||
|
|
c4ed3cac1d | ||
|
|
2e184e6c48 | ||
|
|
71f7fe60f1 | ||
|
|
c80fea1587 | ||
|
|
50cb92e548 | ||
|
|
b5f5624007 | ||
|
|
b514c93c50 | ||
|
|
f9614edfdf | ||
|
|
21325c4366 | ||
|
|
b37c60a342 | ||
|
|
4a01316597 | ||
|
|
eb474fbf52 | ||
|
|
d90fcce25b | ||
|
|
9733cd683a | ||
|
|
8462072dfd | ||
|
|
c482aa6f03 | ||
|
|
c621b64c2f | ||
|
|
9275b7fb84 | ||
|
|
7e0f54bb73 |
@@ -173,6 +173,7 @@
|
||||
metabase.driver.sql-jdbc.execute/execute-prepared-statement!
|
||||
metabase.notification.models/create-notification!
|
||||
metabase.pulse.send/send-pulse!
|
||||
metabase-enterprise.transforms-python.python-runner-test/execute!
|
||||
;; Safe in tests, where we typically use a private, temporary index per thread.
|
||||
metabase.search.core/reindex!
|
||||
metabase.util/run-count!
|
||||
@@ -228,7 +229,7 @@
|
||||
(clojure.core.logic/fresh)
|
||||
(clojure.core.logic/matcha)
|
||||
(clojure.core.logic/run)
|
||||
(clojure.test/is [query= =? malli= qv=])
|
||||
(clojure.test/is [=? malli= qv=])
|
||||
(clojure.tools.macro/macrolet)
|
||||
instaparse.core/transform
|
||||
(metabase.server.streaming-response/streaming-response)
|
||||
@@ -418,6 +419,7 @@
|
||||
metabase-enterprise.transforms.api-test/with-transform-tags clojure.core/let
|
||||
metabase-enterprise.transforms.api-test/with-worker-runs clojure.core/let
|
||||
metabase-enterprise.transforms.test-util/with-transform-cleanup! clojure.core/let
|
||||
metabase-enterprise.transforms.execute/with-transform-lifecycle clojure.core/let
|
||||
metabase.actions.test-util/with-actions clojure.core/let
|
||||
metabase.api.common/let-404 clojure.core/let
|
||||
metabase.app-db.custom-migrations.util/with-temp-scheduler! clojure.core/fn
|
||||
@@ -519,6 +521,7 @@
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
|
||||
metabase-enterprise.semantic-search.db.connection/with-migrate-tx hooks.common/let-one-with-optional-value
|
||||
metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase-enterprise.transforms-python.drivers-test/with-test-table hooks.common/with-two-bindings
|
||||
metabase.api.common.internal-test/with-jetty-server hooks.common/with-one-binding
|
||||
metabase.api.macros/defendpoint hooks.metabase.api.macros/defendpoint
|
||||
metabase.app-db.schema-migrations-test.impl/test-migrations hooks.metabase.app-db.schema-migrations-test.impl/test-migrations
|
||||
@@ -713,13 +716,18 @@
|
||||
{: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.
|
||||
;; QP and driver source namespaces: `metabase.query-processor.*`, `metabase-enterprise.sandbox.query-processor.*`,
|
||||
;; and `metabase.driver.*`, excluding `-test` namespaces.
|
||||
;;
|
||||
{:pattern "metabase(?:(?:(?:-enterprise\\.sandbox)?\\.query-processor\\.)|(?:\\.driver\\.)).*(?<!-test)$"
|
||||
:name qp-and-driver-source-namespaces}
|
||||
;;
|
||||
;; driver namespaces
|
||||
;; QP source namespaces
|
||||
;;
|
||||
{:pattern "metabase(?:(?:-enterprise\\.sandbox)?\\.query-processor\\.).*(?<!-test)$"
|
||||
:name qp-source-namespaces}
|
||||
;;
|
||||
;; driver namespaces, including tests
|
||||
;;
|
||||
{:pattern "metabase\\.driver.*$"
|
||||
:name driver-namespaces}
|
||||
@@ -779,7 +787,7 @@
|
||||
clojure.string/lower-case {:level :off}
|
||||
clojure.string/upper-case {:level :off}}}}
|
||||
|
||||
;; enable all the extra linters we haven't enabled in the core app yet in MLv2. It's all new code so we can adhere
|
||||
;; enable all the extra linters we haven't enabled in the core app yet in Lib. It's all new code so we can adhere
|
||||
;; to the new stricter rules from day 1
|
||||
metabase-lib
|
||||
{:linters
|
||||
@@ -799,19 +807,25 @@
|
||||
metabase.test/with-temp {:message "Don't use application database inside Lib code"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{metabase.driver {:message "MLv2 code should be independent of driver code/methods"}
|
||||
metabase.test {:message "MLv2 tests should only use MLv2 test helpers"}
|
||||
metabase.test.data {:message "MLv2 tests should only use MLv2 test helpers"}
|
||||
metabase.util.malli.schema {:message "Prefer metabase.lib.schema schemas in MLv2 code"}
|
||||
toucan.db {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.models {:message "Don't use application database inside MLv2 code"}
|
||||
toucan.util.test {:message "Don't use application database inside MLv2 code"}
|
||||
toucan2.core {:message "Don't use application database inside MLv2 code"}}}}
|
||||
{metabase.driver {:message "Lib code should be independent of driver code/methods"}
|
||||
metabase.test {:message "Lib tests should only use Lib test helpers"}
|
||||
metabase.test.data {:message "Lib tests should only use Lib test helpers"}
|
||||
metabase.util.malli.schema {:message "Prefer metabase.lib.schema schemas in Lib code"}
|
||||
toucan.db {:message "Don't use application database inside Lib code"}
|
||||
toucan.models {:message "Don't use application database inside Lib code"}
|
||||
toucan.util.test {:message "Don't use application database inside Lib code"}
|
||||
toucan2.core {:message "Don't use application database inside Lib 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"}}}}
|
||||
{metabase.legacy-mbql.util/unique-name-generator {:message "Use metabase.lib.util/unique-name-generator or non-truncating-unique-name-generator"}}}
|
||||
|
||||
:discouraged-namespace
|
||||
{metabase.legacy-mbql.js {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.jvm-util {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.predicates {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}
|
||||
metabase.legacy-mbql.util {:message "Avoid legacy MBQL utils in Lib; move things into Lib if needded."}}}
|
||||
|
||||
driver-namespaces
|
||||
{:linters
|
||||
@@ -821,26 +835,47 @@
|
||||
performance-sensitive
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
{clojure.core/update-keys {:message "Use metabase.util.performance/update-keys"}}}}
|
||||
{clojure.core/every? {:message "Use metabase.util.performance/every?"}
|
||||
clojure.core/mapv {:message "Use metabase.util.performance/mapv"}
|
||||
clojure.core/run! {:message "Use metabase.util.performance/run!"}
|
||||
clojure.core/select-keys {:message "Use metabase.util.performance/select-keys"}
|
||||
clojure.core/some {:message "Use metabase.util.performance/some"}
|
||||
clojure.core/update-keys {:message "Use metabase.util.performance/update-keys"}
|
||||
clojure.walk/keywordize-keys {:message "Use metabase.util.performance/keywordize-keys"}
|
||||
clojure.walk/postwalk {:message "Use metabase.util.performance/postwalk"}
|
||||
clojure.walk/prewalk {:message "Use metabase.util.performance/prewalk"}
|
||||
clojure.walk/walk {:message "Use metabase.util.performance/walk"}}}}
|
||||
|
||||
qp-and-driver-source-namespaces
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
{metabase.query-processor.store {:level :off}
|
||||
metabase.util.malli.schema {:message "Prefer metabase.lib.schema or metabase.legacy-mbql.schema schemas in QP/driver code"}
|
||||
toucan.db {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.models {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan.util.test {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
toucan2.core {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}}
|
||||
toucan.db {:message "Don't use application database directly in QP code; use the MetadataProvider"}
|
||||
toucan.models {:message "Don't use application database directly in QP code; use the MetadataProvider"}
|
||||
toucan.util.test {:message "Don't use application database directly in QP code; use the MetadataProvider"}
|
||||
toucan2.core {:message "Don't use application database directly in QP code; use the MetadataProvider"}}
|
||||
|
||||
:discouraged-var
|
||||
{medley.core/map-keys {:message "Use metabase.util.performance/update-keys"}
|
||||
medley.core/map-vals {:message "Use clojure.core/update-vals"}
|
||||
metabase.app-db.core/query {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
metabase.app-db.core/reducible-query {:message "Don't use application database directly in QP code; use QP Store/metadata provider"}
|
||||
metabase.app-db.core/query {:message "Don't use application database directly in QP code; use the MetadataProvider"}
|
||||
metabase.app-db.core/reducible-query {:message "Don't use application database directly in QP code; use the MetadataProvider"}
|
||||
metabase.lib.convert/->legacy-MBQL {:level :off}
|
||||
metabase.lib.core/->legacy-MBQL {:level :off}}}}
|
||||
|
||||
qp-source-namespaces
|
||||
{:linters
|
||||
{:discouraged-namespace
|
||||
{metabase.legacy-mbql.js {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.jvm-util {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.normalize {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.predicates {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema.helpers {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.schema.macros {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}
|
||||
metabase.legacy-mbql.util {:message "Avoid legacy MBQL namespaces in the QP, use Lib instead"}}}}
|
||||
|
||||
general-qp-and-driver-test-namespaces
|
||||
{:linters
|
||||
{:discouraged-var
|
||||
|
||||
@@ -502,6 +502,7 @@
|
||||
metabase.embedding.init
|
||||
metabase.embedding.jwt
|
||||
metabase.embedding.settings
|
||||
metabase.embedding.util
|
||||
metabase.embedding.validation}
|
||||
:uses #{analytics
|
||||
api
|
||||
@@ -516,6 +517,7 @@
|
||||
public-sharing
|
||||
queries
|
||||
query-processor
|
||||
request
|
||||
server
|
||||
settings
|
||||
tiles
|
||||
@@ -547,6 +549,13 @@
|
||||
settings
|
||||
util}}
|
||||
|
||||
glossary
|
||||
{:team "Workflows"
|
||||
:api #{metabase.glossary.api}
|
||||
:uses #{api
|
||||
models
|
||||
util}}
|
||||
|
||||
indexed-entities
|
||||
{:team "UX West"
|
||||
:api #{metabase.indexed-entities.api
|
||||
@@ -574,7 +583,6 @@
|
||||
:api #{metabase.legacy-mbql.normalize
|
||||
metabase.legacy-mbql.predicates
|
||||
metabase.legacy-mbql.schema
|
||||
metabase.legacy-mbql.schema.helpers
|
||||
metabase.legacy-mbql.util}
|
||||
:uses #{lib
|
||||
models
|
||||
@@ -591,8 +599,7 @@
|
||||
metabase.lib.field
|
||||
metabase.lib.field.resolution
|
||||
metabase.lib.filter
|
||||
metabase.lib.filter.desugar
|
||||
metabase.lib.filter.desugar.jvm
|
||||
metabase.lib.filter.desugar.jvm ; only so [[metabase.legacy-mbql.jvm-util]] can use defined regexes
|
||||
metabase.lib.init
|
||||
metabase.lib.join.util
|
||||
metabase.lib.metadata
|
||||
@@ -718,6 +725,7 @@
|
||||
metabase.native-query-snippets.core}
|
||||
:uses #{api
|
||||
collections
|
||||
lib
|
||||
models
|
||||
permissions
|
||||
premium-features
|
||||
@@ -886,6 +894,19 @@
|
||||
enterprise/advanced-permissions
|
||||
enterprise/sandbox}}
|
||||
|
||||
python-runner
|
||||
{:team "Workflows"
|
||||
:api #{metabase.python-runner.api
|
||||
metabase.python-runner.core
|
||||
metabase.python-runner.init}
|
||||
:uses #{api
|
||||
driver
|
||||
models
|
||||
query-processor
|
||||
request
|
||||
util
|
||||
enterprise/transforms}}
|
||||
|
||||
queries
|
||||
{:team "Querying"
|
||||
:api #{metabase.queries.api
|
||||
@@ -931,6 +952,7 @@
|
||||
:uses #{api
|
||||
legacy-mbql
|
||||
lib
|
||||
lib-be
|
||||
models
|
||||
permissions
|
||||
query-processor
|
||||
@@ -1013,6 +1035,7 @@
|
||||
metabase.request.schema}
|
||||
:uses #{api
|
||||
config
|
||||
embedding
|
||||
permissions ; FIXME
|
||||
session
|
||||
settings
|
||||
@@ -1218,6 +1241,7 @@
|
||||
events
|
||||
lib
|
||||
models
|
||||
premium-features
|
||||
query-processor
|
||||
settings
|
||||
task
|
||||
@@ -1463,6 +1487,7 @@
|
||||
driver
|
||||
legacy-mbql
|
||||
lib
|
||||
lib-be
|
||||
models
|
||||
queries
|
||||
query-permissions
|
||||
@@ -1613,6 +1638,18 @@
|
||||
util
|
||||
enterprise/harbormaster}}
|
||||
|
||||
enterprise/comments
|
||||
{:team "Workflows"
|
||||
:api #{metabase-enterprise.comments.api
|
||||
metabase-enterprise.comments.init}
|
||||
:uses #{api
|
||||
channel
|
||||
events
|
||||
models
|
||||
request
|
||||
users
|
||||
util}}
|
||||
|
||||
enterprise/content-translation
|
||||
{:team "UX West"
|
||||
:api #{metabase-enterprise.content-translation.dictionary
|
||||
@@ -1711,6 +1748,17 @@
|
||||
settings
|
||||
util}}
|
||||
|
||||
enterprise/embedding-hub
|
||||
{:team "Embedding"
|
||||
:api #{metabase-enterprise.embedding-hub.api}
|
||||
:uses #{api
|
||||
appearance
|
||||
audit-app
|
||||
embedding
|
||||
premium-features
|
||||
util
|
||||
enterprise/sso}}
|
||||
|
||||
enterprise/gsheets
|
||||
{:team "Cloud"
|
||||
:api #{metabase-enterprise.gsheets.api
|
||||
@@ -1783,10 +1831,12 @@
|
||||
pulse
|
||||
query-processor
|
||||
request
|
||||
search
|
||||
server
|
||||
settings
|
||||
sync
|
||||
system
|
||||
task
|
||||
users
|
||||
util
|
||||
warehouse-schema
|
||||
@@ -1947,8 +1997,11 @@
|
||||
{:team "Querying"
|
||||
:api #{metabase-enterprise.transforms.api
|
||||
metabase-enterprise.transforms.core
|
||||
metabase-enterprise.transforms.init}
|
||||
:uses #{api
|
||||
metabase-enterprise.transforms.init
|
||||
metabase-enterprise.transforms.instrumentation
|
||||
metabase-enterprise.transforms.util}
|
||||
:uses #{analytics
|
||||
api
|
||||
app-db
|
||||
config
|
||||
driver
|
||||
@@ -1964,7 +2017,25 @@
|
||||
sync
|
||||
task
|
||||
task-history
|
||||
util}}
|
||||
upload
|
||||
util
|
||||
enterprise/transforms-python}}
|
||||
|
||||
enterprise/transforms-python
|
||||
{:team "Querying"
|
||||
:api #{metabase-enterprise.transforms-python.api
|
||||
metabase-enterprise.transforms-python.execute
|
||||
metabase-enterprise.transforms-python.init
|
||||
metabase-enterprise.transforms-python.settings}
|
||||
:uses #{api
|
||||
app-db
|
||||
config
|
||||
driver
|
||||
models
|
||||
query-processor
|
||||
settings
|
||||
util
|
||||
enterprise/transforms}}
|
||||
|
||||
enterprise/upload-management
|
||||
{:team "UX West"
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
toucan2.core/insert-returning-pk!
|
||||
toucan2.core/insert-returning-pks!
|
||||
clojure.core.async/<!!
|
||||
clojure.core.async/<!
|
||||
clojure.core.async/>!!
|
||||
clojure.core.async/>!
|
||||
clojure.core.async/alts!
|
||||
clojure.core.async/alts!!
|
||||
clojure.core.async/close!
|
||||
clojure.core.async/poll!
|
||||
|
||||
@@ -52,7 +52,13 @@
|
||||
|
||||
(defn- lint-require* [node current-ns config]
|
||||
(let [[_require & args] (:children node)
|
||||
required-namespaces (keep unwrap-require args)]
|
||||
required-namespaces (->> (keep unwrap-require args)
|
||||
;; `require` runs through `cc/load-libs` which allows
|
||||
;; #{:as :reload :reload-all :require :use :verbose :refer :as-alias}
|
||||
;; as args. If you use a REPL tool that applies clj-kondo linting (e.g., Clojure MCP)
|
||||
;; you may wish to `:reload` or `:reload-all` a namespace. So we shouldn't assume all
|
||||
;; args are keywords:
|
||||
(remove keyword?))]
|
||||
(lint* node current-ns config required-namespaces)))
|
||||
|
||||
(defn- lint-requiring-resolve* [node current-ns config]
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> If that is your first contribution to Metabase, please sign the [Contributor License Agreement](https://docs.google.com/a/metabase.com/forms/d/1oV38o7b9ONFSwuzwmERRMi9SYrhYeOrkbmNaq9pOJ_E/viewform) (unless it's a tiny documentation change). Also, if you're attempting to fix a translation issue, please submit your changes to our [Crowdin project](https://crowdin.com/project/metabase-i18n) instead of opening a PR.
|
||||
> If that is your first contribution to Metabase, please sign the [Contributor License Agreement](https://docs.google.com/a/metabase.com/forms/d/1oV38o7b9ONFSwuzwmERRMi9SYrhYeOrkbmNaq9pOJ_E/viewform). Also, if you're attempting to fix a translation issue, please submit your changes to our [Crowdin project](https://crowdin.com/project/metabase-i18n) instead of opening a PR.
|
||||
|
||||
Closes https://github.com/metabase/metabase/issues/[issue_number]
|
||||
|
||||
|
||||
2
.github/actions/create-backport/action.yml
vendored
@@ -74,7 +74,7 @@ runs:
|
||||
|
||||
git push -u origin ${BACKPORT_BRANCH}
|
||||
|
||||
BACKPORT_PR_URL=$(gh pr create --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" --label "was-backported" --assignee "${GITHUB_ACTOR}" --title "🤖 backported \"${ORIGINAL_TITLE}\"" --body "${PR_BODY}")
|
||||
BACKPORT_PR_URL=$(gh pr create --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" --label "was-backported" --assignee "${{ github.event.pull_request.user.login }}" --title "🤖 backported \"${ORIGINAL_TITLE}\"" --body "${PR_BODY}")
|
||||
BACKPORT_PR_NUMBER=${BACKPORT_PR_URL##*/}
|
||||
|
||||
echo "backport_pr_number=$BACKPORT_PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
|
||||
7
.github/file-paths.yaml
vendored
@@ -41,10 +41,17 @@ frontend_sources: &frontend_sources
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sources:
|
||||
- "enterprise/frontend/src/{embedding,embedding-*}/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
- "enterprise/frontend/src/metabase-enterprise/{embedding,embedding-*}**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_sources:
|
||||
- *frontend_configs
|
||||
- "enterprise/frontend/!(test)**/**/{!(*.spec),}.{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_embedding_sdk_package_sources:
|
||||
- "enterprise/frontend/src/embedding-sdk-package/**/!(*.spec).{js,jsx,ts,tsx,css}"
|
||||
|
||||
frontend_specs: &frontend_specs
|
||||
- *shared_specs
|
||||
- "frontend/test/**"
|
||||
|
||||
172
.github/scripts/average-e2e-timings.js
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const collectAndAverageTimings = async ({
|
||||
github,
|
||||
context,
|
||||
daysBack = 7,
|
||||
existingTimingsPath = "e2e/support/timings.json",
|
||||
}) => {
|
||||
try {
|
||||
const artifacts = await findMergedTimingArtifacts({
|
||||
github,
|
||||
context,
|
||||
daysBack,
|
||||
});
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
throw new Error("No timing artifacts found in the specified time range");
|
||||
}
|
||||
|
||||
const timingData = await downloadAndExtractArtifacts(
|
||||
artifacts,
|
||||
token,
|
||||
github,
|
||||
context,
|
||||
);
|
||||
|
||||
if (timingData.length === 0) {
|
||||
throw new Error("No valid timing data extracted from artifacts");
|
||||
}
|
||||
|
||||
const averagedTimings = averageTimings(timingData);
|
||||
averagedTimings.durations.sort((a, b) => a.spec.localeCompare(b.spec));
|
||||
|
||||
fs.writeFileSync(
|
||||
existingTimingsPath,
|
||||
JSON.stringify(averagedTimings, null, 2),
|
||||
);
|
||||
console.log(`✅ Updated ${existingTimingsPath} with new timing data`);
|
||||
|
||||
return { artifactCount: timingData.length };
|
||||
} catch (error) {
|
||||
console.error("❌ Error in collectAndAverageTimings:", error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const findMergedTimingArtifacts = async ({ github, context, daysBack }) => {
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - daysBack);
|
||||
|
||||
console.log(
|
||||
`Searching for merged timing artifacts created after ${cutoffDate.toISOString()}`,
|
||||
);
|
||||
|
||||
const artifacts = [];
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { data } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: "merged-e2e-timings",
|
||||
per_page: 100,
|
||||
page,
|
||||
});
|
||||
|
||||
const validArtifacts = data.artifacts
|
||||
.filter((a) => new Date(a.created_at) > cutoffDate && !a.expired)
|
||||
.map(({ name, created_at, id }) => ({ name, created_at, id }));
|
||||
artifacts.push(...validArtifacts);
|
||||
|
||||
if (
|
||||
data.artifacts.some((a) => new Date(a.created_at) <= cutoffDate) ||
|
||||
data.artifacts.length < 100
|
||||
) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Error fetching artifacts page ${page - 1}: ${error.message}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${artifacts.length} merged timing artifacts total`);
|
||||
return artifacts;
|
||||
};
|
||||
|
||||
const downloadAndExtractArtifacts = async (artifacts, github, context) => {
|
||||
const tempDir = "timing-artifacts-temp";
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
const timingData = [];
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
try {
|
||||
console.log(`Downloading artifact: ${artifact.name}`);
|
||||
|
||||
const zipPath = path.join(tempDir, `${artifact.name}.zip`);
|
||||
const extractPath = path.join(tempDir, artifact.name);
|
||||
|
||||
const { data } = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: "zip",
|
||||
});
|
||||
|
||||
fs.writeFileSync(zipPath, Buffer.from(data));
|
||||
fs.mkdirSync(extractPath, { recursive: true });
|
||||
execSync(`unzip -q "${zipPath}" -d "${extractPath}"`);
|
||||
|
||||
const timingFilePath = path.join(extractPath, "runTimings.json");
|
||||
|
||||
if (fs.existsSync(timingFilePath)) {
|
||||
const content = JSON.parse(fs.readFileSync(timingFilePath, "utf8"));
|
||||
|
||||
if (content.durations?.length) {
|
||||
timingData.push(content);
|
||||
console.log(
|
||||
`✅ Extracted timing data from ${artifact.name} (${content.durations.length} specs)`,
|
||||
);
|
||||
} else {
|
||||
console.log(`⚠ Invalid timing data structure in ${artifact.name}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠ No timing data found in ${artifact.name}`);
|
||||
}
|
||||
|
||||
fs.rmSync(extractPath, { recursive: true });
|
||||
} catch (error) {
|
||||
console.log(`❌ Error processing ${artifact.name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
fs.rmSync(tempDir, { recursive: true });
|
||||
|
||||
console.log(
|
||||
`Successfully extracted timing data from ${timingData.length}/${artifacts.length} artifacts`,
|
||||
);
|
||||
return timingData;
|
||||
};
|
||||
|
||||
const averageTimings = (timingDataArray) => {
|
||||
if (!timingDataArray.length) {
|
||||
throw new Error("No timing data to process");
|
||||
}
|
||||
|
||||
const specTimings = timingDataArray
|
||||
.flatMap((data) => data.durations)
|
||||
.filter((item) => item.spec && typeof item.duration === "number")
|
||||
.reduce((acc, { spec, duration }) => {
|
||||
(acc[spec] = acc[spec] || []).push(duration);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const durations = Object.entries(specTimings).map(([spec, durations]) => ({
|
||||
spec,
|
||||
duration: Math.round(
|
||||
durations.reduce((sum, d) => sum + d, 0) / durations.length,
|
||||
),
|
||||
}));
|
||||
|
||||
return { durations };
|
||||
};
|
||||
|
||||
module.exports = { collectAndAverageTimings, averageTimings };
|
||||
132
.github/scripts/average-e2e-timings.unit.spec.js
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
const { averageTimings } = require("./average-e2e-timings");
|
||||
|
||||
describe("averageTimings", () => {
|
||||
it("should throw error when given empty array", () => {
|
||||
expect(() => averageTimings([])).toThrow("No timing data to process");
|
||||
});
|
||||
|
||||
it("should work for n=1", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should calculate average for multiple specs", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 2000 },
|
||||
{ spec: "test2.cy.js", duration: 4000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1500,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should round averages to nearest integer", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [{ spec: "test1.cy.js", duration: 1001 }],
|
||||
},
|
||||
{
|
||||
durations: [{ spec: "test1.cy.js", duration: 1002 }],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations[0].duration).toBe(1002);
|
||||
});
|
||||
|
||||
it("should ignore invalid entries", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: "invalid" },
|
||||
{ duration: 2000 }, // missing spec
|
||||
{ spec: "test3.cy.js", duration: 3000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(2);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle merging different specs", () => {
|
||||
const input = [
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 3000 },
|
||||
{ spec: "test3.cy.js", duration: 1000 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = averageTimings(input);
|
||||
|
||||
expect(result.durations).toHaveLength(3);
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,66 @@
|
||||
exports.updateComment = updateComment;
|
||||
const TYPES = new Set([
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"docs",
|
||||
"style",
|
||||
"refactor",
|
||||
"test",
|
||||
"build",
|
||||
"ci",
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param title {string}
|
||||
*/
|
||||
function isConventionalTitle(title) {
|
||||
const match = /^(?<type>\w+)\((?<rawScopes>[^)]+)\):/.exec(title);
|
||||
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, type, rawScopes] = match;
|
||||
const scopes = rawScopes
|
||||
.split(",")
|
||||
.map((scope) => scope.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return TYPES.has(type) && scopes.length > 0;
|
||||
}
|
||||
|
||||
/** @param {import('@types/github-script').AsyncFunctionArguments} AsyncFunctionArguments */
|
||||
async function updateComment({ github, context }) {
|
||||
async function updateCommentForEmbedding({ github, context }) {
|
||||
const githubUsername = context.payload.sender.login;
|
||||
const comment = `${CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER}
|
||||
@${githubUsername} You have modified embedding SDK code. Please make sure the PR title follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style.
|
||||
Here are all the supported types that will be shown in the changelog:
|
||||
|
||||
const comment = `${CONVENTIONAL_COMMENT_IDENTIFIER}
|
||||
@${githubUsername} You have modified embedding code. Please make sure the PR title follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style.
|
||||
Here are all the supported types:
|
||||
- \`feat\`
|
||||
- \`fix\`
|
||||
- \`perf\`
|
||||
- \`docs\`
|
||||
- \`style\`
|
||||
- \`refactor\`
|
||||
- \`test\`
|
||||
- \`build\`
|
||||
- \`ci\`
|
||||
|
||||
These types \`docs\`, \`style\`, \`refactor\`, \`test\`, \`build\`, and \`ci\` are also encouraged for non-changelog related tasks.
|
||||
|
||||
Please also make sure to include \`sdk\` scope, otherwise, the changelog will not include this task.
|
||||
Please also make sure to include a scope.
|
||||
|
||||
For example, these are valid PR titles:
|
||||
- \`feat(sdk): Add interactive dashboard component\`
|
||||
- \`feat(sdk): Support theming pie chart\`
|
||||
- \`fix(sdk): Fix static dashboard crash\``;
|
||||
- \`feat(sdk-bundle): Add interactive dashboard component\`
|
||||
- \`fix(sdk-package): Fixed MetabaseProviderPropsStore logic\`
|
||||
- \`fix(sdk-package, embed-js): Fix API\``;
|
||||
|
||||
return updateComment(comment, { github, context });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param comment {string}
|
||||
* @param {import('@types/github-script').AsyncFunctionArguments} AsyncFunctionArguments
|
||||
*/
|
||||
async function updateComment(comment, { github, context }) {
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pullRequestNumber = context.issue.number;
|
||||
@@ -49,20 +91,19 @@ For example, these are valid PR titles:
|
||||
}
|
||||
}
|
||||
|
||||
const CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER =
|
||||
"<!---conventional commit reminder comment ID-->";
|
||||
const CONVENTIONAL_COMMENT_IDENTIFIER =
|
||||
"<!---conventional comment identifier-->";
|
||||
|
||||
/**
|
||||
* @typedef Comment
|
||||
* @property {string} body
|
||||
*
|
||||
* @param {Comment[]} comments
|
||||
* @param comments {{body: string}[]}
|
||||
*/
|
||||
function getExistingConventionalCommitReminderComment(comments) {
|
||||
for (const comment of comments) {
|
||||
if (
|
||||
comment.body.startsWith(CONVENTIONAL_COMMIT_REMINDER_COMMENT_IDENTIFIER)
|
||||
) {
|
||||
if (comment.body.startsWith(CONVENTIONAL_COMMENT_IDENTIFIER)) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.isConventionalTitle = isConventionalTitle;
|
||||
exports.updateCommentForEmbedding = updateCommentForEmbedding;
|
||||
|
||||
45
.github/scripts/conventional-commit-pr-title-reminder/utils.unit.spec.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
const { isConventionalTitle } = require("./utils");
|
||||
|
||||
describe("isConventionalTitle", () => {
|
||||
it("returns true for a simple conventional commit", () => {
|
||||
expect(isConventionalTitle("feat: add new feature")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for a commit with a scope", () => {
|
||||
expect(isConventionalTitle("fix(api): correct API bug")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for a commit with multiple scopes, comma separated", () => {
|
||||
expect(isConventionalTitle("feat(build,deps): update dependencies")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns true for a commit with multiple scopes, comma separated with space", () => {
|
||||
expect(isConventionalTitle("feat(build, deps): update dependencies")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for a commit with round brackets in a message", () => {
|
||||
expect(isConventionalTitle("feat: (something) update dependencies")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for missing colon", () => {
|
||||
expect(isConventionalTitle("feat add feature")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for invalid type", () => {
|
||||
expect(isConventionalTitle("invalidType: some message")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty message", () => {
|
||||
expect(isConventionalTitle("feat:")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty input", () => {
|
||||
expect(isConventionalTitle("")).toBe(false);
|
||||
});
|
||||
});
|
||||
39
.github/scripts/extract-changed-timings.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
const extractChangedTimings = ({ oldTimings, newTimings }) => {
|
||||
const oldTimingMap =
|
||||
oldTimings.durations?.reduce((map, item) => {
|
||||
if (item.spec) {
|
||||
map[item.spec] = item.duration;
|
||||
}
|
||||
return map;
|
||||
}, {}) || {};
|
||||
|
||||
const changedDurations = newTimings.durations
|
||||
.filter((item) => {
|
||||
if (!item || !item.spec || typeof item.duration !== "number") {
|
||||
return false;
|
||||
}
|
||||
const newSpec = convertPathFormat(item.spec);
|
||||
const oldDuration = oldTimingMap[newSpec] || oldTimingMap[item.spec];
|
||||
// Include if it's a new spec or the duration has changed
|
||||
return oldDuration === undefined || oldDuration !== item.duration;
|
||||
})
|
||||
.map((item) => ({
|
||||
spec: convertPathFormat(item.spec),
|
||||
duration: item.duration,
|
||||
}));
|
||||
|
||||
return {
|
||||
changedTimings: { durations: changedDurations },
|
||||
hasChanges: changedDurations.length > 0,
|
||||
};
|
||||
};
|
||||
|
||||
// Convert cypress-split path format to timings.json path format
|
||||
// from "e2e/test/scenarios/..." to "../test/scenarios/..."
|
||||
function convertPathFormat(specPath) {
|
||||
return specPath.startsWith("e2e/test/")
|
||||
? specPath.replace("e2e/test/", "../test/")
|
||||
: specPath;
|
||||
}
|
||||
|
||||
module.exports = { extractChangedTimings };
|
||||
195
.github/scripts/extract-changed-timings.unit.spec.js
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
const { extractChangedTimings } = require("./extract-changed-timings");
|
||||
|
||||
describe("extractChangedTimings", () => {
|
||||
it("should return empty durations when no changes", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(0);
|
||||
expect(result.hasChanges).toBe(false);
|
||||
});
|
||||
|
||||
it("should detect new specs", () => {
|
||||
const oldTimings = {
|
||||
durations: [{ spec: "test1.cy.js", duration: 1000 }],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect duration changes", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2200 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2200,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle missing old timings", () => {
|
||||
const oldTimings = { durations: [] };
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
// All timings should be considered "changed" since old timings is empty
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test2.cy.js",
|
||||
duration: 2000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle invalid timing entries", () => {
|
||||
const oldTimings = {
|
||||
durations: [{ spec: "test1.cy.js", duration: 1000 }],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: "invalid" },
|
||||
{ duration: 2000 }, // missing spec
|
||||
{ spec: "test3.cy.js", duration: 3000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
// Only test3.cy.js should be included (new spec with valid data)
|
||||
expect(result.changedTimings.durations).toHaveLength(1);
|
||||
expect(result.changedTimings.durations[0]).toEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle timing data with different specs", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 1000 },
|
||||
{ spec: "test2.cy.js", duration: 2000 },
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{ spec: "test1.cy.js", duration: 3000 },
|
||||
{ spec: "test3.cy.js", duration: 1000 },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test1.cy.js",
|
||||
duration: 3000,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "test3.cy.js",
|
||||
duration: 1000,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
|
||||
it("should convert cypress-split paths to relative paths", () => {
|
||||
const oldTimings = {
|
||||
durations: [
|
||||
{
|
||||
spec: "../test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1000,
|
||||
},
|
||||
{
|
||||
spec: "../test/scenarios/admin/databases.cy.spec.js",
|
||||
duration: 2000,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const newTimings = {
|
||||
durations: [
|
||||
{
|
||||
spec: "e2e/test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1200,
|
||||
},
|
||||
{
|
||||
spec: "e2e/test/scenarios/admin/databases.cy.spec.js",
|
||||
duration: 2000,
|
||||
},
|
||||
{
|
||||
spec: "e2e/test/scenarios/collections/collections.cy.spec.js",
|
||||
duration: 1500,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
expect(result.changedTimings.durations).toHaveLength(2);
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "../test/scenarios/dashboard/dashboard.cy.spec.js",
|
||||
duration: 1200,
|
||||
});
|
||||
expect(result.changedTimings.durations).toContainEqual({
|
||||
spec: "../test/scenarios/collections/collections.cy.spec.js",
|
||||
duration: 1500,
|
||||
});
|
||||
expect(result.hasChanges).toBe(true);
|
||||
});
|
||||
});
|
||||
84
.github/scripts/get-issue-data.js
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
const githubSlackMap = require("../../release/github-slack-map.json");
|
||||
|
||||
async function getIssueData({ context, github, manualIssueNumber }) {
|
||||
let issueNumber;
|
||||
let issueData;
|
||||
let prReviews = [];
|
||||
const triggerType = context.eventName;
|
||||
|
||||
if (context.eventName === "issues") {
|
||||
issueNumber = context.payload.issue.number;
|
||||
issueData = context.payload.issue;
|
||||
} else if (context.eventName === "workflow_dispatch") {
|
||||
issueNumber = parseInt(manualIssueNumber);
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
issueData = data;
|
||||
} else if (context.eventName === "pull_request_review") {
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const { data: prData } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
// Extract issue number from branch name (e.g., "claude-fix/issue-12345-some-title")
|
||||
const branchMatch = prData.head.ref.match(/claude-fix\/issue-(\d+)/);
|
||||
if (!branchMatch) {
|
||||
throw new Error(
|
||||
`Could not extract issue number from branch name: ${prData.head.ref}`,
|
||||
);
|
||||
}
|
||||
|
||||
issueNumber = parseInt(branchMatch[1]);
|
||||
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
issueData = data;
|
||||
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
prReviews = reviews.filter(
|
||||
(review) =>
|
||||
review.state === "CHANGES_REQUESTED" &&
|
||||
isMetabaseMember(review.user.login),
|
||||
);
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
|
||||
const employeeComments = comments.filter((comment) =>
|
||||
isMetabaseMember(comment.user.login),
|
||||
);
|
||||
|
||||
return {
|
||||
issueNumber,
|
||||
issueData,
|
||||
employeeComments,
|
||||
prReviews,
|
||||
triggerType,
|
||||
isIssueAuthorEmployee: isMetabaseMember(issueData.user.login),
|
||||
};
|
||||
}
|
||||
|
||||
function isMetabaseMember(username) {
|
||||
return username in githubSlackMap;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getIssueData,
|
||||
};
|
||||
29
.github/scripts/setup-python-runner.sh
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Parse command line arguments
|
||||
SKIP_START=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--skip-start)
|
||||
SKIP_START=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Usage: $0 [--skip-start]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Cloning python-runner repository..."
|
||||
git clone --depth 1 https://${METABASE_AUTOMATION_USER_TOKEN}@github.com/metabase/python-runner-container.git python-runner
|
||||
|
||||
if [ "$SKIP_START" = false ]; then
|
||||
echo "Starting python-runner..."
|
||||
cd python-runner
|
||||
make run-ci
|
||||
cd ..
|
||||
fi
|
||||
20
.github/workflows/app-db.yml
vendored
@@ -35,21 +35,21 @@ jobs:
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -147,21 +147,21 @@ jobs:
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -259,11 +259,13 @@ jobs:
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
exclude-tag: ':mb/transforms-python-test'
|
||||
- name: Enterprise Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only '"enterprise/backend/test"'
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 1)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
@@ -271,14 +273,14 @@ jobs:
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
:partition/total 2
|
||||
:partition/index 0
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
- name: EE App DB Tests (Part 2)
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:only '"test"'
|
||||
:partition/total 2
|
||||
:partition/index 1
|
||||
exclude-tag: ':mb/driver-tests'
|
||||
exclude-tag: ':mb/driver-tests :mb/transforms-python-test'
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
env:
|
||||
# Only run old migrations tests on pushes to master or release branches. All other branches should skip tests
|
||||
|
||||
16
.github/workflows/auto-backport.yml
vendored
@@ -6,11 +6,19 @@ on:
|
||||
|
||||
jobs:
|
||||
create-backport:
|
||||
if: | # the PR must be merged and have a backport label
|
||||
if:
|
||||
| # the PR must have a backport label when it's merged, or be labeled with a backport label after merge
|
||||
github.event.pull_request.merged == true && (
|
||||
contains(github.event.pull_request.labels.*.name, 'backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'double-backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'triple-backport')
|
||||
github.event.action == 'closed' && (
|
||||
contains(github.event.pull_request.labels.*.name, 'backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'double-backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'triple-backport')
|
||||
) ||
|
||||
github.event.action == 'labeled' && (
|
||||
github.event.label.name == 'backport' ||
|
||||
github.event.label.name == 'double-backport' ||
|
||||
github.event.label.name == 'triple-backport'
|
||||
)
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
280
.github/workflows/claude-code-bug-fix.yml
vendored
@@ -4,97 +4,22 @@ on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: "Issue number to fix"
|
||||
required: true
|
||||
type: string
|
||||
override_employee_check:
|
||||
description: "Override employee check for issue creator"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
check-employee-and-issue:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
if: |
|
||||
(github.event_name == 'issues' && github.event.label.name == 'Claude Bug Fix' && github.event.issue.state == 'open') ||
|
||||
(github.event_name == 'workflow_dispatch')
|
||||
outputs:
|
||||
is_employee: ${{ steps.check_employee.outputs.is_employee }}
|
||||
issue_number: ${{ steps.get_issue.outputs.issue_number }}
|
||||
issue_data: ${{ steps.get_issue.outputs.issue_data }}
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Get issue data
|
||||
id: get_issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let issueNumber, issueData;
|
||||
|
||||
if (context.eventName === 'issues') {
|
||||
// Original issue label event
|
||||
issueNumber = context.payload.issue.number;
|
||||
issueData = context.payload.issue;
|
||||
} else if (context.eventName === 'workflow_dispatch') {
|
||||
// Manual trigger with issue number input
|
||||
issueNumber = parseInt('${{ github.event.inputs.issue_number }}');
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber
|
||||
});
|
||||
issueData = data;
|
||||
}
|
||||
|
||||
core.setOutput('issue_number', issueNumber);
|
||||
core.setOutput('issue_data', JSON.stringify(issueData));
|
||||
|
||||
console.log(`Issue #${issueNumber}: ${issueData.title}`);
|
||||
console.log(`Issue author: ${issueData.user.login}`);
|
||||
|
||||
- name: Check if issue creator is a Metabase employee
|
||||
id: check_employee
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const issueData = JSON.parse(process.env.ISSUE_DATA);
|
||||
const issueAuthor = issueData.user.login;
|
||||
|
||||
try {
|
||||
// Check if user is a member of the metabase organization (including private members)
|
||||
const { data: membership } = await github.rest.orgs.getMembershipForUser({
|
||||
org: 'metabase',
|
||||
username: issueAuthor
|
||||
});
|
||||
|
||||
if (membership.state === 'active') {
|
||||
console.log(`User ${issueAuthor} is an active member of the Metabase organization. Proceeding with bug fix automation.`);
|
||||
core.setOutput('is_employee', 'true');
|
||||
} else {
|
||||
console.log(`User ${issueAuthor} is not an active member of the Metabase organization. Skipping bug fix automation.`);
|
||||
core.setOutput('is_employee', 'false');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
console.log(`User ${issueAuthor} is not a member of the Metabase organization. Skipping bug fix automation.`);
|
||||
} else {
|
||||
console.error('Error checking organization membership:', error);
|
||||
}
|
||||
core.setOutput('is_employee', 'false');
|
||||
}
|
||||
|
||||
auto-fix-bug:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
@@ -103,8 +28,10 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
needs: check-employee-and-issue
|
||||
if: needs.check-employee-and-issue.outputs.is_employee == 'true'
|
||||
if: |
|
||||
(github.event_name == 'issues' && github.event.label.name == 'Claude Bug Fix' && github.event.issue.state == 'open') ||
|
||||
(github.event_name == 'workflow_dispatch') ||
|
||||
(github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'claude-fix/'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -112,67 +39,95 @@ jobs:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get issue data and check employee status
|
||||
id: get_issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { getIssueData } = require('${{ github.workspace }}/.github/scripts/get-issue-data.js');
|
||||
|
||||
try {
|
||||
const result = await getIssueData({
|
||||
context,
|
||||
github,
|
||||
manualIssueNumber: '${{ github.event.inputs.issue_number }}'
|
||||
});
|
||||
|
||||
const { issueNumber, issueData, employeeComments, prReviews, triggerType, isIssueAuthorEmployee } = result;
|
||||
|
||||
// Check employee status and fail if not authorized
|
||||
if (!result.isIssueAuthorEmployee && '${{ github.event.inputs.override_employee_check }}' !== 'true') {
|
||||
core.setFailed(`User ${result.issueData.user.login} is not a Metabase employee. Skipping bug fix automation.`);
|
||||
}
|
||||
|
||||
core.setOutput('issue_number', issueNumber);
|
||||
core.setOutput('issue_data', JSON.stringify(issueData));
|
||||
core.setOutput('issue_comments', JSON.stringify(employeeComments));
|
||||
core.setOutput('pr_reviews', JSON.stringify(prReviews));
|
||||
core.setOutput('trigger_type', triggerType);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
|
||||
- name: Set up git configuration
|
||||
run: |
|
||||
git config --global user.name "Claude Code[bot]"
|
||||
git config --global user.email "noreply@anthropic.com"
|
||||
|
||||
- name: Prepare dynamic issue info
|
||||
id: prepare_issue_info
|
||||
- name: Prepare issue context and bug fix prompt
|
||||
id: prepare_prompt
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_DATA: ${{ needs.check-employee-and-issue.outputs.issue_data }}
|
||||
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
|
||||
ISSUE_COMMENTS: ${{ steps.get_issue.outputs.issue_comments }}
|
||||
PR_REVIEWS: ${{ steps.get_issue.outputs.pr_reviews }}
|
||||
TRIGGER_TYPE: ${{ steps.get_issue.outputs.trigger_type }}
|
||||
with:
|
||||
script: |
|
||||
const issueData = JSON.parse(process.env.ISSUE_DATA);
|
||||
const issueNumber = '${{ needs.check-employee-and-issue.outputs.issue_number }}';
|
||||
const issueComments = JSON.parse(process.env.ISSUE_COMMENTS);
|
||||
const prReviews = JSON.parse(process.env.PR_REVIEWS);
|
||||
const triggerType = process.env.TRIGGER_TYPE;
|
||||
const issueNumber = '${{ steps.get_issue.outputs.issue_number }}';
|
||||
|
||||
// Convert issue title to kebab case
|
||||
// Convert issue title to kebab case and create branch name
|
||||
const issueTitle = issueData.title || 'untitled';
|
||||
const kebabTitle = issueTitle
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.substring(0, 50); // Limit length
|
||||
.substring(0, 50);
|
||||
const branchName = `claude-fix/issue-${issueNumber}-${kebabTitle}`;
|
||||
|
||||
// Create base branch name
|
||||
const baseBranchName = `claude-fix/issue-${issueNumber}-${kebabTitle}`;
|
||||
core.exportVariable('BASE_BRANCH_NAME', baseBranchName);
|
||||
// Format data for bash environment
|
||||
const formattedComments = issueComments.length > 0
|
||||
? issueComments.map(comment =>
|
||||
`**Comment by @${comment.user.login} on ${comment.created_at}:**\n${comment.body}`
|
||||
).join('\n\n---\n\n')
|
||||
: '';
|
||||
const formattedReviews = prReviews.length > 0
|
||||
? prReviews.map(review =>
|
||||
`**Review by @${review.user.login} on ${review.submitted_at} (Changes Requested):**\n${review.body || 'No review comment provided'}`
|
||||
).join('\n\n---\n\n')
|
||||
: '';
|
||||
|
||||
// Export issue info for the prompt
|
||||
core.exportVariable('BRANCH_NAME', branchName);
|
||||
core.exportVariable('ISSUE_NUMBER', issueNumber);
|
||||
core.exportVariable('ISSUE_TITLE', issueData.title);
|
||||
core.exportVariable('ISSUE_AUTHOR', issueData.user.login);
|
||||
core.exportVariable('ISSUE_CREATED', issueData.created_at);
|
||||
core.exportVariable('ISSUE_BODY', issueData.body || 'No description provided');
|
||||
core.exportVariable('ISSUE_COMMENTS_FORMATTED', formattedComments);
|
||||
core.exportVariable('PR_REVIEWS_FORMATTED', formattedReviews);
|
||||
core.exportVariable('TRIGGER_TYPE', triggerType);
|
||||
|
||||
- name: Determine branch strategy
|
||||
- name: Generate bug fix prompt
|
||||
run: |
|
||||
# Use the base branch name (no counters)
|
||||
BRANCH_NAME="${{ env.BASE_BRANCH_NAME }}"
|
||||
|
||||
# Check if this exact branch already exists
|
||||
if git branch -r --list "origin/${BRANCH_NAME}" | grep -q .; then
|
||||
echo "Branch already exists: $BRANCH_NAME"
|
||||
echo "Will push to existing branch and skip PR creation"
|
||||
echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Creating new branch: $BRANCH_NAME"
|
||||
echo "Will create new PR"
|
||||
echo "BRANCH_EXISTS=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Export the branch name
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Prepare bug fix prompt
|
||||
id: prepare_prompt
|
||||
run: |
|
||||
# Prepare the prompt for Claude
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
BUG_FIX_PROMPT<<PROMPT_END
|
||||
You are helping to automatically fix a bug reported in GitHub issue #${{ env.ISSUE_NUMBER }}.
|
||||
You are helping to fix a bug reported in GitHub issue #${{ env.ISSUE_NUMBER }}.
|
||||
|
||||
## Bug Report Details
|
||||
**Title:** ${{ env.ISSUE_TITLE }}
|
||||
@@ -182,8 +137,27 @@ jobs:
|
||||
**Description:**
|
||||
${{ env.ISSUE_BODY }}
|
||||
|
||||
**Comments:**
|
||||
${{ env.ISSUE_COMMENTS_FORMATTED }}
|
||||
EOF
|
||||
|
||||
# Add review feedback section only if this is a review trigger
|
||||
if [ "${{ env.TRIGGER_TYPE }}" = "pull_request_review" ]; then
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
|
||||
## Previous Fix Attempt - Review Feedback (MUST ADDRESS)
|
||||
Your previous automated fix attempt received the following feedback that needs to be addressed:
|
||||
|
||||
${{ env.PR_REVIEWS_FORMATTED }}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Add the rest of the prompt (always the same)
|
||||
cat << 'EOF' >> $GITHUB_ENV
|
||||
|
||||
## Instructions
|
||||
1. **Analyze the bug report** - Understand what the issue is describing
|
||||
1. **Analyze the issue description and comments** - Understand what the issue is describing
|
||||
a. Pay particular attention to reproduction steps and if there's an agreed upon fix.
|
||||
2. **Search the codebase** - Use Grep and Glob tools to find relevant files
|
||||
3. **Implement a fix** - Follow existing code patterns and conventions
|
||||
4. **Test the fix** - Run appropriate tests using the commands in CLAUDE.md:
|
||||
@@ -205,15 +179,26 @@ jobs:
|
||||
- Only modify actual production code in the main repository directories
|
||||
- The scratchpad will be cleaned up automatically - nothing from it will be committed
|
||||
|
||||
## If You Cannot Fix The Bug
|
||||
If you determine that you cannot create a definitive fix, create a file called `FAILED_FIX_ANALYSIS.md` with:
|
||||
1. **Why No Fix** - Specific reason why you couldn't implement a fix
|
||||
2. **Tool Limitations** - If you needed additional tools/access that weren't available
|
||||
|
||||
Start by understanding the bug and then systematically work toward a solution.
|
||||
PROMPT_END
|
||||
EOF
|
||||
|
||||
- name: Determine branch strategy
|
||||
run: |
|
||||
BRANCH_NAME="${{ env.BRANCH_NAME }}"
|
||||
if git branch -r --list "origin/${BRANCH_NAME}" | grep -q .; then
|
||||
echo "Branch already exists: $BRANCH_NAME"
|
||||
echo "Will push to existing branch and skip PR creation"
|
||||
echo "BRANCH_EXISTS=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Creating new branch: $BRANCH_NAME"
|
||||
echo "Will create new PR"
|
||||
echo "BRANCH_EXISTS=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Export the branch name
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Create scratchpad directory for Claude
|
||||
run: |
|
||||
# Create a scratchpad directory for Claude to use for temporary files
|
||||
@@ -249,20 +234,6 @@ jobs:
|
||||
- name: Check for changes and analysis
|
||||
id: check_changes
|
||||
run: |
|
||||
# Check if Claude created an analysis file first and store its content
|
||||
if [ -f "FAILED_FIX_ANALYSIS.md" ]; then
|
||||
echo "has_analysis=true" >> $GITHUB_OUTPUT
|
||||
echo "Analysis file found"
|
||||
# Store analysis content in a GitHub output (base64 encoded to handle multiline)
|
||||
ANALYSIS_CONTENT=$(base64 -w 0 FAILED_FIX_ANALYSIS.md)
|
||||
echo "analysis_content=$ANALYSIS_CONTENT" >> $GITHUB_OUTPUT
|
||||
# Remove analysis file so it doesn't count as a code change
|
||||
rm -f FAILED_FIX_ANALYSIS.md
|
||||
else
|
||||
echo "has_analysis=false" >> $GITHUB_OUTPUT
|
||||
echo "No analysis file found"
|
||||
fi
|
||||
|
||||
# Now check for actual code changes (excluding analysis file)
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
@@ -278,25 +249,16 @@ jobs:
|
||||
id: prepare_reviewers
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_DATA: ${{ needs.check-employee-and-issue.outputs.issue_data }}
|
||||
ISSUE_DATA: ${{ steps.get_issue.outputs.issue_data }}
|
||||
with:
|
||||
script: |
|
||||
// Get existing team labels from the issue
|
||||
const issueData = JSON.parse(process.env.ISSUE_DATA);
|
||||
const issueLabels = issueData.labels.map(label => label.name);
|
||||
const teamLabels = issueLabels.filter(label => label.startsWith('.Team/'));
|
||||
|
||||
console.log('Issue labels:', issueLabels);
|
||||
console.log('Team labels found:', teamLabels);
|
||||
|
||||
// Add issue creator as reviewer
|
||||
const issueCreator = issueData.user.login;
|
||||
|
||||
core.setOutput('team_labels', teamLabels.join(','));
|
||||
core.setOutput('reviewers', issueCreator);
|
||||
|
||||
console.log('Reviewer:', issueCreator);
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
@@ -363,25 +325,3 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
console.log('🤖 No code changes were made by Claude.');
|
||||
|
||||
// Check if Claude provided a detailed analysis
|
||||
if ('${{ steps.check_changes.outputs.has_analysis }}' === 'true') {
|
||||
try {
|
||||
// Decode the base64-encoded analysis content
|
||||
const analysisBase64 = '${{ steps.check_changes.outputs.analysis_content }}';
|
||||
const analysisContent = Buffer.from(analysisBase64, 'base64').toString('utf8');
|
||||
console.log('📋 Claude provided detailed analysis:');
|
||||
console.log('='.repeat(50));
|
||||
console.log(analysisContent);
|
||||
console.log('='.repeat(50));
|
||||
} catch (error) {
|
||||
console.log('❌ Could not decode analysis content:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('ℹ️ No detailed analysis file was created by Claude.');
|
||||
console.log('This could be because:');
|
||||
console.log('- The issue requires more context or clarification');
|
||||
console.log('- The fix is complex and requires human judgment');
|
||||
console.log('- The issue is not a straightforward code bug');
|
||||
console.log('- Claude couldn\'t locate the relevant code to modify');
|
||||
}
|
||||
|
||||
@@ -4,39 +4,53 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- enterprise/frontend/src/embedding-sdk-bundle/**
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_embedding_sources: ${{ steps.changes.outputs.frontend_embedding_sources }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
pr-title-reminder:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pull-requests: write
|
||||
needs: [ files-changed ]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.files-changed.outputs.frontend_embedding_sources == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Check if the PR title is already following Conventional Commits style
|
||||
uses: actions/github-script@v7
|
||||
id: is-conventional-commit
|
||||
with:
|
||||
result-encoding: string
|
||||
script: | # js
|
||||
const { isConventionalTitle } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
const pullRequestTitle = context.payload.pull_request.title
|
||||
const conventionalCommitRegex = /^(?<type>\w+)\((?<scope>\w+)\):/
|
||||
const match = pullRequestTitle.match(conventionalCommitRegex)
|
||||
if (match) {
|
||||
const { type, scope } = match.groups
|
||||
return ['feat', 'fix', 'perf', 'docs', 'style', 'refactor', 'test', 'build', 'ci'].includes(type) && scope === 'sdk'
|
||||
}
|
||||
|
||||
- name: Checkout repository so `gh` CLI works
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
return isConventionalTitle(pullRequestTitle)
|
||||
|
||||
- name: Remind PR authors to use conventional commit in the title
|
||||
if: ${{ steps.is-conventional-commit.outputs.result != 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { updateComment } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
await updateComment({github, context})
|
||||
script: | #js
|
||||
const { updateCommentForEmbedding } = require(".github/scripts/conventional-commit-pr-title-reminder/utils.js")
|
||||
|
||||
await updateCommentForEmbedding({github, context})
|
||||
|
||||
76
.github/workflows/drivers-stress-test.yml
vendored
@@ -689,25 +689,57 @@ jobs:
|
||||
junit-name: 'be-tests-starburst-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
# TODO(rileythomp, 2025-08-20): Re-enable vertica tests once the docker image is updated
|
||||
# be-tests-vertica-ee:
|
||||
# if: ${{ false && inputs.driver == 'Vertica' }}
|
||||
# runs-on: ubuntu-22.04
|
||||
# timeout-minutes: 90
|
||||
# env:
|
||||
# CI: 'true'
|
||||
# DRIVERS: vertica
|
||||
# services:
|
||||
# vertica:
|
||||
# image: vertica/vertica-ce:12.0.2-0
|
||||
# ports:
|
||||
# - "5433:5433"
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Make plugins directory
|
||||
# run: mkdir plugins
|
||||
# - name: Test Vertica driver
|
||||
# uses: ./.github/actions/test-driver
|
||||
# with:
|
||||
# junit-name: 'be-tests-vertica-ee'
|
||||
# test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
login-to-ecr:
|
||||
if: inputs.driver == 'Vertica'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Get ECR password
|
||||
id: login-ecr
|
||||
run: |
|
||||
PASSWORD=$(aws ecr get-login-password --region us-east-1)
|
||||
|
||||
REGISTRY="$(aws sts get-caller-identity --query Account --output text).dkr.ecr.us-east-1.amazonaws.com"
|
||||
|
||||
echo "docker_username=AWS" >> $GITHUB_OUTPUT
|
||||
echo "docker_password=$PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "registry=$REGISTRY" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
registry: ${{ steps.login-ecr.outputs.registry }}
|
||||
docker_username: ${{ steps.login-ecr.outputs.docker_username }}
|
||||
docker_password: ${{ steps.login-ecr.outputs.docker_password }}
|
||||
|
||||
be-tests-vertica-ee:
|
||||
if: inputs.driver == 'Vertica'
|
||||
runs-on: ubuntu-22.04
|
||||
needs: login-to-ecr
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: vertica
|
||||
services:
|
||||
vertica:
|
||||
image: ${{ needs.login-to-ecr.outputs.registry }}/vertica-ce:25.3.0-0
|
||||
ports:
|
||||
- "5433:5433"
|
||||
credentials:
|
||||
username: ${{ needs.login-to-ecr.outputs.docker_username }}
|
||||
password: ${{ needs.login-to-ecr.outputs.docker_password }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make plugins directory
|
||||
run: mkdir plugins
|
||||
- name: Test Vertica driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-vertica-ee'
|
||||
test-args: ":only ${{ inputs.test }} :times ${{ inputs.times }}"
|
||||
|
||||
261
.github/workflows/drivers.yml
vendored
@@ -90,6 +90,7 @@ jobs:
|
||||
junit-name: 'be-tests-athena-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -106,6 +107,19 @@ jobs:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
# DS 2025-09-26 temporarily disabled while we diagnose flakes
|
||||
# - name: Transforms Python Tests
|
||||
# test-args: >-
|
||||
# :only-tags [:mb/transforms-python-test]
|
||||
# needs-python-runner: true
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: bigquery-cloud-sdk
|
||||
@@ -116,15 +130,19 @@ jobs:
|
||||
MB_BIGQUERY_TEST_REFRESH_TOKEN: ${{ secrets.MB_BIGQUERY_TEST_REFRESH_TOKEN }}
|
||||
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON }}
|
||||
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING }}
|
||||
name: BigQuery
|
||||
name: BigQuery ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test BigQuery Cloud SDK driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-bigquery-cloud-sdk-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -141,6 +159,18 @@ jobs:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: clickhouse
|
||||
@@ -148,9 +178,14 @@ jobs:
|
||||
MB_CLICKHOUSE_TEST_PORT: 8123
|
||||
MB_CLICKHOUSE_TEST_OLD_PORT: 8124
|
||||
MB_CLICKHOUSE_TEST_NGINX_PORT: 8127
|
||||
name: Clickhouse
|
||||
name: Clickhouse ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Add ClickHouse TLS instance to /etc/hosts
|
||||
run: |
|
||||
sudo echo "127.0.0.1 server.clickhouseconnect.test" | sudo tee -a /etc/hosts
|
||||
@@ -170,8 +205,7 @@ jobs:
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-clickhouse-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -208,6 +242,7 @@ jobs:
|
||||
junit-name: 'be-tests-druid-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -244,6 +279,7 @@ jobs:
|
||||
junit-name: 'be-tests-druid-jdbc-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -280,6 +316,7 @@ jobs:
|
||||
junit-name: 'be-tests-databricks-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -317,6 +354,7 @@ jobs:
|
||||
junit-name: 'be-tests-databricks-multi-catalog-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -338,12 +376,17 @@ jobs:
|
||||
name: H2
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test H2 driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-h2'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -388,6 +431,11 @@ jobs:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -442,6 +490,11 @@ jobs:
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
#if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
@@ -476,6 +529,15 @@ jobs:
|
||||
- name: MongoDB Latest
|
||||
junit-name: be-tests-mongo-latest-ee
|
||||
image: mongo:latest
|
||||
job:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:exclude-tags '[:mongo-sharded-cluster-tests :mb/transforms-python-test]'
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mongo
|
||||
@@ -489,16 +551,19 @@ jobs:
|
||||
env:
|
||||
MONGO_INITDB_ROOT_USERNAME: metabase
|
||||
MONGO_INITDB_ROOT_PASSWORD: metasample123
|
||||
name: ${{ matrix.version.name }}
|
||||
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -562,7 +627,7 @@ jobs:
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
:exclude-tags [:mongo-sharded-cluster-tests]
|
||||
:exclude-tags '[:mongo-sharded-cluster-tests :mb/transforms-python-test]'
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
@@ -670,6 +735,7 @@ jobs:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -704,6 +770,11 @@ jobs:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
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
|
||||
@@ -745,6 +816,11 @@ jobs:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
#if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
@@ -821,6 +897,7 @@ jobs:
|
||||
junit-name: 'be-tests-presto-jdbc-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -850,8 +927,14 @@ jobs:
|
||||
skip: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/upload-tests]
|
||||
:exclude-tags '[:mb/upload-tests :mb/transforms-python-test]'
|
||||
:fail-fast? true
|
||||
- name: Redshift Transforms Python Tests
|
||||
skip: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
:fail-fast? true
|
||||
needs-python-runner: true
|
||||
|
||||
env:
|
||||
CI: 'true'
|
||||
@@ -864,6 +947,11 @@ jobs:
|
||||
name: ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test ${{ matrix.job.name }}
|
||||
if: ${{ !matrix.job.skip }}
|
||||
uses: ./.github/actions/test-driver
|
||||
@@ -887,6 +975,19 @@ jobs:
|
||||
if: ${{ !inputs.skip }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
# DS 2025-09-26 temporarily disabled while we diagnose flakes
|
||||
# - name: Transforms Python Tests
|
||||
# test-args: >-
|
||||
# :only-tags [:mb/transforms-python-test]
|
||||
# needs-python-runner: true
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: snowflake
|
||||
@@ -905,15 +1006,19 @@ jobs:
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_CUSTOM_USER: RSA_ROLE_TEST_CUSTOM_USER
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_ROLE: RSA_ROLE_TEST_ROLE
|
||||
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
|
||||
name: Snowflake
|
||||
name: Snowflake ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test Snowflake driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-snowflake-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -948,6 +1053,7 @@ jobs:
|
||||
junit-name: 'be-tests-sparksql-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -977,6 +1083,7 @@ jobs:
|
||||
junit-name: 'be-tests-sqlite-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1004,6 +1111,15 @@ jobs:
|
||||
- name: SQL Server 2022
|
||||
junit-name: be-tests-sqlserver-2022-ee
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
job:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: sqlserver
|
||||
@@ -1019,15 +1135,19 @@ jobs:
|
||||
ACCEPT_EULA: Y
|
||||
SA_PASSWORD: 'P@ssw0rd'
|
||||
MSSQL_MEMORY_LIMIT_MB: 1024
|
||||
name: ${{ matrix.version.name }}
|
||||
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
test-args: ${{ matrix.job.test-args }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -1079,42 +1199,75 @@ jobs:
|
||||
# aws-region: ${{ vars.AWS_REGION }}
|
||||
# trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
# TODO(rileythomp, 2025-08-20): Re-enable vertica tests once the docker image is updated
|
||||
# be-tests-vertica-ee:
|
||||
# needs: determine-driver-skips
|
||||
# if: ${{ false && !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
# runs-on: ubuntu-22.04
|
||||
# timeout-minutes: 60
|
||||
# env:
|
||||
# CI: 'true'
|
||||
# DRIVERS: vertica
|
||||
# services:
|
||||
# vertica:
|
||||
# image: vertica/vertica-ce:12.0.2-0
|
||||
# ports:
|
||||
# - "5433:5433"
|
||||
# name: Vertica
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Make plugins directory
|
||||
# run: mkdir plugins
|
||||
# - name: Test Vertica driver
|
||||
# uses: ./.github/actions/test-driver
|
||||
# with:
|
||||
# junit-name: 'be-tests-vertica-ee'
|
||||
# test-args: >-
|
||||
# :only-tags [:mb/driver-tests]
|
||||
# - name: Upload Test Results
|
||||
# uses: ./.github/actions/upload-test-results
|
||||
# if: always()
|
||||
# with:
|
||||
# input-path: ./target/junit/
|
||||
# output-name: ${{ github.job }}
|
||||
# bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
# aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
# aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
# aws-region: ${{ vars.AWS_REGION }}
|
||||
# trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
login-to-ecr:
|
||||
needs: determine-driver-skips
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
|
||||
role-session-name: GitHub_to_AWS_via_FederatedOIDC
|
||||
aws-region: us-east-1
|
||||
- name: Get ECR password
|
||||
id: login-ecr
|
||||
run: |
|
||||
PASSWORD=$(aws ecr get-login-password --region us-east-1)
|
||||
|
||||
REGISTRY="$(aws sts get-caller-identity --query Account --output text).dkr.ecr.us-east-1.amazonaws.com"
|
||||
|
||||
echo "docker_username=AWS" >> $GITHUB_OUTPUT
|
||||
echo "docker_password=$PASSWORD" >> $GITHUB_OUTPUT
|
||||
echo "registry=$REGISTRY" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
registry: ${{ steps.login-ecr.outputs.registry }}
|
||||
docker_username: ${{ steps.login-ecr.outputs.docker_username }}
|
||||
docker_password: ${{ steps.login-ecr.outputs.docker_password }}
|
||||
|
||||
be-tests-vertica-ee:
|
||||
needs: [determine-driver-skips, login-to-ecr]
|
||||
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: vertica
|
||||
services:
|
||||
vertica:
|
||||
image: ${{ needs.login-to-ecr.outputs.registry }}/vertica-ce:25.3.0-0
|
||||
ports:
|
||||
- "5433:5433"
|
||||
credentials:
|
||||
username: ${{ needs.login-to-ecr.outputs.docker_username }}
|
||||
password: ${{ needs.login-to-ecr.outputs.docker_password }}
|
||||
name: Vertica
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make plugins directory
|
||||
run: mkdir plugins
|
||||
- name: Test Vertica driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-vertica-ee'
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
:exclude-tags [:mb/transforms-python-test]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
drivers-tests-result:
|
||||
needs:
|
||||
|
||||
@@ -140,7 +140,10 @@ jobs:
|
||||
run: node e2e/runner/run_cypress_ci.js snapshot --browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run component tests for Embedding SDK
|
||||
run: node e2e/runner/run_cypress_ci.js component --browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
run: |
|
||||
node e2e/runner/run_cypress_ci.js component \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }} \
|
||||
--env grepTags="-@skip-backward-compatibility",grepOmitFiltered=true
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
|
||||
57
.github/workflows/e2e-test.yml
vendored
@@ -60,6 +60,14 @@ jobs:
|
||||
CYPRESS_IS_EMBEDDING_SDK: "${{ inputs.name == 'embedding-sdk' }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
|
||||
- name: Setup python-runner
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
run: ./.github/scripts/setup-python-runner.sh
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
@@ -123,7 +131,7 @@ jobs:
|
||||
SPLIT_INDEX: ${{ inputs.split_index }}
|
||||
SPLIT_FILE: "./e2e/support/timings.json"
|
||||
SPLIT_OUTPUT_FILE: "newTimes.json"
|
||||
SPLIT_TIME_THRESHOLD: 5.0
|
||||
SPLIT_TIME_THRESHOLD: 0.01 # 1% threshold for changes
|
||||
# do not write cypress-split summary to github
|
||||
SPLIT_SUMMARY: false
|
||||
SPEC_PATH: ${{ env.specs || inputs.specs }}
|
||||
@@ -143,7 +151,7 @@ jobs:
|
||||
run: |
|
||||
echo "Running tests with specs: $SPEC_PATH"
|
||||
node e2e/runner/run_cypress_ci.js e2e \
|
||||
--env grepTags="${{inputs.tags}}+-@skip",grepOmitFiltered=true \
|
||||
--env grepTags="${{inputs.tags && format('{0}+-@skip', inputs.tags) || '-@skip'}}",grepOmitFiltered=true \
|
||||
--spec "$SPEC_PATH" \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
@@ -177,6 +185,51 @@ jobs:
|
||||
name: failed-tests-${{ inputs.name }}-${{ inputs.edition }}
|
||||
path: ./cypress/test-results
|
||||
|
||||
- name: Extract chunk-specific changed timings
|
||||
id: extract-timings
|
||||
if: inputs.split_index >= 0 && github.ref == 'refs/heads/master'
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const oldTimingsPath = "./e2e/support/timings.json";
|
||||
const newTimingsPath = "./e2e/support/newTimes.json";
|
||||
|
||||
if (!fs.existsSync(newTimingsPath)) {
|
||||
throw new Error(`newTimes.json not found - cypress-split may not have generated it`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(oldTimingsPath)) {
|
||||
throw new Error(`Original timings file ${oldTimingsPath} not found`);
|
||||
}
|
||||
|
||||
const oldTimings = fs.existsSync(oldTimingsPath)
|
||||
? JSON.parse(fs.readFileSync(oldTimingsPath, "utf8"))
|
||||
: { durations: [] };
|
||||
|
||||
const newTimings = fs.existsSync(newTimingsPath)
|
||||
? JSON.parse(fs.readFileSync(newTimingsPath, "utf8"))
|
||||
: { durations: [] };
|
||||
|
||||
const { extractChangedTimings } = require('${{ github.workspace }}/.github/scripts/extract-changed-timings.js');
|
||||
|
||||
const result = extractChangedTimings({ oldTimings, newTimings });
|
||||
|
||||
if (result.hasChanges) {
|
||||
core.setOutput('has-changes', 'true');
|
||||
fs.writeFileSync('chunkTimings.json', JSON.stringify(result.changedTimings, null, 2));
|
||||
console.log(`Extracted ${Object.keys(result.changedTimings).length} changed timings for chunk index ${{ inputs.split_index }}`);
|
||||
}
|
||||
|
||||
- name: Upload Timing Data
|
||||
uses: actions/upload-artifact@v4
|
||||
if: steps.extract-timings.outcome == 'success' && steps.extract-timings.outputs.has-changes == 'true'
|
||||
with:
|
||||
name: timing-data-${{ inputs.name }}-${{ inputs.split_index }}
|
||||
path: chunkTimings.json
|
||||
retention-days: 1 # gets merged immediately by parent workflow
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
|
||||
61
.github/workflows/e2e-tests.yml
vendored
@@ -87,6 +87,67 @@ jobs:
|
||||
name: Flaky
|
||||
tags: "@flaky"
|
||||
|
||||
collect-timings:
|
||||
name: Collect E2E Timings
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
needs: e2e-tests
|
||||
if: always() && !cancelled() && github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
e2e/support
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Download all timing artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: timing-data-*
|
||||
path: timing-files
|
||||
merge-multiple: false
|
||||
continue-on-error: true
|
||||
|
||||
- name: Merge split timings
|
||||
id: merge
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const chunkFiles = glob.sync('timing-files/*/chunkTimings.json');
|
||||
|
||||
const specs = new Map();
|
||||
chunkFiles.forEach(file => {
|
||||
const { durations = [] } = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
durations.forEach(({ spec, duration }) => specs.set(spec, duration));
|
||||
});
|
||||
|
||||
if (specs.size === 0) {
|
||||
throw new Error('No timing data found - no specs had timing changes');
|
||||
}
|
||||
|
||||
const outputPath = 'e2e/support/runTimings.json';
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
|
||||
const runTimings = {
|
||||
durations: [...specs].map(([spec, duration]) => ({ spec, duration }))
|
||||
};
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(runTimings, null, 2));
|
||||
console.log(`Merged timing data for ${specs.size} specs`);
|
||||
|
||||
- name: Upload merged timing files
|
||||
if: steps.merge.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: merged-e2e-timings
|
||||
path: e2e/support/runTimings.json
|
||||
retention-days: 7
|
||||
|
||||
e2e-tests-result:
|
||||
needs:
|
||||
- e2e-tests
|
||||
|
||||
49
.github/workflows/embedding-sdk-labels-reminder.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Embedding SDK Labels Reminder
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [auto_merge_enabled, review_requested, labeled, unlabeled, synchronize]
|
||||
branches:
|
||||
- "master"
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Check which files changed
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend_embedding_sdk_package_sources: ${{ steps.changes.outputs.frontend_embedding_sdk_package_sources }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test which files changed
|
||||
uses: dorny/paths-filter@v3.0.0
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
embedding-sdk-package-no-release-notes-reminder:
|
||||
needs: [ files-changed ]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
(needs.files-changed.outputs.frontend_embedding_sdk_package_sources == 'true')
|
||||
name: Embedding SDK package release-notes/no-release-notes label reminder
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
|
||||
id: verify-release-notes-labels
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
valid-labels: 'release-notes, no-release-notes'
|
||||
disable-reviews: true
|
||||
- name: Comment on PR if label should be added
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/actions/notify-pull-request
|
||||
with:
|
||||
remove-previous-comment: true
|
||||
add-new-comment: ${{ steps.verify-release-notes-labels.outcome == 'failure' }}
|
||||
message: "⚠️ Changes were made to the <b>Embedding SDK Package</b>. Review this PR and apply one of the following labels.<br><br>- <code>release-notes</code> — select if this PR must be included in the main Metabase changelog release notes.<br>- <code>no-release-notes</code> — select if this PR should not be included in the main Metabase changelog release notes."
|
||||
include-log: true
|
||||
20
.github/workflows/loki-update.yml
vendored
@@ -26,26 +26,8 @@ jobs:
|
||||
- name: Compile CLJS
|
||||
run: yarn build-pure:cljs
|
||||
|
||||
- name: Build Storybook
|
||||
# We won't be able to use Storybook play's `userEvent` with production build
|
||||
# see https://github.com/storybookjs/storybook/issues/19758
|
||||
run: NODE_ENV=development yarn build-storybook
|
||||
|
||||
- name: Serve Storybook
|
||||
run: yarn http-server storybook-static -p 6006 &
|
||||
shell: bash
|
||||
|
||||
- name: Wait for Storybook to become available
|
||||
run: |
|
||||
echo "Waiting for Storybook to be served..."
|
||||
while ! curl --silent --fail http://localhost:6006; do
|
||||
sleep 5
|
||||
echo "Retrying..."
|
||||
done
|
||||
echo "Storybook is being served!"
|
||||
|
||||
- name: Update Loki Snapshots
|
||||
run: yarn test-visual:loki || yarn test-visual:loki-approve-diff && yarn test-visual:loki-prune
|
||||
run: yarn test-visual:loki:ci || yarn test-visual:loki-approve-diff && yarn test-visual:loki-prune
|
||||
|
||||
- name: Check for changes
|
||||
id: check-changes
|
||||
|
||||
8
.github/workflows/loki.yml
vendored
@@ -53,14 +53,8 @@ jobs:
|
||||
- name: Compile CLJS
|
||||
run: NODE_ENV=development yarn build-pure:cljs
|
||||
|
||||
- name: Build Storybook
|
||||
# We won't be able to use Storybook play's `userEvent` with production build
|
||||
# see https://github.com/storybookjs/storybook/issues/19758
|
||||
run: NODE_ENV=development yarn build-storybook
|
||||
|
||||
- name: Run Loki Visual Tests
|
||||
run: |
|
||||
yarn loki --requireReference --reactUri file:./storybook-static --chromeFlags='--headless --disable-gpu' --verboseRenderer
|
||||
run: yarn test-visual:loki:ci
|
||||
|
||||
- name: Generate Visual Report on Failure
|
||||
if: failure()
|
||||
|
||||
8
.github/workflows/perf-test-labeled.yml
vendored
@@ -9,9 +9,13 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
uberjar:
|
||||
if: ${{ github.event.label.name == 'Run-perf' }}
|
||||
uses: ./.github/workflows/uberjar.yml
|
||||
secrets: inherit
|
||||
|
||||
run-perf-test:
|
||||
needs: [uberjar]
|
||||
if: ${{ github.event.label.name == 'Run-perf' }}
|
||||
uses: ./.github/workflows/perf-test.yml
|
||||
with:
|
||||
wait_for_uberjar: true
|
||||
secrets: inherit
|
||||
|
||||
62
.github/workflows/perf-test.yml
vendored
@@ -3,11 +3,6 @@ run-name: Run perf test for ${{ github.ref_name }} by @${{ github.actor }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
wait_for_uberjar:
|
||||
description: "Wait for uberjar build"
|
||||
required: false
|
||||
type: boolean
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -31,63 +26,6 @@ jobs:
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
with:
|
||||
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
|
||||
- name: Wait for uberjar
|
||||
id: wait_for_uberjar
|
||||
if: ${{ inputs.wait_for_uberjar == true }}
|
||||
run: |
|
||||
## Get workflow run id for uberjar build
|
||||
curl -Ls --output e2e-tests.json \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/workflows/run-tests.yml/runs?head_sha=${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ID=$(jq -r '.workflow_runs[0].id' e2e-tests.json)
|
||||
echo "Run ID: ${ID}"
|
||||
|
||||
## Get workflow run id for uberjar build
|
||||
while [ -z "$JOB_ID" ]; do
|
||||
NEXT_URL="https://api.github.com/repos/${{ github.repository }}/actions/runs/${ID}/jobs?filter=latest"
|
||||
|
||||
# Search for build job
|
||||
echo "Searching for build job..."
|
||||
while [ -z "$JOB_ID" ] && [ -n "$NEXT_URL" ]; do
|
||||
# Get one page of jobs
|
||||
RESPONSE=$(curl -s -i \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"$NEXT_URL")
|
||||
|
||||
# Extract headers and body
|
||||
HEADERS=$(echo "$RESPONSE" | sed -n '1,/^\r$/p')
|
||||
BODY=$(echo "$RESPONSE" | sed '1,/^\r$/d')
|
||||
LINK_HEADER=$(echo "$HEADERS" | grep -i '^link:' | sed 's/^link: //')
|
||||
|
||||
# Check for next page in Link header
|
||||
if echo "$LINK_HEADER" | grep -q 'rel="next"'; then
|
||||
NEXT_URL=$(echo "$LINK_HEADER" | sed -E 's/.*<([^>]+)>; rel="next".*/\1/')
|
||||
else
|
||||
NEXT_URL=""
|
||||
fi
|
||||
|
||||
# Check for build job
|
||||
JOB_ID=$(echo "$BODY" | jq -r '.jobs[] | select(.name | contains("build (ee)")) | .id')
|
||||
done
|
||||
done
|
||||
echo "Job ID: ${JOB_ID}"
|
||||
|
||||
## Wait for uberjar build to complete
|
||||
while [ true ]; do
|
||||
curl -Ls --output uberjar.json \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/actions/jobs/${JOB_ID}
|
||||
jq -r '.steps[] | select(.name == "Prepare uberjar artifact") | .status' uberjar.json | grep -q "completed" && break
|
||||
echo "Waiting for uberjar build..."
|
||||
sleep 10
|
||||
done
|
||||
echo "run_id=$(jq -r '.workflow_runs[0].id' e2e-tests.json)" >> $GITHUB_OUTPUT
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: ./.github/actions/fetch-artifact
|
||||
with:
|
||||
|
||||
24
.github/workflows/release-embedding-sdk.yml
vendored
@@ -285,10 +285,6 @@ jobs:
|
||||
run: |
|
||||
./bin/embedding-sdk/release_utils.bash update_package_json_template ${{ needs.determine-sdk-version.outputs.sdk_version }}
|
||||
|
||||
- name: Update changelog
|
||||
run: |
|
||||
yarn embedding-sdk:generate-changelog -o changelog-diff
|
||||
|
||||
- name: Build SDK package
|
||||
run: yarn run build-embedding-sdk-package
|
||||
|
||||
@@ -301,12 +297,6 @@ jobs:
|
||||
name: metabase-sdk
|
||||
path: ./resources/embedding-sdk
|
||||
|
||||
- name: Upload changelog diff
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdk-changelog-diff
|
||||
path: ./changelog-diff
|
||||
|
||||
publish-npm:
|
||||
needs: [build-sdk, determine-sdk-version]
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -335,19 +325,7 @@ jobs:
|
||||
run: |
|
||||
bash ./bin/embedding-sdk/release_utils.bash update_package_json_template ${{ env.sdk_version }}
|
||||
|
||||
- name: Retrieve SDK changelog diff
|
||||
if: inputs.branch != 'master'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: sdk-changelog-diff
|
||||
|
||||
- name: Update changelog
|
||||
if: inputs.branch != 'master'
|
||||
run: |
|
||||
cat changelog-diff enterprise/frontend/src/embedding-sdk-bundle/CHANGELOG.md > new-changelog
|
||||
mv new-changelog enterprise/frontend/src/embedding-sdk-bundle/CHANGELOG.md
|
||||
|
||||
- name: Create a PR updating readme + published version, and changelog (using GitHub Metabase Automation account)
|
||||
- name: Create a PR updating readme + published version (using GitHub Metabase Automation account)
|
||||
run: |
|
||||
git checkout -b update-sdk-version-${{ env.sdk_version }}
|
||||
|
||||
|
||||
5
.github/workflows/run-tests.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
@@ -74,7 +75,7 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
backend-tests:
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ !cancelled() && needs.files-changed.result == 'success' }}
|
||||
needs: [files-changed, static-viz-files-changed]
|
||||
uses: ./.github/workflows/backend.yml
|
||||
secrets: inherit
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
|
||||
e2e-tests:
|
||||
needs: [files-changed, uberjar]
|
||||
if: always() && !cancelled()
|
||||
if: always() && !cancelled() && needs.files-changed.result == 'success'
|
||||
uses: ./.github/workflows/e2e-tests.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
|
||||
92
.github/workflows/update-e2e-timings.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
name: Update E2E Timings
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days_back:
|
||||
description: "Number of days to look back for timing artifacts"
|
||||
type: number
|
||||
default: 7
|
||||
required: false
|
||||
schedule:
|
||||
# Run weekly on Sundays at 6 AM UTC
|
||||
- cron: "0 6 * * 0"
|
||||
|
||||
jobs:
|
||||
collect-and-average-timings:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DAYS_BACK: ${{ inputs.days_back || 7 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Collect and average timing data
|
||||
id: collect-timings
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { collectAndAverageTimings } = require('${{ github.workspace }}/.github/scripts/average-e2e-timings.js');
|
||||
const result = await collectAndAverageTimings({
|
||||
github,
|
||||
context,
|
||||
token: '${{ secrets.GITHUB_TOKEN }}',
|
||||
daysBack: ${{ env.DAYS_BACK }},
|
||||
existingTimingsPath: 'e2e/support/timings.json'
|
||||
});
|
||||
|
||||
core.setOutput('artifact-count', result.artifactCount);
|
||||
|
||||
- name: Set up git configuration
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
BRANCH_NAME="update-e2e-timings-$(date +%Y%m%d-%H%M%S)"
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
echo "Branch name will be: $BRANCH_NAME"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ env.BRANCH_NAME }}
|
||||
base: master
|
||||
commit-message: |
|
||||
Update E2E test timings with averaged data
|
||||
|
||||
Collected timing data from ${{ env.DAYS_BACK }} days of test runs.
|
||||
All specs with timing data were included (simple averages).
|
||||
|
||||
🤖 Generated automatically by update-e2e-timings workflow
|
||||
|
||||
Co-Authored-By: GitHub Actions <actions@github.com>
|
||||
committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
title: "[chore] Update E2E test timings with recent data"
|
||||
body: |
|
||||
## Summary
|
||||
This PR updates the E2E test timing data with averaged values from recent test runs.
|
||||
|
||||
## Details
|
||||
- Collected data from the last **${{ env.DAYS_BACK }} days** of test runs
|
||||
- Averaged timing data from **${{ steps.collect-timings.outputs.artifact-count }}** test runs
|
||||
|
||||
## Generated automatically
|
||||
🤖 This PR was created automatically by the `update-e2e-timings` workflow.
|
||||
The timing data helps cypress-split optimize test distribution across parallel jobs.
|
||||
|
||||
Co-Authored-By: GitHub Actions <actions@github.com>
|
||||
labels: |
|
||||
.CI & Tests
|
||||
add-paths: e2e/support/timings.json
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |