Compare commits
389 Commits
remove-use
...
remove-nes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cdad9644d | ||
|
|
b603c24bff | ||
|
|
6d669a7808 | ||
|
|
19830af9d2 | ||
|
|
8020d35532 | ||
|
|
68b5038d6e | ||
|
|
08f0bf86aa | ||
|
|
8f1b27b774 | ||
|
|
2244872a0f | ||
|
|
1ea72c0b64 | ||
|
|
8ce65f0ec8 | ||
|
|
ed2182b89c | ||
|
|
c3eafa1eb1 | ||
|
|
69bbddbb31 | ||
|
|
81e84ccddc | ||
|
|
7829b67c22 | ||
|
|
9e681c801f | ||
|
|
8f1e55bdb1 | ||
|
|
4f2207b4a9 | ||
|
|
fe092c1156 | ||
|
|
82e4392582 | ||
|
|
9afbb6d62c | ||
|
|
8ceed8d65e | ||
|
|
f0842c1ce8 | ||
|
|
9c9dc1f0c1 | ||
|
|
f1b714aead | ||
|
|
b133ca22f3 | ||
|
|
ccdd98bc68 | ||
|
|
6d285d9311 | ||
|
|
81ce63f45f | ||
|
|
f3e7f3e1c2 | ||
|
|
84b67702a8 | ||
|
|
dab495cf6f | ||
|
|
bd15d5d577 | ||
|
|
8d23fd7406 | ||
|
|
dc33029e96 | ||
|
|
b339c31147 | ||
|
|
8980b64c6d | ||
|
|
933dfd793a | ||
|
|
c1db02efac | ||
|
|
cd9e7c6634 | ||
|
|
58cb47d6ae | ||
|
|
a0ae2a8f28 | ||
|
|
3339090e34 | ||
|
|
dcea62b5e3 | ||
|
|
298bdfe88d | ||
|
|
90321968d5 | ||
|
|
7b7da72b9c | ||
|
|
6445bc91c6 | ||
|
|
beff8ddec7 | ||
|
|
8ef3001c62 | ||
|
|
ec35920631 | ||
|
|
e204b56332 | ||
|
|
b70ec4e804 | ||
|
|
fdafb79120 | ||
|
|
daf0185322 | ||
|
|
aff0dc9790 | ||
|
|
c120c35891 | ||
|
|
58b9992f1f | ||
|
|
5434b36d03 | ||
|
|
4686618c33 | ||
|
|
01242bdb26 | ||
|
|
29dafe465f | ||
|
|
05c93c8b21 | ||
|
|
009c1bfdff | ||
|
|
ccfaf2e9ab | ||
|
|
9ff67b56fc | ||
|
|
bb3abf74e1 | ||
|
|
fdb3a1f127 | ||
|
|
83fe7b1d3f | ||
|
|
c1bf4fb520 | ||
|
|
50e10e1b78 | ||
|
|
d053b2fe80 | ||
|
|
ae97cbde23 | ||
|
|
4eeccde961 | ||
|
|
322b8958c8 | ||
|
|
2ed72d106b | ||
|
|
fe13ca6859 | ||
|
|
5c011ed5ec | ||
|
|
4bc96006af | ||
|
|
73fe6a603c | ||
|
|
e9d13a38c1 | ||
|
|
dea02a4d22 | ||
|
|
84dc6f75ee | ||
|
|
8985d95122 | ||
|
|
263256f7ab | ||
|
|
be4a57cf78 | ||
|
|
68101f50f2 | ||
|
|
393308bc5c | ||
|
|
4b64ef287b | ||
|
|
eb596a48f0 | ||
|
|
0502dd8a8e | ||
|
|
670ee25906 | ||
|
|
7dfe9d4cc4 | ||
|
|
5444d94a13 | ||
|
|
97a47fe9db | ||
|
|
189703e895 | ||
|
|
c170d97352 | ||
|
|
1a8e6132b2 | ||
|
|
4a9e296160 | ||
|
|
6ee87dcc9b | ||
|
|
b7950cd10e | ||
|
|
21e2b99ec6 | ||
|
|
72b3cc3f48 | ||
|
|
aab7f5baee | ||
|
|
0361bdd557 | ||
|
|
7c276e3bbf | ||
|
|
0b44617a5d | ||
|
|
7ff5975436 | ||
|
|
bcac621fa8 | ||
|
|
bea199498e | ||
|
|
5583a5c29a | ||
|
|
96b843e41c | ||
|
|
8b8147a807 | ||
|
|
79b33141f7 | ||
|
|
28a1629790 | ||
|
|
f42b605299 | ||
|
|
c2de5b5e5f | ||
|
|
6a3aa35832 | ||
|
|
48ae90a5b1 | ||
|
|
ff06e1b900 | ||
|
|
53af5593bd | ||
|
|
be1eac5e39 | ||
|
|
718f667515 | ||
|
|
4452359c58 | ||
|
|
428b0c9f86 | ||
|
|
91ddc60070 | ||
|
|
bda324e42b | ||
|
|
6fb5fdc8f4 | ||
|
|
53dee386d3 | ||
|
|
d4db9cd0e8 | ||
|
|
ec0ac2bf7a | ||
|
|
963d308568 | ||
|
|
f99b8baa5f | ||
|
|
522910ec6b | ||
|
|
bc4e2da39b | ||
|
|
9f01f0efa4 | ||
|
|
f29b68770f | ||
|
|
f147c3710b | ||
|
|
a8634811a0 | ||
|
|
482f6bbff4 | ||
|
|
ec4192eb9d | ||
|
|
41bac38d80 | ||
|
|
cfe322223b | ||
|
|
f314f6d56a | ||
|
|
4304b96ce2 | ||
|
|
56357db89f | ||
|
|
b04cfa86a6 | ||
|
|
7a4d682dde | ||
|
|
83cbbd7e45 | ||
|
|
4e451b4692 | ||
|
|
71735c6722 | ||
|
|
dd021f0862 | ||
|
|
6c933f2d91 | ||
|
|
4cfbd67ce2 | ||
|
|
f024e79c85 | ||
|
|
6d4b695b5f | ||
|
|
500b0e6d06 | ||
|
|
8f49ab0dd0 | ||
|
|
aa8a386e34 | ||
|
|
00a087bb52 | ||
|
|
41e4defd29 | ||
|
|
bb32a4111b | ||
|
|
94372d6fa6 | ||
|
|
c29f2e382d | ||
|
|
084384db11 | ||
|
|
e1a1e73128 | ||
|
|
a59eba3e63 | ||
|
|
685c36f599 | ||
|
|
3d01b319e5 | ||
|
|
93a703a911 | ||
|
|
ec2f229fe1 | ||
|
|
f19101b970 | ||
|
|
cce25745a7 | ||
|
|
fd32386973 | ||
|
|
5548e70d57 | ||
|
|
ea928cdb2c | ||
|
|
7bfb4662d1 | ||
|
|
a238b9310f | ||
|
|
f9b87a3f6b | ||
|
|
7178d0fba9 | ||
|
|
775feb1aa1 | ||
|
|
cc15ee649e | ||
|
|
5a52987617 | ||
|
|
11f501e810 | ||
|
|
e0a2a322ba | ||
|
|
19d7f3d58a | ||
|
|
fc85504079 | ||
|
|
ef01a0f9f1 | ||
|
|
08b11e1dfa | ||
|
|
4cba79cdf1 | ||
|
|
a21280000a | ||
|
|
601c4f7412 | ||
|
|
7008d38317 | ||
|
|
e480527e92 | ||
|
|
e56e508989 | ||
|
|
38cc13c3d3 | ||
|
|
bb854dbd50 | ||
|
|
d619950925 | ||
|
|
0fbb656080 | ||
|
|
869efcea7a | ||
|
|
bf6759b08d | ||
|
|
86b994e7a5 | ||
|
|
4bbd4cc2aa | ||
|
|
9dbfd5bc08 | ||
|
|
2021f7daec | ||
|
|
6e72ffbb53 | ||
|
|
da9d62a0e7 | ||
|
|
912ee7b034 | ||
|
|
5bc378d9ca | ||
|
|
0e415e664c | ||
|
|
4df1c417a9 | ||
|
|
6f8ff73343 | ||
|
|
70c6be8670 | ||
|
|
7ab0c413a4 | ||
|
|
ed61e82fa4 | ||
|
|
bd36ca40cc | ||
|
|
2970dc54d5 | ||
|
|
24249e76da | ||
|
|
6059f92ca2 | ||
|
|
4a0c62775f | ||
|
|
f768b844c3 | ||
|
|
79817b4f15 | ||
|
|
b7a030022b | ||
|
|
cd650bfd69 | ||
|
|
cd71625a45 | ||
|
|
2d01597302 | ||
|
|
afc1963a9b | ||
|
|
08771ef685 | ||
|
|
4882239cd9 | ||
|
|
6fa8ff4779 | ||
|
|
db1c4f3741 | ||
|
|
321c37ca1c | ||
|
|
a0d13128dc | ||
|
|
8c08ebbf3d | ||
|
|
d0137526c8 | ||
|
|
f88e47c127 | ||
|
|
591599bf4e | ||
|
|
f5a22e7a3b | ||
|
|
83e55c8dce | ||
|
|
f9545b8740 | ||
|
|
f7c5f1f2f5 | ||
|
|
fb7ae7d97c | ||
|
|
aae8d818ad | ||
|
|
b1de8b8bfd | ||
|
|
7ab2e5ecff | ||
|
|
e8ebd956f4 | ||
|
|
1200eb2f10 | ||
|
|
70b4ad81e9 | ||
|
|
82f13b6742 | ||
|
|
528222e7e2 | ||
|
|
62caae9946 | ||
|
|
a04af019b9 | ||
|
|
29b070c07a | ||
|
|
33e50fa799 | ||
|
|
876eedc92a | ||
|
|
1d8541ca43 | ||
|
|
53568b2570 | ||
|
|
2ed4ad3d7c | ||
|
|
208deba6e6 | ||
|
|
3f629276d8 | ||
|
|
b06e6e9c34 | ||
|
|
e81c4137fc | ||
|
|
3f367ac9a3 | ||
|
|
10986403a0 | ||
|
|
3b2fdbb676 | ||
|
|
9acb577c10 | ||
|
|
b62505f15b | ||
|
|
ceacccd0b1 | ||
|
|
ce49352589 | ||
|
|
219ca91f6d | ||
|
|
1770e0d228 | ||
|
|
0cecef1c38 | ||
|
|
4a47bd20f0 | ||
|
|
600e4aec03 | ||
|
|
c9b18c5fc6 | ||
|
|
d95746367e | ||
|
|
1f8b62c4ea | ||
|
|
6d4ea59746 | ||
|
|
5607cb71c0 | ||
|
|
99a1cdb660 | ||
|
|
ebb3f14c39 | ||
|
|
7c6b8bee37 | ||
|
|
41c73023b6 | ||
|
|
ad25f3c0eb | ||
|
|
130f406b4f | ||
|
|
10f52f57ef | ||
|
|
ccd6b59b34 | ||
|
|
faf82bd8d5 | ||
|
|
cb71e0e908 | ||
|
|
b88e7431b6 | ||
|
|
0c4d8284b0 | ||
|
|
a96a840c78 | ||
|
|
84f6a327fa | ||
|
|
4904ae048f | ||
|
|
115d77dfbe | ||
|
|
a1aff9440f | ||
|
|
f3edd784f0 | ||
|
|
c40657ff3a | ||
|
|
110079b65b | ||
|
|
fba8aab8ac | ||
|
|
f256f2d2eb | ||
|
|
9f3e0bc68a | ||
|
|
5313e018e8 | ||
|
|
c0eab4452e | ||
|
|
179554e564 | ||
|
|
dbd60f5c47 | ||
|
|
59c5690bd6 | ||
|
|
5197e318d7 | ||
|
|
fbe206dfba | ||
|
|
168bd09275 | ||
|
|
41b212a654 | ||
|
|
b2fade6394 | ||
|
|
8f93e5550d | ||
|
|
c3c2e8ee18 | ||
|
|
b03d6f7e2b | ||
|
|
da0397c091 | ||
|
|
47200643e1 | ||
|
|
6c22043070 | ||
|
|
34401cccab | ||
|
|
e80c600db5 | ||
|
|
3d6c90698b | ||
|
|
7b4c298126 | ||
|
|
631947bfb8 | ||
|
|
6ce8344c90 | ||
|
|
6d83cb2b60 | ||
|
|
9f4327cf87 | ||
|
|
989bb6ff23 | ||
|
|
82c39bb942 | ||
|
|
bcd41db9ad | ||
|
|
a135e609e8 | ||
|
|
c2504f4e99 | ||
|
|
fd0cafeb7b | ||
|
|
3e1f18a9cf | ||
|
|
89faa64681 | ||
|
|
8e68606eb4 | ||
|
|
26e8f86868 | ||
|
|
4c27ad5fa3 | ||
|
|
2c76bd4a92 | ||
|
|
8692d9e000 | ||
|
|
02e80cf2ae | ||
|
|
6f1f0c02ed | ||
|
|
3be2611784 | ||
|
|
5b3278831c | ||
|
|
c592413341 | ||
|
|
98e0fce497 | ||
|
|
88d5b7bc17 | ||
|
|
ad3fc72b03 | ||
|
|
51b73954e1 | ||
|
|
85ce744a2b | ||
|
|
33915469e3 | ||
|
|
b85e236113 | ||
|
|
32d8726ac9 | ||
|
|
72f70ade32 | ||
|
|
80cd4f3c80 | ||
|
|
2e73064e94 | ||
|
|
e1f2fb96c3 | ||
|
|
9006696ee3 | ||
|
|
727246d3b1 | ||
|
|
5e6851a8c3 | ||
|
|
83f0c60fea | ||
|
|
414048fba0 | ||
|
|
2b1c6958c0 | ||
|
|
590951cacb | ||
|
|
35ec452c6c | ||
|
|
63ce443b49 | ||
|
|
44dd5df5da | ||
|
|
4e8404d3b7 | ||
|
|
33ff237b59 | ||
|
|
703638cd10 | ||
|
|
142306a3f7 | ||
|
|
9401a0ba6f | ||
|
|
b4aa704be6 | ||
|
|
f5e63b4001 | ||
|
|
2d9d447492 | ||
|
|
0f9bb8045b | ||
|
|
aa0a444be4 | ||
|
|
c7e8d6cae5 | ||
|
|
16117c6423 | ||
|
|
18bfb20523 | ||
|
|
bf184ac3da | ||
|
|
37acc7e854 | ||
|
|
e2a8d43ed5 | ||
|
|
07ee64373f | ||
|
|
c63bfcf186 | ||
|
|
499553cc74 | ||
|
|
8e95128018 | ||
|
|
1470cb1abe | ||
|
|
68f645545d |
@@ -1,25 +1,62 @@
|
||||
;; -*- comment-column: 100; -*-
|
||||
{:config-paths ["macros" "src"]
|
||||
:linters
|
||||
{:aliased-namespace-symbol {:level :warning}
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match]} ; TODO (cam): can we fix these?
|
||||
{
|
||||
;;
|
||||
;; enabled optional linters (these are disabled by default)
|
||||
;;
|
||||
|
||||
:aliased-namespace-symbol {:level :warning}
|
||||
:case-symbol-test {:level :warning}
|
||||
:condition-always-true {:level :warning}
|
||||
:def-fn {:level :warning}
|
||||
:dynamic-var-not-earmuffed {:level :warning}
|
||||
:equals-expected-position {:level :warning, :only-in-test-assertion true}
|
||||
:keyword-binding {:level :warning}
|
||||
:main-without-gen-class {:level :warning}
|
||||
:minus-one {:level :warning}
|
||||
:misplaced-docstring {:level :warning}
|
||||
:missing-body-in-when {:level :warning}
|
||||
:missing-docstring {:level :warning}
|
||||
:missing-else-branch {:level :warning}
|
||||
:namespace-name-mismatch {:level :warning}
|
||||
:non-arg-vec-return-type-hint {:level :warning}
|
||||
:plus-one {:level :warning}
|
||||
:reduce-without-init {:level :warning}
|
||||
:redundant-call {:level :warning}
|
||||
:redundant-fn-wrapper {:level :warning}
|
||||
:refer-all {:level :warning, :exclude [clojure.test]}
|
||||
:self-requiring-namespace {:level :warning}
|
||||
:single-key-in {:level :warning}
|
||||
:unsorted-imports {:level :warning}
|
||||
:unsorted-required-namespaces {:level :warning}
|
||||
:unused-referred-var {:exclude {compojure.core [GET DELETE POST PUT]}}
|
||||
:unused-alias {:level :warning}
|
||||
:use {:level :warning}
|
||||
:warn-on-reflection {:level :warning}
|
||||
|
||||
;;
|
||||
;; other disabled-by-default linters it would be nice to enable soon
|
||||
;;
|
||||
|
||||
;; TODO (cam): most of these either have a ton of things that need to be fixed or cause issues
|
||||
;; with [[metabase.lib.util.match/match]] and friends -- we need to write a custom hook for those macros so Kondo
|
||||
;; can understand them.
|
||||
|
||||
#_:docstring-leading-trailing-whitespace
|
||||
#_:equals-false
|
||||
#_:equals-true
|
||||
#_:used-underscored-binding
|
||||
|
||||
;;
|
||||
;; disabled linters
|
||||
;;
|
||||
|
||||
:redundant-ignore {:level :off} ; lots of false positives, see https://github.com/clj-kondo/clj-kondo/issues/2414
|
||||
:unexpected-recur {:level :off} ; TODO (cam): I think we just need to tell it how to handle MBQL match and we can enable this?
|
||||
|
||||
;;
|
||||
;; globally-enabled custom linters
|
||||
;;
|
||||
:metabase/check-me-humanize {:level :warning}
|
||||
:metabase/defsetting-must-specify-export {:level :warning}
|
||||
:metabase/mbql-query-first-arg {:level :warning}
|
||||
:metabase/missing-test-expr-requires-in-cljs {:level :warning}
|
||||
@@ -168,20 +205,12 @@
|
||||
:vertica}}
|
||||
|
||||
;;
|
||||
;; disabled linters
|
||||
;; misc config for built-in linters
|
||||
;;
|
||||
|
||||
:unexpected-recur {:level :off} ; TODO (cam): I think we just need to tell it how to handle MBQL match and we can enable this?
|
||||
:used-underscored-binding {:level :off} ; false positives: see https://github.com/clj-kondo/clj-kondo/issues/2152
|
||||
|
||||
;;
|
||||
;; TODO (cam): here are some more linters we should experiment with enabling -- some might be useful.
|
||||
;;
|
||||
|
||||
;; :docstring-leading-trailing-whitespace {:level :warning}
|
||||
;; :docstring-no-summary {:level :warning}
|
||||
;; :shadowed-var {:level :warning}
|
||||
;; :def-fn {:level :warning}
|
||||
:invalid-arity {:skip-args [metabase.lib.util.match/match]} ; TODO (cam): can we fix these?
|
||||
:refer-all {:level :warning, :exclude [clojure.test]}
|
||||
:unused-referred-var {:exclude {compojure.core [GET DELETE POST PUT]}}
|
||||
|
||||
:unresolved-symbol
|
||||
{:exclude
|
||||
@@ -216,7 +245,7 @@
|
||||
metabase.driver.sql-jdbc.execute/set-time-zone-if-supported! {:namespaces ["metabase\\.driver.*"]}
|
||||
metabase.driver/current-db-time {:namespaces ["metabase.*"]}
|
||||
metabase.driver/supports? {:namespaces ["metabase.*"]}
|
||||
metabase.public-settings.premium-features/enable-enhancements? {:namespaces ["metabase\\.models.*"]}
|
||||
metabase.premium-features.premium-features/enable-enhancements? {:namespaces ["metabase\\.models.*"]}
|
||||
metabase.query-processor.middleware.resolve-joins/maybe-resolve-source-table {:namespaces ["metabase.*"]}
|
||||
metabase.query-processor.util/normalize-token {:namespaces ["metabase.*"]}
|
||||
schema.core/both {:namespaces ["metabase.*"]}
|
||||
@@ -241,13 +270,16 @@
|
||||
honeysql.core/raw {:message "Use hx/raw instead because it is Honey SQL 2 friendly"}
|
||||
java-time/with-clock {:message "Use mt/with-clock"}
|
||||
java.util.UUID/randomUUID {:message "Use clojure.core/random-uuid instead of java.util.UUID/randomUUID"}
|
||||
malli.core/explainer {:message "Use metabase.util.malli.registry/explainer instead of malli.core/explainer"}
|
||||
malli.core/explain {:message "Use metabase.util.malli.registry/explain instead of malli.core/explain because it has built-in caching"}
|
||||
malli.core/explainer {:message "Use metabase.util.malli.registry/explainer instead of malli.core/explainer because it has built-in caching"}
|
||||
malli.core/validate {:message "Use metabase.util.malli.registry/validate instead of malli.core/validate because it has built-in caching"}
|
||||
malli.core/validator {:message "Use metabase.util.malli.registry/validator instead of malli.core/validator because it has built-in caching"}
|
||||
me.flowthing.pp/pprint {:message "Use metabase.util.log instead of me.flowthing.pp/pprint"}
|
||||
medley.core/random-uuid {:message "Use clojure.core/random-uuid instead of medley.core/random-uuid"}
|
||||
metabase.driver/database-supports? {:message "Use metabase.driver.util/supports? instead of metabase.driver/database-supports?"}
|
||||
metabase.lib.equality/find-column-indexes-for-refs {:message "Use lib.equality/closest-matches-in-metadata or lib.equality/find-closest-matches-for-refs instead of lib.equality/find-column-indexes-for-refs"}
|
||||
metabase.test/with-temp* {:message "Use mt/with-temp instead of mt/with-temp*"}
|
||||
toucan2.tools.with-temp {:message "Use mt/with-temp instead of t2.with-temp/with-temp"}}
|
||||
toucan2.tools.with-temp/with-temp {:message "Use mt/with-temp instead of toucan2.tools.with-temp/with-temp"}}
|
||||
|
||||
:discouraged-namespace
|
||||
{camel-snake-kebab.core {:message "CSK is not Turkish-safe, use the versions in metabase.util instead."}
|
||||
@@ -294,6 +326,7 @@
|
||||
metabase.analyze #{metabase.analyze}
|
||||
metabase.api #{metabase.api.common
|
||||
metabase.api.dataset
|
||||
metabase.api.macros
|
||||
metabase.api.permission-graph
|
||||
metabase.api.routes} ; TODO -- consolidate these into a real API namespace. I think the `*current-user*` type stuff might need to be moved into a separate module.
|
||||
metabase.async #{metabase.async.streaming-response
|
||||
@@ -337,28 +370,32 @@
|
||||
metabase.permissions #{metabase.permissions.util} ; TODO -- this is currently the only namespace in this module. Give it a real API namespace?
|
||||
metabase.plugins #{metabase.plugins
|
||||
metabase.plugins.classloader} ; TODO -- not 100% sure the classloader belongs here
|
||||
metabase.public-settings #{metabase.public-settings
|
||||
metabase.public-settings.premium-features}
|
||||
metabase.premium-features #{metabase.premium-features.core}
|
||||
metabase.public-settings #{metabase.public-settings}
|
||||
metabase.pulse #{metabase.pulse.core}
|
||||
metabase.query-processor :any ; TODO omg scream, 29 namespaces used outside of the module. WHAT THE HECC
|
||||
metabase.query-analysis #{metabase.query-analysis
|
||||
metabase.query-analysis.failure-map}
|
||||
metabase.related #{metabase.related}
|
||||
metabase.request #{metabase.request.core}
|
||||
metabase.sample-data #{metabase.sample-data}
|
||||
metabase.search #{metabase.search
|
||||
;; deprecating naked metabase.search namespace in favor of this.
|
||||
metabase.search.core
|
||||
metabase.search #{metabase.search.core
|
||||
;; Need to expose this submodule, to break circular dependencies with models.
|
||||
metabase.search.spec}
|
||||
metabase.server #{metabase.server
|
||||
metabase.server.handler
|
||||
metabase.server.middleware.auth
|
||||
metabase.server.middleware.exceptions
|
||||
metabase.server #{metabase.server.core
|
||||
;;
|
||||
;; TODO FIXME -- this is loaded for side effects since it contains the JSON
|
||||
;; encoder logic for stuff like `java.time` classes. As a
|
||||
;; result, [[metabase.models.setting]] (and thus the `metabase.models` module)
|
||||
;; has to depend on the `metabase.server` module...we should create either a new
|
||||
;; `metabase.json` or `metabase.encoders` module for stuff like this to live. Or
|
||||
;; maybe it just belongs in a generic `metabase.config` module.
|
||||
metabase.server.middleware.json
|
||||
metabase.server.middleware.misc
|
||||
metabase.server.middleware.offset-paging
|
||||
metabase.server.middleware.session
|
||||
metabase.server.protocols
|
||||
metabase.server.request.util} ; TODO -- consolidate these into a real API namespace.
|
||||
;;
|
||||
;; TODO -- we can't make this part of [[metabase.server.core]] YET because it
|
||||
;; causes circular dependencies. Maybe in the future once we tease apart more of
|
||||
;; the modules tangle we can.
|
||||
metabase.server.protocols}
|
||||
metabase.setup #{metabase.setup}
|
||||
metabase.sync #{metabase.sync
|
||||
metabase.sync.analyze
|
||||
@@ -370,6 +407,7 @@
|
||||
metabase.sync.sync-metadata.tables
|
||||
metabase.sync.util} ; TODO -- consolidate these into a real API namespace.
|
||||
metabase.task #{metabase.task
|
||||
metabase.task.bootstrap
|
||||
metabase.task.index-values
|
||||
metabase.task.persist-refresh
|
||||
;; We need to expose this to the metabase.search module.
|
||||
@@ -397,7 +435,8 @@
|
||||
metabase.api :any
|
||||
metabase.async :any
|
||||
metabase.audit :any
|
||||
metabase.auth-provider #{metabase.util metabase.public-settings}
|
||||
metabase.auth-provider #{metabase.util
|
||||
metabase.premium-features}
|
||||
metabase.bootstrap #{}
|
||||
metabase.cmd :any
|
||||
metabase.compatibility :any
|
||||
@@ -421,6 +460,7 @@
|
||||
metabase.notification :any
|
||||
metabase.permissions :any
|
||||
metabase.plugins :any
|
||||
metabase.premium-features :any
|
||||
metabase.public-settings :any
|
||||
metabase.pulse :any
|
||||
metabase.query-analysis #{metabase.config
|
||||
@@ -433,6 +473,10 @@
|
||||
metabase.util}
|
||||
metabase.query-processor :any
|
||||
metabase.related :any
|
||||
metabase.request #{metabase.config
|
||||
metabase.models
|
||||
metabase.public-settings
|
||||
metabase.util}
|
||||
metabase.sample-data :any
|
||||
metabase.search :any
|
||||
metabase.server :any
|
||||
@@ -442,7 +486,7 @@
|
||||
metabase.troubleshooting :any
|
||||
metabase.types #{metabase.util}
|
||||
metabase.upload :any
|
||||
metabase.util :any
|
||||
metabase.util :any ; TODO -- this should probably be #{}
|
||||
metabase.xrays :any}
|
||||
|
||||
;; namespaces matching these patterns (with `re-find`) are excluded from the module linter. Since regex literals
|
||||
@@ -637,7 +681,7 @@
|
||||
metabase.notification.test-util notification.tu
|
||||
metabase.plugins.initialize plugins.init
|
||||
metabase.public-settings public-settings
|
||||
metabase.public-settings.premium-features premium-features
|
||||
metabase.premium-features.core premium-features
|
||||
metabase.pulse.core pulse
|
||||
metabase.pulse.render render
|
||||
metabase.pulse.render.body body
|
||||
@@ -669,25 +713,16 @@
|
||||
metabase.query-processor.timezone qp.timezone
|
||||
metabase.query-processor.util qp.util
|
||||
metabase.related related
|
||||
metabase.request.core request
|
||||
metabase.search.config search.config
|
||||
metabase.search.filter search.filter
|
||||
metabase.search.in-place.filter search.in-place.filter
|
||||
metabase.search.in-place.util search.util
|
||||
metabase.search.in-place.scoring scoring
|
||||
metabase.server.middleware.auth mw.auth
|
||||
metabase.server.middleware.browser-cookie mw.browser-cookie
|
||||
metabase.server.middleware.exceptions mw.exceptions
|
||||
metabase.server.middleware.json mw.json
|
||||
metabase.server.middleware.log mw.log
|
||||
metabase.server.middleware.misc mw.misc
|
||||
metabase.server.middleware.offset-paging mw.offset-paging
|
||||
metabase.server.middleware.security mw.security
|
||||
metabase.server.middleware.session mw.session
|
||||
metabase.server.middleware.ssl mw.ssl
|
||||
metabase.server.request.util req.util
|
||||
metabase.server.core server
|
||||
metabase.server.protocols server.protocols
|
||||
metabase.sync.analyze.fingerprint sync.fingerprint
|
||||
metabase.sync.field-values field-values
|
||||
metabase.sync.field-values sync.field-values
|
||||
metabase.sync.interface i
|
||||
metabase.sync.schedules sync.schedules
|
||||
metabase.sync.sync-metadata.fields.our-metadata fields.our-metadata
|
||||
@@ -774,8 +809,9 @@
|
||||
metabase.models.json-migration/def-json-migration clj-kondo.lint-as/def-catch-all
|
||||
metabase.models.setting.multi-setting/define-multi-setting clojure.core/def
|
||||
metabase.models.setting/defsetting clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/defenterprise-schema clj-kondo.lint-as/def-catch-all
|
||||
metabase.public-settings.premium-features/define-premium-feature clojure.core/def
|
||||
metabase.premium-features.core/defenterprise-schema clj-kondo.lint-as/def-catch-all
|
||||
metabase.premium-features.defenterprise/defenterprise-schema clj-kondo.lint-as/def-catch-all
|
||||
metabase.premium-features.core/define-premium-feature clojure.core/def
|
||||
metabase.pulse.send-test/with-pulse-for-card clojure.core/let
|
||||
metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/->
|
||||
metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/->
|
||||
@@ -830,6 +866,7 @@
|
||||
clojure.test/deftest hooks.clojure.test/deftest
|
||||
clojure.test/is hooks.clojure.test/is
|
||||
clojure.test/use-fixtures hooks.clojure.test/use-fixtures
|
||||
malli.error/humanize hooks.malli.error/humanize
|
||||
metabase-enterprise.advanced-permissions.models.permissions.application-permissions-test/with-new-group-and-current-graph hooks.common/with-two-top-level-bindings
|
||||
metabase-enterprise.audit-app.pages-test/with-temp-objects hooks.common/with-one-binding
|
||||
metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings
|
||||
@@ -852,6 +889,7 @@
|
||||
metabase.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value
|
||||
metabase.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value
|
||||
metabase.api.macros/defendpoint hooks.metabase.api.macros/defendpoint
|
||||
metabase.api.persist-test/with-setup! hooks.common/with-one-top-level-binding
|
||||
metabase.api.public-test/with-required-param-card! hooks.common/with-one-binding
|
||||
metabase.api.public-test/with-temp-public-card hooks.common/let-one-with-optional-value
|
||||
@@ -885,11 +923,15 @@
|
||||
metabase.models.pulse-test/with-pulse-in-collection! hooks.common/with-four-bindings
|
||||
metabase.models.setting.multi-setting/define-multi-setting hooks.metabase.models.setting/define-multi-setting
|
||||
metabase.models.setting/defsetting hooks.metabase.models.setting/defsetting
|
||||
metabase.public-settings.premium-features/defenterprise hooks.metabase.public-settings.premium-features/defenterprise
|
||||
metabase.premium-features.core/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
|
||||
metabase.premium-features.defenterprise/defenterprise hooks.metabase.premium-features.defenterprise/defenterprise
|
||||
metabase.pulse.test-util/checkins-query-card hooks.metabase.test.data/$ids
|
||||
metabase.query-processor-test.expressions-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity
|
||||
metabase.query-processor-test.filter-test/count-with-filter-clause hooks.metabase.test.data/$ids
|
||||
metabase.query-processor.middleware.cache-test/with-mock-cache! hooks.common/with-two-bindings
|
||||
metabase.request.core/with-current-request hooks.common/with-used-first-arg
|
||||
metabase.request.core/with-current-user hooks.common/with-used-first-arg
|
||||
metabase.request.core/with-limit-and-offset hooks.common/with-used-first-two-args
|
||||
metabase.sample-data-test/with-temp-sample-database-db hooks.common/with-one-binding
|
||||
metabase.stale-test/with-stale-items hooks.toucan2.tools.with-temp/with-temp
|
||||
metabase.test.data.datasets/test-driver hooks.metabase.test.data.datasets/test-driver
|
||||
@@ -936,6 +978,7 @@
|
||||
metabase.util.log/warn hooks.metabase.util.log/info
|
||||
metabase.util.log/warnf hooks.metabase.util.log/infof
|
||||
metabase.util.malli.registry/def hooks.metabase.util.malli.registry/def
|
||||
metabase.util/case-enum hooks.metabase.util/case-enum
|
||||
metabase.util/format-color hooks.metabase.util/format-color}
|
||||
|
||||
:macroexpand
|
||||
|
||||
@@ -238,11 +238,12 @@
|
||||
(cond
|
||||
(hooks/vector-node? node)
|
||||
;; propagate the metadata attached to this vector in case there's a `:clj-kondo/ignore` form.
|
||||
(vary-meta (first (:children node)) (partial merge (meta require-node) (meta node)))
|
||||
(let [symbol-node (first (:children node))]
|
||||
(hooks.common/merge-ignored-linters symbol-node require-node node))
|
||||
|
||||
;; this should also be dead code since we require requires to be vectors
|
||||
(hooks/token-node? node)
|
||||
(vary-meta node (partial merge (meta require-node)))
|
||||
(hooks.common/merge-ignored-linters node require-node)
|
||||
|
||||
:else
|
||||
(printf "Don't know how to figure out what namespace is being required in %s\n" (pr-str node)))))
|
||||
@@ -294,8 +295,7 @@
|
||||
ns-form-node->require-node
|
||||
require-node->namespace-symb-nodes)]
|
||||
(doseq [node required-namespace-symb-nodes
|
||||
:let [clj-kondo-ignore (some-> (meta node) :clj-kondo/ignore hooks/sexpr set)]
|
||||
:when (not (contains? clj-kondo-ignore :metabase/ns-module-checker))
|
||||
:when (not (contains? (hooks.common/ignored-linters node) :metabase/ns-module-checker))
|
||||
:let [required-namespace (hooks/sexpr node)
|
||||
required-module (module required-namespace)]
|
||||
;; ignore stuff not in a module i.e. non-Metabase stuff.
|
||||
|
||||
@@ -71,11 +71,7 @@
|
||||
:type :metabase/i-like-making-cams-eyes-bleed-with-horrifically-long-tests)))))))
|
||||
|
||||
(defn- ignore? [node error-type]
|
||||
(when-let [ignores (some-> node meta :clj-kondo/ignore hooks/sexpr)]
|
||||
(when-let [ignores (cond
|
||||
(coll? ignores) (set ignores)
|
||||
(keyword? ignores) #{ignores})]
|
||||
(contains? ignores error-type))))
|
||||
(contains? (hooks.common/ignored-linters node) error-type))
|
||||
|
||||
(defn deftest-check-no-driver-keywords [node {:keys [drivers], :as _config}]
|
||||
;; fail fast after we see the first error, where we see one hardcoded driver name there are likely several more and we
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
(ns hooks.common
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.pprint]))
|
||||
[clojure.pprint]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defn with-macro-meta
|
||||
"When introducing internal nodes (let, defn, etc) it is important to provide a meta of an existing token
|
||||
@@ -9,6 +10,78 @@
|
||||
[new-node hook-node]
|
||||
(with-meta new-node (meta (first (:children hook-node)))))
|
||||
|
||||
;;; Kondo stores information about ignored linters as rewrite-clj forms matching how they are specified in the code, so
|
||||
;;; either
|
||||
;;;
|
||||
;;; <vector-node [<token-node :discouraged-namespace>]>
|
||||
;;;
|
||||
;;; or
|
||||
;;;
|
||||
;;; <token-node :discouraged-namespace>
|
||||
;;;
|
||||
;;; these are stored in
|
||||
;;;
|
||||
;;; (-> node meta :clj-kondo/ignore :linters).
|
||||
;;;
|
||||
;;; These are not a convenient shape(s) to work with, so the next few functions abstract that shape out and work with a
|
||||
;;; set of keywords like
|
||||
;;;
|
||||
;;; #{:discouraged-namespace}
|
||||
;;;
|
||||
;;; instead.
|
||||
|
||||
(defn- unwrap-ignored-linters [linters]
|
||||
(cond
|
||||
(or (hooks/vector-node? linters)
|
||||
(hooks/list-node? linters))
|
||||
(into #{}
|
||||
(map hooks/sexpr)
|
||||
(:children linters))
|
||||
|
||||
(hooks/keyword-node? linters)
|
||||
#{(hooks/sexpr linters)}
|
||||
|
||||
(and (set? linters)
|
||||
(every? keyword? linters))
|
||||
linters
|
||||
|
||||
(and (sequential? linters)
|
||||
(every? keyword? linters))
|
||||
(into #{} linters)
|
||||
|
||||
(keyword? linters)
|
||||
#{linters}))
|
||||
|
||||
(defn- wrap-ignored-linters [linters]
|
||||
(hooks/vector-node (mapv hooks/keyword-node linters)))
|
||||
|
||||
(defn ignored-linters
|
||||
"Return a set of keywords all ignored linters associated with a node by looking at its metadata."
|
||||
[node]
|
||||
(or (some-> node meta :clj-kondo/ignore :linters unwrap-ignored-linters)
|
||||
;; deal with the old pre-2024.09 shape mentioned in https://github.com/clj-kondo/clj-kondo/issues/2414
|
||||
(some-> node :meta first hooks/sexpr :clj-kondo/ignore unwrap-ignored-linters)))
|
||||
|
||||
(defn update-ignored-linters
|
||||
"Update the `:clj-kondo/ignore` linters in `node`'s metadata. `linters` should be a set of keywords."
|
||||
[node f & args]
|
||||
(letfn [(apply-f [linters]
|
||||
(apply f linters args))
|
||||
(update-linters [linters]
|
||||
(-> (or (unwrap-ignored-linters linters)
|
||||
#{})
|
||||
apply-f
|
||||
wrap-ignored-linters))]
|
||||
(vary-meta node update-in [:clj-kondo/ignore :linters] update-linters)))
|
||||
|
||||
(defn merge-ignored-linters
|
||||
"Merge the ignored linters from `source-nodes` into `node`. You can use this to propagate
|
||||
ignored linters from parent nodes to child nodes."
|
||||
[node & source-nodes]
|
||||
(if-let [linters (not-empty (transduce (map ignored-linters) set/union #{} source-nodes))]
|
||||
(update-ignored-linters node set/union linters)
|
||||
node))
|
||||
|
||||
;;; This stuff is to help debug hooks. Trace a function and it will pretty-print the before & after sexprs.
|
||||
;;;
|
||||
;;; (hooks.common/trace #'calculate-bird-scarcity)
|
||||
@@ -48,9 +121,9 @@
|
||||
[{{[_ & args] :children, :as node} :node}]
|
||||
(let [node* (-> (hooks/list-node
|
||||
(list*
|
||||
(with-meta (hooks/token-node 'do) {:clj-kondo/ignore [:redundant-do]})
|
||||
(update-ignored-linters (hooks/token-node 'do) conj :redundant-do)
|
||||
(for [arg args]
|
||||
(vary-meta arg update :clj-kondo/ignore #(conj (vec %) :unused-value)))))
|
||||
(update-ignored-linters arg conj :unused-value))))
|
||||
(with-meta (meta node)))]
|
||||
{:node node*}))
|
||||
|
||||
@@ -373,6 +446,28 @@
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-used-first-two-args
|
||||
"For macros like
|
||||
|
||||
(with-limit-and-offset limit offset
|
||||
...)
|
||||
|
||||
=>
|
||||
|
||||
(let [_123 limit
|
||||
[_456 offset]
|
||||
...)
|
||||
|
||||
where the first arg should be linted and appear to be used."
|
||||
[{{[_ x y & body] :children} :node}]
|
||||
(let [node* (hooks/list-node
|
||||
(list*
|
||||
(hooks/token-node 'let)
|
||||
(hooks/vector-node [(hooks/token-node (gensym "_")) x
|
||||
(hooks/token-node (gensym "_")) y])
|
||||
body))]
|
||||
{:node node*}))
|
||||
|
||||
(defn with-vec-first-binding
|
||||
"For macros like
|
||||
|
||||
@@ -439,9 +534,4 @@
|
||||
|
||||
metadata to a node to suppress LSP warnings."
|
||||
[node]
|
||||
(letfn [(update-ignores [existing-ignores]
|
||||
(hooks/vector-node
|
||||
(cons
|
||||
(hooks/keyword-node :clojure-lsp/unused-public-var)
|
||||
(:children existing-ignores))))]
|
||||
(vary-meta node update :clj-kondo/ignore update-ignores)))
|
||||
(update-ignored-linters node conj :clojure-lsp/unused-public-var))
|
||||
|
||||
26
.clj-kondo/src/hooks/malli/error.clj
Normal file
@@ -0,0 +1,26 @@
|
||||
(ns hooks.malli.error
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
|
||||
(defn humanize [x]
|
||||
(letfn [(check-node [node]
|
||||
(when (hooks/list-node? node)
|
||||
(let [[_ child] (:children node)]
|
||||
(when (hooks/list-node? child)
|
||||
(let [[symb] (:children child)]
|
||||
(when-let [qualified-symb (hooks.common/node->qualified-symbol symb)]
|
||||
(when ('#{malli.core/validate metabase.util.malli.registry/validate}
|
||||
qualified-symb)
|
||||
(hooks/reg-finding! (assoc (meta symb)
|
||||
:message "Use malli.error/humanize with explain, NOT with validate. [:metabase/check-me-humanize]"
|
||||
:type :metabase/check-me-humanize)))))))))]
|
||||
(check-node (:node x))
|
||||
x))
|
||||
|
||||
(comment
|
||||
(humanize {:node (hooks/parse-string
|
||||
(pr-str '(malli.error/humanize
|
||||
(malli.core/validate
|
||||
metabase.sync.sync-metadata.dbms-version/DBMSVersion
|
||||
metabase.driver.druid.sync-test/dbms-version))))}))
|
||||
49
.clj-kondo/src/hooks/metabase/api/macros.clj
Normal file
@@ -0,0 +1,49 @@
|
||||
(ns hooks.metabase.api.macros
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn defendpoint
|
||||
[arg]
|
||||
(letfn [(update-defendpoint [node]
|
||||
(let [[defendpoint method route & more] (:children node)
|
||||
[result-schema & more] (if (= (api/sexpr (first more)) :-)
|
||||
(drop 1 more)
|
||||
(cons nil more))
|
||||
[_docstring & more] (if (api/string-node? (first more))
|
||||
more
|
||||
(cons nil more))
|
||||
[metadata & more] (if (api/map-node? (first more))
|
||||
more
|
||||
(cons nil more))
|
||||
[params & body] more
|
||||
[bindings schemas] (when (api/vector-node? params)
|
||||
(loop [bindings [], schemas [], [x y & more] (:children params)]
|
||||
(cond
|
||||
(not x)
|
||||
[bindings schemas]
|
||||
|
||||
(= (api/sexpr x) :-)
|
||||
(recur bindings (conj schemas y) more)
|
||||
|
||||
:else
|
||||
(recur (conj bindings x) schemas (cons y more)))))]
|
||||
(-> (api/list-node
|
||||
(list
|
||||
(api/token-node 'do)
|
||||
defendpoint
|
||||
method
|
||||
route
|
||||
(api/list-node
|
||||
(list*
|
||||
(api/token-node 'do)
|
||||
(filter some? (list* metadata result-schema schemas))))
|
||||
(api/list-node
|
||||
(list*
|
||||
(api/token-node `let)
|
||||
(api/vector-node (into []
|
||||
(mapcat (fn [a-binding]
|
||||
[a-binding (api/token-node nil)]))
|
||||
bindings))
|
||||
body))))
|
||||
(with-meta (meta node)))))]
|
||||
(update arg :node update-defendpoint)))
|
||||
@@ -8,19 +8,18 @@
|
||||
[{:keys [node]}]
|
||||
(let [[_ helper-name description argvec & body] (:children node)
|
||||
helper-fn-name (symbol (str (hooks/sexpr helper-name) "*"))]
|
||||
{:node (hooks/list-node
|
||||
(list (hooks/token-node 'do)
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'defn)
|
||||
(hooks/token-node helper-fn-name)
|
||||
description
|
||||
argvec
|
||||
(hooks/list-node body)))
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'def)
|
||||
helper-name
|
||||
description
|
||||
(hooks/list-node
|
||||
(into [(hooks/token-node 'fn)
|
||||
argvec]
|
||||
body))))))}))
|
||||
{:node (-> (hooks/list-node
|
||||
(list (hooks/token-node 'do)
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'defn)
|
||||
(hooks/token-node helper-fn-name)
|
||||
description
|
||||
argvec
|
||||
(hooks/list-node body)))
|
||||
(hooks/list-node
|
||||
(list (hooks/token-node 'defn)
|
||||
helper-name
|
||||
description
|
||||
argvec
|
||||
(hooks/list-node body)))))
|
||||
(with-meta (meta node)))}))
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
database-enable-actions
|
||||
deprecation-notice-version
|
||||
dismissed-custom-dashboard-toast
|
||||
dismissed-onboarding-sidebar-link
|
||||
email-configured?
|
||||
email-from-address
|
||||
email-from-name
|
||||
@@ -161,6 +162,7 @@
|
||||
token-features
|
||||
token-status
|
||||
toucan-name
|
||||
trial-banner-dismissal-timestamp
|
||||
uncached-setting
|
||||
user-visibility
|
||||
version
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns hooks.metabase.public-settings.premium-features
|
||||
(ns hooks.metabase.premium-features.defenterprise
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[hooks.common]))
|
||||
@@ -1,6 +1,8 @@
|
||||
(ns hooks.metabase.test.data.datasets
|
||||
(:require [clj-kondo.hooks-api :as hooks]
|
||||
[clojure.set :as set]))
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.set :as set]
|
||||
[hooks.common]))
|
||||
|
||||
(defn- only-core-drivers?
|
||||
"Whether we're for sure only testing against the 'core' drivers that are also app DB types: `:postgres`, `:mysql`, or
|
||||
@@ -45,7 +47,7 @@
|
||||
(hooks/token-node 'do)
|
||||
test-drivers
|
||||
(-> drivers-expr
|
||||
(vary-meta update :clj-kondo/ignore #(conj (vec %) :unused-value)))
|
||||
(hooks.common/update-ignored-linters conj :unused-value))
|
||||
body))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
|
||||
@@ -15,3 +15,17 @@
|
||||
actual-arg-count)
|
||||
:type :format))))))
|
||||
x)
|
||||
|
||||
(defn case-enum
|
||||
"Lint `(case-enum ...)` like `(condp = ...)`."
|
||||
[x]
|
||||
(letfn [(update-node [node]
|
||||
(let [[_case-enum x & more] (:children node)]
|
||||
(-> (api/list-node
|
||||
(list*
|
||||
(api/token-node 'clojure.core/condp)
|
||||
(api/token-node 'clojure.core/=)
|
||||
x
|
||||
more))
|
||||
(with-meta (meta node)))))]
|
||||
(update x :node update-node)))
|
||||
|
||||
54
.clj-kondo/test/hooks/clojure/core_test.clj
Normal file
@@ -0,0 +1,54 @@
|
||||
(ns hooks.clojure.core-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as hooks]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.clojure.core]))
|
||||
|
||||
(deftest module-checker-allowed-modules-test
|
||||
(let [findings (atom [])]
|
||||
(with-redefs [hooks/reg-finding! (fn [node]
|
||||
(swap! findings conj node))]
|
||||
(hooks.clojure.core/lint-ns
|
||||
{:node (hooks/parse-string "
|
||||
(ns metabase.api.search
|
||||
(:require
|
||||
[metabase.request.core :as request]
|
||||
[metabase.task :as task]))")
|
||||
:config '{:linters {:metabase/ns-module-checker {:allowed-modules {metabase.api #{metabase.request}}
|
||||
:api-namespaces {metabase.request #{metabase.request.core}}}}}})
|
||||
(is (=? [{:message (str "Module metabase.task should not be used in the metabase.api module."
|
||||
" [:metabase/ns-module-checker :allowed-modules metabase.api]")
|
||||
:type :metabase/ns-module-checker}]
|
||||
@findings)))))
|
||||
|
||||
(deftest module-checker-api-namespaces-test
|
||||
(let [findings (atom [])]
|
||||
(with-redefs [hooks/reg-finding! (fn [node]
|
||||
(swap! findings conj node))]
|
||||
(hooks.clojure.core/lint-ns
|
||||
{:node (hooks/parse-string "
|
||||
(ns metabase.api.search
|
||||
(:require
|
||||
[metabase.search.config :as search.config]
|
||||
[metabase.search.core :as search]))")
|
||||
:config '{:linters {:metabase/ns-module-checker {:allowed-modules {metabase.api #{metabase.search}}
|
||||
:api-namespaces {metabase.search #{metabase.search.core}}}}}})
|
||||
(is (=? [{:message (str "Namespace metabase.search.config is not an allowed external API namespace for the"
|
||||
" metabase.search module. [:metabase/ns-module-checker :api-namespaces metabase.search]")
|
||||
:type :metabase/ns-module-checker}]
|
||||
@findings)))))
|
||||
|
||||
(deftest module-checker-api-namespaces-ignore-test
|
||||
(let [findings (atom [])]
|
||||
(with-redefs [hooks/reg-finding! (fn [node]
|
||||
(swap! findings conj node))]
|
||||
(hooks.clojure.core/lint-ns
|
||||
{:node (hooks/parse-string "
|
||||
(ns metabase.api.search
|
||||
(:require
|
||||
^{:clj-kondo/ignore [:metabase/ns-module-checker]}
|
||||
[metabase.search.config :as search.config]
|
||||
[metabase.search.core :as search]))")
|
||||
:config '{:linters {:metabase/ns-module-checker {:allowed-modules {metabase.api #{metabase.search}}
|
||||
:api-namespaces {metabase.search #{metabase.search.core}}}}}})
|
||||
(is (empty? @findings)))))
|
||||
@@ -5,6 +5,35 @@
|
||||
[clojure.test :refer :all]
|
||||
[hooks.common]))
|
||||
|
||||
(deftest ^:parallel ignored-linters-test
|
||||
(doseq [ignored [":metabase/ns-module-checker"
|
||||
"[:metabase/ns-module-checker]"
|
||||
"(:metabase/ns-module-checker)"]
|
||||
prefix ["#_"
|
||||
"^"]
|
||||
:let [s (format "%s{:clj-kondo/ignore %s} [metabase.search.config :as search.config]"
|
||||
prefix
|
||||
ignored)]]
|
||||
(testing (pr-str s)
|
||||
(is (= #{:metabase/ns-module-checker}
|
||||
(hooks.common/ignored-linters (api/parse-string s)))))))
|
||||
|
||||
(deftest ^:parallel merge-ignored-linters-test
|
||||
(let [node-1 (api/parse-string "#_{:clj-kondo/ignore [:a]} [:a]")
|
||||
node-2 (api/parse-string "#_{:clj-kondo/ignore [:b]} [:b]")
|
||||
node-3 (api/parse-string "#_{:clj-kondo/ignore [:c]} [:c]")
|
||||
node-1' (hooks.common/merge-ignored-linters node-1 node-2 node-3)]
|
||||
(is (= #{:a :b :c}
|
||||
(hooks.common/ignored-linters node-1')))
|
||||
(testing "Updated metadata should match the shape Kondo expects"
|
||||
(is (=? {:clj-kondo/ignore {:linters api/vector-node?}}
|
||||
(meta node-1')))
|
||||
(let [children (-> node-1' meta :clj-kondo/ignore :linters :children)]
|
||||
(is (= (count children) 3))
|
||||
(is (every? api/keyword-node? children))
|
||||
(is (= [:a :b :c]
|
||||
(sort (map api/sexpr children))))))))
|
||||
|
||||
(deftest ^:parallel node->qualified-symbol-test
|
||||
(binding [clj-kondo.impl.utils/*ctx* {:namespaces (atom nil)}]
|
||||
(is (= 'metabase.util.i18n/tru
|
||||
|
||||
35
.clj-kondo/test/hooks/metabase/api/macros_test.clj
Normal file
@@ -0,0 +1,35 @@
|
||||
(ns hooks.metabase.api.macros-test
|
||||
(:require
|
||||
[clj-kondo.hooks-api :as api]
|
||||
[clojure.test :refer :all]
|
||||
[hooks.metabase.api.macros]))
|
||||
|
||||
(deftest ^:parallel defendpoint-test
|
||||
(let [form '(api.macros/defendpoint :post "/" :- [:map [:collection_id :int]]
|
||||
"Create a new [[Timeline]]."
|
||||
[_route-params
|
||||
_query-params
|
||||
{:keys [icon], collection-id :collection_id, :as body} :- [:map
|
||||
[:name ms/NonBlankString]
|
||||
[:default {:optional true} [:maybe :boolean]]]]
|
||||
(body icon)
|
||||
(body collection-id))
|
||||
node (-> form pr-str api/parse-string)]
|
||||
(is (= '(do
|
||||
api.macros/defendpoint
|
||||
:post
|
||||
"/"
|
||||
(do
|
||||
[:map [:collection_id :int]]
|
||||
[:map
|
||||
[:name ms/NonBlankString]
|
||||
[:default {:optional true} [:maybe :boolean]]])
|
||||
(clojure.core/let [_route-params nil
|
||||
_query-params nil
|
||||
{:keys [icon], collection-id :collection_id, :as body} nil]
|
||||
(body icon)
|
||||
(body collection-id)))
|
||||
(-> {:node node}
|
||||
hooks.metabase.api.macros/defendpoint
|
||||
:node
|
||||
api/sexpr)))))
|
||||
@@ -13,6 +13,8 @@
|
||||
"test"
|
||||
"enterprise/backend"
|
||||
"bin/build"
|
||||
"bin/lint-migrations-file"
|
||||
"bin/release-list"
|
||||
"modules/drivers"
|
||||
".clj-kondo/src"
|
||||
".clj-kondo/test"
|
||||
@@ -192,6 +194,12 @@
|
||||
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
|
||||
metabase.query-processor.streaming/streaming-response [[:block 1]]
|
||||
metabase.query-processor.test-util/format-rows-by [[:default]]
|
||||
metabase.request.core/as-admin [[:block 0]]
|
||||
metabase.request.core/with-current-request [[:inner 0]]
|
||||
metabase.request.core/with-current-user [[:inner 0]]
|
||||
metabase.request.core/with-limit-and-offset [[:inner 0]]
|
||||
metabase.request.session/as-admin [[:block 0]]
|
||||
metabase.request.session/with-current-user [[:inner 0]]
|
||||
metabase.sample-data-test/with-temp-sample-database-db [[:block 1]]
|
||||
metabase.server.middleware.session/as-admin [[:block 0]]
|
||||
metabase.server.middleware.session/with-current-user [[:inner 0]]
|
||||
@@ -269,6 +277,7 @@
|
||||
metabase.upload.types/with-currency [[:default]]
|
||||
metabase.upload.types/with-parens [[:default]]
|
||||
metabase.util/auto-retry [[:block 1]]
|
||||
metabase.util/case-enum [[:block 1]]
|
||||
metabase.util/ignore-exceptions [[:block 0]]
|
||||
metabase.util/or-with [[:block 1]]
|
||||
metabase.util/profile [[:block 1]]
|
||||
|
||||
71
.github/actions/build-e2e-matrix/action.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: Build E2E Matrix
|
||||
description: Build and output a custom matrix for E2E tests.
|
||||
|
||||
outputs:
|
||||
matrix:
|
||||
description: Parameterized matrix in JSON format
|
||||
value: ${{ steps.matrix.outputs.result }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
id: matrix
|
||||
with:
|
||||
script: | # js
|
||||
const java = 21;
|
||||
|
||||
const defaultRunner = "ubuntu-22.04";
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
runner: defaultRunner,
|
||||
edition: "ee",
|
||||
context: "folder"
|
||||
};
|
||||
|
||||
const testSets = [
|
||||
["actions", {} ],
|
||||
["admin", {} ],
|
||||
["admin-2", {} ],
|
||||
["binning", {} ],
|
||||
["collections", {} ],
|
||||
["custom-column", {} ],
|
||||
["dashboard", {} ],
|
||||
["dashboard-cards", {} ],
|
||||
["dashboard-filters", {} ],
|
||||
["dashboard-filters-2", {} ],
|
||||
["embedding", {} ],
|
||||
["embedding-sdk", { context: "embedding-sdk" } ],
|
||||
["filters", {} ],
|
||||
["filters-reproductions", {}],
|
||||
["joins", {} ],
|
||||
["metrics", {} ],
|
||||
["models", {} ],
|
||||
["native", {} ],
|
||||
["native-filters", {} ],
|
||||
["onboarding", {} ],
|
||||
["organization", {} ],
|
||||
["permissions", {} ],
|
||||
["question", {} ],
|
||||
["question-reproductions", {} ],
|
||||
["search", {}],
|
||||
["sharing", {} ],
|
||||
["visualizations-charts", {} ],
|
||||
["visualizations-tabular", {} ],
|
||||
["oss-subset", { edition: 'oss', context: "special" } ],
|
||||
["slow", { context: "special" } ],
|
||||
["flaky", { context: "special" } ],
|
||||
["mongo", { context: "qa-database" } ],
|
||||
];
|
||||
|
||||
const config = testSets.map(([name, specialOptions]) => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
...specialOptions,
|
||||
name,
|
||||
}
|
||||
});
|
||||
|
||||
const matrix = { include: config };
|
||||
return matrix;
|
||||
@@ -90,7 +90,7 @@ runs:
|
||||
|
||||
if ${{ inputs.webhook }}; then
|
||||
echo -e "${Y}Starting webhook test container..." &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
docker run -d -p 9080:8080/tcp tarampampam/webhook-tester:1.1.0 serve --create-session 00000000-0000-0000-0000-000000000000 &&
|
||||
while ! nc -z localhost 9080; do sleep 1; done &&
|
||||
echo -e "${G}Webhook tester is up and running!"
|
||||
fi
|
||||
|
||||
2
.github/actions/prepare-backend/action.yml
vendored
@@ -5,7 +5,7 @@ inputs:
|
||||
default: '21'
|
||||
clojure-version:
|
||||
required: true
|
||||
default: '1.11.1.1262'
|
||||
default: '1.12.0.1488'
|
||||
m2-cache-key:
|
||||
description: 'Key to cache M2 packages from Maven Central'
|
||||
required: true
|
||||
|
||||
9
.github/actions/test-driver/action.yml
vendored
@@ -5,19 +5,24 @@ inputs:
|
||||
default: "driver"
|
||||
test-args:
|
||||
required: false
|
||||
build-static-viz:
|
||||
required: true
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Prepare back-end environment
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: driver
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
- name: Build static viz frontend
|
||||
run: yarn build-static-viz
|
||||
shell: bash
|
||||
if: ${{ inputs.build-static-viz == 'true' }}
|
||||
- name: Test database driver
|
||||
run: clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
|
||||
shell: bash
|
||||
|
||||
1
.github/file-paths.yaml
vendored
@@ -79,7 +79,6 @@ backend_specs: &backend_specs
|
||||
- "test_modules/**"
|
||||
- "modules/drivers/*/test/**"
|
||||
- "test_config/**"
|
||||
- "dev/**"
|
||||
- "test_resources/**"
|
||||
|
||||
backend_all: &backend_all
|
||||
|
||||
@@ -10,9 +10,9 @@ export PATH="$PATH:$JAVA_HOME/bin"
|
||||
which java
|
||||
|
||||
# install clojure version needed for Metabase
|
||||
curl -O https://download.clojure.org/install/linux-install-1.11.0.1100.sh
|
||||
chmod +x linux-install-1.11.0.1100.sh
|
||||
./linux-install-1.11.0.1100.sh
|
||||
curl -O https://download.clojure.org/install/linux-install-1.12.0.1488.sh
|
||||
chmod +x linux-install-1.12.0.1488.sh
|
||||
./linux-install-1.12.0.1488.sh
|
||||
|
||||
RESOURCES_DIR=/app/source/resources
|
||||
|
||||
|
||||
19
.github/team.json
vendored
@@ -12,19 +12,29 @@
|
||||
"metamben",
|
||||
"lbrdnk",
|
||||
"appleby",
|
||||
"uladzimirdev"
|
||||
"uladzimirdev",
|
||||
"wzimrin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Drivers",
|
||||
"label": ".Team/Drivers",
|
||||
"members": ["snoe"]
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/98/views/1",
|
||||
"members": [
|
||||
"snoe",
|
||||
"rileythomp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Embedding",
|
||||
"label": ".Team/Embedding",
|
||||
"projectUrl": "https://github.com/orgs/metabase/projects/88/views/1",
|
||||
"members": ["WiNloSt", "oisincoveney", "npretto", "heypoom"]
|
||||
"members": [
|
||||
"WiNloSt",
|
||||
"oisincoveney",
|
||||
"npretto",
|
||||
"heypoom"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Workflows",
|
||||
@@ -47,7 +57,8 @@
|
||||
"johnswanson",
|
||||
"rafpaf",
|
||||
"sloansparger",
|
||||
"iethree"
|
||||
"iethree",
|
||||
"nvoxland"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
5
.github/workflows/backend.yml
vendored
@@ -154,6 +154,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-backend
|
||||
with:
|
||||
m2-cache-key: be-tests
|
||||
java-version: "${{ matrix.java-version }}"
|
||||
# Depending on which files changed, we might either have to build static-viz
|
||||
# from scratch or download the previously built artifact
|
||||
- name: Build static-viz frontend
|
||||
@@ -194,9 +195,9 @@ jobs:
|
||||
fail-on-error: false
|
||||
|
||||
be-tests-stub:
|
||||
needs: [be-tests]
|
||||
needs: [files-changed, static-viz-files-changed, be-tests]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
always() &&
|
||||
github.event.pull_request.draft == false && needs.be-tests.result == 'skipped'
|
||||
runs-on: ubuntu-22.04
|
||||
name: be-tests-java-${{ matrix.java-version }}-${{ matrix.edition }}
|
||||
|
||||
91
.github/workflows/backport.yml
vendored
@@ -6,25 +6,50 @@ on:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
check_pull_request_state:
|
||||
name: Checks whether the PR has been merged
|
||||
if: github.event.issue.pull_request && contains(github.event.comment.body, '@metabase-bot backport')
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
canBackport: ${{ steps.check_pull_request_state.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
id: check_pull_request_state
|
||||
with:
|
||||
script: |
|
||||
const { data: pullRequest } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.issue.number
|
||||
});
|
||||
|
||||
const canBackport = pullRequest.state === "closed"
|
||||
if (!canBackport) {
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.payload.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "The backport command can only be used after the pull request has been merged. Please wait until the PR is merged before attempting to backport."
|
||||
});
|
||||
}
|
||||
return canBackport
|
||||
|
||||
create_pull_request:
|
||||
name: Creates a pull request
|
||||
if: contains(github.event.comment.body, '@metabase-bot backport')
|
||||
needs: [check_pull_request_state]
|
||||
if: needs.check_pull_request_state.outputs.canBackport == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: tibdex/github-app-token@v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.METABASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v4
|
||||
name: Cherry-pick commits and create PR
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
- name: Verify Membership
|
||||
id: verify-membership
|
||||
uses: ./.github/actions/verify-membership
|
||||
with:
|
||||
github_token: ${{ steps.generate-token.outputs.token }}
|
||||
github_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
organization_name: metabase
|
||||
username: ${{ github.event.comment.user.login }}
|
||||
- run: |
|
||||
@@ -110,23 +135,50 @@ jobs:
|
||||
|
||||
if [ -n "${SQUASHED_COMMIT}" ]; then
|
||||
echo "found commit ${SQUASHED_COMMIT}"
|
||||
git cherry-pick ${SQUASHED_COMMIT}
|
||||
else
|
||||
echo "probably a PR wasn't merged with Squash And Merge button, searching again"
|
||||
SQUASHED_COMMIT=$(env -i git log ${ORIGINAL_BASE_REF} --grep="#${ORIGINAL_PULL_REQUEST_NUMBER}" --format="%H")
|
||||
|
||||
if [ -n "${SQUASHED_COMMIT}" ]; then
|
||||
echo "found commit ${SQUASHED_COMMIT}"
|
||||
git cherry-pick ${SQUASHED_COMMIT}
|
||||
else
|
||||
echo "No squashed commit found for PR #${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
else
|
||||
echo "PR has not been merged, copying all commits"
|
||||
git cherry-pick ${ORIGINAL_BASE_SHA}..${ORIGINAL_HEAD_SHA}
|
||||
echo "The PR has not been merged"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git cherry-pick ${SQUASHED_COMMIT} || true
|
||||
|
||||
CONFLICTS=$(git ls-files -u | wc -l)
|
||||
if [ "$CONFLICTS" -gt 0 ]; then
|
||||
git cherry-pick --abort
|
||||
|
||||
echo "Could not cherry pick because of a conflict"
|
||||
echo "has-conflicts=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Add a shell script for resolving conflicts
|
||||
echo "git reset HEAD~1" > ./backport.sh
|
||||
echo "rm ./backport.sh" >> ./backport.sh
|
||||
echo "git cherry-pick ${SQUASHED_COMMIT}" >> ./backport.sh
|
||||
echo "echo 'Resolve conflicts and force push this branch'" >> ./backport.sh
|
||||
chmod +x ./backport.sh
|
||||
git add ./backport.sh
|
||||
git commit -m "Add backport resolution script"
|
||||
|
||||
PR_BODY=$(cat <<-END
|
||||
#${ORIGINAL_PULL_REQUEST_NUMBER}
|
||||
> [!IMPORTANT]
|
||||
> Manual conflict resolution is required.
|
||||
Checkout the branch and run \`./backport.sh\` script. Force push your changes after cherry-picking.
|
||||
END
|
||||
)
|
||||
else
|
||||
echo "has-conflicts=false" >> $GITHUB_OUTPUT
|
||||
PR_BODY="#${ORIGINAL_PULL_REQUEST_NUMBER}"
|
||||
fi
|
||||
|
||||
git push origin "${BACKPORT_BRANCH}" --force-with-lease
|
||||
@@ -134,12 +186,14 @@ jobs:
|
||||
if [[ $(gh pr list --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" -s "open") ]]; then
|
||||
echo "PR already exists"
|
||||
else
|
||||
BACKPORT_PR_URL=$(gh pr create --base "${TARGET_BRANCH}" --head "${BACKPORT_BRANCH}" --label "was-backported" --assignee "${GITHUB_ACTOR}" --title "🤖 backported \"${ORIGINAL_TITLE}\"" --body "#${ORIGINAL_PULL_REQUEST_NUMBER}")
|
||||
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_NUMBER=${BACKPORT_PR_URL##*/}
|
||||
|
||||
echo "backport_pr_number=$BACKPORT_PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "New PR has been created"
|
||||
fi
|
||||
|
||||
git checkout master
|
||||
env:
|
||||
ORIGINAL_TITLE: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.title }}
|
||||
ORIGINAL_BASE_REF: ${{ fromJson(steps.branch_info.outputs.result).originalPullRequest.base.ref }}
|
||||
@@ -151,18 +205,25 @@ jobs:
|
||||
BACKPORT_BRANCH: ${{ fromJson(steps.branch_info.outputs.result).backportBranch }}
|
||||
START_SHA: ${{ fromJson(steps.branch_info.outputs.result).startSha }}
|
||||
END_SHA: ${{ fromJson(steps.branch_info.outputs.result).endSha }}
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
- name: Auto approve backport PR
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
uses: juliangruber/approve-pull-request-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
- name: Enable Pull Request Automerge
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'false' }}
|
||||
uses: peter-evans/enable-pull-request-automerge@v2
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
pull-request-number: ${{ steps.create_backport_pull_request.outputs.backport_pr_number }}
|
||||
merge-method: squash
|
||||
- uses: ./.github/actions/notify-pull-request
|
||||
if: ${{ steps.create_backport_pull_request.outputs.has-conflicts == 'true' }}
|
||||
with:
|
||||
include-log: false
|
||||
message: Manual conflict resolution is required
|
||||
|
||||
notify_when_failed:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
11
.github/workflows/build-for-release.yml
vendored
@@ -100,21 +100,14 @@ jobs:
|
||||
oss: getCanonicalVersion(version, 'oss'),
|
||||
};
|
||||
|
||||
const ossReleased = await hasBeenReleased({
|
||||
const released = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.oss,
|
||||
});
|
||||
|
||||
const eeReleased = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.ee,
|
||||
});
|
||||
|
||||
if (ossReleased || eeReleased) {
|
||||
if (released) {
|
||||
throw new Error("This version has already been released!", version);
|
||||
}
|
||||
|
||||
|
||||
18
.github/workflows/doc-md-links.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Lint doc Markdown links
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- docs/**
|
||||
pull_request:
|
||||
paths:
|
||||
- docs/**
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
- name: Lint doc links
|
||||
run: yarn run docs-lint-links
|
||||
248
.github/workflows/drivers.yml
vendored
@@ -144,13 +144,16 @@ jobs:
|
||||
MB_DATABRICKS_TEST_HTTP_PATH: ${{ secrets.MB_DATABRICKS_JDBC_TEST_HTTP_PATH }}
|
||||
MB_DATABRICKS_TEST_TOKEN: ${{ secrets.MB_DATABRICKS_JDBC_TEST_TOKEN }}
|
||||
MB_DATABRICKS_TEST_CATALOG: 'metabase_ci'
|
||||
MB_DATABRICKS_TEST_CLIENT_ID: ${{ secrets.MB_DATABRICKS_JDBC_TEST_CLIENT_ID }}
|
||||
MB_DATABRICKS_TEST_OAUTH_SECRET: ${{ secrets.MB_DATABRICKS_JDBC_TEST_OAUTH_SECRET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test Databricks driver
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-databricks-ee'
|
||||
test-args: ":exclude-tags '[:mb/once]'"
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
@@ -198,7 +201,7 @@ jobs:
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mariadb-10-2-ee:
|
||||
be-tests-mysql-mariadb:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -206,7 +209,36 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- name: MariaDB 10.2
|
||||
junit-name: be-tests-mariadb-10-2-ee
|
||||
image: circleci/mariadb:10.2.23
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MariaDB Latest
|
||||
junit-name: be-tests-mariadb-latest-ee
|
||||
image: circleci/mariadb:latest
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL 8.0
|
||||
junit-name: be-tests-mysql-8-0-ee
|
||||
image: cimg/mysql:8.0
|
||||
env:
|
||||
enable-ssl-tests: 'false'
|
||||
- name: MySQL Latest
|
||||
junit-name: be-tests-mysql-latest-ee
|
||||
image: mysql:latest
|
||||
env:
|
||||
enable-ssl-tests: 'true'
|
||||
partition-index: [0, 1, 2, 3]
|
||||
services:
|
||||
mysql:
|
||||
image: ${{ matrix.version.image }}
|
||||
ports:
|
||||
- "3306:3306"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
@@ -216,75 +248,45 @@ jobs:
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
services:
|
||||
mariadb:
|
||||
image: circleci/mariadb:10.2.23
|
||||
ports:
|
||||
- "3306:3306"
|
||||
#
|
||||
# SSL config: only for mysql latest tests.
|
||||
#
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_SSL: ${{ matrix.version.env.enable-ssl-tests }}
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
# for historic reasons (I don't want to mess around with required jobs) the job name should be something like
|
||||
# "be-tests-mariadb-10-2-ee (0)"
|
||||
name: "${{ matrix.version.junit-name }} (${{ matrix.partition-index }})"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test MariaDB driver (10.2)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mariadb-10-2-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mariadb-latest-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
services:
|
||||
mariadb:
|
||||
image: circleci/mariadb:latest
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test MariaDB driver (latest)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mariadb-latest-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- 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 }}
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test ${{ matrix.version.name }}
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: true
|
||||
junit-name: ${{ matrix.version.junit-name }}
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mongo-4-4-ee:
|
||||
needs: files-changed
|
||||
@@ -543,109 +545,6 @@ jobs:
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mysql-8-0-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
services:
|
||||
mysql:
|
||||
image: cimg/mysql:8.0
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test MySQL driver (8.0)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mysql-8-0-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-mysql-latest-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
partition-index: [0, 1, 2, 3]
|
||||
env:
|
||||
CI: 'true'
|
||||
DRIVERS: mysql
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_HOST: localhost
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_DBNAME: circle_test
|
||||
MB_DB_USER: root
|
||||
MB_MYSQL_TEST_USER: root
|
||||
# set up env vars for something named "MYSQL_SSL" to run MySQL SSL tests verifying connectivity with PEM cert
|
||||
# they are deliberately given a different name to prevent them from affecting the regular test run against
|
||||
# the configured MySQL instance, but there is one particular test (mysql-connect-with-ssl-and-pem-cert-test)
|
||||
# that overrides the MB_MYSQL_TEST_* values with them
|
||||
# the MYSQL_RDS_SSL_INSTANCE vars are defined as secrets and can be altered
|
||||
MB_MYSQL_SSL_TEST_HOST: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_HOST }}
|
||||
MB_MYSQL_SSL_TEST_SSL: true
|
||||
MB_MYSQL_SSL_TEST_ADDITIONAL_OPTIONS: 'verifyServerCertificate=true'
|
||||
# the contents of the ./resources/certificates/rds-combined-ca-bundle.pem file
|
||||
MB_MYSQL_SSL_TEST_SSL_CERT: ${{ secrets.MB_MYSQL_SSL_TEST_SSL_CERT }}
|
||||
MB_MYSQL_SSL_TEST_USER: metabase
|
||||
MB_MYSQL_SSL_TEST_PASSWORD: ${{ secrets.MYSQL_RDS_SSL_INSTANCE_PASSWORD }}
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: true
|
||||
MYSQL_DATABASE: circle_test
|
||||
ports:
|
||||
- "3306:3306"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Test MySQL driver (latest)
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
junit-name: 'be-tests-mysql-latest-ee'
|
||||
test-args: >-
|
||||
:partition/total 4
|
||||
:partition/index ${{ matrix.partition-index }}
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit/
|
||||
output-name: ${{ github.job }}
|
||||
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
|
||||
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
|
||||
|
||||
be-tests-oracle-18-4-ee:
|
||||
needs: files-changed
|
||||
if: github.event.pull_request.draft == false && needs.files-changed.outputs.backend_all == 'true'
|
||||
@@ -754,9 +653,11 @@ jobs:
|
||||
enable-ssl-tests: 'true'
|
||||
job:
|
||||
- name: Driver Tests
|
||||
build-static-viz: false
|
||||
test-args: >-
|
||||
:only-tags [:mb/driver-tests]
|
||||
- name: App DB Tests
|
||||
build-static-viz: true
|
||||
test-args: >-
|
||||
:exclude-tags [:mb/driver-tests]
|
||||
name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
@@ -787,6 +688,7 @@ jobs:
|
||||
- name: Test ${{ matrix.version.name }} (${{ matrix.job.name }})
|
||||
uses: ./.github/actions/test-driver
|
||||
with:
|
||||
build-static-viz: ${{ matrix.job.build-static-viz }}
|
||||
junit-name: 'be-tests-${{ matrix.version.junit-name }}'
|
||||
test-args: >-
|
||||
${{ matrix.job.test-args }}
|
||||
|
||||
47
.github/workflows/drivers_summary.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Driver Tests (Summary)
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Driver Tests]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
#
|
||||
# Moving forward we should be able to make just the single 'be-drivers' job be required, as opposed to all the
|
||||
# different jobs in `drivers.yml` (maybe).
|
||||
#
|
||||
driver-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Wait for all driver tests to pass
|
||||
steps:
|
||||
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
run: |
|
||||
echo ""
|
||||
echo " _------. "
|
||||
echo " / , \_ "
|
||||
echo " / / /{}\ |o\_ "
|
||||
echo " / \ `--' /-' \ "
|
||||
echo " | \ \ | "
|
||||
echo " | |`-, | "
|
||||
echo " / /__/)/ "
|
||||
echo " | | "
|
||||
echo ""
|
||||
echo "All driver tests have passed (or have been skipped). Cam is very proud of you."
|
||||
- if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
run: |
|
||||
echo ""
|
||||
echo " .::::::::::. .::::::::::. "
|
||||
echo " .::::''''''::::. .::::''''''::::. "
|
||||
echo " .:::' `::::.... ....::::' `:::. "
|
||||
echo " .::' `:::::::| |:::::::' `::. "
|
||||
echo " .::| |::::::|_ ___ __|::::::| |::. "
|
||||
echo " `--' |::::::|_()__()_|::::::| `--' "
|
||||
echo " ::: |::-o::| |::o-::| ::: "
|
||||
echo " `::. .|::::::| |::::::|. .::' "
|
||||
echo " `:::. .::\-----' `-----/::. .:::' "
|
||||
echo " `::::......::::' `::::......::::' "
|
||||
echo " `::::::::::' `::::::::::' "
|
||||
echo ""
|
||||
echo "Driver tests have failed. You have been sentenced to MetaJail."
|
||||
exit 1
|
||||
@@ -156,10 +156,20 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Needed to omit newly added tests for functionality that is not yet released
|
||||
- name: Checkout SDK release commit
|
||||
- name: Sparse checkout tests from SDK release commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: embedding-sdk-${{ needs.resolve-sdk-version.outputs.sdk_version }}
|
||||
path: sdk-release-e2e
|
||||
sparse-checkout: e2e
|
||||
|
||||
- name: Move tests from the release commit into the repo
|
||||
run: |
|
||||
rm -rf ./e2e/support/helpers/*
|
||||
mv ./sdk-release-e2e/e2e/support/helpers/* ./e2e/support/helpers
|
||||
rm -rf ./e2e/test-component/*
|
||||
mv ./sdk-release-e2e/e2e/test-component/* ./e2e/test-component
|
||||
shell: bash
|
||||
|
||||
- name: Retrieve uberjar artifact for ee
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
91
.github/workflows/e2e-matrix-builder.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: E2E Matrix Builder
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
chunks:
|
||||
required: true
|
||||
type: number
|
||||
default: 30
|
||||
outputs:
|
||||
matrix:
|
||||
value: ${{ jobs.build-matrix.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
build-matrix:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix: ${{ steps.matrix.outputs.config }}
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20.x'
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: e2e
|
||||
- name: get test file names
|
||||
id: test-files
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=filenames::$(find e2e/test/scenarios -name '*.cy.spec.*' | tr '\n' ',')"
|
||||
- run: yarn add underscore
|
||||
- uses: actions/github-script@v7
|
||||
id: matrix
|
||||
with:
|
||||
script: | # js
|
||||
const _ = require('underscore');
|
||||
|
||||
const java = 21;
|
||||
const defaultRunner = "ubuntu-22.04";
|
||||
|
||||
// these are special and shouldn't be chunked out arbitrarily
|
||||
const blacklist = [
|
||||
"/embedding-sdk/",
|
||||
"/cross-version/"
|
||||
];
|
||||
|
||||
const defaultOptions = {
|
||||
"java-version": java,
|
||||
runner: defaultRunner,
|
||||
specs: "./e2e/test/scenarios/**/*.cy.spec.*",
|
||||
edition: "ee",
|
||||
};
|
||||
|
||||
const specialTests = [
|
||||
{ name: "embedding-sdk", specs: "./e2e/test/scenarios/embedding-sdk/**.cy.spec.*" },
|
||||
{ name: "oss-subset", edition: 'oss', tags: "@OSS" },
|
||||
{ name: "slow", tags: "@slow" },
|
||||
{ name: "mongo", tags: "@mongo" },
|
||||
];
|
||||
|
||||
const fileNames = "${{ steps.test-files.outputs.filenames }}"
|
||||
.split(",")
|
||||
.filter(Boolean)
|
||||
.filter(testPath => !blacklist.some(blacklisted => testPath.includes(blacklisted)));
|
||||
|
||||
const fileCount = fileNames.length;
|
||||
|
||||
// subtract the special tests since they all get their own runners
|
||||
const chunkSize = fileCount / (Number('${{ inputs.chunks }}') - specialTests.length);
|
||||
|
||||
|
||||
|
||||
const chunkedFiles = _.chunk(fileNames, chunkSize);
|
||||
|
||||
const regularTests = chunkedFiles.map((files, index) => ({
|
||||
name: `e2e-group-${index + 1}`,
|
||||
specs: files.join(","),
|
||||
tags: '',
|
||||
}));
|
||||
|
||||
const testSets = [...regularTests, ...specialTests];
|
||||
|
||||
const config = testSets.map((options) => ({
|
||||
...defaultOptions,
|
||||
...options,
|
||||
}));
|
||||
|
||||
console.log("matrix config", config);
|
||||
|
||||
core.setOutput("config", JSON.stringify({ include: config }));
|
||||
151
.github/workflows/e2e-test.yml
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
name: E2E Test
|
||||
run-name: E2E Test - ${{ inputs.name }}
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
type: string
|
||||
edition: # oss | ee
|
||||
required: false
|
||||
type: string
|
||||
default: ee
|
||||
specs:
|
||||
required: false
|
||||
default: ./e2e/test/scenarios/**/*.cy.spec.*
|
||||
type: string
|
||||
tags:
|
||||
required: false
|
||||
type: string
|
||||
runner:
|
||||
required: false
|
||||
type: string
|
||||
default: ubuntu-22.04
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
timeout-minutes: 45
|
||||
name: e2e-tests-${{ inputs.name }}-${{ inputs.edition }}
|
||||
env:
|
||||
MB_EDITION: ${{ inputs.edition }}
|
||||
DISPLAY: ""
|
||||
QA_DB_ENABLED: true
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
CYPRESS_QA_DB_MONGO: ${{ inputs.name == 'mongo' }}
|
||||
CYPRESS_IS_EMBEDDING_SDK: ${{ inputs.name == 'embedding-sdk' }}
|
||||
mongo: ${{ inputs.name == 'mongo' }}
|
||||
postgres: ${{ inputs.name != 'mongo' }}
|
||||
mysql: ${{ inputs.name != 'mongo' }}
|
||||
openldap: ${{ contains(inputs.specs, 'ldap') }}
|
||||
maildev: true
|
||||
webhook: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: ${{ env.openldap }}
|
||||
postgres: ${{ env.postgres }}
|
||||
mysql: ${{ env.mysql }}
|
||||
mongo: ${{ env.mongo }}
|
||||
webhook: ${{ env.webhook }}
|
||||
|
||||
- name: Run Embedding SDK Storybook
|
||||
if: inputs.name == 'embedding-sdk'
|
||||
uses: ./.github/actions/run-embedding-sdk-storybook
|
||||
|
||||
- name: Retrieve uberjar artifact for ${{ inputs.edition }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
|
||||
- name: Run EE Cypress tests on ${{ inputs.name }}
|
||||
if: ${{ !inputs.tags }}
|
||||
shell: bash
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="-@slow+-@mongo+-@flaky+-@OSS --@quarantine",grepOmitFiltered=true \
|
||||
--spec '${{ inputs.specs }}' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run Tagged EE Cypress tests on ${{ inputs.name }}
|
||||
if: ${{ inputs.tags }}
|
||||
shell: bash
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="${{inputs.tags}} --@quarantine",grepOmitFiltered=true \
|
||||
--spec '${{ inputs.specs }}' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit
|
||||
output-name: e2e-${{ inputs.name }}
|
||||
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 }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-recording-${{ inputs.name }}-${{ inputs.edition }}
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const {
|
||||
parseReport,
|
||||
formatSummary
|
||||
} = require("./.github/scripts/handle-mochawesome-report.js");
|
||||
|
||||
const report = parseReport();
|
||||
const summary = formatSummary(report);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
221
.github/workflows/e2e-tests.yml
vendored
@@ -29,15 +29,9 @@ jobs:
|
||||
filters: .github/file-paths.yaml
|
||||
|
||||
e2e-matrix-builder:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix: ${{ steps.e2e-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Generate matrix for E2E tests
|
||||
id: e2e-matrix
|
||||
uses: ./.github/actions/build-e2e-matrix
|
||||
uses: ./.github/workflows/e2e-matrix-builder.yml
|
||||
with:
|
||||
chunks: 50
|
||||
|
||||
# if this is a test on a release branch, we need to check the build requirements
|
||||
get-build-requirements:
|
||||
@@ -70,12 +64,12 @@ jobs:
|
||||
node_version: requirements.node,
|
||||
};
|
||||
|
||||
|
||||
build:
|
||||
needs: [files-changed, e2e-matrix-builder, get-build-requirements]
|
||||
needs: [files-changed, get-build-requirements]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
github.event.pull_request.draft == false &&
|
||||
needs.e2e-matrix-builder.result == 'success' &&
|
||||
needs.files-changed.outputs.e2e_all == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
@@ -108,179 +102,58 @@ jobs:
|
||||
name: metabase-${{ matrix.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
e2e-tests:
|
||||
name: ${{ matrix.name }}-${{ matrix.edition }}
|
||||
uses: ./.github/workflows/e2e-test.yml
|
||||
needs: [build, e2e-matrix-builder]
|
||||
if: |
|
||||
!cancelled() && needs.build.result == 'success'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 45
|
||||
name: e2e-tests-${{ matrix.name }}-${{ matrix.edition }}
|
||||
env:
|
||||
MB_EDITION: ${{ matrix.edition }}
|
||||
DISPLAY: ""
|
||||
QA_DB_ENABLED: true
|
||||
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
|
||||
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
|
||||
CYPRESS_ALL_FEATURES_TOKEN: ${{ secrets.ENTERPRISE_TOKEN }}
|
||||
CYPRESS_NO_FEATURES_TOKEN: ${{ secrets.E2E_STARTER_TOKEN }}
|
||||
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
|
||||
MB_SNOWPLOW_AVAILABLE: true
|
||||
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
|
||||
TZ: US/Pacific # to make node match the instance tz
|
||||
CYPRESS_CI: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.e2e-matrix-builder.outputs.matrix) }}
|
||||
matrix: ${{ fromJson(needs.e2e-matrix-builder.outputs.matrix) }}
|
||||
secrets: inherit
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
edition: ${{ matrix.edition }}
|
||||
specs: ${{ matrix.specs }}
|
||||
tags: ${{ matrix.tags }}
|
||||
runner: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare Docker containers
|
||||
uses: ./.github/actions/e2e-prepare-containers
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
maildev: true
|
||||
openldap: ${{ startsWith(matrix.name, 'admin') }}
|
||||
postgres: ${{ matrix.name != 'mongo'}}
|
||||
mysql: ${{ matrix.name != 'mongo'}}
|
||||
mongo: ${{ matrix.name == 'mongo'}}
|
||||
webhook: true
|
||||
|
||||
- name: Retrieve uberjar artifact for ${{ matrix.edition }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
|
||||
|
||||
- name: Get the version info
|
||||
run: |
|
||||
jar xf target/uberjar/metabase.jar version.properties
|
||||
mv version.properties resources/
|
||||
|
||||
- name: Prepare front-end environment
|
||||
uses: ./.github/actions/prepare-frontend
|
||||
|
||||
- name: Prepare JDK ${{ matrix.java-version }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Prepare Cypress environment
|
||||
id: cypress-prep
|
||||
uses: ./.github/actions/prepare-cypress
|
||||
|
||||
- name: Run Snowplow micro
|
||||
uses: ./.github/actions/run-snowplow-micro
|
||||
|
||||
- name: Run OSS-specific Cypress tests
|
||||
if: matrix.edition == 'oss' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags=@OSS,grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run slow and resource-intensive Cypress tests
|
||||
if: matrix.name == 'slow' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@slow",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run Flaky Cypress tests
|
||||
if: matrix.name == 'flaky' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run EE Cypress tests on ${{ matrix.name }}
|
||||
if: matrix.context == 'folder' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="-@slow+-@mongo+-@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--folder ${{ matrix.name }} \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
|
||||
- name: Run E2E tests that depend on Mongo
|
||||
if: matrix.name == 'mongo'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="@mongo --@quarantine",grepOmitFiltered=true \
|
||||
--spec './e2e/test/scenarios/**/*.cy.spec.(js|ts)' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
env:
|
||||
CYPRESS_QA_DB_MONGO: true
|
||||
|
||||
- name: Run Embedding SDK Storybook
|
||||
if: matrix.context == 'embedding-sdk' && github.event_name != 'schedule'
|
||||
uses: ./.github/actions/run-embedding-sdk-storybook
|
||||
|
||||
- name: Run E2E tests for Embedding SDK
|
||||
if: matrix.context == 'embedding-sdk' && github.event_name != 'schedule'
|
||||
run: |
|
||||
yarn run test-cypress-run \
|
||||
--env grepTags="-@slow+-@flaky --@quarantine",grepOmitFiltered=true \
|
||||
--folder 'embedding-sdk' \
|
||||
--browser ${{ steps.cypress-prep.outputs.chrome-path }}
|
||||
env:
|
||||
CYPRESS_IS_EMBEDDING_SDK: true
|
||||
shell: bash
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: ./.github/actions/upload-test-results
|
||||
if: always()
|
||||
with:
|
||||
input-path: ./target/junit
|
||||
output-name: e2e-${{ matrix.name }}
|
||||
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 }}
|
||||
|
||||
- name: Upload Cypress Artifacts upon failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-recording-${{ matrix.name }}-${{ matrix.edition }}
|
||||
path: |
|
||||
./cypress
|
||||
./logs/test.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Publish Summary
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | #js
|
||||
const {
|
||||
parseReport,
|
||||
formatSummary
|
||||
} = require("./.github/scripts/handle-mochawesome-report.js");
|
||||
|
||||
const report = parseReport();
|
||||
const summary = formatSummary(report);
|
||||
|
||||
await core.summary.addRaw(summary).write();
|
||||
|
||||
e2e-tests-skipped-stub:
|
||||
needs: [e2e-tests, e2e-matrix-builder]
|
||||
# run this separately to make it non-blocking
|
||||
flaky-e2e-tests:
|
||||
name: Flaky e2e Tests
|
||||
uses: ./.github/workflows/e2e-test.yml
|
||||
needs: build
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.e2e-tests.result == 'skipped'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
!cancelled() && needs.build.result == 'success'
|
||||
secrets: inherit
|
||||
with:
|
||||
name: Flaky
|
||||
tags: "@flaky"
|
||||
|
||||
e2e-tests-result:
|
||||
needs: [e2e-tests, flaky-e2e-tests]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
name: e2e-tests-${{ matrix.name }}-${{ matrix.edition }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.e2e-matrix-builder.outputs.matrix) }}
|
||||
name: e2e-tests-result
|
||||
if: always() && !cancelled()
|
||||
env:
|
||||
E2E_TESTS_RESULT: ${{ needs.e2e-tests.result }}
|
||||
FLAKY_E2E_TESTS_RESULT: ${{ needs.flaky-e2e-tests.result }}
|
||||
steps:
|
||||
- run: |
|
||||
- name: Stub
|
||||
if: ${{ needs.e2e-tests.result == 'skipped' }}
|
||||
run: |
|
||||
echo "Didn't run due to conditional filtering"
|
||||
- name: Test results
|
||||
if: ${{ needs.e2e-tests.result != 'skipped' }}
|
||||
run: | # sh
|
||||
echo "E2E Tests: ${{ needs.e2e-tests.result }}"
|
||||
echo "Flaky E2E Tests: ${{ needs.flaky-e2e-tests.result }}"
|
||||
|
||||
if [ "${{ needs.e2e-tests.result }}" != "success" ]; then
|
||||
echo "E2E Tests failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pr-env:
|
||||
needs: [build]
|
||||
|
||||
32
.github/workflows/links.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Links
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * 0"
|
||||
|
||||
jobs:
|
||||
linkChecker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write # required for peter-evans/create-issue-from-file
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Link Checker
|
||||
id: lychee
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
fail: false
|
||||
args: docs --config ./lychee/config.toml --verbose --no-progress
|
||||
format: markdown
|
||||
|
||||
- name: Create Issue From File
|
||||
if: steps.lychee.outputs.exit_code != 0
|
||||
uses: peter-evans/create-issue-from-file@v5
|
||||
with:
|
||||
title: Link Checker Report
|
||||
content-filepath: ./lychee/out.md
|
||||
labels: "Type:Documentation"
|
||||
assignees: jeff-bruemmer
|
||||
38
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Slack Notification for New Feature Requests
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
jobs:
|
||||
on-issue-labeled:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 5
|
||||
if: |
|
||||
contains(github.event.issue.labels.*.name, 'Type:New Feature')
|
||||
steps:
|
||||
- name: Setting title
|
||||
uses: actions/github-script@v7
|
||||
id: vars
|
||||
with:
|
||||
script: |
|
||||
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
|
||||
- name: Send the issue ${{ github.event.issue.number }} to Slack
|
||||
id: slack
|
||||
uses: slackapi/slack-github-action@v1.26.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "New incoming feature request: ${{ steps.vars.outputs.issue_title }}\n${{ github.event.issue.html_url }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_FEATURE_REQUEST_TRIAGE_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
7
.github/workflows/release-embedding-sdk.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
options:
|
||||
- master
|
||||
- release-x.51.x
|
||||
- release-x.52.x
|
||||
|
||||
concurrency:
|
||||
# We want to ensure only one job is running at a time per branch because
|
||||
@@ -255,10 +256,10 @@ jobs:
|
||||
run: |
|
||||
echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_RELEASE_TOKEN }} > .npmrc
|
||||
# Please keep the value in sync with `inputs.branch`'s release branch
|
||||
npm publish --tag ${{fromJson('{"master": "nightly", "release-x.51.x": "51-stable"}')[inputs.branch]}}
|
||||
npm publish --tag ${{fromJson('{"master": "nightly", "release-x.51.x": "51-stable", "release-x.52.x": "52-stable"}')[inputs.branch]}}
|
||||
|
||||
- name: Add `latest` tag to the latest release branch (`release-x.51.x`) deployment
|
||||
if: ${{ inputs.branch == 'release-x.51.x' }}
|
||||
- name: Add `latest` tag to the latest release branch (`release-x.52.x`) deployment
|
||||
if: ${{ inputs.branch == 'release-x.52.x' }}
|
||||
working-directory: sdk
|
||||
run: |
|
||||
npm dist-tag add @metabase/embedding-sdk-react@${{ env.sdk_version }} latest
|
||||
|
||||
47
.github/workflows/release-log.yml
vendored
@@ -1,15 +1,21 @@
|
||||
name: Update Release Log
|
||||
run-name: Update Release Log ${{ inputs.version }}
|
||||
run-name: Update Release Log ${{ inputs.version || github.ref_name }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release-x.*
|
||||
tags:
|
||||
- v*
|
||||
- nightly-*
|
||||
- latest-*
|
||||
- beta-*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Major Metabase version (e.g. 45, 52, 68)'
|
||||
type: number
|
||||
required: true
|
||||
schedule:
|
||||
- cron: '45 * * * *' # hourly
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
@@ -21,11 +27,8 @@ jobs:
|
||||
update-release-log:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
# don't run this workflow on a cron for forks
|
||||
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
|
||||
env:
|
||||
VERSION: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call')
|
||||
&& inputs.version || vars.CURRENT_VERSION }}
|
||||
# don't run this workflow for forks
|
||||
if: ${{ github.event_name != 'push' || github.repository == 'metabase/metabase' }}
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
@@ -35,9 +38,37 @@ jobs:
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master # this only works on master
|
||||
fetch-depth: 0 # we want all branches and tags
|
||||
- name: Install Dependencies
|
||||
run: yarn --cwd release --frozen-lockfile && npm i -g tsx
|
||||
- name: Build release scripts
|
||||
run: yarn --cwd release build
|
||||
- name: Get version number
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: | # js
|
||||
const {
|
||||
getVersionFromReleaseBranch,
|
||||
getMajorVersion,
|
||||
} = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
console.log({ context });
|
||||
|
||||
// if the version is explicitly set, use that
|
||||
if (context.payload?.inputs?.version) {
|
||||
core.exportVariable('VERSION', context.payload.inputs.version);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, get the version from the release branch name
|
||||
const branch = context.payload.ref;
|
||||
const version = getVersionFromReleaseBranch(branch);
|
||||
const majorVersion = getMajorVersion(version);
|
||||
|
||||
console.log({ branch, version, majorVersion });
|
||||
core.exportVariable('VERSION', majorVersion);
|
||||
|
||||
- name: generate release Log
|
||||
run: cd release && tsx ./src/release-log-run.ts $VERSION > v$VERSION.html
|
||||
- name: generate release channel log
|
||||
|
||||
33
.github/workflows/release-tag.yml
vendored
@@ -53,6 +53,9 @@ on:
|
||||
description: Apply to OSS
|
||||
type: boolean
|
||||
default: true
|
||||
dot-x-tag:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
green-check-stub:
|
||||
@@ -101,21 +104,14 @@ jobs:
|
||||
oss: getCanonicalVersion(version, 'oss'),
|
||||
};
|
||||
|
||||
const ossReleased = await hasBeenReleased({
|
||||
const released = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.oss,
|
||||
});
|
||||
|
||||
const eeReleased = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.ee,
|
||||
});
|
||||
|
||||
if (!ossReleased || !eeReleased) {
|
||||
if (!released) {
|
||||
throw new Error("This version has not been released yet!", version);
|
||||
}
|
||||
|
||||
@@ -281,25 +277,24 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # we want all branches and tags
|
||||
fetch-tags: true
|
||||
- name: Add and push git tag
|
||||
- name: Add and push git tag for nightly/beta/latest
|
||||
if: ${{ !inputs.dot-x-tag }}
|
||||
run: | # sh
|
||||
|
||||
if [[ "${{ matrix.edition }}" == "ee" ]]; then
|
||||
git tag -f ${{ inputs.tag_name }}-ee ${{ needs.check-version.outputs.ee }}
|
||||
git tag -f ${{ inputs.tag_name }}-ee ${{ needs.check-version.outputs.oss }}
|
||||
git push origin -f ${{ inputs.tag_name }}-ee
|
||||
elif [[ "${{ matrix.edition }}" == "oss" ]]; then
|
||||
git tag -f ${{ inputs.tag_name }}-oss ${{ needs.check-version.outputs.oss }}
|
||||
git push origin -f ${{ inputs.tag_name }}-oss
|
||||
fi
|
||||
|
||||
update-release-log:
|
||||
needs: check-version
|
||||
uses: ./.github/workflows/release-log.yml
|
||||
with:
|
||||
version: ${{ fromJSON(vars.CURRENT_VERSION) }} # cast string to number
|
||||
secrets: inherit
|
||||
- name: Add and push git tag for dot X tags
|
||||
if: ${{ inputs.dot-x-tag && matrix.edition == 'oss' }} # we only need one git tag for dot X
|
||||
run: | # sh
|
||||
git tag -f ${{ inputs.tag_name }} ${{ needs.check-version.outputs.oss }}
|
||||
git push origin tag -f ${{ inputs.tag_name }}
|
||||
|
||||
update-version-info:
|
||||
if: ${{ !inputs.dot-x-tag }}
|
||||
runs-on: ubuntu-22.04
|
||||
needs: check-version
|
||||
timeout-minutes: 5
|
||||
|
||||
149
.github/workflows/release.yml
vendored
@@ -88,21 +88,14 @@ jobs:
|
||||
oss: getCanonicalVersion(version, 'oss'),
|
||||
};
|
||||
|
||||
const ossReleased = await hasBeenReleased({
|
||||
const released = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.oss,
|
||||
});
|
||||
|
||||
const eeReleased = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.ee,
|
||||
});
|
||||
|
||||
if (ossReleased || eeReleased) {
|
||||
if (released) {
|
||||
throw new Error("This version has already been released!", version);
|
||||
}
|
||||
|
||||
@@ -441,21 +434,7 @@ jobs:
|
||||
const { tagRelease } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const OSSversion = '${{ needs.check-version.outputs.oss }}';
|
||||
const EEversion = '${{ needs.check-version.outputs.ee }}';
|
||||
|
||||
// push EE tag
|
||||
await tagRelease({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: EEversion,
|
||||
commitHash: '${{ inputs.commit }}',
|
||||
}).catch((err) => {
|
||||
console.error('Failed to tag release', EEversion, err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// push OSS tag
|
||||
await tagRelease({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
@@ -466,83 +445,50 @@ jobs:
|
||||
console.error('Failed to tag release', OSSversion, err);
|
||||
process.exit(1);
|
||||
});
|
||||
- name: Reorder release tags to put OSS first
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
$GITHUB_WORKSPACE/release/reorder-tags.sh ${{ needs.check-version.outputs.ee }} ${{ inputs.commit }}
|
||||
|
||||
trigger-docs-update:
|
||||
if: ${{ !inputs.auto }}
|
||||
needs:
|
||||
- push-tags
|
||||
- check-version
|
||||
get-extra-tags:
|
||||
needs: [ check-version ]
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
NEW_OSS_VERSION: ${{ needs.check-version.outputs.oss }}
|
||||
NEW_EE_VERSION: ${{ needs.check-version.outputs.ee }}
|
||||
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
outputs:
|
||||
extra_tags: ${{ steps.taglist.outputs.extra_tags }}
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
script: | # js
|
||||
await github.rest.repos.createDispatchEvent({
|
||||
owner: '${{ github.repository_owner }}',
|
||||
repo: '${{ secrets.DOCS_REPO }}',
|
||||
event_type: 'trigger-docs-update',
|
||||
client_payload: {
|
||||
version: '${{ inputs.version }}'
|
||||
}
|
||||
});
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
- name: update releases.md
|
||||
sparse-checkout: release
|
||||
- name: prepare release scripts
|
||||
run: cd release && yarn && yarn build
|
||||
- name: Get extra tags
|
||||
id: taglist
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
|
||||
result-encoding: string
|
||||
script: | # js
|
||||
const fs = require('fs');
|
||||
const { getExtraTagsForVersion } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const releaseText = fs.readFileSync('./docs/releases.md', 'utf8');
|
||||
const version = '${{ inputs.version }}';
|
||||
|
||||
const newReleaseText = releaseText
|
||||
.replace(
|
||||
/## Metabase Enterprise Edition releases\n\n/,
|
||||
`## Metabase Enterprise Edition releases\n\n- [${process.env.NEW_EE_VERSION}](https://github.com/metabase/metabase/releases/tag/${process.env.NEW_EE_VERSION})\n`
|
||||
)
|
||||
.replace(
|
||||
/## Metabase Open Source Edition releases\n\n/,
|
||||
`## Metabase Open Source Edition releases\n\n- [${process.env.NEW_OSS_VERSION}](https://github.com/metabase/metabase/releases/tag/${process.env.NEW_OSS_VERSION})\n`
|
||||
);
|
||||
const extra_tags = getExtraTagsForVersion({ version });
|
||||
|
||||
fs.writeFileSync('./docs/releases.md', newReleaseText);
|
||||
console.log({ extra_tags });
|
||||
core.setOutput('extra_tags', JSON.stringify(extra_tags));
|
||||
|
||||
- name: commit and push updates
|
||||
run: | # bash
|
||||
git config --global user.email "metabase-bot@metabase.com"
|
||||
git config --global user.name "Metabase bot"
|
||||
|
||||
git checkout -b docs-add-$NEW_OSS_VERSION
|
||||
cd docs/
|
||||
|
||||
git add releases.md
|
||||
git commit -m "Add $NEW_OSS_VERSION to the list of releases"
|
||||
git push origin docs-add-$NEW_OSS_VERSION --force
|
||||
|
||||
- name: open pr with updates
|
||||
run: | # bash
|
||||
gh pr create --fill \
|
||||
--body "Like it says on the tin 😁" \
|
||||
--label "Type:Documentation" \
|
||||
--label "backport" \
|
||||
--reviewer "jeff-bruemmer" \
|
||||
--assignee "jeff-bruemmer"
|
||||
add-extra-tags:
|
||||
needs:
|
||||
- get-extra-tags
|
||||
- push-tags
|
||||
- verify-docker-pull
|
||||
- verify-s3-download
|
||||
strategy:
|
||||
matrix:
|
||||
tag: ${{ fromJson(needs.get-extra-tags.outputs.extra_tags) }}
|
||||
uses: ./.github/workflows/release-tag.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
tag_name: ${{ matrix.tag }}
|
||||
tag_ee: ${{ contains(matrix.tag, 'v1') }}
|
||||
tag_oss: ${{ contains(matrix.tag, 'v0') }}
|
||||
dot-x-tag: true
|
||||
|
||||
trigger-cloud-issues:
|
||||
if: ${{ !inputs.auto }}
|
||||
@@ -590,7 +536,7 @@ jobs:
|
||||
|
||||
draft-release-notes:
|
||||
if: ${{ !inputs.auto }}
|
||||
needs: push-tags
|
||||
needs: add-extra-tags
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
permissions: write-all
|
||||
@@ -598,41 +544,26 @@ jobs:
|
||||
AWS_S3_DOWNLOADS_BUCKET: ${{ vars.AWS_S3_DOWNLOADS_BUCKET }}
|
||||
DOCKERHUB_OWNER: ${{ vars.DOCKERHUB_OWNER }}
|
||||
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
strategy:
|
||||
matrix:
|
||||
edition: [oss, ee]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: release
|
||||
- name: prepare release scripts
|
||||
run: cd release && yarn && yarn build
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Retrieve uberjar artifact
|
||||
with:
|
||||
name: metabase-${{ matrix.edition }}-uberjar
|
||||
- name: Read Checksum
|
||||
run: |
|
||||
CHECKSUM=$(cat SHA256.sum)
|
||||
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||
- name: Publish draft release notes
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
script: | # js
|
||||
const { publishRelease, getCanonicalVersion } = require('${{ github.workspace }}/release/dist/index.cjs');
|
||||
|
||||
const version = '${{ inputs.version }}';
|
||||
const edition = '${{ matrix.edition }}';
|
||||
|
||||
const canonical_version = getCanonicalVersion(version, edition);
|
||||
|
||||
await publishRelease({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: canonical_version,
|
||||
checksum: '${{ env.CHECKSUM }}',
|
||||
version,
|
||||
}).then(() => {
|
||||
console.log('Successfully published release notes', '${{ inputs.version }}');
|
||||
}).catch((err) => {
|
||||
@@ -716,7 +647,7 @@ jobs:
|
||||
|
||||
publish-complete-message:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: set-release-channel
|
||||
needs: [set-release-channel, add-extra-tags]
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -774,11 +705,11 @@ jobs:
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version,
|
||||
});
|
||||
}).catch(console.error);
|
||||
|
||||
await openNextMilestones({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version,
|
||||
});
|
||||
}).catch(console.error);
|
||||
|
||||
44
.github/workflows/rerun-workflows.yml
vendored
@@ -50,6 +50,35 @@ jobs:
|
||||
const { WebClient } = require('@slack/web-api');
|
||||
const slack = new WebClient('${{ secrets.SLACK_BOT_TOKEN }}');
|
||||
|
||||
// find out which jobs failed
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const jobInfo = await github.rest.actions.listJobsForWorkflowRun({
|
||||
owner,
|
||||
repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const failedJobs = jobInfo.data.jobs
|
||||
.filter(job => job.status === "completed" && job.conclusion === "failure");
|
||||
|
||||
// don't bother slack if only the flaky job failed
|
||||
if (failedJobs.every(job => job.name.includes("flaky"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const failedJobsList = failedJobs.map(job => ({
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "link",
|
||||
"text": job.name,
|
||||
"url": job.html_url,
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
await slack.chat.postMessage({
|
||||
channel: 'engineering-ci',
|
||||
text: 'Failing tests',
|
||||
@@ -70,9 +99,20 @@ jobs:
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": `Commit <https://github.com/metabase/metabase/commit/${BREAKING_COMMIT}|${BREAKING_COMMIT.slice(0,7)}> by ${AUTHOR} has failing <${FAILED_RUN_URL}|${FAILED_RUN_NAME}> tests on the <https://github.com/metabase/metabase/commits/${BRANCH}|\`${BRANCH}\`> branch. :sad-panda:`
|
||||
}
|
||||
"text": `Commit <https://github.com/metabase/metabase/commit/${BREAKING_COMMIT}|${BREAKING_COMMIT.slice(0,7)}> by ${AUTHOR} has failing <${FAILED_RUN_URL}|${FAILED_RUN_NAME}> tests on <https://github.com/${owner}/${repo}/commits/${BRANCH}|\`${BRANCH}\`>`
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "block1",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_list",
|
||||
"style": "bullet",
|
||||
"elements": failedJobsList,
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
11
.github/workflows/tag-for-release.yml
vendored
@@ -83,21 +83,14 @@ jobs:
|
||||
oss: getCanonicalVersion(version, 'oss'),
|
||||
};
|
||||
|
||||
const ossReleased = await hasBeenReleased({
|
||||
const released = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.oss,
|
||||
});
|
||||
|
||||
const eeReleased = await hasBeenReleased({
|
||||
github,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
version: versions.ee,
|
||||
});
|
||||
|
||||
if (ossReleased || eeReleased) {
|
||||
if (released) {
|
||||
throw new Error("This version has already been released!", version);
|
||||
}
|
||||
|
||||
|
||||
1
.github/workflows/uberjar.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- "master"
|
||||
- metabot-v3-second-try
|
||||
- feature-visualizer
|
||||
paths-ignore:
|
||||
# config files
|
||||
- ".**"
|
||||
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
.loki/reference/chrome_laptop_Parameters_DatePicker_Exclude.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |