Compare commits

..

133 Commits

Author SHA1 Message Date
Bryan Maass
38883ff7cd add docs for --watch flag on :dev-start alias 2025-05-13 11:11:34 -06:00
Bryan Maass
5cbba5d817 stop leaking watchers 2025-05-09 15:28:34 -06:00
Bryan Maass
ff4a57e933 improve watcher ergonomics 2025-05-09 13:25:24 -06:00
Bryan Maass
93515ebe31 docs 2025-05-09 13:18:32 -06:00
Bryan Maass
d53a7a680b fix watcher alias
- A path object cannot be printed via warnf and %s.
- Some paths from all-paths-to-watch were causing beholder to choke
2025-05-09 12:47:18 -06:00
Eric Normand
8edafd083c Datetime to date coercion (#57523)
* Empty

* kick ci

* Allow temporal type in addition to string for `date()`

* Add date(datetime) test

* Use cast for date for postgres

* Extend date() on mysql

* Extend date() on bigquery

* Extend date() at snowflake

* Extend date() to datetime on clickhouse

* Generalize `date()` to :sql

* Update changelog with `date(datetime)`

* Empty

* DateTime->Date Coercion + test

* Slight cleanup

* Whitespace

* Cleaner implementation and test

* Test timestamp as well as timestamptz

* Correct implementation to get subtypes of DateTime

* Update helper text string for date

* Add e2e tests for date

* Also check to correct coercion of dates

* Add customization points to enable clickhouse implementation.

* Remove changelog-test/ignore true from ->date

* Fix defn ordering

* Fix expected type for datetime coercions

* Fix tests for sqlite

* Implement coercion for Mongo

* Implement specialization for oracle

* Better check

* kick ci

* kick ci

* Format code

* Format code

* Correct changelog text

* Ors on one line

* Or on same line

* Remove zh from the list of supported translations (#57672)

We don't support the `zh` (Traditional Chinese) locale anymore – only `zh-CN` (Simplified Chinese), and `zh-HK` (Hong Kong), and `zh-TW` (Taiwan). Here's a [Slack discussion](https://metaboat.slack.com/archives/C6UBFU9GW/p1741983889165599).

So let's remove `zh` from the list of supported translations.

For some reason the list wasn't sorted alphabetically by language name - this PR fixes that too.

* [Backend modularization] Mega Renaming PR 1 (#57561)

* auth-provider => auth-provider.core

* enterprise/auth-provider

* `metabase.events.core`

* Move `sql-parsing-enabled` Setting into `query-analysis` (DEV-449)

* `metabase.events.core` (DEV-455)

* `metabase-enterprise.stats` => `metabase-enterprise.analytics.stats` (DEV-475)

* `enterprise/stale` cleanup (DEV-474)

* `enterprise/airgap` => `enterprise/premium-features`

* Appease Kondo

* Remove unused

* Mark setup tests flaky (#57710)

* mark setup tests flaky

* add flake

---------

Co-authored-by: Sloan Sparger <sloansparger@gmail.com>

* Fix parameter types! (#57677)

* [Notification] reduce module apis (#57678)

* fix audit log to record external emails when sending notifications (#57679)

* Remove "Model" and "Action" from the "New" button menu (#57647)

* feat: add a link to field filter docs from dashboard sidebar with native question (#57687)

* feat: add a link to field filter docs from dashboard sidebar with native question

* oops

* add missing translation

* Metabot v3 PoC (#52436)

Co-authored-by: Tyler Plude <tsplude@gmail.com>
Co-authored-by: Phoomparin Mano <poom@metabase.com>
Co-authored-by: Mihael Konjević <konjevic@gmail.com>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com>
Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
Co-authored-by: Thomas Schmidt <thomas@metabase.com>
Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>
Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
Co-authored-by: github-automation-metabase <166700802+github-automation-metabase@users.noreply.github.com>
Co-authored-by: Nicolò Pretto <info@npretto.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com>
Co-authored-by: Phoomparin Mano <phoomparin@gmail.com>

* Patch the metabot-v3 merge (#57731)

* Remove unused props from the "New" menu

* Remove Metabot code related to the "New" menu

* Fix flaky e2e test in the onboarding `setup` spec (#57735)

* remove unused slider component (#57745)

* [Admin] Modernize components for People + Groups pages (#57365)

* update components in frontend/src/metabase/admin/people/components to use Mantine components and remove deprecated component usage

* update people and group pages/modals to use more modern components

* use relative sizes

* fix user success modal not closing on "Done" confirm button click

* remove CS from PeopleListRow

* clean up admin/people/colors.ts

* lint fix

* people list footer fix

* pr feedback

* [Admin] Convert people / group pages to RTKQuery (#57490)

* update components in frontend/src/metabase/admin/people/components to use Mantine components and remove deprecated component usage

* update people and group pages/modals to use more modern components

* use relative sizes

* fix user success modal not closing on "Done" confirm button click

* remove CS from PeopleListRow

* clean up admin/people/colors.ts

* lint fix

* people list footer fix

* pr feedback

* wip

* fix issues, people page updates, some clean up

* add unsaved files

* type fix, fetch user list from rktquery for PeopleList

* remove entity usage from unsubscribe modal, lint fix

* remove all remaining usages of user entity

* remove user entity

* test fixes, remove unused user schema

* bit of clean up

* qa pass - invalidate groups cache if user is deactivated/reactivated

* address pr feedback

* Bump org.apache.poi/poi-ooxml-full from 5.4.0 to 5.4.1 (#56357)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Add analytics for new entity creation being initiated (#57673)

This commit adds analytics to the:

1. New button being clicked
    - In the top app bar
    - From the empty collection
2. New menu individual items being clicked:
    - Dashboard
    - ~Question~ (not possible at the moment)
    - ~Native query~ (not possible at the moment)
3. An entity creation being initiated
    - Model from the /browse/models page
    - Metric from the /browse/metrics page
    - Collection from the collection header
    - Collection from the main nav sidebar

* Bump io.github.eerohele/pp from 9598f4c72b528480a85ea41651c45b08d1717428 to 4998a1fdd9d3713d7034d04509e9212204773bf2 (#57577)

Inspect dependency changes here: 9598f4c72b...4998a1fdd9

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Bump commons-io/commons-io from 2.18.0 to 2.19.0 (#56654)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Shutdown index sync while its results are unused (#56885)

* Shutdown index sync while its results are unused

* Update tests

* Update mongo tests

* Bump com.google.guava/guava from 33.4.6-jre to 33.4.8-jre (#56952)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Bump com.amazonaws/aws-java-sdk-core from 1.12.782 to 1.12.783 (#57586)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Bump io.github.tonsky/clj-reload from 0.9.4 to 0.9.5 (#57578)

Inspect dependency changes here: https://github.com/tonsky/clj-reload/blob/0.9.5/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump babashka/fs from 0.5.24 to 0.5.25 (#57575)

Inspect dependency changes here: https://github.com/babashka/fs/blob/v0.5.25/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump org.apache.calcite.avatica/avatica from 1.25.0 to 1.26.0 (#54171)

Inspect dependency changes here: https://github.com/apache/calcite-avatica/compare/avatica-1.25.0-rc0...avatica-1.26.0-rc0

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* fix: do not render gauge if grouping col is date (#56715)

* fix: toolbar button color not changing on hover (#56694)

* Bump refactor-nrepl/refactor-nrepl from 3.10.0 to 3.11.0 (#57584)

Inspect dependency changes here: https://github.com/clojure-emacs/refactor-nrepl/blob/v3.11.0/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump org.jsoup/jsoup from 1.19.1 to 1.20.1 (#57583)

Inspect dependency changes here: https://github.com/jhy/jsoup/blob/jsoup-1.20.1/CHANGES.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* [Backend modularization] `view-log` module (DEV-427) (#57628)

* [Backend modularization] Move anonymous-stats-related stuff into `analytics` module (DEV-437) (#57631)

* Bump org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server from 12.0.19 to 12.0.20 (#57579)

* Bump org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server from 12.0.19 to 12.0.20

Inspect dependency changes here:

* Update deps.edn

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump com.taoensso/nippy from 3.4.2 to 3.5.0 (#56953)

Inspect dependency changes here: https://github.com/taoensso/nippy/blob/v3.5.0/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>

* Bump com.ibm.icu/icu4j from 76.1 to 77.1 (#55241)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump thheller/shadow-cljs from 2.28.21 to 2.28.23 (#56657)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* `date(datetime)` integration branch (#57448)

* Empty

* kick ci

* Allow temporal type in addition to string for `date()`

* Add date(datetime) test

* Use cast for date for postgres

* Extend date() on mysql

* Extend date() on bigquery

* Extend date() at snowflake

* Extend date() to datetime on clickhouse

* Generalize `date()` to :sql

* Update changelog with `date(datetime)`

* Update helper text string for date

* Add e2e tests for date

* Also check to correct coercion of dates

* Better check

* kick ci

* Format code

---------

Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>

* Bump org.apache.poi/poi from 5.4.0 to 5.4.1 (#56355)

* Bump org.apache.poi/poi from 5.4.0 to 5.4.1

Inspect dependency changes here:

* Update deps.edn

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump cider/cider-nrepl from 0.55.4 to 0.55.7 (#57576)

Inspect dependency changes here: https://github.com/clojure-emacs/cider-nrepl/blob/v0.55.7/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump org.postgresql/postgresql from 42.7.4 to 42.7.5 (#57314)

Inspect dependency changes here: https://github.com/pgjdbc/pgjdbc/blob/REL42.7.5/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump clj-kondo/clj-kondo from 2025.02.20 to 2025.04.07 (#56650)

* Bump clj-kondo/clj-kondo from 2025.02.20 to 2025.04.07

Inspect dependency changes here: https://github.com/clj-kondo/clj-kondo/blob/v2025.04.07/CHANGELOG.md

* Add kondo ignore to tests for new :missing-protocol-method linter

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Mike Appleby <86076+appleby@users.noreply.github.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Bump thheller/shadow-cljs from 2.28.21 to 3.0.4 (#57585)

Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>

* Remove unused code in embedding data picker (#57700)

* Remove unused exported components

* Remove references to metrics

* Remove references to saved questions

* Remove irrelevant tests

* Remove unnecessary prop

* Fix more unit tests

* Remove more unnecessary code

* Fix failed unit tests

* Remove unnecessary prop

* speed up notification tests (#57763)

* Update logger presets (#57667)

* Update presets

* Update presets

* Update test

* Apply suggestions from code review

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>

---------

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>

* better snowplow errors when expecting the wrong number of good events (#57730)

* add ?. to make it more resilient

* add more logssss

* cy.log

* some nicer error message

* fix eventType

* removes leftover lines

* typos on comments

* Update e2e/support/helpers/e2e-snowplow-helpers.js

Co-authored-by: Phoomparin Mano <poom@metabase.com>

---------

Co-authored-by: Phoomparin Mano <poom@metabase.com>

---------

Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>
Co-authored-by: Raphael Krut-Landau <raphael.kl@gmail.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
Co-authored-by: Ryan Laurie <30528226+iethree@users.noreply.github.com>
Co-authored-by: Sloan Sparger <sloansparger@gmail.com>
Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
Co-authored-by: Nemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
Co-authored-by: Uladzimir Havenchyk <125459446+uladzimirdev@users.noreply.github.com>
Co-authored-by: metamben <103100869+metamben@users.noreply.github.com>
Co-authored-by: Tyler Plude <tsplude@gmail.com>
Co-authored-by: Phoomparin Mano <poom@metabase.com>
Co-authored-by: Mihael Konjević <konjevic@gmail.com>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com>
Co-authored-by: Thomas Schmidt <thomas@metabase.com>
Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
Co-authored-by: github-automation-metabase <166700802+github-automation-metabase@users.noreply.github.com>
Co-authored-by: Nicolò Pretto <info@npretto.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com>
Co-authored-by: Phoomparin Mano <phoomparin@gmail.com>
Co-authored-by: Sloan Sparger <sloansparger@users.noreply.github.com>
Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: lbrdnk <lbrdnk@users.noreply.github.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
Co-authored-by: Mike Appleby <86076+appleby@users.noreply.github.com>
2025-05-09 14:38:45 +00:00
Nicolò Pretto
557e7d0f34 better snowplow errors when expecting the wrong number of good events (#57730)
* add ?. to make it more resilient

* add more logssss

* cy.log

* some nicer error message

* fix eventType

* removes leftover lines

* typos on comments

* Update e2e/support/helpers/e2e-snowplow-helpers.js

Co-authored-by: Phoomparin Mano <poom@metabase.com>

---------

Co-authored-by: Phoomparin Mano <poom@metabase.com>
2025-05-09 11:13:44 +00:00
lbrdnk
8b2a8b4d42 Update logger presets (#57667)
* Update presets

* Update presets

* Update test

* Apply suggestions from code review

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>

---------

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>
2025-05-09 11:16:32 +02:00
Ngoc Khuat
0e53e2bd21 speed up notification tests (#57763) 2025-05-09 07:44:33 +00:00
Mahatthana (Kelvin) Nomsawadi
8fa039f6dc Remove unused code in embedding data picker (#57700)
* Remove unused exported components

* Remove references to metrics

* Remove references to saved questions

* Remove irrelevant tests

* Remove unnecessary prop

* Fix more unit tests

* Remove more unnecessary code

* Fix failed unit tests

* Remove unnecessary prop
2025-05-09 06:51:43 +01:00
github-automation-metabase
1c5bc70adb Bump thheller/shadow-cljs from 2.28.21 to 3.0.4 (#57585)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-09 00:07:31 +00:00
github-automation-metabase
07235bbde0 Bump clj-kondo/clj-kondo from 2025.02.20 to 2025.04.07 (#56650)
* Bump clj-kondo/clj-kondo from 2025.02.20 to 2025.04.07

Inspect dependency changes here: https://github.com/clj-kondo/clj-kondo/blob/v2025.04.07/CHANGELOG.md

* Add kondo ignore to tests for new :missing-protocol-method linter

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Mike Appleby <86076+appleby@users.noreply.github.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 23:27:10 +00:00
github-automation-metabase
7c0ca3369b Bump org.postgresql/postgresql from 42.7.4 to 42.7.5 (#57314)
Inspect dependency changes here: https://github.com/pgjdbc/pgjdbc/blob/REL42.7.5/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 23:18:34 +00:00
github-automation-metabase
be86f32a0f Bump cider/cider-nrepl from 0.55.4 to 0.55.7 (#57576)
Inspect dependency changes here: https://github.com/clojure-emacs/cider-nrepl/blob/v0.55.7/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 22:09:49 +00:00
github-automation-metabase
d9147bd92c Bump org.apache.poi/poi from 5.4.0 to 5.4.1 (#56355)
* Bump org.apache.poi/poi from 5.4.0 to 5.4.1

Inspect dependency changes here:

* Update deps.edn

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 21:58:39 +00:00
Eric Normand
d55b0e247a date(datetime) integration branch (#57448)
* Empty

* kick ci

* Allow temporal type in addition to string for `date()`

* Add date(datetime) test

* Use cast for date for postgres

* Extend date() on mysql

* Extend date() on bigquery

* Extend date() at snowflake

* Extend date() to datetime on clickhouse

* Generalize `date()` to :sql

* Update changelog with `date(datetime)`

* Update helper text string for date

* Add e2e tests for date

* Also check to correct coercion of dates

* Better check

* kick ci

* Format code

---------

Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>
2025-05-08 21:52:34 +00:00
github-automation-metabase
61b7eeda25 Bump thheller/shadow-cljs from 2.28.21 to 2.28.23 (#56657)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 20:53:47 +00:00
github-automation-metabase
3a8479078c Bump com.ibm.icu/icu4j from 76.1 to 77.1 (#55241)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 20:53:31 +00:00
github-automation-metabase
a743c27bcd Bump com.taoensso/nippy from 3.4.2 to 3.5.0 (#56953)
Inspect dependency changes here: https://github.com/taoensso/nippy/blob/v3.5.0/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 20:47:00 +00:00
github-automation-metabase
139d1605fa Bump org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server from 12.0.19 to 12.0.20 (#57579)
* Bump org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server from 12.0.19 to 12.0.20

Inspect dependency changes here:

* Update deps.edn

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 20:45:10 +00:00
Cam Saul
a6f9bb8167 [Backend modularization] Move anonymous-stats-related stuff into analytics module (DEV-437) (#57631) 2025-05-08 13:22:09 -07:00
Cam Saul
b405045f94 [Backend modularization] view-log module (DEV-427) (#57628) 2025-05-08 13:21:54 -07:00
github-automation-metabase
a669530a55 Bump org.jsoup/jsoup from 1.19.1 to 1.20.1 (#57583)
Inspect dependency changes here: https://github.com/jhy/jsoup/blob/jsoup-1.20.1/CHANGES.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 13:19:06 -07:00
github-automation-metabase
a6ef327bdd Bump refactor-nrepl/refactor-nrepl from 3.10.0 to 3.11.0 (#57584)
Inspect dependency changes here: https://github.com/clojure-emacs/refactor-nrepl/blob/v3.11.0/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 13:17:42 -07:00
Adithya Vardhan
dafbd09482 fix: toolbar button color not changing on hover (#56694) 2025-05-08 13:13:35 -07:00
Adithya Vardhan
87ca8ab2d3 fix: do not render gauge if grouping col is date (#56715) 2025-05-08 13:13:27 -07:00
github-automation-metabase
8827164777 Bump org.apache.calcite.avatica/avatica from 1.25.0 to 1.26.0 (#54171)
Inspect dependency changes here: https://github.com/apache/calcite-avatica/compare/avatica-1.25.0-rc0...avatica-1.26.0-rc0

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 19:56:01 +00:00
github-automation-metabase
8285908e3a Bump babashka/fs from 0.5.24 to 0.5.25 (#57575)
Inspect dependency changes here: https://github.com/babashka/fs/blob/v0.5.25/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 12:27:13 -07:00
github-automation-metabase
58f4e0a9dd Bump io.github.tonsky/clj-reload from 0.9.4 to 0.9.5 (#57578)
Inspect dependency changes here: https://github.com/tonsky/clj-reload/blob/0.9.5/CHANGELOG.md

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Cam Saul <1455846+camsaul@users.noreply.github.com>
2025-05-08 12:26:53 -07:00
github-automation-metabase
431184652f Bump com.amazonaws/aws-java-sdk-core from 1.12.782 to 1.12.783 (#57586)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 12:26:25 -07:00
github-automation-metabase
3cfcb03cf5 Bump com.google.guava/guava from 33.4.6-jre to 33.4.8-jre (#56952)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 12:25:40 -07:00
lbrdnk
76a5231a6f Shutdown index sync while its results are unused (#56885)
* Shutdown index sync while its results are unused

* Update tests

* Update mongo tests
2025-05-08 21:20:00 +02:00
github-automation-metabase
ab296e8d6c Bump commons-io/commons-io from 2.18.0 to 2.19.0 (#56654)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 19:19:07 +00:00
github-automation-metabase
fcb6e8415b Bump io.github.eerohele/pp from 9598f4c72b528480a85ea41651c45b08d1717428 to 4998a1fdd9d3713d7034d04509e9212204773bf2 (#57577)
Inspect dependency changes here: 9598f4c72b...4998a1fdd9

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 11:56:11 -07:00
Nemanja Glumac
38762f1b74 Add analytics for new entity creation being initiated (#57673)
This commit adds analytics to the:

1. New button being clicked
    - In the top app bar
    - From the empty collection
2. New menu individual items being clicked:
    - Dashboard
    - ~Question~ (not possible at the moment)
    - ~Native query~ (not possible at the moment)
3. An entity creation being initiated
    - Model from the /browse/models page
    - Metric from the /browse/metrics page
    - Collection from the collection header
    - Collection from the main nav sidebar
2025-05-08 20:44:35 +02:00
github-automation-metabase
d415393100 Bump org.apache.poi/poi-ooxml-full from 5.4.0 to 5.4.1 (#56357)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-08 11:33:17 -07:00
Sloan Sparger
7db950f952 [Admin] Convert people / group pages to RTKQuery (#57490)
* update components in frontend/src/metabase/admin/people/components to use Mantine components and remove deprecated component usage

* update people and group pages/modals to use more modern components

* use relative sizes

* fix user success modal not closing on "Done" confirm button click

* remove CS from PeopleListRow

* clean up admin/people/colors.ts

* lint fix

* people list footer fix

* pr feedback

* wip

* fix issues, people page updates, some clean up

* add unsaved files

* type fix, fetch user list from rktquery for PeopleList

* remove entity usage from unsubscribe modal, lint fix

* remove all remaining usages of user entity

* remove user entity

* test fixes, remove unused user schema

* bit of clean up

* qa pass - invalidate groups cache if user is deactivated/reactivated

* address pr feedback
2025-05-08 13:00:01 -05:00
Sloan Sparger
c9caad50a4 [Admin] Modernize components for People + Groups pages (#57365)
* update components in frontend/src/metabase/admin/people/components to use Mantine components and remove deprecated component usage

* update people and group pages/modals to use more modern components

* use relative sizes

* fix user success modal not closing on "Done" confirm button click

* remove CS from PeopleListRow

* clean up admin/people/colors.ts

* lint fix

* people list footer fix

* pr feedback
2025-05-08 11:50:57 -05:00
Nicolò Pretto
be81440458 remove unused slider component (#57745) 2025-05-08 18:02:52 +02:00
Nemanja Glumac
fc0a1777ec Fix flaky e2e test in the onboarding setup spec (#57735) 2025-05-08 16:07:56 +02:00
Nemanja Glumac
4c6c580964 Patch the metabot-v3 merge (#57731)
* Remove unused props from the "New" menu

* Remove Metabot code related to the "New" menu
2025-05-08 14:03:24 +00:00
metamben
d3fafe9cf2 Metabot v3 PoC (#52436)
Co-authored-by: Tyler Plude <tsplude@gmail.com>
Co-authored-by: Phoomparin Mano <poom@metabase.com>
Co-authored-by: Mihael Konjević <konjevic@gmail.com>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com>
Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
Co-authored-by: Thomas Schmidt <thomas@metabase.com>
Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>
Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
Co-authored-by: Mahatthana (Kelvin) Nomsawadi <me@bboykelvin.dev>
Co-authored-by: github-automation-metabase <166700802+github-automation-metabase@users.noreply.github.com>
Co-authored-by: Nicolò Pretto <info@npretto.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com>
Co-authored-by: Phoomparin Mano <phoomparin@gmail.com>
2025-05-08 06:57:20 -04:00
Uladzimir Havenchyk
8fc63096b8 feat: add a link to field filter docs from dashboard sidebar with native question (#57687)
* feat: add a link to field filter docs from dashboard sidebar with native question

* oops

* add missing translation
2025-05-08 07:30:39 +00:00
Nemanja Glumac
f53785e010 Remove "Model" and "Action" from the "New" button menu (#57647) 2025-05-08 09:04:20 +02:00
Ngoc Khuat
ee5d61525c fix audit log to record external emails when sending notifications (#57679) 2025-05-08 07:03:41 +00:00
Ngoc Khuat
2f4a3d35c2 [Notification] reduce module apis (#57678) 2025-05-08 05:08:07 +00:00
Alexander Polyankin
c1763bd9d7 Fix parameter types! (#57677) 2025-05-08 01:16:33 +00:00
Ryan Laurie
22e028af3a Mark setup tests flaky (#57710)
* mark setup tests flaky

* add flake

---------

Co-authored-by: Sloan Sparger <sloansparger@gmail.com>
2025-05-07 17:00:28 -06:00
Cam Saul
e58ecd7290 [Backend modularization] Mega Renaming PR 1 (#57561)
* auth-provider => auth-provider.core

* enterprise/auth-provider

* `metabase.events.core`

* Move `sql-parsing-enabled` Setting into `query-analysis` (DEV-449)

* `metabase.events.core` (DEV-455)

* `metabase-enterprise.stats` => `metabase-enterprise.analytics.stats` (DEV-475)

* `enterprise/stale` cleanup (DEV-474)

* `enterprise/airgap` => `enterprise/premium-features`

* Appease Kondo

* Remove unused
2025-05-07 14:32:36 -07:00
Raphael Krut-Landau
a69c66d0e1 Remove zh from the list of supported translations (#57672)
We don't support the `zh` (Traditional Chinese) locale anymore – only `zh-CN` (Simplified Chinese), and `zh-HK` (Hong Kong), and `zh-TW` (Taiwan). Here's a [Slack discussion](https://metaboat.slack.com/archives/C6UBFU9GW/p1741983889165599).

So let's remove `zh` from the list of supported translations.

For some reason the list wasn't sorted alphabetically by language name - this PR fixes that too.
2025-05-07 16:43:16 -04:00
Cam Saul
29efdb4287 Remove unused :model/FieldUsage (#57556) 2025-05-07 12:33:19 -07:00
Cam Saul
655907c7bb Remove unused :model/TablePrivileges (#57551)
* Remove unused `:model/TablePrivileges`

* Remove another stray reference
2025-05-07 12:31:33 -07:00
Nathan Voxland
8c57a1b48a Support bigint model_pks in model_index_value (#57661) 2025-05-07 14:25:55 -05:00
Noah Moss
770207290a Don't add isCollapsed nodes for paths not in the data (#57704) 2025-05-07 11:13:52 -07:00
Kamil Mielnik
802890a8a3 SEM-229 Hide numerical semantic types on string-based fields (#57606)
* Remove hack

* Update test
2025-05-07 19:33:59 +02:00
lbrdnk
3709509a67 Migrate hacked semantic type fields to use coercion (#57518)
* migrate hacked fields

* Format and join subq work differently

* Add engine filtering to migration

* Update migr

* Comment
2025-05-07 19:33:10 +02:00
Cam Saul
0b91af3d7c Functions with side effects should generally end in ! (#57615) 2025-05-07 09:14:10 -07:00
Ryan Laurie
c0d38f98a7 stabilize flaky font unit test (#57699) 2025-05-07 09:03:46 -06:00
Sloan Sparger
6a34d8b120 [Admin] Remove styled component usages from people and group pages + components (#57303)
* convert UnsubscribeUserModal and UnsubscribeUserForm components

* convert UserActivationModal to TS

* convert UserPasswordResetModal to TS

* convert UserSuccessModal to TS

* convert PeopleList and PeopleListingApp components to TS

* convert PeopleListRow to TS

* convert use-people-query to TS

* fix deprecated usages of google_auth and ldap_auth with sso_source field

* fix lint

* convert GroupsListing, GroupsListingApp, and AddRow components to TS

* convert lib/groups, AdminContentTable, and AddMemberRow to TS

* convert GroupDetailApp and GroupDetail to TS

* fix import

* convert GroupSummary to TS, make adjustments to other converted files to make types happy

* remove people/components/SearchInput and convert usage to mantine component to avoid TS migration

* remove styled component usage from people and groups pages and components

* adjustment to people listing app
2025-05-07 09:39:43 -05:00
Nicolò Pretto
319b45efb0 make LocaleProvider change the return of useLocale and set the locale used by Mantine (#57534)
* refactor: rename test file to tsx to have hopefully better diff in the
next commit

* fix EMB-360: Make Mantine components use the locale from LocaleProvider

* close EMB-373: make useLocale return the locale from LocaleProvider if
present
2025-05-07 12:25:12 +02:00
Romeo Van Snick
ce33f7c3b9 Fix missing snippet create button (#57479)
* Use Mantine dropdown for snippet menu

* Fix search icon in snippet header too

* Remove default ICON_SIZE

* Fix MIN_SNIPPETS_FOR_SEARCH

* Add createSnippet helper

* Add repro for metabase#57441

* Add test for snippet search

* Move snippet helper to api

* Use actual ref for input

* Simplify header search rendering

* Use SidebarHeader for snippet sidebar

* Use Flex for most components

* Simplify padding

* Use class component for archived snippets

* Use class for header button

* Use direct properties

* Rely less on custom css

* Fix header padding

* Fix e2e tests

* Remove unused eslint-disable

* Add type annotation

Co-authored-by: Kamil Mielnik <kamil@kamilmielnik.com>

---------

Co-authored-by: Kamil Mielnik <kamil@kamilmielnik.com>
2025-05-07 11:40:42 +02:00
Mahatthana (Kelvin) Nomsawadi
251fb3d05c Copy multi-stage components (#57436)
* Move SimpleDataPicker to a new directory preparing for new components

* Use the embedding data picker from a new folder

This requires turning the component into a plugin, which we'll do in the
next commit.

* Move plugin property to a more appropriate place

* Implement embedding multi-stage data picker as an EE plugin

* Add DataSourceSelectorProps

* Remove reference to query builder data picker

* Fix failing unit tests

* Move embedding data picker components to a proper location
2025-05-07 09:27:04 +01:00
Ryan Laurie
a59d8af558 Update Appearance Settings (#56468)
* Update Appearance Settings

* unit test additions + updates

* update e2e tests

* test updates

* unit test fix

* fix lint errors

* obey the linter

* add test coverage

* add more tests

* support env var override ui

* remove dead code

* nicer routing

* useDisclosure

* stabilize test
2025-05-06 17:22:54 -06:00
Edward Paget
e8ff64fc77 fix: failing kondos on master (#57662)
* fix: failing kondos on master

* fixup! remove all external references to permissions group membership

* fixup! missing export
2025-05-06 15:44:31 -07:00
Ryan Laurie
0beb4fd1ec Add build-args to uberjar job (#57633)
* add build-args to uberjar job

* better sha
2025-05-06 13:01:02 -06:00
Nick Fitzpatrick
f508020b18 Watermarks in development instances - App viz (#57620)
* adding watermark for dynamic viz

* starting to put together e2e tests for watermarks

* adding unit test

* adding loki tests

* adding more e2e tests

* changing test id

* adding e2e tests on chart interactivity

* fixing type error
2025-05-06 23:51:25 +05:30
Aleksandr Lesnenko
61b06cb9ca update chart settings types (#57657) 2025-05-06 13:28:40 -04:00
Case Nelson
c21a754c1a fix: redshift pg_enum error when missing base types (#57397) 2025-05-06 09:49:36 -06:00
github-actions[bot]
cebcccd81a Update translations 2025-04-29 (#57372)
* Update translation sources 2025-04-29

* Update translations

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-05-06 09:21:55 -06:00
Nemanja Glumac
479f6ec15e Remove "Collection" option from the "New" button menu (#57649)
Resolves PRG-29 by removing the "Collection" entry from the main "New" menu.
2025-05-06 17:17:12 +02:00
Nemanja Glumac
e42004d8bd Remove "Metric" option from the "New" button menu (#57641)
Resolves PRG-27 by removing the option to create Metric from the main "New" menu.
2025-05-06 15:03:39 +02:00
Romeo Van Snick
3b9ba47ce5 Clean up helper text utils (#57382)
* Split up config into categories

* Break out clauses into groups

* Add duplicate name check

* Allow setting shared options

* Add category to mbql clause config

* Rename groups

* Use enum for category

* Build examples using helper functions

* Build ExpressionParts directly in helper-text

* Flatten examples

* Wrap example in function

* Enable late-binding for args

* Unwrap example

* Remove get name and get description wrappers

* Remove getHelpDocsUrl helper

* Simplify example fn

* Add missing export

* Remove getFunctionByStructure

* Simplify help text helpers

* Rename structure to displayName

* Make args required

* Manually type clause definition type

* Merge HELPER_TEXT_STRINGS into config

* Move help text helpers to separate file

* Use data from MBQL_CLAUSES instead of HELPER_TEXT_STRINGS

* Allow defining stuff in config

* Move help text into config

* Remove fallback to HELPER_TEXT_STRINGS

* Remove helper-text-strings

* Allow more stuff to be optional

* Build clause in getClauseDefinition

* Avoid self-referential types

* Use getClauseDefinition in completer

* Handle new args definition

* Handle null instead of undefined

* Enforce type on all argument definitions

* Enforce name on all argument definitions

* Get clause defns

* Fix types

* Simplify tests

* Remove unnecessary guard

* Avoid creating unnecessary functions

* Move defineClauses to separate module

* Remove any in config

* Rename helper-text-strings test to help-text
2025-05-06 14:53:52 +02:00
John Swanson
6376201ee2 Implement Tenant Groups (#57050)
* Implement Tenant Groups

- normal users cannot be added to a tenant group

- tenant users cannot be added to a normal group

- tenant groups cannot receive write access to any collection

- tenant groups cannot receive any access to the audit collection

- tenant groups cannot receive any application permissions

* Add a setting to turn tenants off and on

When the setting is turned on:

- create the All External Users group, with
`magic_group_type=all-exsternal-users` and `is_tenant_group=true`

When the setting is turned off:

- make sure there are no remaining tenants or tenant users

- delete the All External Users group

* Remove the need for memoized `tenant?` functions

* Fix tests

* allowe using with-temp with perms group membership

* fix more tests

* remove unnecessary bindings

* Put setting behind a feature flag

* Don't read setting from env

closes adm-648
closes adm-657
closes adm-649
2025-05-06 05:50:11 -07:00
Phoomparin Mano
7f92780e9e fix(sdk): custom GeoJSON maps does not load due to wrong domain (#57539) 2025-05-06 19:02:48 +07:00
Nemanja Glumac
9e13274e72 Add a "new collection" button to the collection header (#57616)
Resolves PRG-25

This PR adds a new button to the collection header menu. The button simply opens a new collection modal (lets you create a new collection). There are two important things to note about this change:

1. This button shows only when a user has a curate permissions for the current collection
2. This change required adding a new icon to our design system, which was approved by Maz.
2025-05-06 13:51:26 +02:00
Phoomparin Mano
92fac6cabb fix(sdk): updating collection's entity id crashes InteractiveQuestion and CollectionBrowser (#57529) 2025-05-06 18:51:14 +07:00
Ngoc Khuat
aca9b98060 move context info to after the log message (#57588) 2025-05-06 07:18:06 +00:00
Nemanja Glumac
d25b43a3cb Add a "new collection" button to the main sidebar (#57605)
Resolves PRG-20

This PR replaces the old `...` menu next to the "Collections" sidebar header. As a reminder, this menu was previously triggering a popup with two items:

1. New collection
2. Other users' personal collections (in case the user is an admin)

We're now improving the UX and replacing this menu with two distinct items:

1. The `+` icon that creates a new collection directly
2. The new individual sidebar link that leads to other users' personal collections (again, only if admin)
2025-05-06 07:03:35 +00:00
Nemanja Glumac
fd2f465639 Add a new model button to the browse models page (#57555)
Resolves PRG-21
2025-05-06 08:16:47 +02:00
Fraser
b7f5e23707 Create new PublicSharingSettingsPage component (#57465)
* Create new PublicSharingSettingsPage component

* Add unit tests
2025-05-06 16:00:56 +12:00
Edward Paget
9e7f0b903a fix: remove version-info from settings list api (#57505)
* fix: remove version-info from settings list api

* fixup! cljfmt

* fixup! remove extra assignment
2025-05-05 18:04:34 -05:00
Alex Yarosh
232ce151c3 help page (#57619) 2025-05-05 21:31:07 +00:00
Cam Saul
5d6b82d6ee Fix Kondo failures not causing CI failures (#57618) 2025-05-05 21:01:28 +00:00
Kyle Doherty
1b0ceb2c48 Update Title component sizes to match Figma (#54750)
* update title sizes to match figma - add multi line title stories for lineheight examples

* little more line height in modal titles

* little fixes

* more little fixes

* reduce smartscalar title size

* Update Loki Snapshots

---------

Co-authored-by: Ryan Laurie <iethree@gmail.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
2025-05-05 13:41:48 -06:00
Nick Fitzpatrick
c42252c802 Add watermark to static viz chart renderings (#57553)
* add watermark to static viz chart renderings

* adjusting unit test
2025-05-05 22:38:28 +05:30
Edward Paget
dd9c170d8d fix: add table-level sql based permission query (#57541)
* fix: add table-level sql based permission query

Some places in our code we may be returning fewer tables than a user has
access to because we load a limited number of models from the database
then apply permissions filters to those. This provides a subquery based
check for table permissions that we can use in other queries to do the
same kind of test for table access that we would do on the clojure-side.

Co-Authored-By: Nathan Voxland <nathan@metabase.com>

* fixup! restore check-permissions-for-model impl

* Update src/metabase/models/table.clj

Co-authored-by: Noah Moss <32746338+noahmoss@users.noreply.github.com>

* fixup! caluse -> clause spelling

* fixup! reformat malli annotations; add docstring

* fixup! add Literal type to honeysql helpers

* fixup! restore accidentally deleted clause

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>
Co-authored-by: Noah Moss <32746338+noahmoss@users.noreply.github.com>
2025-05-05 11:48:38 -05:00
Ryan Laurie
c88a5b5bc5 remove use modal hook (#57601) 2025-05-05 10:39:07 -06:00
github-automation-metabase
aa0bd12ed3 Bump net.snowflake/snowflake-jdbc from 3.23.2 to 3.24.0 (#57587)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Riley Thompson <riley@metabase.com>
2025-05-05 11:20:53 -04:00
github-automation-metabase
a0bee28c26 Bump org.eclipse.jetty/jetty-server from 12.0.19 to 12.0.20 (#57581)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-05 14:04:27 +00:00
Ngoc Khuat
ea279ca19b Email domain check when adding notification recipients (#57416) 2025-05-05 13:59:54 +00:00
github-automation-metabase
a8656deb25 Bump org.eclipse.jetty.ee9/jetty-ee9-servlet from 12.0.19 to 12.0.20 (#57580)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
2025-05-05 13:48:41 +00:00
Nick Fitzpatrick
8ebc12b933 Register Dynamic Shortcuts (#56812)
* Register Dynamic Shortcuts

* regex keys

* things more-less working

* adding admin shortcuts, patching kbar to pass keyboard event

* fixing type issues

* only emit events on keyboard shortcut executions

* remove wait
2025-05-05 17:35:30 +05:30
Nick Fitzpatrick
edb69d6dbb properly invalidate collections when x-raying a table (#57495) 2025-05-05 17:34:15 +05:30
Kamil Mielnik
8aac6aac61 Add label to CodeEditor in LogLevelsForm (#57524)
* Add label to CodeEditor in LogLevelsForm

* Remove required for consistency
2025-05-05 17:56:16 +07:00
Nemanja Glumac
691d1430f8 Add a new metric button to browse metrics page (#57496)
Resolves PRG-22
2025-05-05 12:53:00 +02:00
Nicolò Pretto
2adc027178 fix(sdk): manual fixes for module level ttag calls causing broken translations (#57391)
* fix: filter button labels

* fix: part of EMB-335, uiName: t => getUiName: () => t

* fix linting issue

* fixed unit test mock missing getUiName
frontend/src/metabase/visualizations/components/Visualization/Visualization.unit.spec.js
missing

* make application-permissions.cy.spec.js pass
2025-05-05 10:11:02 +02:00
Aleksandr Lesnenko
e3e035ac6e remove old static pie chart interface (#57545) 2025-05-03 00:56:14 +00:00
Alexander Polyankin
c7ee8b29d2 Fix isDate check in legacy (#57185) 2025-05-03 00:04:44 +00:00
Vamsi Peri
a23481188c Fix for Grey areas around the cards when exporting to PDF (#56928)
* cursor says this could fix the issue

* harder vibe coding

* remove handling of made up by llm elements that do not exist

---------

Co-authored-by: Aleksandr Lesnenko <alxnddr@gmail.com>
2025-05-02 22:49:49 +00:00
Oleksandr Yakushev
0a4625fdbe [svg] Pool static-viz-context objects (#56648) 2025-05-02 23:34:37 +03:00
Nathan Voxland
ac81ee5c25 Fixed catching of 403 errors from harbormaster (#57504) 2025-05-02 14:04:34 -05:00
Cam Saul
99613cfe0d [Backend modularization] sample-data module (#57511)
* [Backend modularization] `sample-data` module

* Remove duplicate ns form
2025-05-02 11:51:47 -07:00
Nathan Voxland
e515a2d6d0 Further limit tsvector size since it's concatinated (#57514) 2025-05-02 13:20:26 -05:00
Riley Thompson
0c0aaf5490 druid jdbc localdate set param impl (#57507) 2025-05-02 10:56:28 -04:00
Riley Thompson
0bc9646ca0 set timezone for identity-hash-test tests (#57530) 2025-05-02 10:56:14 -04:00
Case Nelson
40507a0e21 fix: include system catalog in databricks multicatalog (#57484)
* fix: include system catalog in databricks multicatalog

* Fix formatting
2025-05-02 08:53:37 -06:00
github-automation-metabase
562617890f Bump com.facebook.presto/presto-jdbc from 0.291 to 0.292 (#56659)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Riley Thompson <riley@metabase.com>
2025-05-02 10:43:22 -04:00
github-automation-metabase
9e4fb1181c Bump io.trino/trino-jdbc from 431 to 475 (#57319)
Inspect dependency changes here:

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Riley Thompson <riley@metabase.com>
2025-05-02 10:43:11 -04:00
github-automation-metabase
6422238118 Bump com.clickhouse/clickhouse-jdbc from 0.8.4 to 0.8.5 (#57318)
* Bump com.clickhouse/clickhouse-jdbc from 0.8.4 to 0.8.5

Inspect dependency changes here:

* Fix test for jdbc driver upgrade

---------

Co-authored-by: Metabase bot <metabase-bot@metabase.com>
Co-authored-by: Riley Thompson <rileythompson99@gmail.com>
2025-05-02 10:42:04 -04:00
Romeo Van Snick
c4cb9263f6 Do not set card when using browser back button (#57389)
* Add reproduction for metabase#56775

* Pass the correct card to the location change

* Unwrap card when navigating to model or metric
2025-05-02 13:42:05 +02:00
Romeo Van Snick
003777b77d Clean up expression config (#57109)
* Rename ALL_NODES to NODE_TYPE

* Remove unnecessary name override

* Remove unused EXPRESSION_OPERATOR_WITHOUT_ORDER_PRIORITY

* Use precedence from pratt compiler

* Move EDITOR_FK_SYMBOLS to identifier module

* Lift const descriptor

* Remove unused characters config

* Remove identifierAlwaysQuoted from EDITOR_QUOTES config and make the behaviour the default

* Move identifierDefaultQuote out of config and hard-code the behaviour

* Flatten default quotes config and move default into string module

* Remove wrong comment

* Use parseOperatorType to find operator type

* Remove separate operator groups

* Remove unused FUNCTIONS variable

* Remove barely used getExpressionName

* Use Boolean token to parse booleans

* Remove True and False from operators

* Rename Star and Slash to Multiply and Divide

* Remove FIELD_MARKERS

* Simplify config definition

* Inline getDescriptionForNow

* Move OPERATOR check to formatter

* Use normal quotes in config

* Split up MBQL_CLAUSES to avoid duplicating names

* Split clause helpers into separate files

* Fix sort order in completion tests

* Move operators to a separate group in config

* Use EXPRESSION_OPERATORS in formatter

* Export using star

* Rename OPERATOR to PUNCTUATOR

* Rename parseOperatorType to parsePunctuator

* Rename parseOperatorType to parsePunctuator

* Add helper for potential function prefixes

* Remove unused OPERATORS

* Remove superflouous helper

* Inline formatCallExpression

* Use Lib.ExpressionOperator where possible

* Remove impossible case for expression

* Inline isLogicOperator

* Rename variable for clarity

* Deduplicate helpers

* Move formatter helpers into utils

* Simplify helper

* Fix import order

* Remove reliance on PUNCTUATOR type

* Fix variable name

* Simplify punctuator definition

* Clean up lexifier

* Flatten lexifier results

* Remove errors for parser results

* Flatten parser result

* Simplify parser tests

* Remove cast

* Remove unused import

* Translate all error messages in formatter

* Simplify type

* Use sentence case name for type
2025-05-02 13:19:47 +02:00
Alexander Solovyov
76133ade49 [serdes] break endless loops (#57482)
When making sure stuff can load even with a loop, the circuit breaker was removed, leading to endless loops. That
would happen if the data causing the loop wasn't stripped properly. Right now if stripping things once did not help
and the entity is in the pipeline again, the process will break (with hopefully enough data to detect why).

This also changes the logic from exception to just recursive calls. Plus it's back at skipping all dashcards in a
Dashboard instead of just skipping visualization logic, which should make for a way faster processing (less deep for
sure). Plus it tries to improve logging a bit so it's easier to understand what's going on in complex cases.
2025-05-02 10:30:38 +00:00
Nicolò Pretto
f29902368b sdk-storybook: disable reactDocGen to speed up storybook (#57440) 2025-05-02 10:47:58 +02:00
Sébastien
2c106817a4 closes viz-360 esc key should not add a recipient (#57253)
* closes viz-360 esc key should not add a recipient

* better code in e2e test to avoid eslint-disable-next-line
2025-05-02 09:47:19 +02:00
Sébastien
da5cd4e7cf Closes VIZ-349 unknown date style warnings (#57263)
* closes VIZ-349 unknown date style warnings

* safer approach
2025-05-02 07:20:03 +02:00
Alexander Polyankin
04bf793795 Drop param_values (#57508) 2025-05-02 00:39:30 +00:00
Fraser
fa9ea2a5a9 Create EmailSettingsPage (#56760)
* Create EmailSettingsPage

* Add email settings to mocks

* Create ReplyToWidget

* Add unit tests for email page

* Handle boolean conversion in AdminSettingInput
2025-05-02 11:45:51 +12:00
Cam Saul
a04e9bd9b0 [Backend modularization] testing-api module (#57503) 2025-05-01 15:53:14 -07:00
Cam Saul
85b3ab5d5f [Backend modularization] Move remaining Slack namespaces into channel module (#57502) 2025-05-01 15:09:14 -07:00
Cam Saul
41e3110f20 [Backend modularization] create new api-keys module (#57497)
* [Backend modularization] create new `api-routes` module

* Fix test 🔧

* [Backend modularization] `api-keys` module
2025-05-01 14:22:52 -07:00
Cam Saul
213535773b [Backend modularization] create new api-routes module (#57492)
* [Backend modularization] create new `api-routes` module

* Fix test 🔧
2025-05-01 20:16:03 +00:00
Jeff Bruemmer
c1e301eda6 docs - note on role setting (#57268)
* note on role setting

* format
2025-05-01 16:15:16 -04:00
Edward Paget
d82bf686b6 deps: remove simpleclient_jetty and javax.servlet (#57487) 2025-05-01 18:58:56 +00:00
Oisin Coveney
fe3792cda9 Convert AutomaticDashboardApp to a functional component (#57390) 2025-05-01 17:53:37 +00:00
lbrdnk
8278732860 Fix cumulative aggregations legacy transform (#57258)
* Fix cum-sum and cum-count legacy transform

* Address review remarks

* Adjsut test

* ag idents

* dissoc-in
2025-05-01 09:34:57 +02:00
Oisin Coveney
0fb515cbc6 TS Conversion: Automatic Dashboard App (#57075) 2025-05-01 08:09:35 +01:00
Cam Saul
2d038e518e Remove Legacy Metrics code from the backend (#57401)
* Remove Legacy Metrics code from the backend

* Code cleanup

* Test fix 🔧

* Test fix 🔧

* PR feedback

* Extra test

* Revert some X-Rays changes

* Remove commented-out test

* Test fixes 🔧
2025-04-30 19:06:26 -07:00
Cam Saul
fc56c810f9 [Backend modularization] native-query-snippets (#57460) 2025-04-30 23:18:03 +00:00
Riley Thompson
fd18b7708f Try specifying tz for identity hash test flake (#57408) 2025-04-30 22:40:06 +00:00
Cam Saul
1c113a608e [Backend modularization] upload module cleanup (#57414)
* `upload` module cleanup

* Fix MySQL

* More keyword fixes 🔧

* Keep uploads sorted
2025-04-30 15:15:58 -07:00
Noah Moss
5fabb3e1ec Fix subtotal label formatting for xlsx pivot exports (#57452)
* Fix subtotal label formatting for xlsx pivot exports

* untangle circular dep
2025-04-30 14:57:49 -07:00
Nathan Voxland
6da77fce6f Reindex immediately on start-up if existing table is old (#57159) 2025-04-30 16:39:46 -05:00
Riley Thompson
f947b70c35 Restrict Mongo sandboxing with saved questions (#56983)
Co-authored-by: Nick Fitzpatrick <nickfitz.582@gmail.com>
2025-04-30 17:32:52 -04:00
1179 changed files with 101773 additions and 84863 deletions

View File

@@ -91,7 +91,6 @@
metabase.test.data.users/with-group-for-user
metabase.test.data/with-empty-h2-app-db
metabase.test.util.log/with-log-level
metabase.test.util.misc/with-single-admin-user
metabase.test.util/with-all-users-permission
metabase.test.util/with-discarded-collections-perms-changes
metabase.test.util/with-env-keys-renamed-by
@@ -123,7 +122,6 @@
metabase.test/with-model-cleanup
metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms
metabase.test/with-non-admin-groups-no-root-collection-perms
metabase.test/with-single-admin-user
metabase.test/with-temp-env-var-value
metabase.test/with-temp-vals-in-db
metabase.test/with-temporary-raw-setting-values
@@ -579,13 +577,13 @@
metabase.test/with-actions clojure.core/let
metabase.test/with-grouper-batches! clojure.core/fn
metabase.test/with-open-channels clojure.core/let
metabase.test/with-single-admin-user clojure.core/fn
metabase.test/with-single-admin-user! clojure.core/fn
metabase.test/with-temp-dir clojure.core/let
metabase.test/with-temp-empty-app-db clojure.core/let
metabase.test/with-temp-file clojure.core/let
metabase.test/with-user-in-groups clojure.core/let
metabase.test.gentest/defgentest clojure.test/deftest
metabase.upload-test/with-upload-table! clojure.core/let
metabase.upload.impl-test/with-upload-table! clojure.core/let
metabase.util/with-timer-ms clojure.core/fn
metabase.util.files/with-open-path-to-resource clojure.core/let
metabase.util.malli.defn/defn schema.core/defn
@@ -636,6 +634,7 @@
metabase.api.dashboard-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding
metabase.api.macros/defendpoint hooks.metabase.api.macros/defendpoint
metabase.api.user-test/with-temp-user-email! hooks.common/with-one-binding
metabase.channel.render.js.svg/with-static-viz-context hooks.common/with-one-top-level-binding
metabase.channel.template.handlebars-helper/defhelper hooks.metabase.channel.template.handlebars-helper/defhelper
metabase.channel.template.handlebars-test/with-temp-template! hooks.common/with-vec-first-binding
metabase.dashboard-subscription-test/with-link-card-fixture-for-dashboard hooks.common/let-second
@@ -682,7 +681,7 @@
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.sample-data.impl-test/with-temp-sample-database-db hooks.common/with-one-binding
metabase.search.api-test/with-search-items-in-collection hooks.metabase.search.api-test/with-search-items-in-collection
metabase.search.api-test/with-search-items-in-root-collection hooks.common/do*
metabase.server.streaming-response-test/with-start-execution-chan hooks.common/with-one-binding
@@ -772,7 +771,7 @@
metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter
metabase.models.params.chain-filter-test/chain-filter macros.metabase.models.params.chain-filter-test/chain-filter
metabase.models.params.chain-filter-test/chain-filter-search macros.metabase.models.params.chain-filter-test/chain-filter
metabase.models.user-test/with-groups macros.metabase.models.user-test/with-groups
metabase.models.user-test/with-groups! macros.metabase.models.user-test/with-groups!
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-card-referencing!
metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.public-sharing.api-test/with-sharing-enabled-and-temp-dashcard-referencing!
metabase.query-analysis.models.query-analysis-test/with-test-setup macros.metabase.query-analysis.models.query-analysis-test/with-test-setup

View File

@@ -42,19 +42,22 @@
:uses #{api config db events models util}}
analytics
{:api #{metabase.analytics.core
{:api #{metabase.analytics.api
metabase.analytics.core
metabase.analytics.init
metabase.analytics.prometheus
metabase.analytics.snowplow}
:uses #{api
channel
config
db
driver
eid-translation
integrations
internal-stats
models
premium-features
settings
task
util}}
analyze
{:api #{metabase.analyze.core metabase.analyze.query-results}
@@ -63,21 +66,69 @@
api
{:api #{metabase.api.auth
metabase.api.card
metabase.api.cards
metabase.api.collection
metabase.api.common
metabase.api.common.validation
metabase.api.dashboard
metabase.api.database
metabase.api.dataset
metabase.api.docs
metabase.api.field
metabase.api.geojson
metabase.api.logger
metabase.api.macros
metabase.api.open-api
metabase.api.query-metadata
metabase.api.routes
metabase.api.routes.common
metabase.api.table
metabase.api.user
metabase.api.util
metabase.api.util.handlers}
:uses #{actions
analytics
analyze
api-routes
channel
config
database-routing
db
driver
eid-translation
embedding
events
legacy-mbql
lib
logger
model-persistence
models
permissions
plugins
premium-features
pulse
query-processor
request
revisions
sample-data
search
server
session
settings
sso
sync
types
upload
util
xrays
enterprise/advanced-permissions}}
api-keys
{:api #{metabase.api-keys.api
metabase.api-keys.core}
:uses #{api db events models permissions util}}
api-routes
{:api #{metabase.api-routes.core}
:uses :any}
audit
@@ -85,7 +136,7 @@
:uses #{db settings}}
auth-provider
{:api #{metabase.auth-provider}
{:api #{metabase.auth-provider.core}
:uses #{premium-features}}
bookmarks
@@ -98,6 +149,7 @@
metabase.cache.init}
:uses #{api config db events models premium-features settings util}}
;; TODO -- way too many API namespaces.
channel
{:api #{metabase.channel.api
metabase.channel.core
@@ -106,6 +158,8 @@
metabase.channel.init
metabase.channel.models.channel
metabase.channel.render.core
metabase.channel.settings
metabase.channel.slack
metabase.channel.template.core}
:uses #{analytics
api
@@ -114,7 +168,6 @@
driver
events
formatter
integrations
lib
models
notification
@@ -122,6 +175,7 @@
premium-features
settings
query-processor
task
timeline
types
util}}
@@ -136,7 +190,7 @@
{:api #{metabase.cmd
metabase.cmd.copy
metabase.cmd.dump-to-h2}
:uses #{api config db legacy-mbql models plugins query-processor search settings util}}
:uses #{api api-routes config db legacy-mbql models plugins query-processor search settings util}}
;; technically this 'uses' `enterprise/core` and `test` since it tries to load them to see if they exists so we know
;; if EE/test code is available; however we can ignore them since they're not real usages.
@@ -231,8 +285,9 @@
util}}
events
{:api #{metabase.events metabase.events.init metabase.events.notification}
:uses #{api channel driver models notification premium-features task-history util}}
{:api #{metabase.events.core
metabase.events.init}
:uses #{channel driver models util view-log}}
formatter
{:api #{metabase.formatter}
@@ -242,10 +297,6 @@
{:api #{metabase.indexed-entities.api metabase.indexed-entities.init}
:uses #{analytics api driver legacy-mbql lib models query-processor search sync task util}}
integrations
{:api #{metabase.integrations.slack}
:uses #{events settings util}}
internal-stats
{:api #{metabase.internal-stats.core}
:uses #{db models}}
@@ -318,8 +369,7 @@
:uses #{api db driver events lib models premium-features settings query-processor request task task-history util}}
models
{:api #{metabase.models.api-key
metabase.models.application-permissions-revision
{:api #{metabase.models.application-permissions-revision
metabase.models.audit-log
metabase.models.card
metabase.models.card.metadata
@@ -334,14 +384,11 @@
metabase.models.database
metabase.models.dispatch
metabase.models.field
metabase.models.field-usage
metabase.models.field-values
metabase.models.humanization
metabase.models.init
metabase.models.interface
metabase.models.moderation-review
metabase.models.native-query-snippet
metabase.models.native-query-snippet.permissions
metabase.models.params
metabase.models.params.chain-filter
metabase.models.params.custom-values
@@ -356,11 +403,11 @@
metabase.models.user
metabase.models.user-parameter-value
metabase.models.util.spec-update
metabase.models.view-log-impl
metabase.models.visualization-settings}
:uses #{analytics
analyze
api
api-keys
audit
cache
channel
@@ -391,14 +438,17 @@
{:api #{metabase.moderation}
:uses #{models util}}
native-query-snippets
{:api #{metabase.native-query-snippets.api
metabase.native-query-snippets.core}
:uses #{api models permissions premium-features util}}
notification
{:api #{metabase.notification.api
metabase.notification.core
metabase.notification.init
metabase.notification.models
metabase.notification.api.notification
metabase.notification.payload.core
metabase.notification.send}
metabase.notification.payload.core}
:uses #{analytics
api
channel
@@ -450,9 +500,9 @@
metabase.pulse.core
metabase.pulse.init
metabase.pulse.task.send-pulses}
:uses #{api channel config db driver events integrations models notification permissions plugins premium-features
:uses #{api channel config db driver events models notification permissions plugins premium-features
query-processor request task task-history util
enterprise/advanced-config enterprise/advanced-permissions enterprise/sandbox}}
enterprise/advanced-permissions enterprise/sandbox}}
query-analysis
{:api #{metabase.query-analysis.core metabase.query-analysis.init}
@@ -470,7 +520,6 @@
metabase.query-processor.middleware.annotate
metabase.query-processor.middleware.cache-backend.db
metabase.query-processor.middleware.constraints
metabase.query-processor.middleware.fetch-source-query
metabase.query-processor.middleware.fetch-source-query-legacy
metabase.query-processor.middleware.limit
metabase.query-processor.middleware.permissions
@@ -524,8 +573,9 @@
:uses #{api config events models util}}
sample-data
{:api #{metabase.sample-data}
:uses #{plugins sync util}}
{:api #{metabase.sample-data.core
metabase.sample-data.init}
:uses #{plugins settings sync util}}
search
{:api #{metabase.search.api
@@ -536,7 +586,7 @@
metabase.search.ingestion
metabase.search.init
metabase.search.spec}
:uses #{analytics api audit config db driver models permissions premium-features settings request task util}}
:uses #{analytics api audit config db driver models permissions premium-features settings request startup task util}}
segments
{:api #{metabase.segments.api}
@@ -547,7 +597,7 @@
metabase.server.middleware.json
metabase.server.middleware.session
metabase.server.streaming-response}
:uses #{analytics api config core db driver embedding models premium-features settings session request util enterprise/sso}}
:uses #{analytics api api-keys api-routes config core db driver embedding models premium-features settings session request util enterprise/sso}}
session
{:api #{metabase.session.core
@@ -584,6 +634,10 @@
metabase.sso.init}
:uses #{api models permissions premium-features settings util}}
startup
{:api #{metabase.startup.core}
:uses #{util}}
sync
{:api #{metabase.sync.api
metabase.sync.core
@@ -602,7 +656,6 @@
config
db
driver
integrations
plugins
premium-features
settings
@@ -622,6 +675,12 @@
task
util}}
testing-api
{:api #{metabase.testing-api.api
metabase.testing-api.core
metabase.testing-api.init}
:uses #{analytics api config db premium-features search util}}
tiles
{:api #{metabase.tiles.api
metabase.tiles.init}
@@ -636,7 +695,9 @@
:uses #{util}}
upload
{:api #{metabase.upload}
{:api #{metabase.upload.api
metabase.upload.core
metabase.upload.init}
:uses #{analytics api driver events legacy-mbql lib model-persistence models permissions settings sync util}}
user-key-value
@@ -647,6 +708,11 @@
{:api :any
:uses #{config db driver legacy-mbql lib models plugins settings}}
view-log
{:api #{metabase.view-log.core
metabase.view-log.init}
:uses #{analytics api events models premium-features util}}
xrays
{:api #{metabase.xrays.api
metabase.xrays.core
@@ -655,9 +721,10 @@
enterprise/advanced-config
{:api #{metabase-enterprise.advanced-config.api.logs
metabase-enterprise.advanced-config.models.pulse-channel
metabase-enterprise.advanced-config.models.notification
metabase-enterprise.advanced-config.init
metabase-enterprise.advanced-config.file}
:uses #{api db driver models permissions premium-features setup settings sync util}}
:uses #{api api-keys db driver models permissions premium-features setup settings sync util}}
enterprise/advanced-permissions
{:api #{metabase-enterprise.advanced-permissions.api.routes
@@ -665,9 +732,21 @@
metabase-enterprise.advanced-permissions.models.permissions.group-manager}
:uses #{api enterprise/impersonation lib models permissions premium-features query-processor util}}
enterprise/airgap
enterprise/ai-entity-analysis
{:api #{metabase-enterprise.ai-entity-analysis.api}
:uses #{api enterprise/metabot-v3 premium-features util}}
enterprise/ai-sql-fixer
{:api #{metabase-enterprise.ai-sql-fixer.api}
:uses #{api driver enterprise/metabot-v3 query-analysis query-processor util}}
enterprise/ai-sql-generation
{:api #{metabase-enterprise.ai-sql-generation.api}
:uses #{api driver enterprise/metabot-v3 models util}}
enterprise/analytics
{:api #{}
:uses #{premium-features util}}
:uses #{driver premium-features enterprise/advanced-config enterprise/scim enterprise/sso}}
enterprise/api
{:api #{metabase-enterprise.api.routes
@@ -747,6 +826,15 @@
{:api #{metabase-enterprise.llm.api}
:uses #{analytics analyze api query-processor settings util}}
enterprise/metabot-v3
{:api #{metabase-enterprise.metabot-v3.api metabase-enterprise.metabot-v3.core
metabase-enterprise.metabot-v3.tools.api}
:uses #{api config db legacy-mbql lib models premium-features settings pulse query-processor request util}}
enterprise/premium-features
{:api #{}
:uses #{premium-features util}}
enterprise/query-reference-validation
{:api #{metabase-enterprise.query-reference-validation.api}
:uses #{api models query-analysis request util}}
@@ -757,6 +845,7 @@
:uses #{api
audit
db
driver
legacy-mbql
lib
models
@@ -770,7 +859,7 @@
enterprise/scim
{:api #{metabase-enterprise.scim.api metabase-enterprise.scim.routes}
:uses #{analytics api models permissions settings server util enterprise/serialization}}
:uses #{analytics api api-keys models permissions settings server util enterprise/serialization}}
enterprise/search
{:api #{}
@@ -784,22 +873,19 @@
enterprise/snippet-collections
{:api #{}
:uses #{models permissions premium-features util}}
:uses #{models native-query-snippets permissions premium-features util}}
enterprise/sso
{:api #{metabase-enterprise.sso.api.routes
metabase-enterprise.sso.integrations.sso-settings
metabase-enterprise.sso.integrations.sso-utils}
:uses #{api channel embedding events premium-features request session settings sso util enterprise/scim}}
:uses #{api channel embedding events notification premium-features request session settings sso util enterprise/scim}}
enterprise/stale
{:api #{metabase-enterprise.stale.routes}
{:api #{metabase-enterprise.stale.api
metabase-enterprise.stale.init}
:uses #{analytics api embedding models premium-features request settings util}}
enterprise/stats
{:api #{}
:uses #{driver premium-features enterprise/advanced-config enterprise/scim enterprise/sso}}
enterprise/task
{:api #{metabase-enterprise.task.truncate-audit-tables}
:uses #{premium-features}}

View File

@@ -91,7 +91,7 @@
metabase.query-processor.streaming.interface/begin!
metabase.query-processor.streaming.interface/finish!
metabase.query-processor.streaming.interface/write-row!
metabase.sample-data/try-to-extract-sample-database!
metabase.sample-data.impl/try-to-extract-sample-database!
metabase.settings.models.setting-test/test-user-local-allowed-setting!
metabase.settings.models.setting-test/test-user-local-only-setting!
metabase.settings.models.setting.cache/restore-cache!
@@ -126,7 +126,7 @@
metabase.test/initialize-if-needed!
metabase.test/test-helpers-set-global-values!
metabase.test/with-temp-env-var-value!
metabase.upload-test/set-local-infile!
metabase.upload.impl-test/set-local-infile!
metabase.util.files/create-dir-if-not-exists!
metabase.util.ssh-test/start-mock-servers!
metabase.util.ssh-test/stop-mock-servers!}}}}

View File

@@ -1,9 +1,9 @@
(ns macros.metabase.models.user-test)
(defmacro with-groups [bindings & body]
(defmacro with-groups! [bindings & body]
`(let ~(into []
(comp (partition-all 3)
(mapcat (fn [[group-binding properties members]]
[group-binding [properties members]])))
bindings)
(comp (partition-all 3)
(mapcat (fn [[group-binding properties members]]
[group-binding [properties members]])))
bindings)
~@body))

View File

@@ -0,0 +1,34 @@
(ns hooks.metabase.models.setting-test
(:require
[clj-kondo.hooks-api :as hooks]
[clojure.test :refer :all]
[hooks.metabase.settings.models.setting]))
#_{:clj-kondo/ignore [:metabase/validate-deftest]}
(deftest ^:parallel defsetting-test
(let [node (-> '(defsetting active-users-count
(deferred-tru "Cached number of active users. Refreshed every 5 minutes.")
:visibility :admin
:type :integer
:default 0
:getter (fn []
(if-not ((requiring-resolve 'metabase.db/db-is-set-up?))
0
(cached-active-users-count))))
pr-str
hooks/parse-string)]
(is (= '(let [_ (deferred-tru "Cached number of active users. Refreshed every 5 minutes.")
_ {:default 0
:type :integer
:getter (fn []
(if-not ((requiring-resolve 'metabase.db/db-is-set-up?))
0
(cached-active-users-count)))
:visibility :admin}]
:active-users-count
(defn active-users-count "Docstring." [])
(defn active-users-count! "Docstring." [_value-or-nil]))
(-> {:node node}
hooks.metabase.settings.models.setting/defsetting
:node
hooks/sexpr)))))

View File

@@ -190,7 +190,7 @@
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.sample-data.impl-test/with-temp-sample-database-db [[:block 1]]
metabase.search.api-test/do-test-users [[:block 1]]
metabase.server.middleware.session/as-admin [[:block 0]]
metabase.server.middleware.session/with-current-user [[:inner 0]]
@@ -274,7 +274,7 @@
metabase.test/with-user-in-groups [[:inner 0]]
metabase.timeseries-query-processor-test.util/test-timeseries-drivers [[:block 0]]
metabase.timeseries-query-processor-test/run-mbql-query [[:inner 0]]
metabase.upload-test/with-upload-table! [[:inner 0]]
metabase.upload.impl-test/with-upload-table! [[:inner 0]]
metabase.upload.types/with-currency [[:default]]
metabase.upload.types/with-parens [[:default]]
metabase.util.async/cancelable-thread [[:block 0]]

View File

@@ -6,7 +6,7 @@
// can use this flag to enable it. This is set to true in CI
const shouldLintCssModules =
process.env.LINT_CSS_MODULES === "true" || process.env.CI;
const plugins = ["react", "no-only-tests","ttag"];
const plugins = ["react", "no-only-tests", "ttag"];
if (shouldLintCssModules) {
plugins.push("postcss-modules");
}
@@ -118,7 +118,7 @@ module.exports = {
],
},
],
complexity: ["error", { max: 54 }],
complexity: ["error", { max: 55 }],
...(shouldLintCssModules
? {
"postcss-modules/no-undef-class": "error",
@@ -195,12 +195,15 @@ module.exports = {
"*.unit.spec.*",
"frontend/src/metabase/admin/**/*",
"frontend/src/metabase/setup/**/*",
"enterprise/frontend/src/metabase-enterprise/whitelabel/**/*",
"frontend/lint/**/*",
"*.stories.*",
"stories-data.*",
"e2e/**/*",
"**/tests/*",
"release/**/*",
"webpack.config.js",
"rspack.config.js",
],
rules: {
"no-color-literals": "off",
@@ -258,9 +261,9 @@ module.exports = {
"jest/expect-expect": [
"error",
{
"assertFunctionNames": ["expect*", "assert*"],
"additionalTestBlockFunctions": []
}
assertFunctionNames: ["expect*", "assert*"],
additionalTestBlockFunctions: [],
},
],
},
},
@@ -291,7 +294,7 @@ module.exports = {
selector: "Literal[value=/mb-base-color-/]",
message:
"You may not use base colors in the application, use semantic colors instead. (see colors.module.css)",
}
},
],
},
},

View File

@@ -16,6 +16,8 @@ on:
release:
type: string
default: 'true'
build-args:
type: string
workflow_dispatch:
inputs:
artifact-name:
@@ -87,6 +89,7 @@ jobs:
tags: ${{ inputs.repo }}:${{ inputs.tag }}
no-cache: true
push: true
build-args: ${{ inputs.build-args }}
# we need to do this to get the nice fallback behavior of the legacy manifest file
- name: Build container (x86 only)

View File

@@ -7,6 +7,7 @@ on:
push:
branches:
- master
- metabot-v3-main
- feature-visualizer
- internal-tools
paths-ignore:
@@ -141,6 +142,8 @@ jobs:
repo: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).repo }}
tag: ${{ fromJson(needs.get-container-info.outputs[matrix.edition]).tag }}
release: "false"
build-args: |
GIT_COMMIT_SHA=${{ github.event.inputs.commit || github.sha }}
run-trivy:
needs: [get-container-info, containerize]

5
.gitignore vendored
View File

@@ -6,6 +6,7 @@
*.po~
*.sqlite
*.trace.db
*.trace.db.old
*.xcworkspacedata
.DS_Store
.\#*
@@ -133,6 +134,10 @@ modules/drivers/*/.lsp/*
# jar downloader
/jars/
# metabot
llm-payloads
/bin/bb
.aider*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -11,7 +11,7 @@ const {
module.exports = {
stories: ["../enterprise/frontend/src/embedding-sdk/**/*.stories.tsx"],
staticDirs: ["../resources/frontend_client"],
staticDirs: ["../resources/frontend_client", "./msw-public"],
addons: [
"@storybook/addon-webpack5-compiler-babel",
"@storybook/addon-interactions",
@@ -25,9 +25,9 @@ module.exports = {
options: {},
},
typescript: {
reactDocgen: "react-docgen-typescript",
reactDocgen: false,
},
webpackFinal: storybookConfig => ({
webpackFinal: (storybookConfig) => ({
...storybookConfig,
plugins: [
...storybookConfig.plugins,
@@ -44,10 +44,10 @@ module.exports = {
...storybookConfig.module,
rules: [
...storybookConfig.module.rules.filter(
rule => !isCSSRule(rule) && !isSvgRule(rule),
(rule) => !isCSSRule(rule) && !isSvgRule(rule),
),
...appConfig.module.rules.filter(
rule => isCSSRule(rule) || isSvgRule(rule),
(rule) => isCSSRule(rule) || isSvgRule(rule),
),
],
},
@@ -65,8 +65,8 @@ module.exports = {
}),
};
const isCSSRule = rule => rule.test?.toString() === "/\\.css$/";
const isSvgRule = rule => rule.test && rule.test?.test(".svg");
const isCSSRule = (rule) => rule.test?.toString() === "/\\.css$/";
const isSvgRule = (rule) => rule.test && rule.test?.test(".svg");
function resolveEmbeddingSdkPackage() {
let isEmbeddingSdkPackageInstalled = false;

View File

@@ -0,0 +1,307 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.7.5'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}

View File

@@ -1,4 +1,5 @@
import { GlobalTypes } from "@storybook/types";
import { initialize, mswLoader } from "msw-storybook-addon";
// @ts-expect-error: See metabase/lib/delay
// This will skip the skippable delays in stories
@@ -7,7 +8,7 @@ window.METABASE_REMOVE_DELAYS = true;
import { storybookThemeOptions } from "embedding-sdk/test/storybook-themes";
import { availableLocales } from "./constants";
export const parameters = {
const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
@@ -17,9 +18,9 @@ export const parameters = {
},
};
export const decorators = []; // No decorators for Embedding SDK stories, as we want to simulate real use cases
const decorators = []; // No decorators for Embedding SDK stories, as we want to simulate real use cases
export const globalTypes: GlobalTypes = {
const globalTypes: GlobalTypes = {
sdkTheme: {
name: "SDK Theme",
description: "Global theme for sdk components",
@@ -54,3 +55,4 @@ export const globalTypes: GlobalTypes = {
},
},
};

View File

@@ -18,7 +18,7 @@ import { getMetabaseCssVariables } from "metabase/styled-components/theme/css-va
import { css, Global, useTheme } from "@emotion/react";
import { baseStyle, rootStyle } from "metabase/css/core/base.styled";
import { defaultFontFiles } from "metabase/css/core/fonts.styled";
import { saveDomImageStyles } from "metabase/visualizations/lib/save-chart-image";
import { saveDomImageStyles } from "metabase/visualizations/lib/image-exports";
// Note: Changing the names of the stories may impact loki visual testing. Please ensure that
// Any story name changes are also reflected in the loki.config.js storiesFilter array.
@@ -72,7 +72,7 @@ const globalStyles = css`
`;
const decorators = [
Story => {
(Story) => {
if (!document.body.classList.contains("mb-wrapper")) {
document.body.classList.add("mb-wrapper");
}

View File

@@ -74,10 +74,10 @@ The `./bin/mage -check-readable <file> <optional: line-number>` command checks i
This ensures your changes maintain valid syntax and structure.
- Edit Clojure files one step at a time.
- After EVERY change to a Clojure form, call `mage -check-readable src/metabase/thefile.clj <line-number>` with the line number.
- After EVERY change to a Clojure form, call `mage -check-readable src/metabase/thefile.clj <line-number>` with the line number.
- If it's readable then call `mage -check-readable dev/src/dev.clj` without the line number to check the entire file.
- If the change results in unreadable code, try again until it is readable.
- To overcome errors about parens, pay close attention to them. Count opening/closing parens you add/remove.
- To overcome errors about parens, pay close attention to them. Count opening/closing parens you add/remove.
#### Bottom-up dev loop
@@ -104,9 +104,9 @@ mage -repl --namespace metabase.db.connection
To call `your.namespace/your-function` on `arg1` and `arg2`:
```
./bin/mage -repl --namespace your.namespace '(your-function arg1 arg2)'
```
```
./bin/mage -repl --namespace your.namespace '(your-function arg1 arg2)'
```
DO NOT use "require", "load-file" etc in the code string argument.

View File

@@ -8,7 +8,7 @@
:exclusions [org.clojure/clojure
org.clojure/clojurescript]}
amalloy/ring-gzip-middleware {:mvn/version "0.1.4"} ; Ring middleware to GZIP responses if client can handle it
babashka/fs {:mvn/version "0.5.24"} ; Portable filesystem operations
babashka/fs {:mvn/version "0.5.25"} ; Portable filesystem operations
bigml/histogram {:mvn/version "5.0.0" ; Histogram data structure
:exclusions [junit/junit]}
buddy/buddy-core {:mvn/version "1.12.0-430" ; various cryptographic functions
@@ -51,19 +51,19 @@
com.github.seancorfield/next.jdbc {:mvn/version "1.3.1002"} ; Talk to JDBC DBs
com.github.steffan-westcott/clj-otel-api {:mvn/version "0.2.7"} ; Telemetry library
com.github.vertical-blank/sql-formatter {:mvn/version "2.0.5"} ; Java SQL formatting library https://github.com/vertical-blank/sql-formatter
com.google.guava/guava {:mvn/version "33.4.6-jre"} ; dep for BigQuery, Spark, and GA. Require here rather than letting different dep versions stomp on each other — see comments on #9697
com.google.guava/guava {:mvn/version "33.4.8-jre"} ; dep for BigQuery, Spark, and GA. Require here rather than letting different dep versions stomp on each other — see comments on #9697
com.h2database/h2 ^:antq/exclude ; https://metaboat.slack.com/archives/CKZEMT1MJ/p1727191010259979
{:mvn/version "2.1.214"} ; embedded SQL database
com.ibm.icu/icu4j {:mvn/version "76.1"} ; Used to transliterate Unicode characters into Latin characters
{:mvn/version "2.1.214"} ; embedded SQL database
com.ibm.icu/icu4j {:mvn/version "77.1"} ; Used to transliterate Unicode characters into Latin characters
com.mchange/c3p0 {:mvn/version "0.10.1"} ; DB connection pool
com.snowplowanalytics/snowplow-java-tracker ^:antq/exclude ; 2+ depend on apache httpclient 5.x, but saml and clj-http depend on 4.x
{:mvn/version "1.0.1"} ; Snowplow analytics
com.taoensso/nippy {:mvn/version "3.4.2"} ; Fast serialization (i.e., GZIP) library for Clojure
com.taoensso/nippy {:mvn/version "3.5.0"} ; Fast serialization (i.e., GZIP) library for Clojure
com.vladsch.flexmark/flexmark {:mvn/version "0.64.8"} ; Markdown parsing
com.vladsch.flexmark/flexmark-ext-autolink
{:mvn/version "0.64.8"} ; Flexmark extension for auto-linking bare URLs
commons-codec/commons-codec {:mvn/version "1.18.0"} ; Apache Commons -- useful codec util fns
commons-io/commons-io {:mvn/version "2.18.0"} ; Apache Commons -- useful IO util fns
commons-io/commons-io {:mvn/version "2.19.0"} ; Apache Commons -- useful IO util fns
commons-net/commons-net {:mvn/version "3.11.1"} ; Apache Commons -- useful network utils. Transitive dep of Snowplow, pinned due to CVE-2021-37533
commons-validator/commons-validator {:mvn/version "1.9.0" ; Apache Commons -- useful validation util fns
:exclusions [commons-beanutils/commons-beanutils
@@ -82,14 +82,12 @@
instaparse/instaparse {:mvn/version "1.5.0"} ; Make your own parser
io.github.camsaul/toucan2 {:mvn/version "1.0.561"}
io.github.eerohele/pp {#_#_:git/tag "2024-11-13.77" ; super fast pretty-printing library
:git/sha "9598f4c72b528480a85ea41651c45b08d1717428"
:git/sha "4998a1fdd9d3713d7034d04509e9212204773bf2"
:git/url "https://github.com/eerohele/pp"}
io.github.metabase/macaw {:mvn/version "0.2.25"} ; Parse native SQL queries
io.github.resilience4j/resilience4j-retry ^:antq/exclude ; 2.x do not support Java 8
{:mvn/version "1.7.1"} ; Support for retrying operations
{:mvn/version "1.7.1"} ; Support for retrying operations
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"} ; prometheus jvm collector
io.prometheus/simpleclient_jetty {:mvn/version "0.16.0"} ; prometheus jetty collector
javax.servlet/servlet-api {:mvn/version "2.5"} ; used by ring's multipart-params (file upload)
junegunn/grouper {:mvn/version "0.1.1"} ; Batch processing helper
kixi/stats {:mvn/version "0.5.7" ; Various statistic measures implemented as transducers
:exclusions [org.clojure/data.avl]}
@@ -126,11 +124,11 @@
{:mvn/version "2.24.3"} ; allows the custom json logging format
org.apache.logging.log4j/log4j-slf4j2-impl
{:mvn/version "2.24.3"} ; allows the slf4j2 API to work with log4j 2
org.apache.poi/poi {:mvn/version "5.4.0"} ; Work with Office documents (e.g. Excel spreadsheets) -- newer version than one specified by Docjure
org.apache.poi/poi-ooxml {:mvn/version "5.4.0"
org.apache.poi/poi {:mvn/version "5.4.1"} ; Work with Office documents (e.g. Excel spreadsheets) -- newer version than one specified by Docjure
org.apache.poi/poi-ooxml {:mvn/version "5.4.1"
:exclusions [org.bouncycastle/bcpkix-jdk15on
org.bouncycastle/bcprov-jdk15on]}
org.apache.poi/poi-ooxml-full {:mvn/version "5.4.0"}
org.apache.poi/poi-ooxml-full {:mvn/version "5.4.1"}
org.apache.sshd/sshd-core {:mvn/version "2.15.0" ; ssh tunneling and test server
:exclusions [org.slf4j/slf4j-api
org.slf4j/jcl-over-slf4j]}
@@ -139,6 +137,7 @@
org.bouncycastle/bcpkix-jdk18on {:mvn/version "1.80"} ; Bouncy Castle crypto library -- explicit version of BC specified to resolve illegal reflective access errors
org.bouncycastle/bcprov-jdk18on {:mvn/version "1.80"}
org.clj-commons/claypoole {:mvn/version "1.2.2"} ; Threadpool tools for Clojure
org.clj-commons/dirigiste {:mvn/version "1.0.4"} ; Instrumented object pooling
org.clj-commons/hickory {:mvn/version "0.7.7" ; Parse HTML into Clojure data structures
:exclusions [org.jsoup/jsoup]}
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} ; LDAP client
@@ -161,14 +160,14 @@
org.clojure/tools.namespace {:mvn/version "1.5.0"}
org.clojure/tools.reader {:mvn/version "1.5.2"}
org.clojure/tools.trace {:mvn/version "0.8.0"} ; function tracing
org.eclipse.jetty/jetty-server {:mvn/version "12.0.19"}
org.eclipse.jetty/jetty-unixdomain-server {:mvn/version "12.0.19"}
org.eclipse.jetty.ee9/jetty-ee9-servlet {:mvn/version "12.0.19"}
org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server {:mvn/version "12.0.19"}
org.eclipse.jetty/jetty-server {:mvn/version "12.0.20"}
org.eclipse.jetty/jetty-unixdomain-server {:mvn/version "12.0.20"}
org.eclipse.jetty.ee9/jetty-ee9-servlet {:mvn/version "12.0.20"}
org.eclipse.jetty.ee9.websocket/jetty-ee9-websocket-jetty-server {:mvn/version "12.0.20"}
org.flatland/ordered {:mvn/version "1.15.12"} ; ordered maps & sets
org.graalvm.polyglot/js-community {:mvn/version "24.2.0" :extension "pom"} ; JavaScript engine
org.graalvm.polyglot/polyglot {:mvn/version "24.2.0"} ; GraalVM platform, required by JS engine
org.jsoup/jsoup {:mvn/version "1.19.1"} ; required by hickory
org.jsoup/jsoup {:mvn/version "1.20.1"} ; required by hickory
org.liquibase/liquibase-core ^:antq/exclude ; FIXME: no idea why it fails
{:mvn/version "4.26.0" ; migration management (Java lib)
:exclusions [ch.qos.logback/logback-classic]}
@@ -176,7 +175,7 @@
{:mvn/version "2.7.10"} ; MySQL/MariaDB driver
org.mindrot/jbcrypt {:mvn/version "0.4"} ; Crypto library
; NOTE: Postgres fix in 42.7.5 breaks liquibase on case-sensitive databases until https://github.com/liquibase/liquibase/pull/6755 is released
org.postgresql/postgresql {:mvn/version "42.7.4"} ; Postgres driver
org.postgresql/postgresql {:mvn/version "42.7.5"} ; Postgres driver
org.slf4j/slf4j-api {:mvn/version "2.0.17"} ; abstraction for logging frameworks -- allows end user to plug in desired logging framework at deployment time
org.tcrawley/dynapath {:mvn/version "1.1.0"} ; Dynamically add Jars (e.g. Oracle or Vertica) to classpath
org.threeten/threeten-extra {:mvn/version "1.8.0"} ; extra Java 8 java.time classes like DayOfMonth and Quarter
@@ -241,18 +240,20 @@
{:extra-deps
{clj-http-fake/clj-http-fake {:mvn/version "1.0.4"
:exclusions [slingshot/slingshot]}
clj-kondo/clj-kondo {:mvn/version "2025.02.20"} ; included here mainly to facilitate working on the stuff in `.clj-kondo/src`
clj-kondo/clj-kondo {:mvn/version "2025.04.07"} ; included here mainly to facilitate working on the stuff in `.clj-kondo/src`
cloverage/cloverage {:mvn/version "1.2.4"}
com.clojure-goes-fast/clj-async-profiler
{:mvn/version "1.6.1"} ; Enables local profiling and heat map generation
com.clojure-goes-fast/clj-memory-meter
{:mvn/version "0.4.0"} ; Enables easy memory measurement
com.gfredericks/test.chuck {:mvn/version "0.2.15"} ; generating strings from regexes (useful with malli)
com.nextjournal/beholder {:mvn/version "1.0.2"} ; watcher
criterium/criterium {:mvn/version "0.4.6"} ; benchmarking library
dev.weavejester/hashp {:mvn/version "0.4.0"} ; debugging/spying utility
djblue/portal {:mvn/version "0.59.0"} ; ui for inspecting values
io.github.camsaul/humane-are {:mvn/version "1.0.2"}
io.github.metabase/hawk {:mvn/version "1.0.7"}
io.github.camsaul/humane-are {:mvn/version "1.0.2"} ; cleaner test output
io.github.metabase/hawk {:mvn/version "1.0.7"} ; test runner
io.github.tonsky/clj-reload {:mvn/version "0.9.5"} ; reloader
jonase/eastwood {:mvn/version "1.4.3" ; inspects namespaces and reports possible problems using tools.analyzer
:exclusions
[org.ow2.asm/asm-all]}
@@ -307,6 +308,9 @@
;; 1. Runs (dev) (start!) for you
;; 2. This starts the repl too: just like the :nrepl alias below. (see [[user/-main]])
;; clj -M:dev:dev-start:drivers:drivers-dev:ee:ee-dev
;;
;; To start it with hot code reloading, use:
;; clj -M:dev:dev-start:drivers:drivers-dev:ee:ee-dev --watch
:dev-start {:main-opts ["-m" "user"]
:jvm-opts ["-Dmb.run.mode=dev"
"-Dmacaw.run.mode=dev"
@@ -316,6 +320,7 @@
:test
{:extra-paths ["test_config"]
:exec-fn metabase.test-runner/find-and-run-tests-cli
:exec-args {:exclude-tags [:metabot-v3/e2e]}
:jvm-opts ["-Dmb.run.mode=test"
"-Dmacaw.run.mode=test"
"-Dmb.db.in.memory=true"
@@ -397,15 +402,15 @@
{:extra-paths ["test"]
:extra-deps
{binaryage/devtools {:mvn/version "1.0.7"}
cider/cider-nrepl {:mvn/version "0.55.4"}
cider/cider-nrepl {:mvn/version "0.55.7"}
cider/piggieback {:mvn/version "0.6.0"}
cljs-bean/cljs-bean {:mvn/version "1.9.0"}
com.lambdaisland/glogi {:mvn/version "1.3.169"}
io.github.metabase/hawk {:mvn/version "1.0.7"}
org.clojars.mmb90/cljs-cache {:mvn/version "0.1.4"}
refactor-nrepl/refactor-nrepl {:mvn/version "3.10.0"}
refactor-nrepl/refactor-nrepl {:mvn/version "3.11.0"}
;; keep it in sync with package.json
thheller/shadow-cljs {:mvn/version "2.28.21"}}}
thheller/shadow-cljs {:mvn/version "3.0.4"}}}
;; for local dev -- include the drivers locally with :dev:drivers
:drivers
@@ -453,7 +458,7 @@
;; Use this to only run Kondo against specific files.
:kondo
{:replace-deps
{clj-kondo/clj-kondo {:mvn/version "2025.02.20"}}
{clj-kondo/clj-kondo {:mvn/version "2025.04.07"}}
:main-opts
["-m" "clj-kondo.main"]}
@@ -868,13 +873,6 @@
:test/kondo
{:exec-args {:only [".clj-kondo/test"]}}
;; watch and reload clojure namespaces
:watch {:extra-deps
{com.nextjournal/beholder {:mvn/version "1.0.2"} ;; watcher
io.github.tonsky/clj-reload {:mvn/version "0.9.4"}} ;; reloader
:extra-paths "dev/src/watch"
:main-opts ["-m" "watch.watcher"]}
;; DB configs -- start DBs with:
;; ./bin/mage start-db postgres oldest

View File

@@ -51,11 +51,19 @@
(in-ns 'dev)
:loaded)
(defn watch? []
(contains? (set *command-line-args*) "--watch"))
(defn -main
"This is called by the `:dev-start` cli alias.
Try it out: `clj -M:dev:dev-start:drivers:drivers-dev:ee:ee-dev`"
Try it out: `clj -M:dev:dev-start:drivers:drivers-dev:ee:ee-dev`
Command line options:
- `--watch` - start the hot-reload clojure code watcher"
[& _args]
(future (nrepl.cmdline/-main "-p" "50605" "-b" "0.0.0.0"))
(when (watch?)
(future (requiring-resolve 'watch.watcher/watcher)))
((requiring-resolve 'dev/start!))
(deref (promise)))

View File

@@ -1,51 +1,29 @@
(ns watch.watcher
(:require [clj-reload.core :as reload]
[clojure.edn :as edn]
[clojure.string :as str]
[dev :as dev]
[metabase.util.log :as log]
[nextjournal.beholder :as beholder]
[user :as user])
(ns ^:clj-reload/no-unload
^:clj-reload/no-reload
watch.watcher
(:require
[clj-reload.core :as reload]
[dev :as dev]
[metabase.util.log :as log]
[nextjournal.beholder :as beholder]
[user :as user])
(:gen-class))
(set! *warn-on-reflection* true)
(def all-paths-to-watch
"Returns all paths that contain code. These are the paths we should watch."
(memoize
(fn all-paths-to-watch* []
(->> (str (System/getProperty "user.dir") "/deps.edn")
slurp
edn/read-string
:aliases
vals
(keep :extra-paths)
(mapv #(if (coll? %) % [%]))
(apply concat)
(map (comp first #(str/split % #"/")))
(concat ["dev" "src" "test"])
distinct
vec))))
(defn watch-fn
"Reloads the system when a file changes."
[{:keys [type path]}]
(log/warnf "type: %s | path: %s" type path)
(let [before (System/currentTimeMillis)
_ (log/warn
(-> (reload/reload {:throw false
:no-reload '#{watch.watcher user dev}})
(update :unloaded count)
(update :loaded count)))
after (System/currentTimeMillis)
_ (log/warn (str "Reloaded in " (- after before) " ms"))]))
[change]
(log/warn "Change detected. Reloading system. " (pr-str change) " ...")
(reload/reload {:throw :false}))
(def ^:private paths
["src" "test" "enterprise/backend" "modules"])
#_:clj-kondo/ignore
(defonce watcher
(do
(log/warn "Watching paths:" (all-paths-to-watch))
(reload/init {:dirs (all-paths-to-watch)})
(apply (partial beholder/watch watch-fn) (all-paths-to-watch))))
(log/warn "Watching paths:" paths)
(apply beholder/watch watch-fn paths)))
(defn -main "Watcher main function"
[& _args]
@@ -54,6 +32,6 @@
(comment
(beholder/stop watcher)
(reload/reload)
)
(beholder/stop watcher))

View File

@@ -12,7 +12,6 @@ clojure -M:doc:ee config-template
The template lists example `database`, `user`, and `settings` sections for the [config file](./config-file.md).
```yaml
# A config file template for Metabase.
# You'll need to update (or remove) the `users` and `databases` sections.
@@ -27,45 +26,45 @@ The template lists example `database`, `user`, and `settings` sections for the [
version: 1
config:
users:
- first_name: First
last_name: Person
password: metabot1
email: first@example.com
- first_name: Normal
last_name: Person
password: metabot1
email: normal@example.com
- first_name: Admin
last_name: Person
password: metabot1
is_superuser: true
email: admin@example.com
- first_name: First
last_name: Person
password: metabot1
email: first@example.com
- first_name: Normal
last_name: Person
password: metabot1
email: normal@example.com
- first_name: Admin
last_name: Person
password: metabot1
is_superuser: true
email: admin@example.com
databases:
- name: Sample PostgreSQL
engine: postgres
details:
host: postgres-data
port: 5432
user: metabase
password: metasample123
dbname: sample
- name: Sample MySQL
engine: mysql
details:
host: mysql-data
port: 3306
user: metabase
password: metasample123
dbname: sample
- name: Sample PostgreSQL
engine: postgres
details:
host: postgres-data
port: 5432
user: metabase
password: metasample123
dbname: sample
- name: Sample MySQL
engine: mysql
details:
host: mysql-data
port: 3306
user: metabase
password: metasample123
dbname: sample
api-keys:
- name: Admin API key
group: admin
creator: first@example.com
key: mb_firsttestapikey123
- name: All Users API key
group: all-users
creator: first@example.com
key: mb_secondtestapikey456
- name: Admin API key
group: admin
creator: first@example.com
key: mb_firsttestapikey123
- name: All Users API key
group: all-users
creator: first@example.com
key: mb_secondtestapikey456
settings:
admin-email: null
aggregated-query-row-limit: null
@@ -136,7 +135,6 @@ config:
enable-embedding-interactive: false
enable-embedding-sdk: false
enable-embedding-static: false
enable-field-usage-analysis: false
enable-password-login: true
enable-pivoted-exports: true
enable-public-sharing: true
@@ -149,7 +147,7 @@ config:
gsheets: null
health-check-logging-enabled: true
help-link: metabase
help-link-custom-destination: https://www.metabase.com/help/premium
help-link-custom-destination: https://www.metabase.com/help-premium
humanization-strategy: simple
jdbc-data-warehouse-max-connection-pool-size: 15
jwt-attribute-email: email
@@ -162,7 +160,7 @@ config:
jwt-identity-provider-uri: null
jwt-shared-secret: null
jwt-user-provisioning-enabled: true
landing-page: ''
landing-page: ""
landing-page-illustration: default
landing-page-illustration-custom: null
ldap-attribute-email: mail

View File

@@ -45,7 +45,7 @@ docker run -d -p 3000:3000 -e MB_SITE_NAME="Awesome Company" --name metabase met
## Environment variables on Metabase Cloud
If you're running Metabase Cloud, you can [contact support](https://www.metabase.com/help/premium) to adjust environment variables for your Metabase.
If you're running Metabase Cloud, you can [contact support](https://www.metabase.com/help-premium) to adjust environment variables for your Metabase.
---
@@ -573,17 +573,6 @@ Allow admins to embed Metabase via the SDK?
Allow admins to embed Metabase via static embedding?
### `MB_ENABLE_FIELD_USAGE_ANALYSIS`
- Type: boolean
- Default: `false`
- [Configuration file name](./config-file.md): `enable-field-usage-analysis`
Enable field usage analysis for queries. This will analyze the fields used in queries and store them in the
application database.
Turn off by default since we haven't had an user-facing feature that uses this data yet.
### `MB_ENABLE_PASSWORD_LOGIN`
> Only available on Metabase [Pro](https://www.metabase.com/product/pro) and [Enterprise](https://www.metabase.com/product/enterprise) plans.
@@ -690,7 +679,7 @@ Keyword setting to control whitelabeling of the help link. Valid values are `:me
> Only available on Metabase [Pro](https://www.metabase.com/product/pro) and [Enterprise](https://www.metabase.com/product/enterprise) plans.
- Type: string
- Default: `https://www.metabase.com/help/premium`
- Default: `https://www.metabase.com/help-premium`
- [Configuration file name](./config-file.md): `help-link-custom-destination`
Custom URL for the help link.
@@ -2241,4 +2230,3 @@ Type: string<br>
Default: `null`
Base-64 encoded public key for this sites SSL certificate. Specify this to enable HTTP Public Key Pinning. Using HPKP is no longer recommended. See http://mzl.la/1EnfqBf for more information.

View File

@@ -26,10 +26,9 @@ Supported languages include:
| Arabic (Saudi Arabia) | `ar-SA` |
| Bulgarian | `bg` |
| Catalan | `ca` |
| Chinese (Traditional) | `zh` |
| Chinese (Taiwanese) | `zh-TW` |
| Chinese (Hong Kong) | `zh-HK` |
| Chinese (Simplified) | `zh-CN` |
| Chinese (Taiwanese) | `zh-TW` |
| Czech | `cs` |
| Dutch | `nl` |
| Farsi/Persian | `fa` |
@@ -39,11 +38,11 @@ Supported languages include:
| Hebrew | `he` |
| Hungarian | `hu` |
| Indonesian | `id` |
| Malay | `ms` |
| Italian | `it` |
| Japanese | `ja` |
| Korean | `ko` |
| Latvian | `lv` |
| Malay | `ms` |
| Norwegian Bokmål | `nb` |
| Polish | `pl` |
| Portuguese (Brazilian) | `pt-BR` |

View File

@@ -80,7 +80,7 @@ If you check this box, Metabase will drop any visualization settings applied to
Here you can specify which questions Metabase should attach results for.
The attached files will include up to 2000 rows by default. If you're self-hosting Metabase, you can adjust this row limit by setting the environment variable [MB_UNAGGREGATED_QUERY_ROW_LIMIT](../configuring-metabase/environment-variables.md#mb_unaggregated_query_row_limit). To change this row limit on your Metabase Cloud instance, you can [contact us](https://www.metabase.com/help/premium) and request a different row limit.
The attached files will include up to 2000 rows by default. If you're self-hosting Metabase, you can adjust this row limit by setting the environment variable [MB_UNAGGREGATED_QUERY_ROW_LIMIT](../configuring-metabase/environment-variables.md#mb_unaggregated_query_row_limit). To change this row limit on your Metabase Cloud instance, you can [contact us](https://www.metabase.com/help-premium) and request a different row limit.
## Slack subscription options

View File

@@ -94,6 +94,8 @@ Note that only the `*` wildcard is supported; you can't use other special charac
## Role (optional)
> **WARNING:** Metabase gets _all the permissions_ combined from _every_ role you grant the user in Snowflake. Setting the 'Role' here just adds the role to the connection string to tell Snowflake which role to default to – it **won't restrict** access on its own. So **be careful which roles you assign to the connection user**: if down the line you grant that user broader roles in Snowflake, Metabase will automatically gets those new permissions, potentially showing data you didn't mean for it to see. If instead you want Metabase to issue `SET ROLE` commands before querying anything, check out [connection impersonation](../../permissions/impersonation.md).
Specify a role to override the database user's default role. For example, if the database user `METABASE` has the roles:
- Default role `APPLICATION`.

View File

@@ -4,6 +4,12 @@ title: API changelog
# Breaking changes to the API interface
## Metabase 0.55.0
- `POST /api/card/from-csv` has been renamed to `POST /api/upload/csv`.
- `GET /api/util/stats` has been renamed to `GET /api/analytics/anonymous-stats`.
## Metabase 0.54.0
- The alert system has been migrated from the legacy pulse infrastructure to the new notification system. This migration includes the following changes:

View File

@@ -6,6 +6,12 @@ title: Driver interface changelog
## Metabase 0.55.0
- Add the multi-method `->date` that allows the driver to control how to cast strings and temporal types to dates.
- Add the multi-method `date-dbtype` that allows the driver to control which types dates are cast to.
- Extend `date()` to accept a DateTime (or DB equivalent) in addition to an ISO string. When given a DateTime, it will truncate it to a date.
- Added a feature `:expression-literals` for drivers that support expressions consisting of a single string, number, or boolean literal value.
- Added a feature `:multi-level-schema` for drivers that support hierarchical levels between database and schema. Such as databricks' catalog. Defaults to false.
@@ -20,6 +26,11 @@ title: Driver interface changelog
- Added the multi-method `integer-dbtype` that allows the driver to control which type integers are cast to.
- The `metabase.upload` namespace has been replaced with `metabase.upload.core`, but upload type keywords e.g.
`:metabase.upload/varchar-255` remain unchanged. Make sure you weren't using `::` keywords inside methods like
`metabase.driver/upload-type->database-type` or `metabase.driver/allowed-promotions` -- make sure you use
`:metabase.upload/varchar-255` rather than something like `::upload/varchar-255`.
## Metabase 0.54.0
- Added the multi-method `allowed-promotions` that allows driver control over which column type promotions are supported for uploads.

View File

@@ -57,7 +57,7 @@ Assuming you've been using a [production application database](../installation-a
Migrating to the Enterprise Edition will keep all of your questions, dashboards, people, settings — everything in your existing Metabase.
And don't stress. You won't lose any of your work, and if you get stuck, we're [here to help](https://www.metabase.com/help/premium).
And don't stress. You won't lose any of your work, and if you get stuck, we're [here to help](https://www.metabase.com/help-premium).
## Validating your token
@@ -99,12 +99,12 @@ If you're hosting Metabase behind a firewall that blocks outgoing connections, *
44.212.138.188
```
To verify your license with a token check to `token-check.metabase.com`, your Metabase will make GET HTTP requests to these IP addresses and parse their responses. If you can't allow outbound connections for security reasons, please [contact us](https://www.metabase.com/help/premium).
To verify your license with a token check to `token-check.metabase.com`, your Metabase will make GET HTTP requests to these IP addresses and parse their responses. If you can't allow outbound connections for security reasons, please [contact us](https://www.metabase.com/help-premium).
## Note about Zscaler deployments
When Metabase is deployed inside infrastructure that uses Zscaler, you should do the following:
1. Contact your networking team and let them know that Metabase will need to perform token checks in order for paid features to work. If you need an air-gapped version of Metabase, [contact us](https://www.metabase.com/help/premium).
1. Contact your networking team and let them know that Metabase will need to perform token checks in order for paid features to work. If you need an air-gapped version of Metabase, [contact us](https://www.metabase.com/help-premium).
2. Make sure Zscaler isn't acting as a proxy or DNS for the server where Metabase is running. Metabase needs a direct connection to the token check service without any gateway acting as a proxy.
3. Make sure the server where Metabase is running isn't using Zscaler root CA certificates for all websites. Otherwise, the Java virtual machine where Metabase runs will determine that the certificate authority is incorrect.

View File

@@ -11,7 +11,7 @@ Metabase supports user provisioning via the System for Cross-domain Identity Man
- **Decouple authentication from provisioning**. Even though anyone could authenticate with SSO, you may only want some people to be able to create an account in Metabase.
- **Support deprovisioning user accounts**. If you deactivate someone from your SSO, SCIM can let Metabase know to deactivate their Metabase account as well.
> For now, Metabase officially supports SCIM for [Okta](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_scim.htm?cshid=ext_Apps_App_Integration_Wizard-scim) and [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/configure-automatic-user-provisioning-portal). Other SCIM providers may work, but we haven't tested them. If you're having issues with another identity provider, please [reach out to us](https://www.metabase.com/help/premium).
> For now, Metabase officially supports SCIM for [Okta](https://help.okta.com/en-us/content/topics/apps/apps_app_integration_wizard_scim.htm?cshid=ext_Apps_App_Integration_Wizard-scim) and [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/configure-automatic-user-provisioning-portal). Other SCIM providers may work, but we haven't tested them. If you're having issues with another identity provider, please [reach out to us](https://www.metabase.com/help-premium).
## Setting up user provisioning

View File

@@ -94,6 +94,11 @@ export const SECOND_COLLECTION_ID = _.findWhere(
{ name: "Second collection" },
).id;
export const SECOND_COLLECTION_ENTITY_ID = _.findWhere(
SAMPLE_INSTANCE_DATA.collections,
{ name: "Second collection" },
).entity_id;
export const THIRD_COLLECTION_ID = _.findWhere(
SAMPLE_INSTANCE_DATA.collections,
{ name: "Third collection" },

View File

@@ -0,0 +1,12 @@
export function createSnippet({
name,
content,
}: {
name: string;
content: string;
}): Cypress.Chainable<Cypress.Response<NativeQuerySnippet>> {
cy.request("POST", "/api/native-query-snippet", {
name,
content,
});
}

View File

@@ -22,6 +22,7 @@ export type {
} from "./createQuestion";
export { createQuestionAndAddToDashboard } from "./createQuestionAndAddToDashboard";
export { createQuestionAndDashboard } from "./createQuestionAndDashboard";
export { createSnippet } from "./createSnippet";
export { createTimeline } from "./createTimeline";
export { createTimelineEvent } from "./createTimelineEvent";
export { createTimelineWithEvents } from "./createTimelineWithEvents";

View File

@@ -1,5 +1,10 @@
import { capitalize } from "inflection";
import {
commandPalette,
commandPaletteButton,
commandPaletteInput,
} from "./e2e-command-palette-helpers";
import { NativeEditor } from "./e2e-native-editor-helpers";
export function setActionsEnabledForDB(dbId, enabled = true) {
@@ -48,3 +53,13 @@ export function createImplicitActions({ modelId }) {
createImplicitAction({ model_id: modelId, kind: "update" });
createImplicitAction({ model_id: modelId, kind: "delete" });
}
export function startNewAction() {
commandPaletteButton().click();
commandPalette().within(() => {
commandPaletteInput().type("Ac");
cy.findByLabelText("New action").click();
});
commandPalette().should("not.exist");
cy.findByTestId("action-creator").should("be.visible");
}

View File

@@ -3,10 +3,18 @@ import {
entityPickerModalLevel,
entityPickerModalTab,
getFullName,
navigationSidebar,
popover,
} from "e2e/support/helpers";
import type { CollectionId } from "metabase-types/api";
export function startNewCollectionFromSidebar() {
return navigationSidebar()
.findByLabelText("Create a new collection")
.should("be.visible")
.click();
}
export function getCollectionActions() {
return cy.findByTestId("collection-menu");
}

View File

@@ -3,7 +3,7 @@ export const shortcutModal = () =>
cy.findByRole("dialog", { name: "Shortcuts" });
export const openCommandPalette = () => cy.get("body").type("{ctrl+k}{cmd+k}");
export const openShortcutModal = () => cy.get("body").type("?");
export const openShortcutModal = () => cy.get("body").type("{shift+?}");
export const commandPaletteButton = () =>
cy.findByTestId("app-bar").findByRole("button", { name: /Search/ });

View File

@@ -0,0 +1,78 @@
import type { StaticResponse } from "cypress/types/net-stubbing";
import { newButton } from "./e2e-ui-elements-helpers";
export function assertChatVisibility(visibility: "visible" | "not.visible") {
cy.findByTestId("metabot-chat").should(
visibility === "visible" ? "be.visible" : "not.exist",
);
}
export function openMetabotViaShortcutKey(assertVisibility = true) {
if (assertVisibility) {
assertChatVisibility("not.visible");
}
cy.realPress(["Meta", "b"]);
if (assertVisibility) {
assertChatVisibility("visible");
}
}
export function closeMetabotViaShortcutKey(assertVisibility = true) {
if (assertVisibility) {
assertChatVisibility("visible");
}
cy.realPress(["Meta", "b"]);
if (assertVisibility) {
assertChatVisibility("not.visible");
}
}
export function openMetabotViaNewMenu(assertVisibility = true) {
if (assertVisibility) {
assertChatVisibility("not.visible");
}
newButton("Metabot request").click();
if (assertVisibility) {
assertChatVisibility("visible");
}
}
export function closeMetabotViaCloseButton(assertVisibility = true) {
cy.findByTestId("metabot-close-chat").click();
if (assertVisibility) {
assertChatVisibility("not.visible");
}
}
export function metabotChatInput() {
return cy.findByTestId("metabot-chat-input");
}
export function sendMetabotMessage(input: string) {
metabotChatInput()
.should("not.be.disabled")
.click()
.should("be.focused")
.type(input)
.type("{Enter}");
}
export function userMessages() {
return cy.findAllByTestId("metabot-chat-message");
}
export const mockMetabotResponse = (response: StaticResponse) => {
return cy
.intercept("POST", "/api/ee/metabot-v3/agent", (req) => {
req.reply(response);
})
.as("metabotAgent");
};

View File

@@ -36,7 +36,8 @@ export const expectGoodSnowplowEvent = (eventData, count = 1) => {
retrySnowplowRequest(
"micro/good",
({ body }) => {
lastReceivedEvent = body?.[0].event?.unstruct_event?.data?.data;
lastReceivedEvent =
body?.[0]?.event?.unstruct_event?.data?.data ?? body?.[0];
lastFoundEventCount = body.filter((snowplowEvent) =>
isDeepMatch(
snowplowEvent?.event?.unstruct_event?.data?.data,
@@ -101,10 +102,45 @@ function isArrayDeepMatch(array, partialArray) {
return true;
}
const getEventNames = (body) => {
return body?.map((event) => {
// get the event name if it exists
const eventName = event?.event?.unstruct_event?.data?.data?.event;
if (eventName) {
return eventName;
}
// get the eventType if it exists, print it as object so we can tell
if (event.eventType) {
return {
eventType: event.eventType,
};
}
// fallback to logging it
console.log("[not able to parse event name]", event);
return "[not able to parse event name]";
});
};
export const expectGoodSnowplowEvents = (count) => {
retrySnowplowRequest("micro/good", ({ body }) => body.length >= count)
.its("body")
.should("have.length", count);
return retrySnowplowRequest(
"micro/good",
({ body }) => body.length >= count,
({ body }) => {
// this gets printed when the timeout is reached, meaning that we never reach the count we expect
const eventNamesReceived = getEventNames(body);
return `Timeout while waiting for ${count} good Snowplow events, reached ${eventNamesReceived.length} events. ${eventNamesReceived ? `Events names received: ${JSON.stringify(eventNamesReceived, null, 2)}` : ""}`;
},
).then(({ body }) => {
const eventNamesReceived = getEventNames(body);
if (body.length !== count) {
// this gets called when the number of events is greater than what we expected
throw new Error(
`Expected ${count} good Snowplow events, but got ${eventNamesReceived.length}. ${eventNamesReceived ? `Events names received: ${JSON.stringify(eventNamesReceived, null, 2)}` : ""}`,
);
}
return cy.wrap(body);
});
};
export const expectNoBadSnowplowEvents = () => {
@@ -138,7 +174,7 @@ const retrySnowplowRequest = (
} else {
const message =
typeof messageOrMessageFn === "function"
? messageOrMessageFn()
? messageOrMessageFn(response)
: messageOrMessageFn;
throw new Error("Snowplow retry timeout " + message);
}

View File

@@ -56,7 +56,7 @@ export function uploadFile(
testFile,
uploadMode = "upload",
) {
cy.intercept("POST", "/api/card/from-csv").as("uploadCSV");
cy.intercept("POST", "/api/upload/csv").as("uploadCSV");
cy.intercept("POST", "/api/table/*/append-csv").as("appendCSV");
cy.intercept("POST", "/api/table/*/replace-csv").as("replaceCSV");
@@ -104,7 +104,7 @@ export function headlessUpload(collectionId, file) {
formData.append("collection_id", collectionId);
cy.request({
url: "/api/card/from-csv",
url: "/api/upload/csv",
method: "POST",
headers: {
"content-type": "multipart/form-data",

View File

@@ -21,6 +21,7 @@ export * from "./e2e-filter-helpers";
export * from "./e2e-ldap-helpers";
export * from "./e2e-misc-helpers";
export * from "./e2e-mock-app-settings-helpers";
export * from "./e2e-metabot-helpers";
export * from "./e2e-models-metadata-helpers";
export * from "./e2e-native-editor-helpers";
export * from "./e2e-notebook-helpers";

View File

@@ -1,5 +1,10 @@
import { CollectionBrowser } from "@metabase/embedding-sdk-react";
import { useState } from "react";
import {
FIRST_COLLECTION_ENTITY_ID,
SECOND_COLLECTION_ENTITY_ID,
} from "e2e/support/cypress_sample_instance_data";
import { signInAsAdminAndEnableEmbeddingSdk } from "e2e/support/helpers/component-testing-sdk";
import {
mockAuthProviderAndJwtSignIn,
@@ -67,4 +72,49 @@ describe("scenarios > embedding-sdk > collection browser", () => {
getSdkRoot().findByText("Our analytics").should("exist");
});
});
describe("collection using entity ids", () => {
beforeEach(() => {
signInAsAdminAndEnableEmbeddingSdk();
cy.signOut();
mockAuthProviderAndJwtSignIn();
});
it("can change collection to a different entity id without crashing (metabase#57438)", () => {
const TestComponent = () => {
const [collectionId, setCollectionId] = useState<string | null>(
FIRST_COLLECTION_ENTITY_ID!,
);
return (
<div>
<div>id = {collectionId}</div>
<CollectionBrowser collectionId={collectionId} />
<div onClick={() => setCollectionId(SECOND_COLLECTION_ENTITY_ID!)}>
use second collection
</div>
</div>
);
};
cy.intercept("GET", "/api/collection/*").as("getCollection");
mountSdkContent(<TestComponent />);
getSdkRoot().within(() => {
cy.findByText(`id = ${FIRST_COLLECTION_ENTITY_ID}`).should("exist");
cy.findByText("Our analytics").should("not.exist");
cy.findByText("Second collection").should("not.exist");
cy.findByText("First collection").should("exist");
cy.findByText("use second collection").click();
cy.log("ensure that the collection id is updated and does not crash");
cy.findByText(`id = ${SECOND_COLLECTION_ENTITY_ID}`).should("exist");
cy.findByText("First collection").should("not.exist");
cy.findByText("Second collection").should("exist");
});
});
});
});

View File

@@ -102,6 +102,22 @@ describe("scenarios > embedding-sdk > interactive-dashboard", () => {
});
});
it("should show a watermark on dashcards in development mode", () => {
cy.intercept("/api/session/properties", (req) => {
req.continue((res) => {
res.body["token-features"]["development-mode"] = true;
});
});
cy.get("@dashboardId").then((dashboardId) => {
mountSdkContent(<InteractiveDashboard dashboardId={dashboardId} />);
});
getSdkRoot().within(() => {
cy.findAllByTestId("development-watermark").should("have.length", 1);
});
});
describe("loading behavior for both entity IDs and number IDs (metabase#49581)", () => {
const successTestCases = [
{

View File

@@ -3,11 +3,13 @@ import {
InteractiveQuestion,
type MetabaseQuestion,
} from "@metabase/embedding-sdk-react";
import type { ComponentProps } from "react";
import { type ComponentProps, useState } from "react";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import {
FIRST_COLLECTION_ENTITY_ID,
FIRST_COLLECTION_ID,
SECOND_COLLECTION_ENTITY_ID,
THIRD_COLLECTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import {
@@ -65,6 +67,20 @@ describe("scenarios > embedding-sdk > interactive-question", () => {
});
});
it("should show a watermark in development mode", () => {
cy.intercept("/api/session/properties", (req) => {
req.continue((res) => {
res.body["token-features"]["development-mode"] = true;
});
});
mountInteractiveQuestion();
getSdkRoot().within(() => {
cy.findByTestId("development-watermark").should("exist");
});
});
it("should not fail on aggregated question drill", () => {
mountInteractiveQuestion();
@@ -410,4 +426,43 @@ describe("scenarios > embedding-sdk > interactive-question", () => {
H.visualize();
H.cartesianChartCircle().should("have.length", LIMIT);
});
it("can change target collection to a different entity id without crashing (metabase#57438)", () => {
const TestComponent = () => {
const [targetCollection, setTargetCollection] = useState<string | null>(
FIRST_COLLECTION_ENTITY_ID!,
);
return (
<div>
<div>id = {targetCollection}</div>
<InteractiveQuestion
questionId="new"
targetCollection={targetCollection}
onSave={() => {}}
isSaveEnabled
/>
<div
onClick={() => setTargetCollection(SECOND_COLLECTION_ENTITY_ID!)}
>
use second collection
</div>
</div>
);
};
mountSdkContent(<TestComponent />);
getSdkRoot().within(() => {
cy.findByText(`id = ${FIRST_COLLECTION_ENTITY_ID}`).should("exist");
cy.findByText("use second collection").click();
cy.findByText(`id = ${SECOND_COLLECTION_ENTITY_ID}`).should("exist");
cy.log("ensure that the interactive question still works");
H.popover().findByRole("link", { name: "Orders" }).click();
cy.findByRole("button", { name: "Visualize" }).should("be.visible");
});
});
});

View File

@@ -331,8 +331,7 @@ describe("issue 51020", () => {
createTemporaryTable();
H.resyncDatabase({ dbId: WRITABLE_DB_ID, tableName: "foo" });
cy.visit("/");
H.newButton("Model").click();
cy.visit("/model/new");
cy.findByTestId("new-model-options")
.findByText("Use the notebook editor")
.click();
@@ -455,22 +454,6 @@ describe("issue 32840", () => {
});
});
describe("issue 41831", () => {
beforeEach(() => {
H.restore("without-models");
cy.signInAsAdmin();
H.setActionsEnabledForDB(SAMPLE_DB_ID);
cy.visit("/");
});
it("new action button is hidden without models", () => {
cy.findByRole("button", { name: "New" }).click();
cy.findByRole("dialog").within(() => {
cy.findByText("Action").should("not.exist");
});
});
});
describe("issue 32750", () => {
beforeEach(() => {
H.restore();
@@ -480,12 +463,7 @@ describe("issue 32750", () => {
});
it("modal do not dissapear on viewport change", () => {
cy.findByRole("button", { name: "New" }).click();
cy.findByRole("dialog").within(() => {
cy.findByText("Action").click();
});
cy.findByTestId("action-creator").should("be.visible");
H.startNewAction();
cy.viewport(320, 800);
cy.findByTestId("action-creator").should("be.visible");
cy.viewport(1440, 800);

View File

@@ -155,36 +155,6 @@ describe(
});
});
it("should allow to create an action with the New button", () => {
const QUERY = "UPDATE orders SET discount = {{ discount }}";
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
H.popover().findByText("Action").click();
cy.wait("@getDatabase");
H.fillActionQuery(QUERY);
cy.findByRole("dialog").within(() => {
cy.findByText(/New Action/)
.clear()
.type("Discount order");
cy.findByRole("button", { name: "Save" }).click();
});
H.modal().eq(1).findByText("Select a model").click();
H.entityPickerModal().within(() => {
cy.findByText("Order").click();
});
cy.findByRole("button", { name: "Create" }).click();
cy.get("@modelId").then((modelId) => {
cy.url().should("include", `/model/${modelId}/detail/actions`);
});
});
it("should respect permissions", () => {
// Enabling actions for sample database as well
// to test database picker behavior in the action editor
@@ -266,8 +236,7 @@ describe(
it("should display parameters for variable template tags only", () => {
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
H.popover().findByText("Action").click();
H.startNewAction();
H.fillActionQuery("{{#1-orders-model}}");
cy.findByLabelText("#1-orders-model").should("not.exist");

View File

@@ -436,11 +436,6 @@ describe("scenarios > admin > people", () => {
const NEW_USERS = 18;
const NEW_TOTAL_USERS = TOTAL_USERS + NEW_USERS;
const waitForUserRequests = () => {
cy.wait("@users");
cy.wait("@memberships");
};
beforeEach(() => {
generateUsers(NEW_USERS);
@@ -453,8 +448,6 @@ describe("scenarios > admin > people", () => {
cy.visit("/admin/people");
waitForUserRequests();
// Total
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText(`${NEW_TOTAL_USERS} people found`);
@@ -465,11 +458,8 @@ describe("scenarios > admin > people", () => {
assertTableRowsCount(PAGE_SIZE);
cy.findByLabelText("Previous page").should("be.disabled");
// cy.findByLabelText("Next page").click();
cy.findByTestId("next-page-btn").click();
waitForUserRequests();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Loading...").should("not.exist");
cy.wait("@users");
// Page 2
cy.findByTestId("people-list-footer")
@@ -479,9 +469,6 @@ describe("scenarios > admin > people", () => {
cy.findByLabelText("Next page").should("be.disabled");
cy.findByLabelText("Previous page").click();
cy.wait("@users");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Loading...").should("not.exist");
// Page 1
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
@@ -504,7 +491,6 @@ describe("scenarios > admin > people", () => {
cy.findByLabelText("Previous page").should("be.disabled");
cy.findByLabelText("Next page").click();
waitForUserRequests();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Loading...").should("not.exist");
@@ -514,7 +500,6 @@ describe("scenarios > admin > people", () => {
cy.findByLabelText("Next page").should("be.disabled");
cy.findByLabelText("Previous page").click();
cy.wait("@users");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Loading...").should("not.exist");

View File

@@ -548,9 +548,12 @@ describe("scenarios > admin > settings > email settings", () => {
Cypress.config().baseUrl + "/admin/settings/email",
);
cy.findByTestId("smtp-connection-card").should("exist");
// Non SMTP-settings should save automatically
cy.findByLabelText("From Address").type("mailer@metabase.test").blur();
cy.findByLabelText("From Address")
.clear()
.type("mailer@metabase.test")
.blur();
cy.findByLabelText("From Name").type("Sender Name").blur();
cy.findByLabelText("Reply-To Address")
.type("reply-to@metabase.test")
@@ -1286,13 +1289,16 @@ describe("admin > settings > updates", () => {
cy.signInAsAdmin();
cy.visit("/admin/settings/updates");
cy.intercept("GET", "/api/session/properties", (req, res) => {
cy.intercept("GET", "/api/session/properties", (req) => {
req.continue((res) => {
res.body["version-info"] = versionInfo;
res.body.version.tag = currentVersion;
return res.body;
});
});
cy.intercept("GET", "/api/setting/version-info", (req) => {
req.reply(versionInfo);
});
});
it("should show the updates page", () => {

View File

@@ -32,8 +32,7 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
.should("have.text", "Appearance")
.and("not.have.descendants", ".Icon-gem");
cy.log("By default redirects to the branding tab");
cy.location("pathname").should("eq", "/admin/settings/whitelabel/branding");
cy.log("By default shows the branding tab");
cy.findByRole("tab", { name: "Branding" }).should(
"have.attr",
"aria-selected",
@@ -45,8 +44,7 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
cy.log("Should show the upsell if the feature is missing");
H.deleteToken();
cy.visit("/admin/settings/whitelabel");
cy.location("pathname").should("eq", "/admin/settings/whitelabel");
cy.visit("/admin/settings/appearance");
cy.findByRole("heading", { name: "Make Metabase look like you" }).should(
"be.visible",
);
@@ -70,34 +68,32 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
});
describe("company name", () => {
const COMPANY_NAME = "Test Co";
const NEW_COMPANY_NAME = "New Test Co";
beforeEach(() => {
cy.log("Change company name");
cy.visit("/admin/settings/whitelabel");
cy.findByLabelText("Application Name").clear().type(COMPANY_NAME);
// Helps scroll the page up in order to see "Saved" notification
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Application Name").click();
cy.visit("/admin/settings/whitelabel/conceal-metabase");
cy.findByLabelText("Application Name")
.clear()
.type(NEW_COMPANY_NAME)
.blur();
H.undoToast().findByText("Changes saved").should("be.visible");
cy.findByDisplayValue(COMPANY_NAME);
cy.log("Company name has been updated!");
cy.findByDisplayValue(NEW_COMPANY_NAME);
});
it.skip("should not show the old name in the admin panel (metabase#17043)", () => {
cy.reload();
cy.findByDisplayValue(COMPANY_NAME);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.contains(
`These are the primary colors used in charts and throughout ${COMPANY_NAME}.`,
);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.contains(`The top nav bar of ${COMPANY_NAME}.`);
it("should not show the old name in the admin panel (metabase#17043)", () => {
cy.visit("/admin/settings/general");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.contains(`The name used for this instance of ${COMPANY_NAME}.`);
cy.findByTestId("site-name-setting")
.findByText(`The name used for this instance of ${NEW_COMPANY_NAME}.`)
.should("be.visible");
});
it("should show the new name in the main app", () => {
cy.visit("/");
cy.icon("gear").click();
H.popover().findByText(`About ${NEW_COMPANY_NAME}`).click();
H.modal()
.findByText(`Thanks for using ${NEW_COMPANY_NAME}!`)
.should("be.visible");
});
});
@@ -326,24 +322,22 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
it("should allow display the selected illustration on the landing page", () => {
cy.visit("/admin/settings/whitelabel/conceal-metabase");
cy.findByRole("textbox", { name: "Landing page" }).should(
"have.value",
"Lighthouse",
);
cy.findByTestId("landing-page-illustration-setting")
.findByDisplayValue("Lighthouse")
.click();
cy.findByRole("textbox", { name: "Landing page" }).click();
H.selectDropdown().findByText("Custom").click();
cy.findByTestId("file-input").selectFile(
{
contents: "e2e/support/assets/logo.jpeg",
mimeType: "image/jpeg",
},
{ force: true },
);
cy.findByTestId("landing-page-illustration-setting")
.findByText("logo.jpeg")
.should("be.visible");
cy.findByTestId("landing-page-illustration-setting").within(() => {
cy.findByTestId("file-input").selectFile(
{
contents: "e2e/support/assets/logo.jpeg",
mimeType: "image/jpeg",
},
{ force: true },
);
cy.findByText("logo.jpeg").should("be.visible");
});
H.undoToast().findByText("Changes saved").should("be.visible");
cy.readFile("e2e/support/assets/logo.jpeg", "base64").then(
@@ -361,7 +355,9 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
cy.log("test no illustration");
cy.visit("/admin/settings/whitelabel/conceal-metabase");
cy.findByLabelText("Landing page").click();
cy.findByTestId("landing-page-illustration-setting")
.findByDisplayValue("Custom")
.click();
H.selectDropdown().findByText("No illustration").click();
cy.visit("/");
@@ -532,21 +528,21 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
describe("loading message", () => {
it("should update loading message", () => {
cy.visit("/question/" + ORDERS_QUESTION_ID);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Doing science...");
cy.intercept("PUT", "/api/setting/loading-message").as(
"putLoadingMessage",
);
const messages = [
"Loading results...",
"Doing science...",
"Running query...",
];
const runningQueryMessage = "Running query...";
changeLoadingMessage(runningQueryMessage);
cy.visit("/question/" + ORDERS_QUESTION_ID);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText(runningQueryMessage);
const loadingResultsMessage = "Loading results...";
changeLoadingMessage(loadingResultsMessage);
cy.visit("/question/" + ORDERS_QUESTION_ID);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText(loadingResultsMessage);
messages.forEach((message) => {
changeLoadingMessage(message);
// can't use visitQuestion helper because it waits for loading to be finished
cy.visit(`/question/${ORDERS_QUESTION_ID}`);
cy.findByTestId("query-builder-main").findByText(message);
});
});
});
@@ -573,14 +569,31 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
beforeEach(() => {
cy.log("Change Application Font");
cy.signInAsAdmin();
setApplicationFontTo(font);
});
it("should apply correct font", () => {
setApplicationFontTo(font);
cy.signInAsNormalUser();
cy.visit("/");
cy.get("body").should("have.css", "font-family", `"${font}", sans-serif`);
});
it("should be able to make multiple font changes (metabase#45486)", () => {
cy.intercept("PUT", "/api/setting/application-font").as("saveFont");
const fonts = ["Lora", "Merriweather", "Montserrat", "Lato"];
cy.visit("/admin/settings/whitelabel/branding");
fonts.forEach((newFont) => {
cy.findByLabelText("Font").click();
H.selectDropdown().findByText(newFont).click();
cy.wait("@saveFont");
cy.get("body").should(
"have.css",
"font-family",
`${newFont}, sans-serif`,
);
});
});
});
describe("Help link", () => {
@@ -619,6 +632,7 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
getHelpLinkCustomDestinationInput()
.should("have.focus")
.clear()
.type("https://example.org/custom-destination")
.blur();
@@ -701,7 +715,7 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
.findByText("Please make sure this is a valid URL")
.should("exist");
getHelpLinkCustomDestinationInput().type("example");
getHelpLinkCustomDestinationInput().type("example").blur();
H.main()
.findByText("Please make sure this is a valid URL")
@@ -761,8 +775,9 @@ describe("formatting > whitelabel", { tags: "@EE" }, () => {
function changeLoadingMessage(message) {
cy.visit("/admin/settings/whitelabel");
cy.findByLabelText("Loading message").click();
cy.findByText(message).click();
cy.findByLabelText("Loading Message").click();
H.selectDropdown().findByText(message).click();
cy.wait("@putLoadingMessage");
}
function setApplicationFontTo(font) {

View File

@@ -10,6 +10,7 @@ import {
FIRST_COLLECTION_ENTITY_ID,
FIRST_COLLECTION_ID,
ORDERS_QUESTION_ID,
READ_ONLY_PERSONAL_COLLECTION_ID,
SECOND_COLLECTION_ID,
THIRD_COLLECTION_ID,
} from "e2e/support/cypress_sample_instance_data";
@@ -29,6 +30,94 @@ describe("scenarios > collection defaults", () => {
cy.intercept("GET", "/api/collection/*/items?**").as("getCollectionItems");
});
H.describeWithSnowplow("new collection button", () => {
beforeEach(() => {
H.restore();
H.resetSnowplow();
cy.signInAsAdmin();
H.enableTracking();
});
afterEach(() => {
H.expectNoBadSnowplowEvents();
});
it("should show the new collection button in the root collection", () => {
cy.intercept("POST", "/api/collection").as("createCollection");
visitRootCollection();
cy.findByTestId("collection-menu")
.findByLabelText("Create a new collection")
.click();
cy.log("Track the collection initiation from the header");
H.expectGoodSnowplowEvent({
event: "plus_button_clicked",
triggered_from: "collection-header",
});
cy.findByTestId("new-collection-modal").within(() => {
cy.findByLabelText("Name").type("MCL");
cy.findByTestId("collection-picker-button").should(
"contain",
"Our analytics",
);
cy.button("Create").click();
cy.wait("@createCollection");
});
cy.location("pathname").should("match", /^\/collection\/\d+-mcl/);
cy.findByTestId("collection-empty-state").should("be.visible");
cy.log(
"Newly created collection should also have the new collection button",
);
cy.findByTestId("collection-menu")
.findByLabelText("Create a new collection")
.should("be.visible");
cy.log("Track the collection initiation from the main navbar");
H.navigationSidebar().findByLabelText("Create a new collection").click();
cy.findByTestId("new-collection-modal").should("be.visible");
H.expectGoodSnowplowEvent({
event: "plus_button_clicked",
triggered_from: "collection-nav",
});
});
it("user without curate permissions should only be allowed to create a new collection inside their personal collection scope", () => {
cy.intercept("POST", "/api/collection").as("createCollection");
cy.signIn("readonly");
visitRootCollection();
cy.findByTestId("collection-menu")
.findByLabelText("Create a new collection")
.should("not.exist");
H.visitCollection(READ_ONLY_PERSONAL_COLLECTION_ID);
cy.findByTestId("collection-menu")
.findByLabelText("Create a new collection")
.click();
cy.findByTestId("new-collection-modal").within(() => {
cy.findByLabelText("Name").type("sub");
cy.findByTestId("collection-picker-button").should(
"contain",
"Read Only Tableton's Personal Collection",
);
cy.button("Create").click();
cy.wait("@createCollection");
});
cy.location("pathname").should("match", /^\/collection\/\d+-sub/);
cy.findByTestId("collection-empty-state").should("be.visible");
cy.log(
"Newly created personal sub-collection should also have the new collection button",
);
cy.findByTestId("collection-menu")
.findByLabelText("Create a new collection")
.should("be.visible");
});
});
describe("new collection modal", () => {
it("should be usable on small screens", () => {
const COLLECTIONS_COUNT = 5;
@@ -43,11 +132,7 @@ describe("scenarios > collection defaults", () => {
cy.viewport(800, 500);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("New").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Collection").click();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
cy.findByPlaceholderText("My new fantastic collection").type(
"Test collection",
@@ -854,11 +939,7 @@ describe("scenarios > collection defaults", () => {
it("should create new collections within the current collection", () => {
H.visitCollection(THIRD_COLLECTION_ID);
cy.findByTestId("app-bar").findByText("New").click();
H.popover().within(() => {
cy.findByText("Collection").click();
});
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
cy.findByText("Collection it's saved in").should("be.visible");

View File

@@ -416,6 +416,31 @@ describe("collection permissions", () => {
);
cy.findByTestId("permission-table");
});
it("should show the new collection button in a sidebar even to users without collection access", () => {
cy.intercept("POST", "/api/collection").as("createCollection");
cy.signIn("nocollection");
cy.visit("/");
H.navigationSidebar()
.findByLabelText("Create a new collection")
.should("be.visible")
.click();
cy.findByTestId("new-collection-modal").within(() => {
cy.findByLabelText("Name").type("Foo");
cy.log(
"The only possible location to save the new collection is this user's personal collection",
);
cy.findByTestId("collection-picker-button").should(
"contain",
"No Collection Tableton's Personal Collection",
);
cy.button("Create").click();
cy.wait("@createCollection");
});
cy.location("pathname").should("match", /^\/collection\/\d+-foo/);
});
});
function clickButton(name) {

View File

@@ -56,10 +56,9 @@ describe("personal collections", () => {
cy.findAllByRole("tree")
.contains("Your personal collection")
.should("be.visible");
H.navigationSidebar().within(() => {
cy.icon("ellipsis").click();
});
H.popover().findByText("Other users' personal collections").click();
H.navigationSidebar()
.findByLabelText("Other users' personal collections")
.click();
cy.location("pathname").should("eq", "/collection/users");
cy.findByTestId("browsercrumbs").findByText(/All personal collections/i);
Object.values(USERS).forEach((user) => {
@@ -75,12 +74,13 @@ describe("personal collections", () => {
parent_id: ADMIN_PERSONAL_COLLECTION_ID,
});
// Go to admin's personal collection
cy.visit("/collection/root");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Your personal collection").click();
H.visitCollection(ADMIN_PERSONAL_COLLECTION_ID);
cy.log(
"Make sure it's not possible to edit personal collection's permissions",
);
H.getCollectionActions().within(() => {
cy.icon("info").should("exist");
cy.icon("ellipsis").should("not.exist");
});
@@ -96,11 +96,14 @@ describe("personal collections", () => {
// Go to the newly created sub-collection "Foo"
H.navigationSidebar().findByText("Foo").click();
// It should be possible to edit sub-collections' details, but not its permissions
cy.findByDisplayValue("Foo").should("be.enabled");
cy.log(
"Other menu options exist, but editing permissions is not possible",
);
H.openCollectionMenu();
H.popover().within(() => {
cy.findByText("Move to trash").should("be.visible");
cy.findByText("Edit permissions").should("not.exist");
});
@@ -112,9 +115,10 @@ describe("personal collections", () => {
// });
// Go to random user's personal collection
cy.visit(`/collection/${NO_DATA_PERSONAL_COLLECTION_ID}`);
H.visitCollection(NO_DATA_PERSONAL_COLLECTION_ID);
H.getCollectionActions().within(() => {
cy.icon("info").should("exist");
cy.icon("ellipsis").should("not.exist");
});
});
@@ -138,8 +142,7 @@ describe("personal collections", () => {
cy.signIn(user);
cy.visit("/collection/root");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Your personal collection").click();
H.navigationSidebar().findByText("Your personal collection").click();
// Create initial collection inside the personal collection and navigate to it
addNewCollection("Foo");
@@ -161,11 +164,9 @@ describe("personal collections", () => {
);
H.openCollectionMenu();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
H.popover().within(() => cy.findByText("Move to trash").click());
H.modal().findByRole("button", { name: "Move to trash" }).click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Trashed collection");
H.popover().findByText("Move to trash").click();
H.modal().button("Move to trash").click();
cy.findByTestId("toast-undo").should("contain", "Trashed collection");
cy.get("@sidebar").findByText("Foo").should("not.exist");
});
});
@@ -174,7 +175,7 @@ describe("personal collections", () => {
});
function addNewCollection(name) {
H.newButton("Collection").click();
H.startNewCollectionFromSidebar();
cy.findByPlaceholderText("My new fantastic collection").type(name, {
delay: 0,
});

View File

@@ -6,6 +6,10 @@ type CastTestCase = {
filterOperator: string;
filterValue: string;
expectedRowCount: number;
expectedTableData?: {
columns: string[];
firstRows: string[][];
};
};
const TEXT_TEST_CASES: CastTestCase[] = [
@@ -113,6 +117,28 @@ const DATE_TEST_CASES: CastTestCase[] = [
filterValue: "March 15, 2025",
expectedRowCount: 1,
},
{
name: "Datetime",
expression: "date([Created At])",
filterOperator: "Before",
filterValue: "April 27, 2016",
expectedRowCount: 1,
expectedTableData: {
columns: ["ID", "Datetime"],
firstRows: [["36", "April 26, 2016"]],
},
},
{
name: "DatetimeExpression",
expression: "date(dateTimeAdd([Created At], 1, 'day'))",
filterOperator: "Before",
filterValue: "April 28, 2016",
expectedRowCount: 1,
expectedTableData: {
columns: ["ID", "DatetimeExpression"],
firstRows: [["36", "April 27, 2016"]],
},
},
];
const FLOAT_TEST_CASES: CastTestCase[] = [
@@ -219,6 +245,12 @@ function testFilterWithExpressions(
addFilter(testCase);
H.visualize();
H.assertQueryBuilderRowCount(testCase.expectedRowCount);
if (testCase.expectedTableData) {
// @ts-expect-error: assertTableData is not typed
H.assertTableData(testCase.expectedTableData);
}
H.openNotebook();
removeCustomColumn(testCase);
});

View File

@@ -1797,7 +1797,6 @@ describe("issue 48878", () => {
// Create a dummy model so that GET /api/search does not return the model want to test.
// If we don't do this, GET /api/search will return and put card object with dataset_query
// attribute in the redux store (entity framework) which would prevent the issue from happening.
cy.visit("/model/new");
createModel({
name: "Dummy model",
query: "select 1",
@@ -1805,8 +1804,6 @@ describe("issue 48878", () => {
cy.log("create model");
cy.button("New").click();
H.popover().findByText("Model").click();
createModel({
name: "SQL Model",
query: "select * from orders limit 5",
@@ -1863,6 +1860,7 @@ describe("issue 48878", () => {
}
function createModel({ name, query }) {
cy.visit("/model/new");
cy.findByTestId("new-model-options")
.findByText("Use a native query")
.click();

View File

@@ -187,6 +187,12 @@ describe("scenarios > x-rays", { tags: "@slow" }, () => {
cy.findAllByTestId("dashcard").contains("18,760");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("How these transactions are distributed");
H.openNavigationSidebar();
H.navigationSidebar()
.findByRole("link", { name: /Automatically generated dashboards/i })
.should("exist");
});
it("should start loading cards from top to bottom", () => {

View File

@@ -0,0 +1,120 @@
import {
assertChatVisibility,
closeMetabotViaCloseButton,
closeMetabotViaShortcutKey,
metabotChatInput,
mockMetabotResponse,
openMetabotViaNewMenu,
openMetabotViaShortcutKey,
popover,
restore,
sendMetabotMessage,
setTokenFeatures,
userMessages,
} from "e2e/support/helpers";
describe("Metabot UI", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
cy.intercept("POST", "/api/ee/metabot-v3/agent").as("agentReq");
cy.intercept("GET", "/api/session/properties").as("sessionProperties");
});
describe("OSS", { tags: "@OSS" }, () => {
beforeEach(() => {
cy.visit("/");
cy.wait("@sessionProperties");
});
it("should not be available in OSS", () => {
openMetabotViaShortcutKey(false);
assertChatVisibility("not.visible");
cy.findByLabelText("Navigation bar").within(() => {
cy.findByText("New").click();
});
popover().findByText("Metabot request").should("not.exist");
assertChatVisibility("not.visible");
});
});
// token feature has been disabled so skipping for now - there's currently decent coverage for UI related things in our unit tests
describe.skip("EE", () => {
beforeEach(() => {
setTokenFeatures("all");
cy.visit("/");
cy.wait("@sessionProperties");
});
// FIXME: shortcut keys aren't working in CI only, but work locally
it.skip("should be able to be opened and closed", () => {
openMetabotViaNewMenu();
closeMetabotViaCloseButton();
openMetabotViaShortcutKey();
closeMetabotViaShortcutKey();
});
it("should allow a user to send a message to the agent and handle successful or failed responses", () => {
openMetabotViaNewMenu();
userMessages().should("not.exist");
mockMetabotResponse({
statusCode: 200,
delay: 100, // small delay to detect loading state
body: whoIsYourFavoriteResponse,
});
sendMetabotMessage("Who is your favorite?");
metabotChatInput()
.should("have.attr", "placeholder")
.and("eq", "Doing science...");
userMessages()
.should("exist")
.should("have.text", "You are... but don't tell anyone!");
mockMetabotResponse({
statusCode: 500,
body: whoIsYourFavoriteResponse,
});
sendMetabotMessage("Who is your favorite?");
userMessages()
.should("exist")
.should("have.text", "I'm currently offline, try again later.");
});
});
});
const whoIsYourFavoriteResponse = {
reactions: [
{
type: "metabot.reaction/message",
message: "You are... but don't tell anyone!",
},
],
history: [
{
role: "user",
content: "Who is your favorite?",
},
{
content: "",
role: "assistant",
"tool-calls": [
{
id: "call_PVmnR8mcnYFF2AmqupSKzJDh",
name: "who-is-your-favorite",
arguments: {},
},
],
},
{
role: "tool",
"tool-call-id": "call_PVmnR8mcnYFF2AmqupSKzJDh",
content: "You are... but don't tell anyone!",
},
{
content: "You are... but don't tell anyone!",
role: "assistant",
},
],
};

View File

@@ -1,13 +1,15 @@
const { H } = cy;
import { USERS } from "e2e/support/cypress_data";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import {
FIRST_COLLECTION_ID,
ORDERS_MODEL_ID,
} from "e2e/support/cypress_sample_instance_data";
import type { StructuredQuestionDetails } from "e2e/support/helpers";
const { ORDERS_ID, ORDERS, PRODUCTS_ID, PRODUCTS } = SAMPLE_DATABASE;
type StructuredQuestionDetailsWithName = H.StructuredQuestionDetails & {
type StructuredQuestionDetailsWithName = StructuredQuestionDetails & {
name: string;
};
@@ -117,6 +119,35 @@ describe("scenarios > browse > metrics", () => {
).should("be.visible");
cy.findByText("Create metric").should("not.exist");
});
cy.log("New metric header button should not show either");
cy.findByTestId("browse-metrics-header")
.findByLabelText("Create a new metric")
.should("not.exist");
});
it("user without a collection access should still be able to create and save a metric in his own personal collection", () => {
cy.intercept("POST", "/api/card").as("createMetric");
cy.signIn("nocollection");
cy.visit("/browse/metrics");
cy.findByTestId("browse-metrics-header")
.findByLabelText("Create a new metric")
.click();
cy.findByTestId("entity-picker-modal").findByText("People").click();
cy.findByTestId("edit-bar")
.should("contain", "New metric")
.button("Save")
.click();
H.modal()
.should("contain", "Save metric")
.and("contain", H.getPersonalCollectionName(USERS["nocollection"]))
.button("Save")
.click();
cy.wait("@createMetric");
cy.location("pathname").should("match", /^\/metric\/\d+-.*$/);
});
});

View File

@@ -73,27 +73,6 @@ describe("scenarios > metrics > editing", () => {
});
describe("organization", () => {
it("should be able to create a new metric from the homepage", () => {
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
H.popover().findByText("Metric").click();
H.entityPickerModal().within(() => {
H.entityPickerModalTab("Tables").click();
cy.findByText("Orders").click();
});
addAggregation({ operatorName: "Count of rows" });
saveMetric({ name: "my new metric" });
verifyScalarValue("18,760");
cy.log(
"newly created metric should be visible in recents (metabase#44223)",
);
H.appBar()
.findByText(/search/i)
.click();
H.commandPalette().findByText("my new metric").should("be.visible");
});
it("should be able to rename a metric", () => {
const newTitle = "New metric name";
H.createQuestion(ORDERS_SCALAR_METRIC).then(({ body: card }) => {
@@ -135,9 +114,11 @@ describe("scenarios > metrics > editing", () => {
});
it("should pin new metrics automatically", () => {
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
H.popover().findByText("Metric").click();
cy.visit("/browse/metrics");
cy.findByTestId("browse-metrics-header")
.findByLabelText("Create a new metric")
.click();
H.entityPickerModal().within(() => {
H.entityPickerModalTab("Tables").click();
cy.findByText("Orders").click();
@@ -571,17 +552,6 @@ function addNumberBetweenFilter({ tableName, columnName, minValue, maxValue }) {
});
}
function addAggregation({ operatorName, columnName, stageIndex }) {
startNewAggregation({ stageIndex });
H.popover().within(() => {
cy.findByText(operatorName).click();
if (columnName) {
cy.findByText(columnName).click();
}
});
}
function addBreakout({ tableName, columnName, bucketName, stageIndex }) {
startNewBreakout({ stageIndex });
if (tableName) {

View File

@@ -1,7 +1,5 @@
const { H } = cy;
import { THIRD_COLLECTION_ID } from "e2e/support/cypress_sample_instance_data";
const modelName = "A name";
import { USERS } from "e2e/support/cypress_data";
describe("scenarios > models > create", () => {
beforeEach(() => {
@@ -10,8 +8,8 @@ describe("scenarios > models > create", () => {
cy.intercept("POST", "/api/dataset").as("dataset");
});
it("creates a native query model via the New button", () => {
cy.visit("/");
it("creates a native query model", () => {
const modelName = "m42";
navigateToNewModelPage();
@@ -25,68 +23,81 @@ describe("scenarios > models > create", () => {
// Clicking on metadata should not work until we run a query
cy.findByTestId("editor-tabs-metadata").should("be.disabled");
H.NativeEditor.focus().type("select * from ORDERS");
H.NativeEditor.focus().type("select 42");
cy.findByTestId("native-query-editor-container").icon("play").click();
cy.wait("@dataset");
cy.findByTestId("dataset-edit-bar").button("Save").click();
cy.findByPlaceholderText("What is the name of your model?").type(modelName);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Save").click();
cy.findByTestId("save-question-modal").within(() => {
cy.findByLabelText("Name").type(modelName);
cy.button("Save").click();
});
// After saving, we land on view mode for the model
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Summarize");
cy.location("pathname").should("match", /^\/model\/\d+-.*$/);
cy.findByTestId("question-row-count").should("have.text", "Showing 1 row");
checkIfPinned();
checkIfPinned(modelName);
});
it("suggest the currently viewed collection when saving a new native query", () => {
H.visitCollection(THIRD_COLLECTION_ID);
// This covers creating a GUI model from the browse page + nocollection permissions (2 in 1)
it("user without a collection access should still be able to create and save a model in his own personal collection", () => {
cy.intercept("POST", "/api/card").as("createModel");
navigateToNewModelPage();
H.NativeEditor.focus().type("select 1");
cy.findByTestId("native-query-editor-container").icon("play").click();
cy.wait("@dataset");
cy.signIn("nocollection");
cy.visit("/browse/models");
cy.findByTestId("dataset-edit-bar").within(() => {
cy.contains("button", "Save").click();
});
cy.findByTestId("save-question-modal").within(() => {
cy.findByLabelText(/Where do you want to save this/).should(
"have.text",
"Third collection",
);
});
cy.findByLabelText("Create a new model").click();
cy.findByTestId("new-model-options")
.findByText("Use the notebook editor")
.click();
H.entityPickerModal().findByText("People").click();
cy.findByTestId("dataset-edit-bar").button("Save").click();
cy.findByTestId("save-question-modal")
.should("contain", "Save model")
.and("contain", H.getPersonalCollectionName(USERS["nocollection"]))
.button("Save")
.click();
cy.wait("@createModel");
cy.location("pathname").should("match", /^\/model\/\d+-.*$/);
});
it("suggest the last accessed collection when saving a new structured query model", () => {
H.visitCollection(THIRD_COLLECTION_ID);
navigateToNewModelPage("structured");
H.entityPickerModal().within(() => {
H.entityPickerModalTab("Tables").click();
cy.findByText("Orders").click();
});
cy.findByTestId("dataset-edit-bar").within(() => {
cy.contains("button", "Save").click();
});
it("should be able to create a new native model from the browse page", () => {
cy.intercept("POST", "/api/dataset").as("previewModel");
cy.intercept("POST", "/api/card").as("createModel");
cy.visit("/browse/models");
cy.findByLabelText("Create a new model").click();
cy.findByTestId("new-model-options")
.findByText("Use a native query")
.click();
H.NativeEditor.focus().type("select 42");
cy.findByTestId("native-query-editor-sidebar")
.findByLabelText("Get Answer")
.click();
cy.wait("@previewModel");
cy.findByTestId("visualization-root").should("contain", "42");
cy.findByTestId("dataset-edit-bar").button("Save").click();
cy.findByTestId("save-question-modal").within(() => {
cy.findByLabelText(/Where do you want to save this/).should(
"have.text",
"Third collection",
);
cy.findByLabelText("Name").type("m42");
cy.button("Save").click();
});
cy.wait("@createModel");
cy.location("pathname").should("match", /^\/model\/\d+-.*$/);
});
it("should not be possible to initiate a new model creation without native permissions", () => {
cy.signIn("nosql");
cy.visit("/browse/models");
cy.findByTestId("browse-models-header").within(() => {
cy.findByRole("heading").should("contain", "Models").and("be.visible");
cy.findByLabelText("Create a new model").should("not.exist");
});
});
});
function navigateToNewModelPage(queryType = "native") {
H.newButton("Model").click();
cy.visit("/model/new");
if (queryType === "structured") {
cy.findByText("Use the notebook editor").click();
} else {
@@ -94,13 +105,14 @@ function navigateToNewModelPage(queryType = "native") {
}
}
function checkIfPinned() {
H.visitCollection("root");
function checkIfPinned(modelName) {
cy.findByTestId("app-bar").findByText("Our analytics").click();
cy.location("pathname").should("eq", "/collection/root");
cy.findByText(modelName)
.closest("a")
.find(".Icon-ellipsis")
.click({ force: true });
cy.findByText("Unpin").should("be.visible");
H.popover().findByText("Unpin").should("be.visible");
}

View File

@@ -47,7 +47,9 @@ describe("issue 19180", () => {
cy.wait("@cardQuery");
cy.button("Cancel").click();
H.tableInteractive();
cy.findByText("Query results will appear here.").should("not.exist");
cy.findByText("Here's where your results will appear").should(
"not.exist",
);
},
);
});
@@ -929,27 +931,22 @@ describe("issue 28971", () => {
beforeEach(() => {
H.restore();
cy.signInAsNormalUser();
cy.intercept("POST", "/api/card").as("createCard");
cy.intercept("POST", "/api/card").as("createModel");
cy.intercept("POST", "/api/dataset").as("dataset");
});
it("should be able to filter a newly created model (metabase#28971)", () => {
cy.visit("/");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("New").click();
H.popover().findByText("Model").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Use the notebook editor").click();
H.startNewModel();
H.entityPickerModal().within(() => {
H.entityPickerModalTab("Tables").click();
cy.findByText("Orders").click();
});
cy.button("Save").click();
cy.findByTestId("save-question-modal").within((modal) => {
cy.findByText("Save").click();
});
cy.wait("@createCard");
cy.findByTestId("run-button").click();
cy.wait("@dataset");
cy.findByTestId("dataset-edit-bar").button("Save").click();
cy.findByTestId("save-question-modal").button("Save").click();
cy.wait("@createModel");
H.filter();
H.popover().within(() => {
@@ -958,50 +955,12 @@ describe("issue 28971", () => {
cy.button("Apply filter").click();
});
cy.wait("@dataset");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Quantity is equal to 20").should("exist");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Showing 4 rows").should("exist");
});
});
describe("issue 28971", () => {
beforeEach(() => {
H.restore();
cy.signInAsNormalUser();
cy.intercept("POST", "/api/card").as("createCard");
cy.intercept("POST", "/api/dataset").as("dataset");
});
it("should be able to filter a newly created model (metabase#28971)", () => {
cy.visit("/");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("New").click();
H.popover().findByText("Model").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Use the notebook editor").click();
H.entityPickerModal().within(() => {
H.entityPickerModalTab("Tables").click();
cy.findByText("Orders").click();
});
cy.button("Save").click();
cy.findByTestId("save-question-modal").within((modal) => {
cy.findByText("Save").click();
});
cy.wait("@createCard");
H.filter();
H.popover().within(() => {
cy.findByText("Quantity").click();
cy.findByText("20").click();
cy.button("Apply filter").click();
});
cy.wait("@dataset");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Quantity is equal to 20").should("exist");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Showing 4 rows").should("exist");
cy.findByTestId("filter-pill").should(
"have.text",
"Quantity is equal to 20",
);
cy.findByTestId("question-row-count").should("have.text", "Showing 4 rows");
});
});

View File

@@ -783,8 +783,7 @@ describe("issue 33844", () => {
}
it("should show hidden PKs in model metadata editor and object details after creating a model (metabase#33844)", () => {
cy.visit("/");
H.newButton("Model").click();
cy.visit("/model/new");
cy.findByTestId("new-model-options")
.findByText("Use the notebook editor")
.click();
@@ -1058,10 +1057,9 @@ describe("issue 34514", () => {
cy.intercept("GET", "/api/database/*/schema/*").as("fetchTables");
cy.intercept("GET", "/api/database/*").as("fetchDatabase");
cy.visit("/");
// It's important to navigate via UI so that there are
// enough entries in the browser history to go back to.
H.newButton("Model").click();
cy.visit("/model/new");
cy.findByTestId("new-model-options")
.findByText("Use the notebook editor")
.click();
@@ -1134,7 +1132,9 @@ describe("issue 34514", () => {
H.tableInteractive().should("not.exist");
cy.findByTestId("query-visualization-root").within(() => {
cy.findByText("We're experiencing server issues").should("not.exist");
cy.findByText("Query results will appear here.").should("be.visible");
cy.findByText("Here's where your results will appear").should(
"be.visible",
);
});
}
});
@@ -1435,6 +1435,21 @@ describe("issue 51925", () => {
});
});
describe("issue 53649", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
it("should not get caught in an infinite loop when opening the native editor (metabase#53649)", () => {
H.startNewNativeModel();
// If the app freezes, this won't work
H.NativeEditor.type("select 1");
H.NativeEditor.get().should("contain", "select 1");
});
});
describe("issue 56698", () => {
beforeEach(() => {
H.restore();
@@ -1461,3 +1476,34 @@ describe("issue 56698", () => {
H.assertQueryBuilderRowCount(1);
});
});
describe("issue 56775", () => {
const MODEL_NAME = "Model 56775";
beforeEach(() => {
H.restore();
cy.signInAsNormalUser();
H.createQuestion(
{
type: "model",
name: MODEL_NAME,
query: {
"source-table": PRODUCTS_ID,
},
},
{ visitQuestion: true },
);
});
it("should render the correct query after using the back button in a model (metabase#56775)", () => {
H.openNotebook();
cy.button("Visualize").click();
cy.go("back");
H.openQuestionActions("Edit query definition");
cy.log("verify that the model definition is visible");
H.getNotebookStep("data").findByText(MODEL_NAME).should("not.exist");
H.getNotebookStep("data").findByText("Products").should("be.visible");
});
});

View File

@@ -948,7 +948,7 @@ describe("issue 27257", () => {
it("should not drop numeric filter widget value on refresh even if it's zero (metabase#27257)", () => {
cy.reload();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Query results will appear here.");
cy.findByText("Here's where your results will appear");
cy.findByDisplayValue("0");
});
});

View File

@@ -0,0 +1,40 @@
import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
const { H } = cy;
const FIX_MESSAGE = "Fixes applied. Run your query to view results.";
describe.skip("scenarios > native > ai sql fixer", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.setTokenFeatures("all");
cy.intercept("POST", "/api/ee/ai-sql-fixer/fix").as("fixSql");
});
it("should be able to fix SQL with AI", () => {
H.visitQuestionAdhoc({
display: "table",
dataset_query: {
database: SAMPLE_DB_ID,
type: "native",
native: {
query: "SELECT1 * FROM ORDERS",
},
},
});
cy.wait("@fixSql");
cy.findByTestId("query-visualization-root").within(() => {
cy.button(/Have Metabot fix it/).click();
cy.findByText(FIX_MESSAGE).should("be.visible");
});
H.runNativeQuery();
H.NativeEditor.get()
.should("be.visible")
.and("contain", "SELECT * FROM ORDERS");
cy.findByTestId("query-visualization-root").within(() => {
H.tableInteractive().should("be.visible");
cy.findByText(FIX_MESSAGE).should("not.exist");
});
});
});

View File

@@ -115,7 +115,8 @@ describe(
it("selecting a database in native editor for model actions should not persist the database", () => {
[SAMPLE_DB_ID, PG_DB_ID].forEach(enableModelActionsForDatabase);
startNewAction();
cy.visit("/");
H.startNewAction();
assertNoDatabaseSelected();
selectDatabase("Sample Database");
@@ -129,7 +130,8 @@ describe(
selectDatabase(postgresName);
cy.wait("@persistDatabase");
startNewAction();
cy.visit("/");
H.startNewAction();
assertNoDatabaseSelected();
});
@@ -367,12 +369,6 @@ function startNativeModel() {
cy.findByRole("heading", { name: "Use a native query" }).click();
}
function startNewAction() {
cy.visit("/");
cy.findByTestId("app-bar").findByText("New").click();
H.popover().findByTextEnsureVisible("Action").click();
}
function assertNoDatabaseSelected() {
cy.findByTestId("selected-database").should("not.exist");
cy.findByTestId("native-query-top-bar").should(

View File

@@ -668,7 +668,7 @@ describe("issue 34330", () => {
it("should only call the autocompleter with all text typed (metabase#34330)", () => {
cy.findByTestId("query-visualization-root")
.findByText("Query results will appear here.")
.findByText("Here's where your results will appear")
.should("be.visible");
H.NativeEditor.type("SEAT", { delay: 10 });

View File

@@ -34,7 +34,9 @@ describe("issue 11727", { tags: "@external" }, () => {
H.runNativeQuery({ wait: false });
cy.findByText("Doing science...").should("be.visible");
cy.get("body").type("{cmd}{enter}");
cy.findByText("Query results will appear here.").should("be.visible");
cy.findByText("Here's where your results will appear").should(
"be.visible",
);
});
});
});
@@ -561,3 +563,21 @@ describe("issue 53649", () => {
H.NativeEditor.get().should("contain", "select 1");
});
});
describe("issue 57441", () => {
beforeEach(() => {
H.restore();
cy.signInAsNormalUser();
});
it("should be possible to create a new snippet from the sidebar (metabase#57441)", () => {
H.startNewNativeQuestion();
H.createSnippet({ name: "snippet 1", content: "select 1" });
cy.findByTestId("native-query-editor-sidebar").icon("snippet").click();
H.rightSidebar().icon("add").click();
H.popover().findByText("New snippet").click();
H.modal().findByText("Create your new snippet").should("be.visible");
});
});

View File

@@ -300,7 +300,7 @@ describe("scenarios > question > native", () => {
);
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Query results will appear here.").should("be.visible");
cy.findByText("Here's where your results will appear").should("be.visible");
});
it("should allow to preview a fully parameterized query", () => {

View File

@@ -1,6 +1,5 @@
const { H } = cy;
import { USER_GROUPS } from "e2e/support/cypress_data";
import { modal } from "e2e/support/helpers";
const { ALL_USERS_GROUP } = USER_GROUPS;
@@ -150,6 +149,24 @@ describe("scenarios > question > snippets", () => {
cy.findByTestId("native-query-editor-container").icon("play").click();
cy.get("@results").contains(/christ/i);
});
it("should be possible to search snippets", () => {
for (let i = 0; i < 16; i++) {
H.createSnippet({ name: `snippet ${i}`, content: `select ${i}` });
}
H.startNewNativeQuestion();
cy.icon("snippet").click();
H.rightSidebar().icon("search").click();
H.rightSidebar().findByRole("textbox").type("snippet 14");
H.rightSidebar().findByText("snippet 14").should("be.visible");
H.rightSidebar().findByText("snippet 2").should("not.exist");
H.rightSidebar().icon("close").click();
H.rightSidebar().findByText("snippet 2").should("be.visible");
});
});
describe("scenarios > question > snippets (OSS)", { tags: "@OSS" }, () => {
@@ -370,15 +387,13 @@ describe("scenarios > question > snippets (EE)", () => {
cy.icon("snippet").click();
// Edit permissions for a snippet folder
cy.findByTestId("sidebar-right").within(() => {
cy.findByText("Snippet Folder")
.next()
.find(".Icon-ellipsis")
.click({ force: true });
});
H.rightSidebar()
.findByText("Snippet Folder")
.next()
.find(".Icon-ellipsis")
.click({ force: true });
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Change permissions").click();
H.popover().findByText("Change permissions").click();
// Update permissions for "All users" and let them only "View" this folder
H.modal().within(() => {
@@ -388,15 +403,17 @@ describe("scenarios > question > snippets (EE)", () => {
});
H.popover().contains("View").click();
modal().button("Save").click();
H.modal().button("Save").click();
cy.wait("@updatePermissions");
// Now let's do the sanity check for the top level (root) snippet permissions and make sure nothing changed there
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Snippets").parent().next().find(".Icon-ellipsis").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Change permissions").click();
H.rightSidebar()
.findByTestId("snippet-header-buttons")
.icon("ellipsis")
.click();
H.popover().findByText("Change permissions").click();
// UI check
H.modal().within(() => {

View File

@@ -5,10 +5,32 @@ import {
ORDERS_BY_YEAR_QUESTION_ID,
ORDERS_COUNT_QUESTION_ID,
ORDERS_DASHBOARD_ID,
ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import { createMockDashboardCard } from "metabase-types/api/mocks";
const { admin } = USERS;
const TAB_1 = {
id: 1,
name: "Tab 1",
};
const TAB_2 = {
id: 2,
name: "Tab 2",
};
const TAB_3 = {
id: 3,
name: "Tab 3",
};
const TAB_4 = {
id: 4,
name: "Tab 4",
};
describe("command palette", () => {
beforeEach(() => {
H.restore();
@@ -413,13 +435,31 @@ H.describeWithSnowplow("shortcuts", { tags: ["@actions"] }, () => {
event: "keyboard_shortcut_performed",
event_detail: "create-new-collection",
});
H.openCommandPalette();
H.commandPalette().findByRole("option", { name: "New dashboard" }).click();
cy.findByRole("dialog", { name: /dashboard/i }).should("exist");
cy.realPress("Escape");
// Using a command palette action registered as a shortcut should only
// emit snowplow events when using keyboard shortcuts, not command palette
H.expectGoodSnowplowEvent(
{
event: "keyboard_shortcut_performed",
event_detail: "create-new-dashboard",
},
0,
);
cy.realPress("c").realPress("d");
cy.findByRole("dialog", { name: /dashboard/i }).should("exist");
cy.realPress("Escape");
H.expectGoodSnowplowEvent({
event: "keyboard_shortcut_performed",
event_detail: "create-new-dashboard",
});
H.expectGoodSnowplowEvent(
{
event: "keyboard_shortcut_performed",
event_detail: "create-new-dashboard",
},
1,
);
cy.realPress("g").realPress("d");
cy.location("pathname").should("contain", "browse/databases");
@@ -461,9 +501,9 @@ H.describeWithSnowplow("shortcuts", { tags: ["@actions"] }, () => {
H.navigationSidebar().should("be.visible");
// Mantine Modals
H.newButton("Collection").click();
H.startNewCollectionFromSidebar();
H.modal()
cy.findByTestId("new-collection-modal")
.findByLabelText(/collection it's saved in/i)
.click();
@@ -478,7 +518,8 @@ H.describeWithSnowplow("shortcuts", { tags: ["@actions"] }, () => {
cy.realPress("Escape");
// Legacy Modals
H.newButton("Action").click();
H.startNewAction();
// Remove focus
H.modal()
.findByText(/Build custom forms/)
@@ -488,18 +529,54 @@ H.describeWithSnowplow("shortcuts", { tags: ["@actions"] }, () => {
cy.realPress("Escape");
cy.realPress("[");
H.navigationSidebar().should("not.visible");
cy.findByLabelText("Settings menu").click();
H.popover().findByText("Admin settings").click();
cy.findByTestId("site-name-setting").should("exist");
cy.location("pathname").should("contain", "/admin/settings");
cy.realPress("3");
cy.location("pathname").should("contain", "/admin/datamodel");
cy.realPress("7");
cy.location("pathname").should("contain", "/admin/tools");
});
it("should support dashboard shortcuts", () => {
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.createDashboardWithTabs({
tabs: [TAB_1, TAB_2, TAB_3, TAB_4],
dashcards: [
createMockDashboardCard({
id: -1,
card_id: ORDERS_QUESTION_ID,
dashboard_tab_id: TAB_1.id,
}),
createMockDashboardCard({
id: -2,
card_id: ORDERS_QUESTION_ID,
dashboard_tab_id: TAB_2.id,
}),
createMockDashboardCard({
id: -3,
card_id: ORDERS_QUESTION_ID,
dashboard_tab_id: TAB_3.id,
}),
createMockDashboardCard({
id: -4,
card_id: ORDERS_QUESTION_ID,
dashboard_tab_id: TAB_4.id,
}),
],
}).then((dashboard) => H.visitDashboard(dashboard.id));
H.openShortcutModal();
H.shortcutModal().should("exist");
cy.realPress("Escape");
cy.realPress("o");
H.openNavigationSidebar();
H.navigationSidebar()
.findByRole("tab", { name: /bookmarks/i })
.should("contain.text", "Orders in a dashboard");
.should("contain.text", "Test Dashboard");
cy.realPress("o");
H.navigationSidebar()
.findByRole("tab", { name: /bookmarks/i })
@@ -520,6 +597,31 @@ H.describeWithSnowplow("shortcuts", { tags: ["@actions"] }, () => {
cy.findByRole("dialog", { name: "Info" }).should("exist");
cy.realPress("]");
cy.findByRole("dialog", { name: "Info" }).should("not.exist");
cy.findByRole("tab", { name: "Tab 1" }).should(
"have.attr",
"aria-selected",
"true",
);
cy.realPress("3");
cy.findByRole("tab", { name: "Tab 3" }).should(
"have.attr",
"aria-selected",
"true",
);
cy.realPress("1");
cy.findByRole("tab", { name: "Tab 1" }).should(
"have.attr",
"aria-selected",
"true",
);
// Doesn't error on pressing numbers out of bounds
cy.realPress("7");
cy.findByRole("tab", { name: "Tab 1" }).should(
"have.attr",
"aria-selected",
"true",
);
});
it("should support query builder shortcuts", () => {

View File

@@ -102,6 +102,35 @@ H.describeWithSnowplow("scenarios > browse", () => {
});
});
it("tracks when a new model creation is initiated", () => {
cy.visit("/browse/models");
cy.findByTestId("browse-models-header")
.findByLabelText("Create a new model")
.should("be.visible")
.click();
cy.location("pathname").should("eq", "/model/new");
H.expectNoBadSnowplowEvents();
H.expectGoodSnowplowEvent({
event: "plus_button_clicked",
triggered_from: "model",
});
});
it("tracks when a new metric creation is initiated", () => {
cy.visit("/browse/metrics");
cy.findByTestId("browse-metrics-header")
.findByLabelText("Create a new metric")
.should("be.visible")
.click();
cy.findByTestId("entity-picker-modal").should("be.visible");
H.expectNoBadSnowplowEvents();
H.expectGoodSnowplowEvent({
event: "plus_button_clicked",
triggered_from: "metric",
});
});
it("browsing to a database only triggers a request for schemas for that specific database", () => {
cy.intercept("GET", `/api/database/${SAMPLE_DB_ID}/schemas`).as(
"schemasForSampleDatabase",

View File

@@ -603,6 +603,76 @@ H.describeWithSnowplow("scenarios > setup", () => {
source: "homepage",
});
});
it("should track when 'New' button is clicked", () => {
cy.visit("/");
cy.log("From the app bar");
H.newButton().should("be.visible").click();
cy.findByRole("dialog").should("be.visible");
H.expectGoodSnowplowEvent({
event: "new_button_clicked",
triggered_from: "app-bar",
});
cy.log("Track closing the button as well");
H.newButton().should("be.visible").click();
cy.findByRole("dialog").should("not.exist");
H.expectGoodSnowplowEvent(
{
event: "new_button_clicked",
triggered_from: "app-bar",
},
2,
);
cy.log("From the empty collection");
H.navigationSidebar().findByText("Your personal collection").click();
cy.findByTestId("collection-empty-state").within(() => {
cy.findByText("This collection is empty").should("be.visible");
cy.findByText("New").click();
});
cy.findByRole("dialog").should("be.visible");
H.expectGoodSnowplowEvent({
event: "new_button_clicked",
triggered_from: "empty-collection",
});
});
/**
* Until we refactor the NewItem menu component and drop EntityMenu from it,
* the only menu item that can have onClick handler is a "dashboard".
*/
it("should track when a 'New' button's menu item is clicked", () => {
cy.visit("/");
H.newButton().should("be.visible").click();
cy.findByRole("dialog").findByText("Dashboard").click();
cy.findByTestId("new-dashboard-modal").should("be.visible");
H.expectGoodSnowplowEvent({
event: "new_button_item_clicked",
triggered_from: "dashboard",
});
cy.findByTestId("new-dashboard-modal").button("Cancel").click();
cy.findByTestId("new-dashboard-modal").should("not.exist");
H.navigationSidebar().findByText("Your personal collection").click();
cy.findByTestId("collection-empty-state").within(() => {
cy.findByText("This collection is empty").should("be.visible");
cy.findByText("New").click();
});
cy.findByRole("dialog").findByText("Dashboard").click();
cy.findByTestId("new-dashboard-modal").should("be.visible");
H.expectGoodSnowplowEvent(
{
event: "new_button_item_clicked",
triggered_from: "dashboard",
},
2,
);
});
});
const pinItem = (name) => {

View File

@@ -26,26 +26,4 @@ describe("metabase > scenarios > navbar > new menu", () => {
cy.url("should.contain", "/question#");
H.NativeEditor.get().should("be.visible");
});
it("collection opens modal and redirects to a created collection after saving", () => {
H.popover().within(() => {
cy.findByText("Collection").click();
});
cy.findByTestId("new-collection-modal").then((modal) => {
cy.findByTestId("collection-picker-button").findByText("Our analytics");
cy.findByPlaceholderText("My new fantastic collection").type(
"Test collection",
);
cy.findByLabelText("Description").type("Test collection description");
cy.findByText("Create").click();
});
cy.findByTestId("collection-name-heading").should(
"have.text",
"Test collection",
);
});
});

View File

@@ -56,10 +56,13 @@ describe("nav > what's new notification", () => {
function mockVersions({ currentVersion, versions = [] }) {
const [latest, ...older] = versions;
cy.intercept("GET", "/api/setting/version-info", (req) => {
req.reply(createMockVersionInfo({ latest, older }));
}).as("versionInfo");
cy.intercept("GET", "/api/session/properties", (req) => {
req.reply((res) => {
res.body["version"] = { tag: currentVersion };
res.body["version-info"] = createMockVersionInfo({ latest, older });
});
}).as("sessionProperties");
}
@@ -68,6 +71,7 @@ function loadHomepage() {
cy.visit("/");
cy.wait("@sessionProperties");
cy.wait("@versionInfo");
// make sure page is loaded
cy.findByText("loading").should("not.exist");

View File

@@ -9,9 +9,9 @@ const { admin } = USERS;
const locales = ["en", "xx"];
describe("scenarios > setup", () => {
locales.forEach((locale) => {
beforeEach(() => H.restore("blank"));
beforeEach(() => H.restore("blank"));
locales.forEach((locale) => {
it(
`should allow you to sign up using "${locale}" browser locale`,
{ tags: ["@external"] },
@@ -223,7 +223,6 @@ describe("scenarios > setup", () => {
});
it("should pre-fill user info for hosted instances (infra-frontend#1109)", () => {
H.setTokenFeatures("none");
H.mockSessionProperty("is-hosted?", true);
cy.visit(
@@ -238,9 +237,7 @@ describe("scenarios > setup", () => {
cy.findByDisplayValue("Doe").should("exist");
cy.findByDisplayValue("john@doe.test").should("exist");
cy.findByDisplayValue("Doe Unlimited").should("exist");
cy.findByLabelText("Create a password")
.should("be.focused")
.and("be.empty");
cy.findByLabelText("Create a password").should("be.empty");
});
});
@@ -454,7 +451,7 @@ H.describeWithSnowplow("scenarios > setup", () => {
H.expectNoBadSnowplowEvents();
});
it("should send snowplow events", () => {
it("should send snowplow events", { tags: "@flaky" }, () => {
let goodEvents = 0;
goodEvents++; // 1 - new_instance_created
@@ -576,14 +573,18 @@ H.describeWithSnowplow("scenarios > setup", () => {
});
});
it("should ignore snowplow failures and work as normal", () => {
H.blockSnowplow();
cy.visit("/setup");
skipWelcomePage();
it(
"should ignore snowplow failures and work as normal",
{ tags: "@flaky" },
() => {
H.blockSnowplow();
cy.visit("/setup");
skipWelcomePage();
// 1 event is sent from the BE, which isn't blocked by blockSnowplow()
H.expectGoodSnowplowEvents(1);
});
// 1 event is sent from the BE, which isn't blocked by blockSnowplow()
H.expectGoodSnowplowEvents(1);
},
);
});
const skipWelcomePage = () => {

View File

@@ -90,13 +90,11 @@ describe("URLs", () => {
});
it("should not slugify users' collections page URL", () => {
cy.visit("/collection/root");
H.navigationSidebar().within(() => {
cy.icon("ellipsis").click();
});
H.popover().findByText("Other users' personal collections").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("All personal collections");
cy.visit("/collection/users");
cy.findByTestId("browsercrumbs").should(
"contain",
/All personal collections/i,
);
cy.location("pathname").should("eq", "/collection/users");
});

View File

@@ -10,12 +10,16 @@ import {
ORDERS_DASHBOARD_ID,
ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import type {
DashboardDetails,
StructuredQuestionDetails,
} from "e2e/support/helpers";
import type { DashboardCard } from "metabase-types/api";
const { ORDERS_ID } = SAMPLE_DATABASE;
const { ALL_USERS_GROUP } = USER_GROUPS;
const cardDetails: H.StructuredQuestionDetails = {
const cardDetails: StructuredQuestionDetails = {
name: "Question",
type: "question",
query: {
@@ -1032,8 +1036,8 @@ describe("scenarios > organization > entity picker", () => {
cy.visit("/");
// New Collection Flow
H.newButton("Collection").click();
H.modal()
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal")
.findByLabelText(/Collection it's saved in/)
.click();
@@ -1123,8 +1127,8 @@ describe("scenarios > organization > entity picker", () => {
cy.visit("/");
// New Collection Flow
H.newButton("Collection").click();
H.modal()
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal")
.findByLabelText(/Collection it's saved in/)
.click();
H.entityPickerModalTab("Collections").click();
@@ -1253,7 +1257,7 @@ function createTestDashboards() {
}
function createTestDashboardWithEmptyCard(
dashboardDetails: H.DashboardDetails = {},
dashboardDetails: DashboardDetails = {},
) {
const dashcardDetails: Partial<DashboardCard>[] = [
{
@@ -1285,7 +1289,7 @@ function createTestDashboardWithEmptyCard(
});
}
function selectQuestionFromDashboard(dashboardDetails?: H.DashboardDetails) {
function selectQuestionFromDashboard(dashboardDetails?: DashboardDetails) {
createTestDashboardWithEmptyCard(dashboardDetails).then((dashboard) => {
H.visitDashboard(dashboard.id);
H.editDashboard();

View File

@@ -39,7 +39,7 @@ describe("official collections", () => {
// Gate the UI
cy.visit("/collection/root");
startNewCollectionCreation();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
assertNoCollectionTypeInput();
cy.findByLabelText("Close").click();
@@ -84,7 +84,7 @@ describe("official collections", () => {
openCollection("First collection");
startNewCollectionCreation();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
assertNoCollectionTypeInput();
cy.findByLabelText("Close").click();
@@ -107,7 +107,7 @@ describe("official collections", () => {
H.popover().findByText("Make collection official").should("exist");
startNewCollectionCreation();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
assertHasCollectionTypeInput();
cy.findByPlaceholderText("My new fantastic collection").type(
@@ -124,7 +124,7 @@ describe("official collections", () => {
});
H.popover().findByText("Make collection official").should("exist");
startNewCollectionCreation();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
assertHasCollectionTypeInput();
cy.findByLabelText("Close").click();
@@ -236,7 +236,7 @@ function openCollection(collectionName) {
}
function createAndOpenOfficialCollection({ name }) {
startNewCollectionCreation();
H.startNewCollectionFromSidebar();
cy.findByTestId("new-collection-modal").then((modal) => {
cy.findByPlaceholderText("My new fantastic collection").type(name);
cy.findByText("Official").click();
@@ -310,5 +310,3 @@ const assertHasCollectionBadgeInNavbar = (expectBadge = true) => {
}
});
};
const startNewCollectionCreation = () => H.newButton("Collection").click();

View File

@@ -306,14 +306,8 @@ describe("UI elements that make no sense for users without data permissions (met
cy.icon("refresh").should("not.exist");
});
cy.visit("/collection/root");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("New").click();
H.popover()
.should("contain", "Dashboard")
.and("contain", "Collection")
.and("not.contain", "Question");
H.newButton().click();
H.popover().should("contain", "Dashboard").and("not.contain", "Question");
});
it("should not show visualization or question settings to users with block data permissions", () => {
@@ -341,14 +335,9 @@ describe("UI elements that make no sense for users without data permissions (met
cy.findByTestId("qb-header-action-panel").within(() => {
cy.icon("refresh").should("not.exist");
});
cy.visit("/collection/root");
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("New").click();
H.popover()
.should("contain", "Dashboard")
.and("contain", "Collection")
.and("not.contain", "Question");
H.newButton().click();
H.popover().should("contain", "Dashboard").and("not.contain", "Question");
});
});

View File

@@ -183,9 +183,7 @@ describe("issue 25144", { tags: "@OSS" }, () => {
});
it("should show Models tab after creation the first model (metabase#24878)", () => {
cy.visit("/");
H.newButton("Model").click();
cy.visit("/model/new");
cy.findByTestId("new-model-options")
.findByText(/use the notebook/i)
.click();
@@ -500,7 +498,7 @@ describe("issue 30165", () => {
cy.get("@dataset.all").should("have.length", 0);
cy.get("@cardQuery.all").should("have.length", 0);
cy.findByTestId("query-builder-main")
.findByText("Query results will appear here.")
.findByText("Here's where your results will appear")
.should("be.visible");
});
});

Some files were not shown because too many files have changed in this diff Show More