Compare commits
406 Commits
proj-pytho
...
python-run
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64977bffa6 | ||
|
|
25dddd8f32 | ||
|
|
ee806be8b7 | ||
|
|
0ea6e387c0 | ||
|
|
f6f128b87d | ||
|
|
8cb9ca883d | ||
|
|
384d0b5d3b | ||
|
|
1561702bdb | ||
|
|
4d0eef7217 | ||
|
|
cc6827ae8c | ||
|
|
08dffba554 | ||
|
|
18b59c1b4d | ||
|
|
d56be957f0 | ||
|
|
6631ed5389 | ||
|
|
16c4a16025 | ||
|
|
2219786d93 | ||
|
|
93052b8a8e | ||
|
|
e01cbfc87e | ||
|
|
899d6edb3c | ||
|
|
5705ba7f81 | ||
|
|
0261a15e7b | ||
|
|
02cec9f38d | ||
|
|
8fa9204d57 | ||
|
|
88229c321e | ||
|
|
4d0d576c13 | ||
|
|
7e4dfde94f | ||
|
|
85fc7cb3fd | ||
|
|
16dc5bece0 | ||
|
|
3f15dcd0ad | ||
|
|
6e17e96bf0 | ||
|
|
59dc6a1376 | ||
|
|
4614a37972 | ||
|
|
648039ca1a | ||
|
|
2804f26f4b | ||
|
|
158b78fb25 | ||
|
|
9b82a4d92d | ||
|
|
5fb41fd3b1 | ||
|
|
db70faad90 | ||
|
|
dddf9d679c | ||
|
|
c6c0bf73dd | ||
|
|
c5da59566e | ||
|
|
f914bf8714 | ||
|
|
448e9c8278 | ||
|
|
0ac7bce92b | ||
|
|
ccd8a11ee7 | ||
|
|
5164a08b2a | ||
|
|
e50317d86d | ||
|
|
3800f7452a | ||
|
|
f89073a6bd | ||
|
|
d848a88e64 | ||
|
|
356025f41b | ||
|
|
a0c2ebd9e9 | ||
|
|
c9bc786c60 | ||
|
|
1f024e148d | ||
|
|
7c8a53c4d5 | ||
|
|
bc28d50013 | ||
|
|
d0af14f90d | ||
|
|
6afcf2aed5 | ||
|
|
99d7d54af3 | ||
|
|
1610538386 | ||
|
|
1d94009c3d | ||
|
|
b8360b6da3 | ||
|
|
cb13bdc086 | ||
|
|
1704f14bda | ||
|
|
593ecfb952 | ||
|
|
611220c5e0 | ||
|
|
b77f570c57 | ||
|
|
8ba64b7bcd | ||
|
|
e1dcfdab2e | ||
|
|
5c9b282361 | ||
|
|
cf5c7bd4c3 | ||
|
|
46adff882f | ||
|
|
87923e1e1d | ||
|
|
5bd544baf5 | ||
|
|
f8d410f858 | ||
|
|
cfa62e4b8b | ||
|
|
fa2820320e | ||
|
|
33070f6e06 | ||
|
|
f43c6462d8 | ||
|
|
8f4ccdf2ba | ||
|
|
788bf01ea9 | ||
|
|
c4de2725e3 | ||
|
|
cd0965a299 | ||
|
|
95452f41d3 | ||
|
|
b323ecd565 | ||
|
|
b2939990fc | ||
|
|
c63dff0d7b | ||
|
|
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 |
@@ -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
|
||||
@@ -621,10 +624,11 @@
|
||||
metabase.test/with-non-admin-groups-no-root-collection-perms hooks.common/do*
|
||||
metabase.test/with-persistence-enabled! hooks.metabase.model-persistence.test-util/with-persistence-enabled!
|
||||
metabase.test/with-prometheus-system! hooks.common/with-two-bindings
|
||||
metabase.test/with-random-premium-token! hooks.common/with-one-binding
|
||||
metabase.test/with-temp hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.test/with-temp-file hooks.metabase.test.util/with-temp-file
|
||||
metabase.test/with-temporary-setting-values hooks.metabase.test.util/with-temporary-setting-values
|
||||
metabase.users.api-test/with-temp-user-email! hooks.common/with-one-binding
|
||||
metabase.users.api-test/with-temp-user-email! hooks.common/with-one-binding
|
||||
metabase.util.i18n/deferred-trs hooks.metabase.util.i18n/deferred-trs
|
||||
metabase.util.i18n/deferred-tru hooks.metabase.util.i18n/deferred-tru
|
||||
metabase.util.i18n/trs hooks.metabase.util.i18n/trs
|
||||
@@ -713,13 +717,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 +788,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 +808,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 +836,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
|
||||
|
||||
@@ -428,6 +428,11 @@
|
||||
:api #{metabase.database-routing.core}
|
||||
:uses #{premium-features}}
|
||||
|
||||
dependencies
|
||||
{:team "Querying"
|
||||
:api #{metabase.premium-features.core}
|
||||
:uses #{premium-features}}
|
||||
|
||||
driver
|
||||
{:team "Drivers"
|
||||
:api #{metabase.driver
|
||||
@@ -502,6 +507,7 @@
|
||||
metabase.embedding.init
|
||||
metabase.embedding.jwt
|
||||
metabase.embedding.settings
|
||||
metabase.embedding.util
|
||||
metabase.embedding.validation}
|
||||
:uses #{analytics
|
||||
api
|
||||
@@ -516,6 +522,7 @@
|
||||
public-sharing
|
||||
queries
|
||||
query-processor
|
||||
request
|
||||
server
|
||||
settings
|
||||
tiles
|
||||
@@ -547,6 +554,18 @@
|
||||
settings
|
||||
util}}
|
||||
|
||||
glossary
|
||||
{:team "Workflows"
|
||||
:api #{metabase.glossary.api}
|
||||
:uses #{api
|
||||
models
|
||||
util}}
|
||||
|
||||
graph
|
||||
{:team "Querying"
|
||||
:api #{metabase.graph.core}
|
||||
:uses #{util}}
|
||||
|
||||
indexed-entities
|
||||
{:team "UX West"
|
||||
:api #{metabase.indexed-entities.api
|
||||
@@ -716,6 +735,7 @@
|
||||
metabase.native-query-snippets.core}
|
||||
:uses #{api
|
||||
collections
|
||||
events
|
||||
lib
|
||||
models
|
||||
permissions
|
||||
@@ -885,6 +905,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
|
||||
@@ -930,6 +963,7 @@
|
||||
:uses #{api
|
||||
legacy-mbql
|
||||
lib
|
||||
lib-be
|
||||
models
|
||||
permissions
|
||||
query-processor
|
||||
@@ -1012,6 +1046,7 @@
|
||||
metabase.request.schema}
|
||||
:uses #{api
|
||||
config
|
||||
embedding
|
||||
permissions ; FIXME
|
||||
session
|
||||
settings
|
||||
@@ -1217,6 +1252,7 @@
|
||||
events
|
||||
lib
|
||||
models
|
||||
premium-features
|
||||
query-processor
|
||||
settings
|
||||
task
|
||||
@@ -1325,9 +1361,11 @@
|
||||
users
|
||||
{:team "UX West"
|
||||
:api #{metabase.users.api
|
||||
metabase.users.core
|
||||
metabase.users.init
|
||||
metabase.users.models.user
|
||||
metabase.users.models.user-parameter-value}
|
||||
metabase.users.models.user-parameter-value
|
||||
metabase.users.schema}
|
||||
:uses #{analytics
|
||||
api
|
||||
appearance
|
||||
@@ -1462,6 +1500,7 @@
|
||||
driver
|
||||
legacy-mbql
|
||||
lib
|
||||
lib-be
|
||||
models
|
||||
queries
|
||||
query-permissions
|
||||
@@ -1612,6 +1651,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
|
||||
@@ -1678,6 +1729,29 @@
|
||||
warehouse-schema
|
||||
warehouses}}
|
||||
|
||||
enterprise/dependencies
|
||||
{:team "Querying"
|
||||
:api #{metabase-enterprise.dependencies.api
|
||||
metabase-enterprise.dependencies.core
|
||||
metabase-enterprise.dependencies.init}
|
||||
:uses #{analyze
|
||||
api
|
||||
app-db
|
||||
collections
|
||||
config
|
||||
driver
|
||||
events
|
||||
graph
|
||||
lib
|
||||
lib-be
|
||||
models
|
||||
native-query-snippets
|
||||
premium-features
|
||||
queries
|
||||
query-processor
|
||||
task
|
||||
util}}
|
||||
|
||||
enterprise/documents
|
||||
{:team "UX West"
|
||||
:api #{metabase-enterprise.documents.api
|
||||
@@ -1710,6 +1784,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
|
||||
@@ -1782,10 +1867,13 @@
|
||||
pulse
|
||||
query-processor
|
||||
request
|
||||
search
|
||||
server
|
||||
settings
|
||||
store-api
|
||||
sync
|
||||
system
|
||||
task
|
||||
users
|
||||
util
|
||||
warehouse-schema
|
||||
@@ -1946,8 +2034,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
|
||||
@@ -1963,7 +2054,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!
|
||||
|
||||
@@ -232,7 +232,6 @@
|
||||
metabase.test.data/$ids [[:inner 0]]
|
||||
metabase.test.data/dataset [[:inner 0]]
|
||||
metabase.test.data/mbql-query [[:inner 0]]
|
||||
metabase.test.data/native-query [[:block 0]]
|
||||
metabase.test.data/query [[:block 1]]
|
||||
metabase.test.data/run-mbql-query [[:inner 0]]
|
||||
metabase.test.data/with-empty-h2-app-db! [[:block 0]]
|
||||
@@ -253,7 +252,6 @@
|
||||
metabase.test/format-rows-by [[:default]]
|
||||
metabase.test/let-url [[:block 1]]
|
||||
metabase.test/mbql-query [[:inner 0]]
|
||||
metabase.test/native-query [[:block 0]]
|
||||
metabase.test/query [[:block 1]]
|
||||
metabase.test/run-mbql-query [[:inner 0]]
|
||||
metabase.test/test-driver [[:inner 0]]
|
||||
|
||||
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]
|
||||
|
||||
|
||||
36
.github/actions/cleanup-python-runner/action.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Cleanup Python Runner
|
||||
description: Cleanup python-runner services and upload logs as artifacts
|
||||
|
||||
inputs:
|
||||
upload-logs:
|
||||
description: 'Whether to upload logs (typically on failure)'
|
||||
required: false
|
||||
default: 'false'
|
||||
logs-artifact-suffix:
|
||||
description: 'The unique name of the .zip artifact containing the logs'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cleanup python-runner services
|
||||
if: always()
|
||||
run: |
|
||||
echo "Stopping python-runner services..."
|
||||
if [ -d "python-runner" ]; then
|
||||
cd python-runner
|
||||
make stop || echo "WARNING: Failed to stop python runner"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Upload python-runner logs
|
||||
if: ${{inputs.upload-logs == 'true' && inputs.logs-artifact-suffix != ''}}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-runner-logs-${{inputs.logs-artifact-suffix}}
|
||||
path: |
|
||||
python-runner.out.log
|
||||
python-runner.err.log
|
||||
retention-days: 1
|
||||
if-no-files-found: warn
|
||||
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
|
||||
|
||||
27
.github/actions/setup-python-runner/action.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Setup Python Runner
|
||||
description: Setup python-runner for tests
|
||||
inputs:
|
||||
metabase-automation-token:
|
||||
description: Token for accessing the python-runner repository
|
||||
required: true
|
||||
python-runner-version:
|
||||
description: Git tag or commit of the python-runner repository to checkout
|
||||
required: false
|
||||
default: 'v0.0.1'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Clone and setup python-runner
|
||||
run: |
|
||||
echo "Cloning python-runner repository..."
|
||||
git clone --depth 1 --branch ${{ inputs.python-runner-version }} \
|
||||
https://${{ inputs.metabase-automation-token }}@github.com/metabase/python-runner-container.git python-runner
|
||||
|
||||
echo "Starting python-runner..."
|
||||
cd python-runner
|
||||
make run-ci 1> ../python-runner.out.log 2> ../python-runner.err.log
|
||||
make logs 1>> ../python-runner.out.log 2>> ../python-runner.err.log &
|
||||
cd ..
|
||||
shell: bash
|
||||
env:
|
||||
METABASE_AUTOMATION_USER_TOKEN: ${{ inputs.metabase-automation-token }}
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
30
.github/scripts/setup-python-runner.sh
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/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 1> ../python-runner.out.log 2> ../python-runner.err.log
|
||||
make logs 1>> ../python-runner.out.log 2>> ../python-runner.err.log &
|
||||
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:
|
||||
|
||||
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 }}"
|
||||
|
||||
337
.github/workflows/drivers.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
skip:
|
||||
type: boolean
|
||||
default: false
|
||||
python-runner-version:
|
||||
type: string
|
||||
default: 'v0.0.1'
|
||||
description: 'Git tag or commit of python-runner repository to use'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-drivers
|
||||
@@ -90,6 +94,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 +111,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 +134,21 @@ 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 }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test BigQuery Cloud SDK driver
|
||||
id: test-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()
|
||||
@@ -136,11 +160,29 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-clickhouse-ee:
|
||||
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 +190,15 @@ 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 }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Add ClickHouse TLS instance to /etc/hosts
|
||||
run: |
|
||||
sudo echo "127.0.0.1 server.clickhouseconnect.test" | sudo tee -a /etc/hosts
|
||||
@@ -167,11 +215,11 @@ jobs:
|
||||
clickhouse_cluster_node2
|
||||
nginx
|
||||
- name: Test Clickhouse driver
|
||||
id: test-driver
|
||||
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()
|
||||
@@ -183,6 +231,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-druid-ee:
|
||||
needs: determine-driver-skips
|
||||
@@ -208,6 +262,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 +299,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 +336,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 +374,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 +396,19 @@ jobs:
|
||||
name: H2
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test H2 driver
|
||||
id: test-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()
|
||||
@@ -355,6 +420,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-mysql-mariadb:
|
||||
if: ${{ !inputs.skip }}
|
||||
@@ -388,6 +459,11 @@ jobs:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
exclude-tag: ':mb/transforms-python-test'
|
||||
- name: Transforms Python Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/transforms-python-test]
|
||||
needs-python-runner: true
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -442,7 +518,14 @@ jobs:
|
||||
name: "${{ matrix.version.name }} ${{ matrix.job.name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
@@ -460,6 +543,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-mongo:
|
||||
needs: determine-driver-skips
|
||||
@@ -476,6 +565,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 +587,21 @@ 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 }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
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()
|
||||
@@ -510,6 +613,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-mongo-ssl:
|
||||
needs: determine-driver-skips
|
||||
@@ -562,7 +671,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 +779,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 +814,11 @@ jobs:
|
||||
- name: Driver Tests
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
exclude-tag: ':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,7 +860,14 @@ jobs:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
id: test-driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
@@ -763,6 +885,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-presto-jdbc-ee:
|
||||
needs: determine-driver-skips
|
||||
@@ -821,6 +949,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 +979,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,7 +999,14 @@ jobs:
|
||||
name: ${{ matrix.job.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup python-runner
|
||||
if: ${{ matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test ${{ matrix.job.name }}
|
||||
id: test-driver
|
||||
if: ${{ !matrix.job.skip }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
@@ -882,11 +1024,30 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-snowflake-ee:
|
||||
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 +1066,21 @@ 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 }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test Snowflake driver
|
||||
id: test-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()
|
||||
@@ -925,6 +1092,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
be-tests-sparksql-ee:
|
||||
needs: determine-driver-skips
|
||||
@@ -948,6 +1121,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 +1151,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 +1179,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 +1203,21 @@ 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 }}
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
id: test-driver
|
||||
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()
|
||||
@@ -1039,6 +1229,12 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
- name: Cleanup python-runner
|
||||
if: ${{ always() && matrix.job.needs-python-runner == true }}
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: ${{ steps.test-driver.outcome == 'failure' }}
|
||||
logs-artifact-suffix: ${{ matrix.job.name }} ${{ matrix.version.name }}
|
||||
|
||||
# TODO(dpsutton, 2025-08-20): Re-enable starburst when we figure out why it randomly dies in CI
|
||||
# be-tests-starburst-ee:
|
||||
@@ -1079,42 +1275,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:
|
||||
|
||||
69
.github/workflows/e2e-test.yml
vendored
@@ -28,6 +28,11 @@ on:
|
||||
split_index: # index of the chunk
|
||||
required: false
|
||||
type: number
|
||||
python-runner-version:
|
||||
type: string
|
||||
default: 'v0.0.1'
|
||||
description: 'Git tag or commit of python-runner repository to use'
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
@@ -60,6 +65,15 @@ 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
|
||||
uses: ./.github/actions/setup-python-runner
|
||||
with:
|
||||
metabase-automation-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
python-runner-version: ${{ inputs.python-runner-version }}
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
@@ -123,7 +137,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 +157,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 +191,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
|
||||
@@ -193,3 +252,9 @@ jobs:
|
||||
const summary = formatSummary(results);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
|
||||
- name: Cleanup python-runner
|
||||
if: always()
|
||||
uses: ./.github/actions/cleanup-python-runner
|
||||
with:
|
||||
upload-logs: false
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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 |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |