Compare commits

..

271 Commits

Author SHA1 Message Date
Edward Paget
7c43b8ccbb fix: python tests failing when client on same thread 2026-01-19 16:31:45 -06:00
bryan
58e4025456 Fix flaky dataset-already-loaded?-test for Redshift (#68292) 2026-01-19 13:44:59 -07:00
bryan
9ceae0061a Fix mage doctor on Linux: use which instead of command -v (#68381)
`command` is a shell builtin that doesn't exist as a standalone binary on
Linux (only macOS has /usr/bin/command as a wrapper). When babashka tries
to execute `command -v git` directly via process/shell, it fails on Linux
because there's no `command` binary to run.

Using `which` instead works on both platforms since it's an actual binary.
2026-01-19 20:04:10 +01:00
metamben
2cb8a32def Add back typo fix removed by merge (#68375) 2026-01-19 20:00:32 +01:00
Edward Paget
59adba6f98 feat: generic oidc provider using auth-identity (#65872)
* feat: add generic OIDC provider infrastructure

Add OSS infrastructure for OIDC-based authentication providers:

- OIDC discovery: automatic endpoint discovery from issuer URL
- Token handling: ID token validation, parsing, and claim extraction
- State management: encrypted state cookies for CSRF protection
- Schema validation: Malli schemas for OIDC configuration and tokens
- Common utilities: shared HTTP and request utilities

This provides a foundation for enterprise SSO integrations that use
OIDC/OAuth2 flows (e.g., Slack Connect, generic OIDC providers).

* fixup! dead code clean up

There was a bunch of speculative code we didn't end up using when this
change from a 'configurable OIDC' to a just support slack course

* fixup! ttls for caches for jwks/discovery

fixup! check for open redirect when creating oidc state

* fixup! use valid-hosts? to check before making jwks/discovery requests

* fixup! don't use hardcoded jwt algo

* fixup! use java-time

* fixup! kondo

* fixup! use system.core not system.settings
2026-01-19 12:04:55 -06:00
Denis Berezin
1da7a523b8 Use proper link in public powered by metabase footer link (#68301)
* Use proper link in public powered by metabase footer link

* Fix tests

* Fix tests
2026-01-19 20:47:41 +03:00
Alexander Polyankin
6165e70718 Fix :view_count not returned for relevant entities in the dependencies graph (#68370) 2026-01-19 17:43:54 +00:00
metamben
1a2f012afe Show sensitive columns in the sandboxing UI (#66757)
* Show sensitive columns in the sandboxing UI

Closes #53378
2026-01-19 16:33:20 +00:00
Brad Anderson
c8b6f74578 UXW-2341 Add Caching tab for metrics in Data Studio (#66513) 2026-01-19 10:51:51 -05:00
metamben
905c5dde99 Report datetime insights even if columns is PK or FK (#67536)
Closes #35281
2026-01-19 15:10:27 +00:00
Alex Yarosh
913e0c9ee8 docs: IAM for RDS (#68349)
* Update IAM authentication details for RDS instances

Clarified the types of RDS instances that support IAM authentication.

* Update docs/databases/connections/aws-rds.md

* Update docs/databases/connections/aws-rds.md
2026-01-19 09:20:26 -05:00
github-automation-metabase
9eb6d7c74d Auto-update documentation for master (#68332)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-01-19 09:08:06 -05:00
Phoomparin Mano
a27a3d286b Fix flaky assertion for "sdk iframe embedding fires ready event after iframe is loaded" (#68336) 2026-01-19 19:45:38 +07:00
Nicola Mometto
03d26b2a19 fix spelling typo (#68342) 2026-01-19 11:46:08 +00:00
Dan Stone
4fa44b5fc7 Use Soft/Weak references for malli cache entries (#68076) 2026-01-19 10:51:25 +00:00
Nicola Mometto
2e3746de55 Task Run Monitoring (#68116)
* add task_run table and followup migrations

* fix: missing remarks

* feat: task-run model

* wip: logs and run-id in task_history model

* wip: endpoints

* chore: dead code

* simplify

* simplify

* Add log capture factory

* Move log capture to history

* feat: prevent running if nested

* Capture logs during task history

* Tests + fixes for log capture

* Avoid log aliases and kondo the discouraged c.t.logging ns

* style

* fix

* feat: wrap with with-task-run

* Add `:ts` timestamps to captured log entries

* Skip DEBUG/TRACE

* Add basic log truncation

* Test log truncation

* Fix sync tests

* Add :model/TaskRun resolution.clj entry

* GDGT-1497 add TaskRun UI part

* feat: hydrate-task-counts

* fix: nil protect limit/offset

* style

* fix: move with-task-run to core ns

* fix: ignore TaskRun from copy test

* fix: no need for name

* fix: typo

* wip

* fix: build-run-where-clause

* exclude task run from entity_id/deser

* feat: add with-run-id helpers

* chore: make complete-task-run idempotent

* feat: export more utils

* fix: support async notification/alerts

* kondo

* fix: test

* support legacy cards

* make sure we complete-task-run in send- sync

* add index to fk

* fix: current-run-id

* fix: move around

* add tests

* fix: needs keyword

* hook up to test endpoints too

* preserver ordering

* handle non hydrated

* add integration tests

* depend on no-op nested behavior, add comment

* fix

* fix: coerce

* GDGT-1497 add TaskRun UI part

* conform to logs format

* chore: add process_uuid

* GDGT-1497 add TaskRun UI part

* GDGT-1497 add TaskRun UI part

* feat: track uuid/updated_at in task run for orphan detection

* add unknown status

* feat: store updated_at/process on insert

* chore: use abandoned

* feat: orphan task

* make not nullable

* chore: add abandoned to FE

* fix

* feat: also abandon if exceeds max duration

* add integration test

* appease prettier

* GDGT-1497 add TaskRun UI part

* GDGT-1497 addressing PR comments

* GDGT-1497 fix url

* GDGT-1497 fix db name and db engine rendering

* GDGT-1497 extract TaskStatusBadge and TaskRunStatusBadge

* GDGT-1497 clean up

* fix typo

* feat: onDelete set null instead of cascade

* GDGT-1497 fix e2e tetst

* Update src/metabase/task_history/models/task_history.clj

Co-authored-by: Dan Stone <wotbrew@gmail.com>

* GDGT-1497 add new e2e tests

* GDGT-1497 fix eslint rules disabling in new e2e tests

* GDGT-1497 addressing PR comments

* fix out of order timestamps

* fix: missed two

* GDGT-1497 addressing PR comments

* GDGT-1497 fix ts issues

* GDGT-1497 fix flakey test

---------

Co-authored-by: Dan Stone <wotbrew@gmail.com>
Co-authored-by: Egor Grushin <egor@metabase.com>
2026-01-19 09:58:36 +00:00
Romeo Van Snick
46b0aa85a2 Fix broken loki test (#68334)
Update Loki Snapshots

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-19 09:48:08 +00:00
Gabriel Moreno
bb66df77e1 Add library stats to anonymous daily stats ping (#68071)
* feat: add library stats to anonymous daily stats ping

* test: add unit spec for library stats

* test: use the `mt/with-temp` macro
2026-01-16 18:31:25 -04:00
Eric Normand
293d47d3e8 Transforms job runs keep trying other transforms if one fails. (#68141)
Previously, when running a transform job (e.g., daily), it would fail if a single transform in the run failed. This PR makes it so that the job continues to run transforms that don't depend on any failed transform. As previously, if any transform in the run fails, the job is marked as failed.
2026-01-16 22:30:31 +00:00
Gabriel Moreno
3f1ff1ba60 Library empty state: expand Data, Metrics and SQL snippets (#68241)
* feat: add empty states for library items

* test: add e2e specs

* refactor: cleanup library section layout

* test: compact e2e specs into 2 major flows
2026-01-16 18:26:46 -04:00
Timothy S. Dean
e694056f0f Fix coercion for UNIX timestamp fields via implicit joins (#68164)
Addresses #67704.

When a UNIX timestamp is accessed through an implicit join, the coercion to DateTime wasn't being applied. The `allow-casting?` check requires `pos-int?` on `::source-table` to apply coercion. For implicit joins, `::source-table` is set to a string, so it was being skipped.

Fix: in `add-alias-info/add-source-to-field-ref`, wet the flag `:qp/allow-coercion-for-columns-without-integer-qp.add.source-table`. I cribbed this approach from the SparkSQL driver, when it has to rewrite a numeric ID with a string.
2026-01-16 15:26:26 -07:00
Gabriel Moreno
de171c2092 Make Python and SQL editing in Data Studio consistent (#68212)
* feat: configure initial read-only mode for python transforms

* refactor: hide UI elements in readonly mode

* refactor: move database selector to the topbar

* style: add in/out animations for bottom panel

* refactor: move common library controls to the topbar

* test: add unit specs for python transform editor sections

* test: add e2e spec

* fix: show correct run query button state after edits

* refactor: general cleanup & test trimming

* refactor: remove source controls

* refactor: trim down test mocks

* fix: pass missing `editable` flag to transform editor

* fix: types

* fix: e2e data source selector

* refactor: use `isEditMode` instead of `readOnly`
2026-01-16 18:12:20 -04:00
github-automation-metabase
fd2bf97c1f Auto-update documentation for master (#68219)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-01-16 16:44:55 -05:00
Denis Berezin
0bba5e8742 UXW-46 Safari smart scalar truncation (#68248)
* UXW-46 refactor solution

* Update Loki Snapshots

* Fix typecheck

* Fix e2e

* Refactor to simpler solution

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-17 00:43:41 +03:00
Nick Fitzpatrick
3302e9cafd Gdgt 912 update scalar second tab and configuration to support color (#67877)
* adding segment editor to scalar

* temp

* adding tooltip

* pretend i'm a clojure dev, disable tooltip when there are no segments

* adding unit test and story

* loki tests

* silly me

* I am a clojure god

* adjustments

* adding docstring, adjusting loki test

* combining scalar loki tests

* CardSegment -> ScalarSegment

* BE update, filter unvalid segments
2026-01-16 16:37:41 -04:00
Denis Berezin
18c485d06c Fix missing labels for bar chart (#68299)
* Fix missing label for cartesian

* Add e2e tests

* Add comment

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-16 22:54:53 +03:00
Aleksandr Lesnenko
68d40f2770 Ask for loki stress testing (#68302)
update docs, pr template
2026-01-16 14:51:09 -05:00
Nemanja Glumac
b92393b9f0 Enable codespell GitHub action for src folder (#68213) 2026-01-16 13:37:22 -06:00
Egor Grushin
83ca220730 GDGT-1419 warn when incremental edits break checkpoint auto selection (#67933) 2026-01-16 17:49:14 +00:00
Thomas Schmidt
ba7c5d1fc7 Require free text for ui-bug and other feedback types (#68234)
* Require free text for ui-bug and other feedback types

Add Yup validation to MetabotFeedbackModal that requires free text feedback when issue type is ui-bug or other. Issue types requiring free text are maintained in a constant array for easy extension.

* Address PR feedback: improve type safety and UX in feedback modal

- Replace 'as any' with proper TypeScript type assertion
- Fix type inconsistency between schema and initial value for issue_type
- Add dynamic label showing '(required)' when ui-bug or other is selected

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* pr feedback

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Sloan Sparger <sloansparger@gmail.com>
2026-01-16 11:34:12 -06:00
Nemanja Glumac
2cf11310a8 Fix untranslated string in an alert modal (#68287)
Co-authored-by: Joseph Adams <danielowenderefaka@gmail.com>
2026-01-16 17:53:24 +01:00
Riley Thompson
06d4da18c3 Surface analysis errors with upstream dependencies (#68092)
* initial work

* fix test

* fix another test

* source entity malli schema

* clean up and add test

* add tests

* clean up

* fix tests

* drop finding_details column

* fix analysis version

* Update enterprise/backend/src/metabase_enterprise/dependencies/models/analysis_finding.clj

Co-authored-by: Timothy S. Dean <timothy.dean@metabase.com>

* use transaction

* use node-errors refactor suggestion

* use maybe-resolve-join

* add schemas

* add comment

---------

Co-authored-by: Timothy S. Dean <timothy.dean@metabase.com>
2026-01-16 15:08:44 +00:00
John Swanson
44e118b5e3 Manage table metadata allows syncing table (#67868)
This is the permission we're correctly checking with the below
`api/check-403` but by using `warehouses/get-database` (which, btw, has
the MOST confusing possible arguments I've ever seen) we were throwing a
403 if we didn't have read access to the whole database (which
checks *create-queries* perms on the entire database).

We still need to exclude mirror databases. In the medium term there
might be a better way to do this - maybe we could somehow have two
different models, so `(t2/select :model/Database)` would always exclude
mirror databases, while `(t2/select
:model/DatabaseIncludingMirrorDatabase)` would return them? I dunno.
2026-01-16 06:13:15 -08:00
Gabriel Moreno
3a490774cc Fix the Glossary deduplication UX (#68243)
* feat: prevent duplicate glossary terms from being added

* test: add unit spec
2026-01-16 09:55:25 -04:00
Timofey Kachalov
d5ad9d6142 fix(embed-js): adjust experience reset logic to avoid unexpected experience resets (#68273) 2026-01-16 17:09:53 +04:00
William
eacff63768 Disable native sql validation (#68148)
Native sql validation is a bit too unreliable for general deployment at the moment, so we are disabling it for check_* endpoints and the broken entities list unless/until we can make it more reliable.

Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
2026-01-16 11:28:04 +00:00
Timofey Kachalov
6cea29bace Fix wrong BG value in TableActionInputSearchableSelect (#68275) 2026-01-16 15:21:35 +04:00
Dan Stone
e3d6d76bf8 Hide 0 estimate row counts to avoid confusion (#68190)
Treats 0 n_live_tup as NULL, due to confusion reported when auto analyze
thresholds have not been met on a table.

To justify:
- You have a non-zero estimate (No change)
- A zero estimate but actually have rows (We now hide the estimate)
- A zero estimate and actually have zero rows (We now hide the estimate,
unfortunately)

This behaviour is considered on balance preferable to the behaviour now
where users will likely be confused, even thought we explicitly label
the field 'estimated'.

Other options considered that we might explore in the future:
- Forcing ANALYZE, e.g. on sync
- Bounded/LIMIT count
- Some policy based on last analyze timestamps
- Better tooltips or explaination on frontend

We should revisit in the future once we have a better idea for how
estimates should work across drivers (as its only postgres for now).
2026-01-16 09:03:21 +00:00
Timothy S. Dean
37243f5469 Delete invalid source-card-ids on native SQL questions (#68100)
When a user creates a question based on a model, `source_card_id` was set to the model's ID. If that question was later converted to native SQL, `source_card_id` wouldn't get cleared. This means that deleting the model would cause the question to be deleted even though they no longer have any relationship to one another.

The code fix won't help anyone whose data is already incorrect, so this adds a migration to correct the inconsistent data. I added an assertion to the t2 hook to catch this at dev time in the future. And ultimately, the delete cascade seems overkill, so there's a migration to replace that with SET NULL instead.
2026-01-15 16:19:13 -07:00
bryan
ad60b57c4a Fake sync for Redshift tests (branch-conditional, refactored) (#67603) 2026-01-15 15:53:45 -07:00
Anderson
fa924880ad Implement read-only on Library & Data Structure pages (#68209)
* Implement read-only on Library page

* Add some gorgeous unit tests

* Make segment and measures read-only / Add tests

* Apply read-only logic to measure list and segment list pages
2026-01-15 19:50:51 -03:00
Romeo Van Snick
bace1d68c5 Flake fix: allow clicking gridcells while animating (#68218)
Allow clicking while animating
2026-01-16 09:44:15 +11:00
Maksym Yakubych
0523cf2397 fix: dropdown menu in create/edit record on database table (#67807) 2026-01-15 17:51:15 -04:00
Alexander Polyankin
c75465cd07 FE fixes for upstream dependencies (#68254) 2026-01-15 16:44:08 -05:00
kevinkelleher12
a76e880fdd Fix filters not closing properly on native SQL questions (#68216) 2026-01-15 16:06:26 -05:00
Alexander Polyankin
032d1e9ea5 Add server-side sorting to broken & unreferenced items pages (#68221) 2026-01-15 19:59:14 +00:00
Oleksandr Yakushev
b2840f32f9 Replace amalloy/ring-buffer dependency with already included CircularFifoQueue (#66007) 2026-01-15 20:34:29 +02:00
Timofey Kachalov
499eacf3d1 Fix EE Starter Edition Embedding Admin page visuals (#68226) 2026-01-15 21:27:33 +04:00
Denis Berezin
23d9096d0b UXW-136 Fix issue with bar viz on filtered column (#67573)
* UXW-136 Fix issue with bar viz on filtered column

* Review fixes

* Refactor solution

* Review fix
2026-01-15 17:17:07 +00:00
Dan Stone
594cd0cc89 Remove ^:parallel meta for tests that search + hold locks (#68240)
A recent change to search permissions queries in [#67055](https://github.com/metabase/metabase/pull/67055) will cause H2 to use SYS locks during search operations. This is because H2 uses temporary tables to implement CTE's, and their creation/dropping requires global locks.

This commit removes some ^:parallel meta in an attempt to avoid test
flakes due to lock timeout.

This change is a bit speculative:

- It is not 100% confirmed that parallel tests are the only problem (e.g. some background task might also hold a lock)
- The tests changed are based on a cursory scan for search tests that
  hold transactions (mt/with-temp). There may be other tests.

The impact of the CTE lock behaviour in production searches is likely
minimal because:

a. Search queries will typically run in auto commit sessions and so lock
lifetime will be short
b. concurrent multi-user deployments are most likely not on H2.

Should keep an eye on both tests and any reports in production of lock
timeout errors / slow searches when using H2.
2026-01-15 17:05:14 +00:00
da
f3c0be5fc1 Log error instead of warning if a subscription is not sent through the channel rather than a warning (#68214)
* Why is it a warning when it should be an error?

* Fix error logging format in notification worker

* Improve error logging in notification send process

Fix silly mistakes that I do when I'm sleepy
2026-01-15 13:20:40 -03:00
Timofey Kachalov
950b555558 feat(sdk, embed-js): Add Content Translations for SDK InteractiveQuestion new query Editor (#68063) 2026-01-15 20:13:21 +04:00
Jeff Bruemmer
2e65eac90e docs- remove sample app (#68235)
remove sample app
2026-01-15 14:59:24 +00:00
Timofey Kachalov
c73ccb8be7 Cleanup additional space to properly build Sample Apps (#68229) 2026-01-15 17:31:59 +04:00
William
7976ad4f47 Refresh child card result metadata on card edit (#68043)
When a card is updated, look for all mbql child cards and recalculate their result metadata.  With models, calculate
what their "correct" result metadata is pre-update, use that to calculate user overrides, and then reapply those
overrides when computing the new result metadata.

This should fix a variety of bugs around users updating a model and then not seeing those change downstream.
2026-01-15 09:41:36 +00:00
Thomas Schmidt
825719067a Improve Metabot input and message scrolling UX (#68117)
- Add max-height and vertical scrolling to input area
- Fix newline preservation in multi-line messages
- Add horizontal scrolling for wide code blocks in messages
- Style code blocks with proper background, border, and custom scrollbar
- Reduce code font size to 0.6875rem for better proportion
- Increase user message bubble width to 90%
- Add inline code styling with background and border
2026-01-15 08:43:34 +00:00
Anderson
95af300c15 Disable git pull button when there are no changes to pull (#67968)
* Add /has-remote-changes endpoint to remote-sync API

Adds a new endpoint GET /api/ee/remote-sync/has-remote-changes that checks
if there are changes on the remote (Git) repository that can be pulled.

Response format:
{
  "has_changes": true,
  "remote_version": "abc123...",
  "local_version": "789xyz...",
  "cached": false
}

* Disable git pull button when there are no changes to pull

* Fix unit test

* Fix e2e

* Adjust conflict modal 'push' option to always force

* Fix conflict resolution options when switching branches

* Fix modal title not wrapping properly

* Fix long long modal title e2e issue

---------

Co-authored-by: Edward Paget <ed.paget@gmail.com>
2026-01-14 23:38:57 -03:00
Nemanja Glumac
d2f3648298 Remove embedding dev-related folders from Snyk (#68207) 2026-01-14 22:12:35 +01:00
Edward Paget
60093c461b Add /has-remote-changes endpoint to remote-sync API (#67924)
* Add /has-remote-changes endpoint to remote-sync API

Adds a new endpoint GET /api/ee/remote-sync/has-remote-changes that checks
if there are changes on the remote (Git) repository that can be pulled.

Response format:
{
  "has_changes": true,
  "remote_version": "abc123...",
  "local_version": "789xyz...",
  "cached": false
}

* Update api_test.clj
2026-01-14 14:25:41 -06:00
da
aab9fc000a Increase verbosity in the caching middleware (#68121)
* Increase verbosity in the caching middleware

* Fix linter

* Make it simpler

* Broke so many tests

* Make the linter happy

* Fix tests (going back to old implementation)
2026-01-14 17:22:58 -03:00
Timofey Kachalov
28baf73201 Cache Main App/Embed JS fonts (#68120) 2026-01-14 23:14:22 +04:00
Timofey Kachalov
2a70fd1794 fix(embed-js): Fix EmbedJS/SDK dashboard styles to properly display the EmbedJS branding footer (#68090) 2026-01-14 20:06:35 +04:00
github-actions[bot]
05105be4c7 Fix: Add Error state for incorrect CSV/TSV file upload type (#67150)
* Fix: Add Error state for incorrect CSV/TSV file upload type

Fixes #40589

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* make the AI code a bit better

---------

Co-authored-by: Claude Code[bot] <noreply@anthropic.com>
Co-authored-by: Ryan Laurie <iethree@gmail.com>
2026-01-14 15:53:57 +00:00
github-actions[bot]
c0663854c7 Fix: Having : as a special character in the DB name breaks the UI for Clickhouse (#68013) 2026-01-14 10:51:18 -05:00
Alexander Polyankin
fe4d26ab63 Fixes computing columns for Question -> Question -> Model on the FE (#68140) 2026-01-14 09:46:11 -05:00
Edward Paget
30cd48cc7c feat: add table/field/segment support to remote-sync (#67327)
Fully support remote-sync for data studio and make it easier to extend support for remote-sync to other models in the future.

    Supports serializing tables with their fields and segments via remote sync if they are published to a remote-synced collection.
    Supports dirty state tracking of tables, fields, and segments.
    Refetches dirty state when tables, fields, and segments are created/updated/deleted in the data studio
    Make sure the data studio still displays content when remote-sync is in read-only mode
    enforce that tables, fields, and segments published to remote-synced collections are not editable when remote-sync is in read-only mode.
    Deletes, tables, fields, and segments from the remote-sync repo when they are removed or archived.

This also creates two small abstractions to make it easier to add new models (like measures) to remote-sync when they are ready. The remote-sync.events models adds a defmodel-change-handler macro that setups handlers for -create, -update, -delete, events models can publish that the dirty state tracking will follow.

Right now the UI does not change when the data studio is in read-only mode, but all create/edit operations correctly return 403 if you try to modify things. I have a follow up branch to support disabling UI elements.

* feat: add dirty tracking for model changes

* feat: add table, field, segement to dirty state query

* feat: extract fields with tables and segments

when exporting a collection with a table we want to include its children
like fields and segments

* feat: allow importing/deleting tables

and segments and fields

* feat: add dirty tracking for field/segment

* feat: show path in git sync controls

* fix: skip archived segments in table descendants

* fix: view library when in read-only remote-sync

Allows users to still see library folder they can't edit when that
condition is caused by the library being remote-sync enabled and the
instance being in remote-sync mode.

* feat: use remote-sync status in table edit perms

* fix: don't use include_editable_data_model param

...in data studio so these will still load when remote sync is read-only.

* fix: always show dirty library state on removed

We can't track what collections a table was removed from so we just want
to show the library as dirty if any item has a 'removed' dirty state.

* chore: update modules config

* chore: fix kondos

* fix: only publish table update events on ee

otherwise a handler doesn't exist which causes them to throw

* fixup! whitespace

* refactor: make the table changes rendering simpler

* fixup! missing when

* fixup! tests shouldn't mock modules

* fix: make dirty tracking work with new implementation

* refactor: make the hydration step in events cleaner

* fix: make sure dirty returns lowercase

* fix: delete segments correctly

* fixup! remove extra module

* fixup! correct migration order

* fixup! FE lints and typecheck

* fixup! remove correct extraneous module

* Update enterprise/frontend/src/metabase-enterprise/remote_sync/components/ChangesLists/EntityLink.tsx

Co-authored-by: Anderson <ands.alves.nunes@gmail.com>

* fixup! PR feedback and tsc

* fixup! segment failing to redirect

* fixup! better fix for navigate after save

---------

Co-authored-by: Anderson <ands.alves.nunes@gmail.com>
2026-01-14 08:35:16 -06:00
Jeff Bruemmer
b3f62b6b6d note on slack notifications (#68153)
* note on slack notifications

* fix link
2026-01-14 09:23:07 -05:00
github-automation-metabase
50270bc649 Auto-update documentation for master (#68167)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-01-14 09:09:04 -05:00
Romeo Van Snick
b595e58985 Fix a broken model e2e test (#67499) 2026-01-14 15:07:53 +01:00
John Swanson
08b309f4c8 Fix perms checks for unarchiving collections (#67729)
* Fix perms checks for unarchiving collections

* use `can_restore` as single source of truth
2026-01-14 05:10:29 -08:00
John Swanson
db6808c754 Nicer error msg when trying to pull tenant colls (#67948)
Previously if you tried to pull from a remote sync repository that
contained tenant collections you would get an incomprehensible error
that didn't explain what was wrong.

[EMB-1104: Update error message when trying to pull Tenant collections with Tenants turned off](https://linear.app/metabase/issue/EMB-1104/update-error-message-when-trying-to-pull-tenant-collections-with)
2026-01-14 05:08:42 -08:00
John Swanson
ce252a2a8b include API keys when copying DBs (#67998)
[UXW-2690: Instance restored from a H2 snapshot is missing API keys](https://linear.app/metabase/issue/UXW-2690/instance-restored-from-a-h2-snapshot-is-missing-api-keys)
2026-01-14 05:08:27 -08:00
John Swanson
701b90bb9c Increase the unreturned cxn timeout for appdb (#67571)
For the data warehouses we can safely set a fixed
unreturnedConnectionTimeout to a fairly low number (5 minute default)
because we also have a default maximum query length for data warehouses,
which can't be exceeded anywhere.

For the application DB though, this isn't safe, as operations against it
can legitimately hold connections for extended periods - for example,
dump-to-h2 on large databases streams data for as long as it takes to
copy all rows.

Bumping this to 1 hour by default (configurable via
`MB_APPLICATION_DB_UNRETURNED_CONNECTION_TIMEOUT`).
2026-01-14 05:08:10 -08:00
John Swanson
16c291f753 Add support for transforms to collection items (#67832)
* add support for transforms

* tests and hide from non-superusers
2026-01-14 05:07:54 -08:00
dpsutton
a469da0955 simpler tests? you decide (#68144)
* simpler tests? you decide

* ignore discouraged var. just tests. don't need api docs

* avec-server -> with-server

originally there was another `with-server` and i didn't want to clobber
it until i was ready

* bit of cleanup

* cljfmt

* bit of linter appeasement
2026-01-13 16:39:38 -06:00
Nemanja Glumac
9a6393ff4a Enable codespell for dev folder (#68124) 2026-01-13 15:23:18 -07:00
Jeff Bruemmer
46bef2348b docs - skip txt (#68154)
skip txt
2026-01-13 17:10:42 -05:00
John Swanson
0266970fff Admins see personal collections for tenant users (#67949)
* Admins see personal collections for tenant users

[EMB-1110: As an Admin, I want to be able to see personal collections of external users](https://linear.app/metabase/issue/EMB-1110/as-an-admin-i-want-to-be-able-to-see-personal-collections-of-external)

This was already implemented on the BE. Claude helped with the FE but I
think it seems reasonable.

* CR resp

* Remove pagination
2026-01-13 13:17:40 -08:00
Riley Thompson
88ec74f7b3 Upgrade clickhouse to 0.9.5 (#67297) 2026-01-13 15:48:20 -05:00
bryan
b07c00001d Fix nested & misused is in tests found by new kondo version (#67939) 2026-01-13 13:08:40 -07:00
Gabriel Moreno
11e96da48b UXW-2666 Row chart tooltip doesn't reflect column rename (#68137)
* fix: prefer title_text over display_name

* test: add dimension key unit spec

* refactor: rename to `columnTitle`
2026-01-13 18:11:57 +00:00
Thomas Schmidt
5ab27ac0fb Support measures and segments for Metabot (#67368)
* Support measures and metrics for Metabot

* Add segment support to metric querying

* Minor clojure tweaks for metabot measures and segments poc (#67425)

* Handle measures and segments like metrics in table-details and card-details

Move these into the existing m/assoc-some and handle them in the same way as metrics

* Remove query->output-format

This was just a wrapper around lib/->pMBQL, but all these queries are already MBQL5 queries

* Metabot measures and segments cleanup and tests (#67488)

* some refactor

* draft some happy path tests

* use lib for creating measure and segment clauses

* fix tests

* fix some filter tests to use mbql5

* update more tests

* fix more tests

* fix model query syntax

* lint

* remove normalize call again

* add back normalize

* minor test cleanup

* address comments

* tweak another test

* add a query= helper that strips uuids, update a number of tests to use it

* add basic error handing for bad ids

* Minor cleanup

* address mike's comment

---------

Co-authored-by: appleby <86076+appleby@users.noreply.github.com>
Co-authored-by: Noah Moss <32746338+noahmoss@users.noreply.github.com>
Co-authored-by: Noah Moss <noahbmoss@gmail.com>
2026-01-13 12:50:17 -05:00
Alexander Polyankin
7cd7ef457a Broken & unreferenced entities - add pagination, expose tables (#68110) 2026-01-13 16:42:14 +00:00
Edward Paget
79f43e1259 fix: don't unset remote-sync settings on api req (#67605)
* fix: don't unset remote-sync settings on api req

...if request is partial. Fixes an issue where the token could be unset
when switching between read-write and read-only modes.

* add debugging

* remove debugging

* fixup! handle extra collection

* fixup! update syntax
2026-01-13 09:46:14 -06:00
Jakub Chodorowicz
739a915d5d Uxw 2596 color range on table crashes it if values span a large scale (#68022) 2026-01-13 16:39:04 +01:00
Anderson
5c6c075103 Improve design of no-owner state in data structure side panel (#67772)
* Improve design of no-owner state in data structure side panel

* fix e2e test
2026-01-13 12:12:03 -03:00
Denis Berezin
2d76c2fb74 Fix public powered by metabase footer link (#68069)
* Fix public powered by metabase footer link

* Fix tests
2026-01-13 17:43:47 +03:00
Jakub Chodorowicz
63be293945 Prg 160 add ssl grouping config (#67682)
Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
2026-01-13 15:09:30 +01:00
Nemanja Glumac
221b76041d Enable codespell GH action for frontend (#68073) 2026-01-13 13:05:19 +00:00
Nemanja Glumac
8d4837c6b0 Remove deprecated moveDnDKitElement helper (#67604)
Co-authored-by: metabase-bot[bot] <109303359+metabase-bot[bot]@users.noreply.github.com>
2026-01-13 13:49:49 +01:00
github-automation-metabase
7879a97840 [chore] Update llms.txt for master (#68113)
Update llms.txt and llms-full.txt (master)

Regenerated llms.txt to include all markdown files from the docs/ directory.
Also generated llms-full.txt files for the embedding section.

🤖 Generated automatically by llms-txt-generate workflow

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <actions@github.com>
2026-01-13 09:49:53 +00:00
github-automation-metabase
4d820ce589 Auto-update documentation for master (#68105)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-01-12 20:35:29 -05:00
Nathan Voxland
cdcaaf5f33 If the entity id is unknown when calling the export API, throw an error (#68040) 2026-01-12 17:07:26 -06:00
bryan
a2e9d66d7a Disable regex lookaheads/lookbehinds for Snowflake driver (#68084) 2026-01-12 14:57:04 -07:00
github-actions[bot]
04d68a6a41 UXW-46 In Safari Scalar is truncated like a string rather than a number (#67207)
* Fix: In Safari, Reducing the Tile Size of a Number viz Might Result in the Number Being truncated like a String Rather than Rounded

Fixes #51082

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* UXW-46 refactor claude solution, implement proper fix

* Update Loki Snapshots

* Fix e2e tests

---------

Co-authored-by: Claude Code[bot] <noreply@anthropic.com>
Co-authored-by: Denis Berezin <denis.berezin@metabase.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-13 00:10:24 +03:00
Denis Berezin
ad87e0366a UXW-64 Fix table cell formatting for temporal breakout by week (#67477)
* UXW-64 Fix table cell formatting for temporal breakout by week

* Review fixes
2026-01-13 00:09:17 +03:00
Edward Paget
90ba3e39e7 feat: split can-read from can-query in data model (#67815)
* feat: split can-read from can-query in data model

Adds a new kind of permission type `can-query` that describes when a
user is allowed to query the data model vs `can-read` which encompasses
when a user can see the metadata of a query. This makes api calls to
read table metadata more straightforward and the eventual integration
with remote sync easier.

* feat: update api to use can-edit/can-query params

* fixup! allow can-write-metadata queries

* fixup! field test

* fixup! whitespace

* fixup! returning empty lists instead of 403

* fixup! schema response

* fixup! enforce query permissions where necessary

* fixup! add last tests

* fixup! can query filter before saved questions
2026-01-12 14:46:40 -06:00
Cam Saul
ee9ba65762 Introduce module :friends (#67292) 2026-01-12 11:54:04 -08:00
Cam Saul
469c2f4681 Minor cleanup: fix function with side effects name (#68044) 2026-01-12 18:29:26 +00:00
Maksym Yakubych
696d8eec73 fix: text selection inside card embed when interacting with card embeds (#67761) 2026-01-12 13:25:34 -05:00
kevinkelleher12
fbe2dd23a4 Fix funnel viz to handle increasing data (#68031) 2026-01-12 18:02:03 +00:00
Anderson
85fe26063c Fix data studio missing some hover states (#67891)
* use ActionIcon for data studio toggle icon

* Use ActionIcon on snippet tree row ellipsis menu

* Change link colors in breadcrumbs on hover

* Use ActionIcon for tree expand icon

* Update hover state on transforms runs filters

* Fix data studio nav collapsed logo hover

* Match hover state on snippets collection and snippets folders

* Fix ts
2026-01-12 14:24:53 -03:00
Alex Yarosh
afdd39c531 docs: Update link to Metabase pricing page (#68067)
Update link to Metabase pricing page
2026-01-12 14:35:23 +00:00
github-automation-metabase
fb22fed921 Auto-update documentation for master (#68052)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-01-12 09:34:12 -05:00
Edward Paget
07c5e47251 fix: remote-sync origin url errors (#67594)
* fix: remote-sync origin url errors

Fixes issues where the tmp directory for the remote-sync repo can get
into a bad state where it doesn't have an origin URL. This calls a
function that explicitly sets our desired origin URL and updates all
calls to use the more specific :remote-url keyword rather than
inconsistently using :url vs :remote-url.

* Update git_test.clj
2026-01-12 08:23:50 -06:00
metamben
fd9a13e05e Measures cleanup (#67960)
* Extract cycle detection algo and fix available-measures
* Rremove duplicated code.
* Add more serialization tests
* Fix CLJS
* Remove MBQL4 support for measures at the  model level
2026-01-12 17:09:35 +03:00
Phoomparin Mano
a46764dd85 Add virtualized grid to collection list view for tenants (#67958)
Co-authored-by: Sébastien <sebastien@metabase.com>
2026-01-12 13:42:20 +00:00
Phoomparin Mano
a4e306f9cf Fix issues with the llms.txt generation workflow (#68019) 2026-01-12 17:07:07 +07:00
Phoomparin Mano
4ec8c0cb0e fix(sdk): fix crashes on React 19 due to findDOMNode on Gauge and Modal (#67953) 2026-01-12 16:56:43 +07:00
Alexander Polyankin
bff5dfb5eb Fixes (#68046) 2026-01-10 03:31:22 +00:00
Aleksandr Lesnenko
0deffd6231 add loki stress test workflow (#68042) 2026-01-09 20:36:22 -05:00
Aleksandr Lesnenko
9311117086 tweak tree table column widths (#68035)
* tweak tree table column widths

* tweak widths

* specs
2026-01-09 23:11:23 +00:00
Alex Yarosh
9a37785bc3 Fix sentence in JWT Identity Provider URI description (#68038) 2026-01-09 17:44:33 -05:00
Alexander Polyankin
3b52306116 Add a filter for personal collections; preserve filters in the URL, drop the broken dependencies page for now (#68008)
* wip

* wip

* wip

* wip

* wip

* Filters

* wip

* tests

* Fixes

* wip

* Add e2e test

* wip

* comment

* wip initial personal coll filter

* allow deleting personal collections

* used by

* whitespace

* lint

---------

Co-authored-by: Riley Thompson <rileythompson99@gmail.com>
2026-01-09 17:33:18 -05:00
Edward Paget
7a753622e0 fix: make sure we don't sync unchanged collections (#67795)
* fix: make sure we don't sync unchanged collections

* fixup! bad test
2026-01-09 15:55:35 -06:00
Edward Paget
09860c0032 fix: limit can_run_adhoc_query hydration (#68028)
We can't hydrate unbounded collections of cards with the
can_run_adhoc_query hydration since it linearly adds db queries to the
request. Users with > 10000k can have severe CPU/memory issues
processing this.
2026-01-09 15:55:06 -06:00
Egor Grushin
48e3b5c190 QUE-2687 remove automatic selection of the first option in TemporalUnitDropdown. (#67598) 2026-01-09 20:59:29 +00:00
Ryan Laurie
08421d9ec1 remove broken overlays demo (#67995) 2026-01-09 20:26:08 +00:00
Jeff Bruemmer
1c9de41832 docs - fix translation docs (#68011)
* fix translation docs

* comment

* sdk

* data

* cleanup
2026-01-09 20:03:15 +00:00
Nemanja Glumac
4497b15aed Enable codespell for modules/drivers (#68010) 2026-01-09 20:58:06 +01:00
Abed Habli
7a8b15e271 Docs: AI exploration (#67859)
* AI exploration section

* Optimized 2 images, saved 221.4 KB (51.0% smaller)

* fix the heading link

* Update docs/ai/metabot.md

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>

* Update docs/ai/settings.md

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>

* few edits

* typo

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
2026-01-09 20:54:55 +01:00
Nemanja Glumac
75476f4ae1 Fix frontend typos (#68003) 2026-01-09 20:41:58 +01:00
Robert Kosara
4e60b2efeb Mention search in data picker docs (#67930)
* Replace data picker image, add a line about search

* Optimized 1 images, saved -360 bytes (-1.1% smaller)

* Fix screenshot and description to talk about v58

* Incorporate bot suggestion

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-09 10:41:05 -08:00
Alex Yarosh
3315a9a65a plan callouts (#68014) 2026-01-09 13:33:29 -05:00
Alex Yarosh
d20f6f9f11 docs: minor edits to tenants (#67961)
* Fix heading level for multi-tenant strategy section

* Correct 'Admin setting' to 'Admin settings'

Updated references from 'Admin setting' to 'Admin settings' for consistency throughout the document.

* more edits

* typos
2026-01-09 13:32:51 -05:00
Riley Thompson
686de1c06e Exclude archived items from unreferenced and broken items endpoints (#67950) 2026-01-09 13:08:25 -05:00
Nicolò Pretto
7b6b37264a docs(sdk): remove links to package changelog (#67997) 2026-01-09 18:37:36 +01:00
Nick Fitzpatrick
20ec35314f Typesafe c and bg props (#67899)
* most of the way there

* happy types, simpler themeColor function

* PR Feedback

* make editor buttons slightly less offensive

* final adjustments
2026-01-09 13:31:22 -04:00
Maksym Yakubych
c7f4935d58 fix: ellipsis breadcrumb shows gray background in Dark Mode (#67805)
* fix: ellipsis breadcrumb shows gray background in Dark Mode

* refactor: use `metabase/ui` in `CollectionBreadcrumbs`
2026-01-09 16:55:35 +00:00
Nemanja Glumac
80b6060ab7 Enable codespell for the enterprise folder (#67979) 2026-01-09 17:55:26 +01:00
Timofey Kachalov
b374706a41 feat(embed-js): Change guest embeds disabled tooltip to display a link to admin settings (#67925) 2026-01-09 20:29:38 +04:00
github-automation-metabase
a131b9c921 Update llms.txt and llms-full.txt (master) (#67993)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <actions@github.com>
2026-01-09 16:00:20 +00:00
Nicolò Pretto
d4486c9dff fix: make dashboard header use container queries to respond to metabot (#67376)
* fix: make dashboard header use container query

* use named container everywhere

* fix syntax

* rename and extract to other class
2026-01-09 16:59:33 +01:00
Sébastien
ad5cc2877d Allow passing arrays of string as sqlInitialParameters in InteractiveQuestion (#67282)
* Allow passing arrays of string as sqlInitialParameters in InteractiveQuestion

* nit

* better assertion

* pass all initial sql params as is
2026-01-09 16:50:19 +01:00
Jeff Bruemmer
88647269a0 docs - embedding browser options (#67957)
* browser params

* array options
2026-01-09 10:08:14 -05:00
Anderson
f0ab6a222c Show border on segment name editable content in new segment page header (#67946)
* Use PaneHeaderInput on NewSegmentHeader

* Apply segment name on content change
2026-01-09 12:01:39 -03:00
Nicola Mometto
cc9f7e63ad submit transform metering data to metering endpoint (#67902) 2026-01-09 14:36:52 +00:00
Nemanja Glumac
3798862761 chore: Fix safe typos in enterprise/ folder (#67912) 2026-01-09 15:05:04 +01:00
Nemanja Glumac
3fbd0ff00d Increase the viewport width for a flaky "data studio" e2e tests (#67901) 2026-01-09 16:55:20 +03:00
Nicola Mometto
9f30405075 Fix serialization in the presence of xray of usage analytics (#67848)
* feat: separate analytics vs non analytics escapes

* update callsite

* fix: descendant-ids requires coll models

* add test

* fix: nil safe
2026-01-09 09:43:16 +00:00
Romeo Van Snick
363baef1d9 Add defensive toString to column name (#67893) 2026-01-09 14:10:36 +11:00
github-automation-metabase
6cdb558f0e Update generated docs (master) (#67966)
Co-authored-by: github-actions <github-actions@github.com>
2026-01-08 20:40:59 -05:00
Aleksandr Lesnenko
13ec0f6fc8 fix tasks table (#67964) 2026-01-09 01:34:50 +00:00
Nick Fitzpatrick
a6b5cb7371 updating ClauseStep type, fixing segment and measure editors (#67956) 2026-01-08 20:53:19 -04:00
Maz Ameli
2e6bb7f669 Rename Tasks page to Dependency diagnostics and update labels (#67935) 2026-01-08 19:23:03 -05:00
Egor Grushin
c822a6228c GIT-8461 hide "Visualize another way" button for Sankey charts (#67753) 2026-01-08 23:46:37 +00:00
Phoomparin Mano
2229469ca4 Add documentation button to tenants list page (#67884) 2026-01-08 23:03:36 +00:00
Egor Grushin
43ad099236 fix flake unit test (#67942) 2026-01-08 21:53:50 +00:00
Braden Shepherdson
b496e05834 [perf] Add a unit test for #66955 as a follow-up (#66958) 2026-01-08 16:18:09 -05:00
Gabriel Moreno
2847a18d60 UXW-2623 Update dependency graph labels and icons for sql questions (#67870)
* feat: include query type on dependencies api response

* feat: show icons based on query type

* test: add unit spec for dependency graph utils

* refactor: restore changes after merge conflict
2026-01-08 16:05:42 -04:00
Braden Shepherdson
413b32e277 [perf] Expand the qp.perf-test benchmark suite (#65806)
Lots of new tests, mostly aimed at seeing how the lib and QP scale on
clear dimensions: more stages, wider tables, joins, big expressions.
2026-01-08 19:43:10 +00:00
Alex Yarosh
4a2fade226 Revert "remove transforms once again (#67809)" (#67945)
This reverts commit 7f08349271.
2026-01-08 14:27:24 -05:00
Gabriel Moreno
117e2a22a3 UXW-2629 Adjust library breadcrumb folder expansion behavior (#67862) 2026-01-08 15:25:24 -04:00
Braden Shepherdson
cd5bc6bf72 [QP] Reject fuzzy match of join condition LHS to that join's RHS (#67804)
Fixes #67667.

`lib.equality` has logic to do "fuzzy" matching of field refs with
the corresponding columns. This logic exists to support somewhat
malformed older queries with impoverished field refs. Refs in healthy
recent queries are fully specified and there's nothing ambiguous to
required the fuzzy matching.

The bug #67667 caught a case where this fuzzy matching could find a
column with the same name on the RHS of a join, if the original target
column has disappeared from an upstream query.

This change causes the QP to fail in exactly this case: when a would-be
LHS ref in a join condition ends up pointing at the RHS of the same
join. That would produce a strange join condition like
`LEFT JOIN Orders ON Orders.ID = Orders.PRODUCT_ID`
which produces incoherent results, and could result in a very costly
Cartesian join.

This is a user-level error, since the query is broken and needs repair.
2026-01-08 14:13:34 -05:00
kevinkelleher12
f1626f8a81 Fix search links for actions and segments 2026-01-08 19:03:19 +00:00
Nemanja Glumac
c82582de0c Provide Cypress env (CI) for the support file (#67934) 2026-01-08 18:49:56 +00:00
Nemanja Glumac
d124262aca Fix typo in a group membership endpoint (#67923) 2026-01-08 12:36:32 -06:00
Aleksandr Lesnenko
22fa6d762a skip flaking loki bar chart spec (#67929) 2026-01-08 18:26:29 +00:00
William
c771b10ea8 Readded fk field ids to v_content's fk fields (#67867)
Basically, the issue is that trying to implicit join using the instance analytics content model's made_public_by_user field made qp throw an error. The underlying problem is that the field in question wasn't defined as a foreign key, even though the model's result metadata claimed it was a foreign key. Updating the field to actually be a foreign key fixed the problem. This also applies to the content model's question_database_id field, which was also fixed.
2026-01-08 10:57:49 -06:00
Nemanja Glumac
98adee8b12 Fix typos in Metabot code (#67918) 2026-01-08 17:18:17 +01:00
Nemanja Glumac
7fe74f70f8 Fix typo in test metadata (#67909) 2026-01-08 14:58:36 +00:00
Riley Thompson
63ce826faa Use source alias for breakout with implicit join (#67480)
* use source alias if we have a fk-field-id

* try only on single stages

* try to identify implicit joins

* try fix

* Clean up

* move test and test all drivers

* move test

* only test drivers with left join feature

* adjust length for certain drivers

* smaller for mysql

* fix starts-with filter

* improve test

* case insensitive filter for h2

* move let bindings up
2026-01-08 14:55:19 +00:00
Nemanja Glumac
63f180034d Fix typos in FE enterprise code translations (#67908) 2026-01-08 15:45:37 +01:00
Anderson
89fa63c99b Show Metabot button on transform creation page (#67837) 2026-01-08 11:19:40 -03:00
Timofey Kachalov
670828db79 Fix flaky "shows a static dashboard using drills" e2e (#67874) 2026-01-08 17:43:16 +04:00
Timofey Kachalov
88684d3b2c fix(embed-js, sdk): Properly handle restricted collection access in MetabaseBrowser (#67523) 2026-01-08 16:47:53 +04:00
Phoomparin Mano
d7b8e86987 Remove static llms.txt and embedding/llms.txt (#67878) 2026-01-08 19:45:56 +07:00
kevinkelleher12
322a6a0323 Reroute admin/tenants to admin/people/tenants (#67869) 2026-01-08 07:34:21 -05:00
Timofey Kachalov
d5b070b8a3 fix(SDK, Embed JS): Hide Static Question topbar from DOM when no children rendered (#67852) 2026-01-08 14:33:40 +04:00
Timofey Kachalov
aa5a35f94c Add support for content translations for auth embeds (#67730) 2026-01-08 14:33:01 +04:00
Romeo Van Snick
adf35e2c4c Use formatting settings for date parameters (#67713)
* Use instance settings for date filters in parameters

* Fix test instance to use default time format

* Add unit tests for date paramater formatting with custom settings
2026-01-08 14:23:42 +11:00
Alexander Polyankin
da9fbb67a2 Dependencies - unreferenced & broken items (#66279)
* entity check jobs seems to work

* reset analysis version of children when updating a card or transform

* transform event tests

* event handlers actually update analyses

* transform runs update dependents and api exists

* initial changes

* params

* fixes

* Fix

* Fix

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* X

* Fix

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fix

* Fixes

* Fixes

* Fixes

* Fixes

* added more tests

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Columns

* Fixes

* Errors column

* Fixes

* Revert "added more tests"

This reverts commit c7188e55dd.

* Fixes

* Revert "Revert "added more tests""

This reverts commit 66b2b7efd9.

* fix compilation

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Add link

* make query validation errors consistent and return them in the broken api

* use the same table

* Fixes

* Fixes

* Fixes

* /graph/broken and /graph/unrecognized match /graph

* Fixes

* Polish

* possibly fix check_card 500

* Fixes

* refactor query-error

* add test for validating sql column with no available sources

* backfill correctly finds dashboard dependencies

* fix comment wording

* bump current-analysis-version

* revert error type names

* make validation exceptions store errors in the standard format

* rename unknown-error to validation-error

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fix

* Fixes

* panel

* Fixes

* Fixes

* Fixes

* Save

* Fixes

* Add missing types

* Fixes

* Fixes

* Fixes

* also analyze segments

* add more tests

* Fixes

* Fix

* Fixes

* Fixes

* status

* Hide dependencies when not analyzed

* Tests

* Tests

* Tests

* Tests

* Tests

* Fixes

* Fixes

* Remove columns

* run backfills immediately during test startup

* remove token checks for maintaining dependencies

* run dependency backfill on serdes

* Tests

* Fixes

* Fixes

* Fix types

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* Fixes

* fix bug involving nonstandard tag spacing

* fix some broken tests

* Tests

* Tests

* Fixes

* Tests

* Fixes

* FixesTests

* Fixes

* Fixes

* Tests

* Fixes

* Fixes

* Fixes

* Fixes

* fix another test

* Fixes

* Add missing info

* Fix tests

* Hydrate collection for snippets

* Fix

* Fixes

* revert unnecessary change

* some lint fixes and a bunch of schemas

* fix compilation

* lint fixes

* new events derive from metabase/event

* simply specs to work with open-api spec gen

* dependency backfill checks tokens again

* Fixes

* fixed tests

* don't use ::ns/blah in a #_ comment when ns isn't in scope

* fix more tests

* add and update more tests

* lint fixes

* fix linting and make lib changes backwards compatible

* maybe fix more tests?

* fix ns name

* fix another test

* update modules config

* fix one test and debug second

* more test debugging

* yet more test debugging

* fix test?

* fix more tests?

* revert another unexpected migration change

* fix another test

* fix linting

* fix more tests

* update openapi spec

* fix unrelated bug with nil ancestors

* add test for nil ancestors bug

* Remove /status call

* Merge

* Fixes

* Fixes

* Fixes

* Fixes

* review feedback

* more cleanup

* yet more cleanup

* support unnormalized snippet tags

* more review feedbac

* fix build

* fix linting

* update lib.parameters.parse to match driver.common.parameters.parse

* fix compilation

* fix tests

* fix more tests

* fix more tests

* possibly fix linting

* fix more tests

* fix even more tests

* maybe fix tests?

* actually make backend support currently impossible snippet names

* Use TreeTable

* Remove unused component

* Renaming

* FE fixes

* switched migration to 59

* fully revert 58 migrations

* most review feedback

* factor out common setup from events tests

* deduplicate analysis gen tests

* more test fiddling

* final review feedback

* actual final review feedback

* linting

* Fix merge

* Fix lint

* reformat

* add measures to spec

* fix final test

* Fix links

---------

Co-authored-by: William Zimrin <william@metabase.com>
2026-01-07 21:57:09 -05:00
github-automation-metabase
314122dee5 Update generated docs (master) (#67890)
Co-authored-by: github-actions <github-actions@github.com>
2026-01-07 21:50:19 -05:00
Nick Fitzpatrick
3975adb932 Color Key Cleanup (#65855)
* Migrating Color Keys

temp

background keys

fixing some errors

border colors

removing duplicate keys

types

* enforce types on c prop

* more type cleanup

* rebase catch up

* unit tests

* re-add embedding sdk setup plugin

* e2e tests

* Sankey chart using correct label color in static viz, fix background detection

* some loki changes

* adjust border color

* sdk: updated sdk color mapping

* type adjustments, unit test fixes

* loki tests passing

* last e2e test

* cleanup

* Update Loki Snapshots

* updating alert colors

* Claude nits

* Update Alert Loki tests

* text-tertiary

* Update Loki Snapshots

* missed one

* last adjustmnet

* Update Loki Snapshots

* text-white

---------

Co-authored-by: Phoomparin Mano <phoomparin@gmail.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-07 21:15:01 -04:00
Aleksandr Lesnenko
65a2afa186 Let users with data model perm access data studio (#67653)
* let users with data model permission access data studio

* respect manage table metadata permission in data studio

* fix ci

* fix flaky specs

* review

* review
2026-01-08 01:14:00 +00:00
Alexander Polyankin
c65cbf7791 Remove description (#67887) 2026-01-07 16:26:04 -08:00
Alexander Polyankin
42312e5fc2 Allow to overwrite id only for native models (#67773) 2026-01-07 23:56:31 +00:00
Egor Grushin
376ff5d1cf UXW-49 fixing log scale axis value (#67668)
Use a linear scale for values between 0 and 1, and a logarithmic scale for values greater than 1.
2026-01-07 22:54:03 +00:00
Aleksandr Lesnenko
8cbc058a0c table measures (#66576)
---------

Co-authored-by: Tamás Benkő <tamas@metabase.com>
Co-authored-by: metamben <103100869+metamben@users.noreply.github.com>
Co-authored-by: Alexander Polyankin <alexander.polyankin@metabase.com>
Co-authored-by: Brad Anderson <brad@metabase.com>
Co-authored-by: Romeo Van Snick <romeo@romeovansnick.be>
2026-01-07 22:41:11 +00:00
Romeo Van Snick
6b04138204 Add tooltip to run button when field filters are missing a target field in Native editor (#67670)
* Add e2e test for missing field mapping

* Make validateTemplateTags public

* Render errors in run button tooltip

* Pass errors to run button

* Fix selector in test

* Simplify test

* Try with realClick

* Add Lib implementation of validateTemplateTags

* Also validate display-name

* Use Lib implementation of validateTemplateTags

* Localize error messages

* fixup! Add Lib implementation of validateTemplateTags

* Add Lib.ValidationError type

* Remove unused code

* Also validate temporal-units dimension

* Use validate-template-tags in can-run

* Add tag-name to template tag errors

* Fix malli types

* Add tests for validate-template-tags

* Add tooltip to save button too

* Also test time grouping and save button

* Remove more unused code

* Remove simple wrapper

* Use cond-> pattern instead of filter identity
2026-01-08 09:26:43 +11:00
Phoomparin Mano
65d34e4e37 Add GitHub actions to generate llms.txt and llms-embedding-full.txt at repo root during release (#67737)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <actions@github.com>
Co-authored-by: github-automation-metabase <166700802+github-automation-metabase@users.noreply.github.com>
2026-01-07 22:17:34 +00:00
Phoomparin Mano
fd6740eee7 Block official collections in shared collections and its subcollections (#67537) 2026-01-08 05:12:16 +07:00
Riley Thompson
18cf4fac85 fix typo (#67863) 2026-01-07 16:44:43 -05:00
da
17349e4f12 Fix logging messages for field values updates (#66767)
We're logging "Field Field xxx xxxx". I'm just simplifying that log line
2026-01-07 14:43:59 -06:00
kevinkelleher12
bfb78b8d6a Fill line series symbol with appropriate color in dark mode (#67811) 2026-01-07 18:41:03 +00:00
Rodrigo López Dato
343ae8d213 Fix -{un,}install-merge-drivers when using a git worktree (#67865) 2026-01-07 15:28:25 -03:00
kevinkelleher12
e69b260f0b Fix dark text on dark verification tooltip background in light mode (#67799) 2026-01-07 12:13:49 -05:00
Anderson
336180ee0c Make stacked row chart tooltips use custom display names (#67706)
* Make stacked row chart tooltips use custom display names

* Change the way we override seriesName with custom title
2026-01-07 12:16:57 -03:00
Nemanja Glumac
d5925e28f7 Bump Storybook to 8.6.15 (#67473) 2026-01-07 15:52:37 +01:00
Jakub Chodorowicz
5bc3af2630 update husky to v9 (#67846) 2026-01-07 15:41:35 +01:00
kevinkelleher12
c5f433336a add kevinkelleher12 to team.json and github-slack-map.json (#67752) 2026-01-07 08:25:39 -05:00
Dan Stone
dca8465cfb Index incremental filter column for OLTP databases (#66478)
* Add indexes to incremental checkpoint filter columns

* Fix bad barf

* Fix drop-index arg-list

* Less indirect supports test

* Comment on sync order

* Cleanup driver changelog entry

* Move changelog note back to 0.58 (backport)

* Fix passive voice
2026-01-07 12:36:53 +00:00
Alexander Polyankin
618e6e8701 Hide graph watermark (#67839) 2026-01-07 02:13:36 +00:00
Romeo Van Snick
6f637ee050 Test coercion strategy (#67496)
* Also test coerced datetime column

* Add e2e reproduction for opening the datetime filter for coerced fields

* Also check :effective-type in original-isa

* Fix test
2026-01-07 11:19:18 +11:00
github-automation-metabase
c06b4c2511 Update translations 2026-01-06 (#67800)
* Update translation sources 2026-01-06

* Update translations

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-01-07 00:13:27 +00:00
Alex Yarosh
7cdbdc8e94 docs: Add redirect for interactive embedding quickstart guide (#67833)
* Add redirect for interactive embedding quickstart guide

* Add redirect for quick-start guide
2026-01-06 18:48:13 -05:00
dpsutton
1bcc022f6b Reduce number of modules that trigger driver tests (#67769)
Before:

76 modules trigger:

```clojure
mage.modules=> (let [deps (dependencies)
                     all-deps (keys deps)]
                 (reduce (fn [acc module]
                           (if (driver-deps-affected? deps [module])
                             (conj acc module)
                             acc))
                         []
                         all-deps))
[actions analytics analyze api api-keys app-db appearance audit-app auth-identity auth-provider batch-processing cache channel classloader collections config content-verification dashboards database-routing driver driver-api eid-translation embedding events formatter initialization-status internal-stats legacy-mbql lib lib-be logger login-history model-persistence models notification parameters permissions pivot plugins premium-features public-sharing pulse queries query-permissions query-processor remote-sync request search secrets server session settings setup sso startup sync system task task-history tenants timeline types upload users util version view-log warehouse-schema warehouses enterprise/advanced-permissions enterprise/api enterprise/impersonation enterprise/sandbox enterprise/scim enterprise/serialization enterprise/sso]
mage.modules=> (count *1)
76
```

After:

Only 28 modules trigger driver tests:
```clojure
mage.modules=> (let [deps (dependencies)
                     all-deps (keys deps)]
                 (reduce (fn [acc module]
                           (if (driver-deps-affected? deps [module])
                             (conj acc module)
                             acc))
                         []
                         all-deps))
[actions analyze app-db cache database-routing driver driver-api legacy-mbql lib lib-be logger model-persistence models parameters pivot plugins premium-features queries query-permissions query-processor sync tenants upload warehouse-schema warehouses enterprise/advanced-permissions enterprise/impersonation enterprise/sandbox]
mage.modules=> (count *1)
28
```

How does this magic work? Just with an exclusion list:

```clojure
mage.modules=> driver-affecting-overrides
 #{batch-processing timeline eid-translation version
 enterprise/serialization initialization-status session users enterprise/api login-history sso classloader
 enterprise/sso permissions request view-log system api task-history notification task dashboards startup config
 content-verification secrets server formatter events util settings channel search collections remote-sync types
 api-keys setup enterprise/scim audit-app analytics public-sharing pulse embedding auth-identity auth-provider
 appearance internal-stats}
```
2026-01-06 16:13:33 -06:00
Anderson
0d13842be7 Fix dependency graph zoom controls colors in dark mode (#67774)
* Fix dependency graph zoom controls colors in dark mode

* Do not use :global
2026-01-06 22:00:37 +00:00
Phoomparin Mano
a7d35f1baf Add a tooltip message if model persistence is unsupported for a database (#67566) 2026-01-07 04:09:29 +07:00
Sloan Sparger
0c04a6a3c1 [Metabot] Split up metabot.unit.spec.tsx (#67764)
* init pass at adding an error boundary to metabot

* update the illustration used for error boundary and remove simulated error ui

* add unit tests

* split up the mega metabot.unit.spec.tsx unit test file

* fix lint
2026-01-06 14:47:18 -06:00
Jeff Bruemmer
1888c80fff 58 as latest (#67820) 2026-01-06 15:42:50 -05:00
Alex Yarosh
c1f08f65a6 docs: remove temp tenants docs (#67819)
* remove temp tenants

* jwt

* more link
2026-01-06 14:43:50 -05:00
Jeff Bruemmer
844123dbf2 docs - note on subscriptions (#67816)
* note on subscriptions

* typo
2026-01-06 18:54:00 +00:00
Alex Yarosh
ebd1476a1e docs: tenants (#67474)
* tenants

* Apply suggestions from code review

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>

* links

* add links and images and edit remote sync

* links

* one more link

* Optimized 12 images, saved 930.1 KB (47.9% smaller)

* Apply suggestions from code review

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>

* edits

---------

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-01-06 18:47:27 +00:00
Alex Yarosh
78263d64ce documents oss (#67810) 2026-01-06 18:19:25 +00:00
Alex Yarosh
7f08349271 remove transforms once again (#67809) 2026-01-06 13:19:19 -05:00
Jakub Chodorowicz
bfa61de71f add all .env file except .env.example to gitignore (#67793) 2026-01-06 18:27:13 +01:00
Sloan Sparger
d1a55ddff1 [Metabot] Metabot error boundary (#67749) 2026-01-06 11:02:26 -06:00
Nicola Mometto
4e3d8f0caf Move metering events off the token check (#67584)
* chore: remove private from token check defs

* feat: don't send metering stats during token check

* add task for sending metering events

* lift try-catch

* add tests

* move around

* remove duplicate set!

* send ints/bools as strings, for backwards compatibility

* reduce metering frequency

* configurable interval, clamped between 1 min and 12 hours
2026-01-06 16:46:32 +00:00
Nicola Mometto
75ab330328 Add transform metrics to anonymous stats ping (#67786)
* feat: add transforms metrics

* feat: send transform metrics

* fix: don't require transforms feature, we send stats if we disabled the feature

* add tests

* fix: ee test
2026-01-06 15:42:03 +00:00
Ryan Laurie
69abc4cf1c fix translations workflow permissions (#67794) 2026-01-06 15:37:58 +00:00
Noah Moss
6cef5f80d0 Misc cleanup in Metabot clojure code (#67701)
* Misc cleanup in Metabot clojure code

* More misc cleanup

* Lint
2026-01-06 09:27:32 -05:00
Maksym Yakubych
5dd7e2049e chore: add myself to team.json and github-slack-map.json (#67754) 2026-01-06 11:19:09 -03:00
John Swanson
2393146b8c perms for moving collections with archived kids (#67728)
Previously if you had:

```
A (curate)
  B (curate)
    C (no permissions)
      D (curate)
```

Then if C was archived, you would be unable to move or archive A or
B.
2026-01-06 04:42:49 -08:00
Mahatthana (Kelvin) Nomsawadi
112d1d0235 Fix pie chart label not visible on dark theme (#67639)
* Fix `text-secondary` not working on dark theme on charts

* Support all colors so it's easier to reason about

* Add tests

* Fix type error

* Revert part of the code that's not used anymore

* Add test snapshots

* Make overriding font more consistency across environments

e.g. From storybook to SDK to the main app.

* Fix formatting
2026-01-06 12:34:27 +00:00
Sébastien
3f2ad43a43 Fix MB in an iframe running on a subpath (#67724)
* fix MB in an iframe running on a subpath

* fix

* extracted out the util fn and added unit tests for it

* addressed comments

* fixed unit tests
2026-01-06 13:19:02 +01:00
github-actions[bot]
835b3efc27 Update translations 2026-01-06 (#67782)
* Update translation sources 2026-01-06

* Update translations

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-01-06 11:58:16 +01:00
Romeo Van Snick
d8d4537f29 Fix broken python e2e test (#67435) 2026-01-06 10:00:38 +01:00
Nemanja Glumac
3f35a82ea7 Upgrade jspdf (#67760) 2026-01-06 08:58:38 +01:00
Maz Ameli
a1a62cd603 Add keyboard shortcut to close/open side menu in Data Studio (#67413)
* add keyboard shortcut and tooltip

* refine tooltip position and delay

* also listen for left bracket [ key
2026-01-06 00:56:48 +00:00
Romeo Van Snick
8542738fe7 Add repro for metabase#45890 (#67669) 2026-01-06 09:57:51 +11:00
bryan
f290bbbbe0 Skip cloud driver tests when possible + consolidate test skipping logic (#67568)
* Skips cloud driver tests on PRs unless files changed

Cloud drivers (Athena, BigQuery, Databricks, Redshift, Snowflake) now
only run on PRs when their files changed or 'ci:all-cloud-drivers' label
is present. Full coverage maintained on master/release branches.

* [do not merge] add failing databricks test

* Revert "[do not merge] add failing databricks test"

This reverts commit de5b6fa602.

* empty commit

* Fix cloud driver skip logic to respect ci:all-cloud-drivers label

The previous condition had three checks that could block cloud drivers:
1. !inputs.skip - false when no backend files changed
2. skip != 'true' - from mage can-skip-driver-tests
3. X-should-run == 'true' - the per-driver decision

The problem: checks 1 and 2 would override check 3, so the label had no
effect. Even with ci:all-cloud-drivers, cloud drivers were skipped.

We don't need inputs.skip or can-skip-driver-tests for cloud drivers
because the X-should-run output already encapsulates the full decision:
- master/release branch → run
- ci:all-cloud-drivers label → run
- driver files changed → run
- otherwise → skip

The label is an explicit override, so it should work even on PRs with no
backend changes. Removing the redundant checks lets the label work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* mage -driver-decisions + roll in module analysis

* Remove quarantine check from test-driver action

Quarantine logic is now consolidated in mage -driver-decisions.
The check happens earlier in determine-driver-skips, so quarantined
drivers get should-run=false and their jobs never start at all.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fail CI if quarantined driver has file changes without break-quarantine label

If you change files in a quarantined driver's module (e.g., modules/drivers/snowflake/)
but don't add the break-quarantine-<driver> label, a new check-quarantine-conflicts
job will fail with a clear error message.

This prevents accidentally merging changes to quarantined drivers without running
their tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add --github-output-only flag to mage driver-decisions

Instead of using grep to filter mage output, the task now supports two modes:
- Default: prints human-readable analysis + GITHUB_OUTPUT preview
- --github-output-only: prints only clean key=value lines for CI

The workflow now calls mage twice: once for debugging visibility,
once with --github-output-only redirected to $GITHUB_OUTPUT.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add quarantine conflict checks to all driver jobs

Extends the quarantine conflict detection to ALL drivers, not just cloud
drivers. When a driver is quarantined but has file changes in
modules/drivers/<driver>/, the job will run but immediately fail with
a clear error message explaining how to add the break-quarantine label.

Changes:
- Add quarantine conflict check steps to presto-jdbc, sparksql, sqlite,
  sqlserver, and vertica jobs
- Update login-to-ecr job condition for vertica quarantine conflicts
- Fix verbose output suppression in --github-output-only mode by adding
  verbose? parameter to changes-important-file-for-drivers?

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add driver decision priority documentation and tests

Adds comprehensive documentation to driver-decision function showing the
priority order as a table, plus explanations of what triggers "driver
module affected" and the difference between cloud vs self-hosted drivers.

Adds mage.modules-test with 14 tests (78 assertions) covering:
- Quarantine logic and break-quarantine labels
- Global skip behavior
- H2 always-runs behavior
- Master/release branch behavior
- Driver module affected behavior
- Cloud driver special rules (labels, file changes)
- Self-hosted driver skip logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Comment out starburst from driver-directory mapping

Starburst tests are currently disabled in drivers.yml, so the mapping
entry was inconsistent with all-drivers list. Commented out with a note
explaining why, so it can be easily re-enabled when starburst tests
come back.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* silence messages when in github-output-only mode

* remove redundant cloud-driver change detection

- this happens inside the script instead

* get rid of redundant rules definitions

- now only print how we make decisions in help for the task

* make a temporary redshift change

* Revert "make a temporary redshift change"

This reverts commit 3d93c60368.

* format usage fn code

* light refactor

* Rename driver decision variables for clarity and flatten decision tree

- driver-module-affected? → driver-deps-affected? (module dependency graph)
- drivers-changed → particular-driver-changed? (specific driver's files)
- Reorder priority: skip/H2/master now before quarantine
- Flatten nested cond for cloud driver rules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Quarantine now respected on master/release branches

- Move quarantine check before master/release in driver-decision priority
- Quarantined drivers are now skipped even on master/release (unless break-quarantine label)
- Update tests and priority table documentation to reflect new order
- Cloud drivers don't benefit from driver-deps-affected (only label or file changes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 16:15:58 -06:00
Nemanja Glumac
1f366dde49 Update copyright year in README.md (#67677)
It's that time of the year again...
2026-01-06 11:05:33 +13:00
Aleksandr Lesnenko
edeea2ead0 fix saved question intro modal layout (#67703) 2026-01-05 21:23:25 +00:00
appleby
fbdf7574ed Fix parsing of :ERROR chunks in metabase-enterprise.metabot-v3.util/aisdk->messages (#67674)
* Fix parsing of :ERROR chunks in metabase-enterprise.metabot-v3.util/aisdk->messages

There was a typo in the case expr where :ERROR chunks were being handled like :DATA chunks, but in fact should be
handled like :TEXT chunks. This was resulting in an error like

    1. Unhandled java.lang.ClassCastException
       class java.lang.String cannot be cast to class
       clojure.lang.Associative (java.lang.String is in module java.base
       of loader 'bootstrap'; clojure.lang.Associative is in unnamed
       module of loader 'app')
                       RT.java:  847  clojure.lang.RT/assoc
                      core.clj:  193  clojure.core/assoc
                      core.clj:  192  clojure.core/assoc
                      util.clj:   56  metabase-enterprise.metabot-v3.util/aisdk->messages/fn/fn
                      ...

whenever an :ERROR chunk was encountered as we tried to ASSOC a string where we were expecting a map.

https://v4.ai-sdk.dev/docs/ai-sdk-ui/stream-protocol#text-part
https://v4.ai-sdk.dev/docs/ai-sdk-ui/stream-protocol#error-part

059d904d6e/ai_service/ai_sdk.py (L50)
059d904d6e/ai_service/ai_sdk.py (L203)
059d904d6e/ai_service/ai_sdk.py (L324)

* Add some error chunk parsing tests to aisdk-line-parse-test

* Mark aisdk-line-parse-test as ^:parallel

This does io to read some files, but should be threadsafe as no other tests read or modify those files.

* Add make-mock-error-stream-response test util

And rename make-mock-stream-response to make-mock-text-stream-response

* Add client-error-test to test streaming-request with error chunks in response
2026-01-05 12:38:04 -08:00
Sloan Sparger
a2aea53b1c [Metabot] Update Metabot icon (#67741) 2026-01-05 13:29:48 -06:00
metamben
d972f4529f Patch malli to avoid OOM related to recursive memoization (#67733)
This is a temporary fix until https://github.com/metosin/malli/pull/1250 gets merged into Malli.
2026-01-05 21:46:29 +03:00
Noah Moss
f1f47d4050 Clarify permissions model for suggested prompts generation (#67656)
* Run suggested prompts generator as admin & fix test

* Add a prompt suggestions GET perms test

* small cleanups

* use mbql5
2026-01-05 13:33:26 -05:00
dpsutton
476f61760c Handle unroutable token checks (#67698)
We have a grace periods and circuit breakers for token checks. But one
issue is that if the token check doesn't throw an error we can't catch
it.

An easy way to see this is to use a local network address that won't
respond like 192.168.random.random (i used 192.168.40.40) then see the
behavior. Without the timeouts, the threads all just get stuck here and
the page never loads. With timeouts, you'll see a few stutters until the
circuit breaker steps in, at which point you have a non-enterprise
version.

https://www.loom.com/share/6ae056dc126b48c9abe8a7ad9366fd05
2026-01-05 11:52:56 -06:00
Copilot
b24727409c Migrate React components from dispatch(addUndo) to useToast hook (#66778)
* Update React components to use useToast hook instead of dispatch(addUndo)

Co-authored-by: iethree <30528226+iethree@users.noreply.github.com>

# Conflicts:
#	enterprise/frontend/src/metabase-enterprise/tenants/containers/NewTenantModal/NewTenantModal.tsx
#	enterprise/frontend/src/metabase-enterprise/tenants/containers/TenantActivationModal.tsx
#	frontend/src/metabase/admin/people/components/GroupDetail.tsx

* type fix

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Ryan Laurie <iethree@gmail.com>
2026-01-05 10:48:54 -07:00
dpsutton
119487b1cc remove api "uses" dependency on api-routes (#67650)
* remove api "uses" dependency on api-routes

api-routes can use all modules to expose their routes. ie it uses
timeline module, etc. It has `:uses :any`.

```clojure
mage.modules=> (let [deps (dependencies)
                     all-deps (keys deps)]
                 (reduce (fn [c module]
                           (update c (if (skip-driver-tests? deps [module])
                                       :skip :run) inc))
                         {:skip 0 :run 0}
                         all-deps))
{:skip 0, :run 153}
```

This is shown in this path here:

```clojure
mage.modules=> (let [ds (dependencies)
                     ds (into {}
                              (map (fn [[k v]] (if (= v :any)
                                                 [k (-> ds keys set)]
                                                 [k v])))
                              ds)]
                 (letfn [(driver-path [to]
                           (loop [q '[[driver]]
                                  seen #{}]
                             (let [p (first q)]
                               (when-let [node (peek p)]
                                 (if (= node to)
                                   p
                                   (recur (into (vec (rest q))
                                                (for [adjacent (remove seen (ds node))]
                                                  (conj p adjacent)))
                                          (conj seen node)))))))]
                   (driver-path 'enterprise/remote-sync)))
[driver warehouse-schema api api-routes enterprise/remote-sync]
```

driver uses arehouse-schema uses api uses api-routes which uses
any. Therefore all changes will trigger driver tests.

So after these changes we observe:
- the path above is empty (drivers don't transitively use remote-sync
module) and therefore would not trigger driver tests
- only 76 modules trigger driver tests

```clojure
mage.modules=> (let [deps (dependencies)
                     all-deps (keys deps)]
                 (reduce (fn [c module]
                           (update c (if (skip-driver-tests? deps [module])
                                       :skip :run) inc))
                         {:skip 0 :run 0}
                         all-deps))
{:skip 77, :run 76}
```

What else did i try?

1. add a `#_{:clj-kondo/ignore [:metabase/modules]}` to the ns
I didn't love this because it felt a bit like cheating. All of this is
kinda annoying because it's fundamentally about changes during dev time.

2. remove the ns require and add a requiring-resolve
This actually doesn't break the module usage analysis. It's smart enough
to see this so this just makes it more tenuous and easy to miss if we
change anything while also still leaving in place the module
entanglement.

So I just fire the event from the api module when rebuilding routes and
handle the event from the api-routes module. The trick is that the api
module sends along the function to rebuild the docs

* handle cycling ref

* remove reference to deleted file

* our linter is scared of an extra line

* use an events namespace

not doing an init namespace as that's similar but not the same. Those
are for settings and models. It's fine to just require the event
namespace in the core namespace. it's a bit of ceremony but the pattern
is useful

* include random event correctly

used to be just `(first (descendants :metabase/event))`. This was
fragile and just now started to fail:

```clojure
models-test=> (first (descendants :metabase/event))
:metabase.api-routes.events/api-events
```

because of course do we things like `(derive ::api-events
:metabase/event)`.

```clojure
models-test=> (filter (comp (complement #{"event"}) namespace)
                      (descendants :metabase/event))
(:metabase.api-routes.events/api-events
 :metabase.audit-app.events.audit-log/card-event
 :metabase.audit-app.events.audit-log/cloud-add-on-event
 :metabase-enterprise.dependencies.events/transform-delete
 :metabase.activity-feed.events.recent-views/legacy-card-event
 :metabase.view-log.events.view-log/collection-read-event
 :metabase.audit-app.events.audit-log/notification-handler-event
 :metabase.audit-app.events.audit-log/user-update-event
 :metabase-enterprise.dependencies.events/transform-run
 :metabase.revisions.events/transform-event
 ...
 )
```

This has been lucky for a long time
2026-01-05 11:28:46 -06:00
Anderson
a5afeea12d Update Remote Sync branch picker to match new designs (#67620)
* Update Remote Sync branch picker to match new designs

* Move git sync controls next data studio logo

* Fix unit test

* Fix e2e test

* Address Claude's review

* Remove console.error

* Use css modules for the chevron icon transition

* Use popover instead of findByRole('presentation')
2026-01-05 17:21:58 +00:00
Ryan Laurie
4521f75ee6 stabilize flaky test (#67646) 2026-01-05 14:05:56 -03:00
Noah Moss
cfdd4de405 try to fix flake in semantic index dlq tests (#67700) 2026-01-05 09:22:27 -05:00
Noah Moss
24f586428d Disable progress bar in whitespace linter github actions workflow (#67695)
* disable progress bar in whitespace linter github actions workflow

* add extra whitespace to demonstrate failure

* Revert "add extra whitespace to demonstrate failure"

This reverts commit ba34650efd.
2026-01-05 09:21:56 -05:00
Nemanja Glumac
0128771ad1 Revert "Replace Cypress grep with Bahmutov's cy-grep (#67660)" (#67725)
This reverts commit d2d8d1fe83.
2026-01-05 14:06:11 +00:00
Timofey Kachalov
e3f05d0cbb Don't close InteractiveQuestion editor on Filters change (#67517) 2026-01-05 17:02:24 +04:00
Dan Stone
53203af754 Add some additional incremental transforms tests (#67683)
* Add some additional incremental transforms tests

* Add missing require

* Use driver/table-name-length-limit
2026-01-05 11:02:08 +00:00
Aleksandr Lesnenko
71c188c09f Use tree table in data model (#67117)
* use tree table in the data model section of data studio

* review
2026-01-02 18:05:57 +00:00
Nick Fitzpatrick
db48071330 Don't show metrics in the join step (#67490) 2026-01-02 13:01:25 -04:00
Nick Fitzpatrick
05d04cf64b specify gauge label sizes in px (#67386) 2026-01-02 12:54:35 -04:00
Nick Fitzpatrick
6c99df07a8 Adding e2e repro for drill through entity keys (#67467) 2026-01-02 12:54:19 -04:00
Alex Yarosh
5b92cc2b02 Improve formatting and clarity in serialization.md (#67587)
Updated curl command formatting and clarified instructions for exporting and importing Metabase data.
2026-01-02 10:41:15 -05:00
Vamsi Peri
fea1cbad4b Update issue templates (#67687)
* Update issue tempaltes

* Delete .github/ISSUE_TEMPLATE/task.md

* Update verbiage for new feature request
2026-01-02 10:24:50 -05:00
Jakub Chodorowicz
024c43ff39 fix: broken date filter test (#67684) 2026-01-02 16:18:14 +01:00
Edward Paget
ff4f2bf956 feat: add git settings modal to data studio (#67178)
* feat: add git settings modal to data studio

* Refactor GitSettingsModal to reuse existing settings form / Adjust some styles

* Fix and improve unit tests

* Adjust font size according to variant

* fix unit test

* fixup! mock API not other plugins in this layout

* Split unit test files and improve endpoints setup

* Implement PLUGIN_REMOTE_SYNC.GitSyncSetupMenuItem with internal rendering condition

---------

Co-authored-by: Anderson <anderson@metabase.com>
2026-01-02 11:55:52 -03:00
Nemanja Glumac
d2d8d1fe83 Replace Cypress grep with Bahmutov's cy-grep (#67660) 2026-01-01 12:33:26 +01:00
Eric Normand
e5ab9db7bc Extra remapped fields (#67601)
* kick ci

* Disable remaps when calculating columns for a query.

* Add tests for result_metadata remappings

* Add comments

* Whitespace
2025-12-31 19:07:17 +00:00
Braden Shepherdson
4786076692 [dashboards] Handle parallel FKs with dashboard filter values (#66287)
Fixes #65838.

This issue was about dashboard filters showing the wrong value when
the filter was bound to an FK field from a table with multiple FKs to
the same foreign table. The dashboard parameters logic on the BE was
choosing which FK to use for the filter values arbitrarily, meaning that
binding the filter to one of them would work fine, but the other would
give incoherent results.

The fix is to be deliberate about selecting the parameter's column. That
doesn't solve all ambiguous cases, but we really can't help if there's
a multi-hop join that has parallel edges in the middle!
2025-12-31 11:46:34 -05:00
Nicola Mometto
1e3a879116 Mage python runner (#67265)
* disable chunked encoding if using path style access

* mage task to start python runner with mock s3
2025-12-31 11:03:13 +00:00
Aleksandr Lesnenko
2c01e5063b series limit for row chart (#67419) 2025-12-30 20:08:11 -05:00
Ryan Laurie
b4912b71f1 more entity cleanup (#66659) 2025-12-30 17:23:34 -07:00
Nathan Voxland
1d8b669f62 Avoid joins of remote_sync_object with actual tables (#67042) 2025-12-30 15:37:13 -06:00
Denis Berezin
25091a2fd1 UXW-603 unified picker for Document smart link (#67408)
* UXW-603 temp unified picker

* Add unified data picker

* Fix issues with tabs, add e2e tests

* Self review
2025-12-30 18:06:54 -03:00
Timothy S. Dean
6f00f47c47 Simpler fix than first attempt. (#67300) 2025-12-30 22:41:43 +03:00
Aleksandr Lesnenko
07e2560596 fix zoom resets on resize (#67420) 2025-12-30 13:15:21 -05:00
dpsutton
d47b9d6759 bumping deps: (#67638)
- https://github.com/metabase/metabase/security/code-scanning/543
  about snowflake's of grpc netty shaded. We already have this
  version elsewhere so now it matches
- https://github.com/metabase/metabase/security/code-scanning/549
  and https://github.com/metabase/metabase/security/code-scanning/557
  issue in databricks. We bumped that and now it uses the new coordinate
  `at.yawk.lz4/lz4-java 1.10.1`
- https://github.com/metabase/metabase/security/code-scanning/496 bump commons-lang3.
  actually just exclude it from the driver jar so it will be found on parent
  classpath with coordinate from deps.edn: `{:mvn/version "3.19.0"}`
2025-12-30 10:44:10 -06:00
Chris Truter
b222e32bdf Revert "Add transforms.target_database_id column (#66953)" (#67630) 2025-12-30 16:25:10 +00:00
Jakub Chodorowicz
390e559dc2 Prg 160 add field grouping format and group host and port fields (#64280)
* Add field grouping of host and port

* Add host and port settings to postgres, mysql

* Add host and port grouping to db drivers

* Add database story with host and port group

* Add groupFields unit tests

* Fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix missing tunnel parameter value

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor and simplify field grouping function

* feat: add nested fields support for db format

* feat: remove id field, adapt new format foy postgres, mongo, mysql, remove leftovers

* feat: support nested fields, add backdrop component

* fix: group inputs loosing focus on type/render

* refactor: simplify custom containers logic

* style: adjust backdrop container style

* refactor: field mappers for db forms

* refactor: move host and port config to the new format

* refactor: cleanup old grouping approach

* refactor: remove unneeded property

* fix: typo in the html prop name

* internal: validate container style comping from BE

this value can be set be external drivers so it's better to validate  it

Signed-off-by: Jakub Chodorowicz <jakub.chodorowicz@metabase.com>

* fix: type errors

* fix: driver translation test

* refator: use existing getFlattenedFields function

* fix: flatten properties for connection property uniquness

* backtrack from ssl grouping for mongo

---------

Signed-off-by: Jakub Chodorowicz <jakub.chodorowicz@metabase.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-30 14:51:28 +01:00
John Swanson
44aa361a2c UXW-2586: Acquire locks before changing data permissions (#67201)
* Acquire locks before changing data permissions

Data permission have a read-modify-write cycle:

- we read the existing permissions to see what's currently set
- then we decide what to do (e.g. delete the existing DB level
  permissions to write a granular table-level permission)
- then we do the write

This has a race condition if multiple Metabase instances are modifying
permissions at the same time.

For example, say we have:

Initial state: `{:db_id 1 :table_id nil :perm_value :unrestricted}`

Instance A: set table 1 to :blocked
Instance B: set database to :blocked

T1: A reads state, sees db-level permission, plans to expand to table-level
T2: B reads state, sees db-level permission, plans to replace it
T3: A deletes db-level, inserts table-level rows for all tables
T4: B deletes db-level (no-op), inserts new db-level :blocked

Result (CORRUPT):
```
{:db_id 1 :table_id 1 :perm_value :blocked}      ;; from A
{:db_id 1 :table_id 2 :perm_value :unrestricted} ;; from A
{:db_id 1 :table_id nil :perm_value :blocked}    ;; from B
```

Both db-level AND table-level permissions now exist.

For the permissions API, we take a cluster lock, but sync operations
bypassed this cluster lock. This PR creates a lower-level cluster lock
per `[database, group, perm-type]` so only one instance/thread can do
the read/decide/write cycle for a particular db/group/perm at a time.

* Add a test for race conditions setting data perms

This took a while before I was able to track down the fact that using
`(mt/with-temp )` caused both threads to share the same connection, so
they could obtain the lock simultaneously 🤦

* Use a slightly coarser lock

I realized that if an instance has a huge number of groups, we'll obtain
a lock and ask Postgres to maintain an active savepoint for each group,
which might be untenable.

I think the best solution here is to use slightly coarser locks. This
might increase contention slightly, since we won't be able to change
permissions for `db 1, group 1, perm type view-data` at the same time
that we change permissions for `db 1, group 2, perm type view-data`. But
I think the tradeoff here is necessary.
2025-12-30 13:08:39 +00:00
Mahatthana (Kelvin) Nomsawadi
cf7026a6a2 Fix empty cards with no results making dashboard longer than expected (#67581)
* Prevent unnecessary height calculation

* Prioritize local layout from `react-grid-layout` for height calculation

* Refix the issue for real

* Add tests
2025-12-30 12:51:29 +00:00
Jakub Chodorowicz
afef9ac2ae fix: hide editing toggle for unsupported engines (#67564)
* fix: hide untested and unsupported db engines in table editing section

* move allowed engines to constants file

* test: add hidden editing toggle test case
2025-12-30 11:50:11 +01:00
Nemanja Glumac
b5f483d951 E2E workflows env vars cleanup (#66592) 2025-12-30 10:56:23 +01:00
Jakub Chodorowicz
7247ffca2d feat: allow setting custom y axis range, even if points are outside of the range (#67533)
* feat: allow setting custom y axis range, even if points are outside of the range

* fix: adjust Waterfall null x value loki screenshot test
2025-12-30 09:15:10 +01:00
github-automation-metabase
8d7ffb6845 Update generated docs (master) (#67618)
Co-authored-by: github-actions <github-actions@github.com>
2025-12-30 00:06:07 -05:00
Egor Grushin
2b97f841bc UXW-2597 fix row bar charts' tooltip (#67617)
* UXW-2597 fix Row Bar tooltip data error
2025-12-30 01:33:34 +00:00
Gabriel Moreno
fe7668963c UXW-69 Avoid persisting non-dashboard settings (#67592)
* fix: avoid persisting settings that are hidden from dashboard UI

* test: add `sanitizeDashcardSettings` unit spec

* refactor: use `_.pick` for sanitizing dashcard settings
2025-12-29 18:52:06 -04:00
bryan
29b1e70d21 fix perf/mapv to handle infinite and large lazy sequences (#66984) 2025-12-29 15:02:00 -07:00
Ryan Laurie
b8113ad526 Get personal collection hook (#66701)
* get personal collection hook

* use the hook

* better tests

* barrell

* Apply suggestions from code review

Co-authored-by: metabase-bot[bot] <109303359+metabase-bot[bot]@users.noreply.github.com>

* cmon claude

* review fixes

---------

Co-authored-by: metabase-bot[bot] <109303359+metabase-bot[bot]@users.noreply.github.com>
2025-12-29 14:42:33 -07:00
metamben
6d1cb34859 Normalize aggregation references, sort maps and remove all uuids (#67521)
* Normalize aggregation references, sort maps and remove all uuids

Fixes QUE-2916
2025-12-29 21:32:10 +00:00
Nemanja Glumac
0c2996b7da Re-introduce: Upload failed e2e test artifacts for quarantined tests (#67600) 2025-12-29 21:10:32 +00:00
2709 changed files with 194971 additions and 142278 deletions

View File

@@ -42,7 +42,7 @@ This guide covers Clojure and ClojureScript coding conventions for Metabase. See
- Break up functions > 20 lines
- Lines ≤ 120 characters
- No blank lines within definition forms (except pairwise `let`/`cond`)
- No blank non-comment lines within definition forms (except pairwise `let`/`cond`)
## Style Conventions

View File

@@ -61,7 +61,7 @@ Use this to scan through changes efficiently:
- [ ] Everything `^:private` unless used elsewhere
- [ ] No `declare` when avoidable (public functions near end)
- [ ] Functions under 20 lines when possible
- [ ] No blank lines within definition forms (except pairwise constructs in `let`/`cond`)
- [ ] No blank, non-comment lines within definition forms (except pairwise constructs in `let`/`cond`)
- [ ] Lines ≤ 120 characters
### Tests

View File

@@ -10,7 +10,7 @@
;; ## `team`:
;;
;; The team that owns this module. This has to match one of the team names in `.github/team.json` EXACTLY. You can
;; run [metabase.core.modules-test/all-modules-have-teams-test]] to make sure you did this correctly.
;; run [[metabase.core.modules-test/all-modules-have-teams-test]] to make sure you did this correctly.
;;
;; ## `api`:
;;
@@ -39,6 +39,17 @@
;; 2. A set of module symbols. This is the list of modules that are allowed to be referenced. An empty set means no
;; other modules are allowed to be referenced; this is the default for any modules that aren't listed here.
;;
;; ## `friends`:
;;
;; 'Friend' in the C++ sense; set of modules that are allowed free access into the internals of this module including
;; namespaces that are not `:api` namespaces. This is useful when writing modules that are 'extensions' of other
;; modules, for example `enterprise/search` might be enterprise extensions of `search`, or `search-rest` might be the
;; REST API component for `search`.
;;
;; search
;; {...
;; :friends #{enterprise/search search-rest}}
;;
;; ## Tips
;;
;; PRO TIP: Check out the [[dev.deps-graph]] namespace for helpful tools to see where a module is used externally, and
@@ -147,8 +158,7 @@
metabase.api.settings
metabase.api.util
metabase.api.util.handlers}
:uses #{api-routes
config
:uses #{config
events
models
settings
@@ -368,7 +378,8 @@
collections-rest
{:team "UX West"
:api #{metabase.collections-rest.api}
:api #{metabase.collections-rest.api
metabase.collections-rest.init}
:uses #{api
app-db
collections
@@ -383,6 +394,7 @@
queries
request
revisions
settings
upload
util}}
@@ -499,7 +511,8 @@
:api #{metabase.documents.api
metabase.documents.core
metabase.documents.init
metabase.documents.prose-mirror}
metabase.documents.prose-mirror
metabase.documents.schema}
:uses #{activity-feed
api
app-db
@@ -702,19 +715,13 @@
lib
{:team "Querying"
:api #{metabase.lib.binning
metabase.lib.binning.util
metabase.lib.card
metabase.lib.computed ; temporarily so that `qp.compile` can use it as lib memoization evolves
metabase.lib.convert ; Only so [[metabase.queries.models.card]] can set dynamic vars.
metabase.lib.core
metabase.lib.equality
metabase.lib.field
metabase.lib.field.resolution
metabase.lib.filter
metabase.lib.filter.desugar.jvm ; only so [[metabase.legacy-mbql.jvm-util]] can use defined regexes
metabase.lib.hierarchy
metabase.lib.init
metabase.lib.join.util
metabase.lib.metadata
metabase.lib.metadata.cached-provider
metabase.lib.metadata.column
@@ -738,25 +745,28 @@
metabase.lib.schema.join
metabase.lib.schema.literal
metabase.lib.schema.mbql-clause
metabase.lib.schema.measure
metabase.lib.schema.metadata
metabase.lib.schema.metadata.fingerprint
metabase.lib.schema.middleware-options
metabase.lib.schema.order-by
metabase.lib.schema.parameter
metabase.lib.schema.ref
metabase.lib.schema.settings
metabase.lib.schema.template-tag
metabase.lib.schema.temporal-bucketing
metabase.lib.schema.util
metabase.lib.schema.validate
metabase.lib.temporal-bucket
metabase.lib.types.isa
metabase.lib.util
metabase.lib.util.match
metabase.lib.walk}
:uses #{config
graph
legacy-mbql
types
util}}
util}
:friends #{query-processor}}
lib-be
{:team "Querying"
@@ -791,6 +801,18 @@
settings
util}}
measures
{:team "Querying"
:api #{metabase.measures.api}
:uses #{api
events
lib
lib-be
models
permissions
search
util}}
model-persistence
{:team "Querying"
:api #{metabase.model-persistence.api
@@ -952,8 +974,10 @@
app-db
classloader
config
events
internal-stats
settings
task
util}}
product-feedback
@@ -1042,6 +1066,7 @@
config
content-verification
dashboards
documents
events
lib
lib-be
@@ -1254,11 +1279,11 @@
metabase.segments.schema}
:uses #{api
events
legacy-mbql
lib
lib-be
models
permissions
remote-sync
search
util
xrays}}
@@ -1364,6 +1389,7 @@
permissions
premium-features
settings
system
util}}
startup
@@ -1426,6 +1452,8 @@
metabase.task-history.core
metabase.task-history.init}
:uses #{api
app-db
config
models
permissions
premium-features
@@ -1611,6 +1639,7 @@
permissions
premium-features
query-processor
remote-sync
search
util
warehouses}}
@@ -1621,6 +1650,7 @@
:uses #{analytics
api
app-db
config
database-routing
driver
events
@@ -1636,7 +1666,6 @@
upload
util
warehouse-schema
warehouses-rest
xrays}}
warehouses
@@ -1754,6 +1783,7 @@
premium-features
query-permissions
query-processor
remote-sync
util
warehouses
enterprise/impersonation}}
@@ -1948,7 +1978,9 @@
metabase-enterprise.dependencies.init}
:uses #{analyze
api
app-db
collections
config
documents
driver
events
@@ -1961,10 +1993,12 @@
premium-features
queries
query-processor
request
revisions
settings
task
util}}
util
enterprise/transforms}}
enterprise/email
{:team "UX West"
@@ -2198,6 +2232,7 @@
appearance
collections
config
events
logger
models
plugins
@@ -2283,10 +2318,12 @@
metabase-enterprise.transforms.init
metabase-enterprise.transforms.instrumentation
metabase-enterprise.transforms.interface
metabase-enterprise.transforms.schema
metabase-enterprise.transforms.util}
:uses #{analytics
api
app-db
channel
collections
driver
events
@@ -2297,6 +2334,7 @@
queries
query-processor
request
revisions
search
settings
sync

View File

@@ -1,9 +1,6 @@
(ns macros.metabase.server.statistics-handler-test)
(defmacro with-server
[_ _ [connector chan-start-handle chan-finish-handle chan-done-request] & body]
`(let [~connector nil
~chan-start-handle nil
~chan-finish-handle nil
~chan-done-request nil]
[[_hold-chan _hit-chan] & body]
`(let []
~@body))

View File

@@ -31,7 +31,7 @@
second
symbol))))
(defn module-api-namespaces
(defn- module-api-namespaces
"Set of API namespace symbols for a given module. `:any` means you can use anything, there are no API namespaces for
this module (yet). If unspecified, the default is just the `<module>.core` namespace."
[config module]
@@ -51,6 +51,12 @@
(symbol (str ns-prefix ".core"))
(symbol (str ns-prefix ".init"))}))))
(defn- module-friends
[config module]
"Set of modules that are `:friends` of `module`, i.e. allowed to use *any* namespace from the module, not just the
designated [[module-api-namespaces]]."
(set (get-in config [:metabase/modules module :friends])))
(defn allowed-modules
"Set of namespace symbols that `module` is allowed to use. `:any` means it's allowed to use anything."
[config module]
@@ -61,11 +67,13 @@
(or (= allowed-modules :any)
(contains? (set allowed-modules) required-module))))
(defn allowed-module-namespace? [config ns-symb]
(defn- allowed-module-namespace? [config current-module ns-symb]
(let [module (module ns-symb)
module-api-namespaces (module-api-namespaces config module)]
module-api-namespaces (module-api-namespaces config module)
module-friends (module-friends config module)]
(or (empty? module-api-namespaces)
(contains? module-api-namespaces ns-symb))))
(contains? module-api-namespaces ns-symb)
(contains? module-friends current-module))))
(defn usage-error
"Find usage errors when a `required-namespace` is required in the `current-module`. Returns a string describing the
@@ -81,7 +89,7 @@
current-module
current-module)
(not (allowed-module-namespace? config required-namespace))
(not (allowed-module-namespace? config current-module required-namespace))
(format "Namespace %s is not an allowed external API namespace for the %s module. [:metabase/modules %s :api]"
required-namespace
required-module

View File

@@ -43,6 +43,17 @@
'{:metabase/modules {api {:uses #{search}}
search {:api #{metabase.search.core}}}}))))
(deftest ^:parallel module-checker-friends-test
(is (= []
(lint-modules
'(ns metabase.search-rest.api
(:require
[metabase.search.config :as search.config]
[metabase.search.core :as search]))
'{:metabase/modules {search {:api #{metabase.search.core}
:friends #{search-rest}}
search-rest {:uses #{search}}}}))))
(deftest ^:parallel module-checker-api-namespaces-ignore-test
(is (empty? (lint-modules
'(ns metabase.api.search

View File

@@ -1,23 +1,35 @@
Brite
afterAll
ags
altho
Brite
cancelation
cancelations
caracteres
copyin
datas
edn
eduction
hasTable
hasTables
horizontalX
horizontalY
juxt
methodd
nd
notIn
optionA
ot
pass-thru
requestor
re-use
re-used
ser
serieses
sortings
titel
trs
trsn
tru
trun
unparseable
withs

View File

@@ -1,3 +1,24 @@
[codespell]
skip = *.csv,*.edn,*.geojson,*.json,*.tsv,*.xml,docs/developers-guide/drivers/driver-tests.md,docs/developers-guide/e2e-tests.md,docs/questions/query-builder/expressions-list.md,docs/questions/visualizations/country-codes.md,e2e/test/scenarios/admin/i18n/content-translation/constants.ts,frontend/src/cljs,frontend/src/metabase/querying/expressions/tokenizer/lezer.js,frontend/src/metabase/static-viz/constants/char-sizes.ts,frontend/src/metabase/visualizations/lib/mapping_codes.ts,modules/drivers/mongo/src/metabase/driver/mongo/operators.clj
skip =
*.csv,
*.edn,
*.geojson,
*.json,
*.tsv,
*.unit.spec.*,
*.xml,
docs/developers-guide/drivers/driver-tests.md,
docs/developers-guide/e2e-tests.md,
docs/questions/query-builder/expressions-list.md,
docs/questions/visualizations/country-codes.md,
e2e/test/scenarios/admin/i18n/content-translation/constants.ts,
frontend/src/cljs,
frontend/src/metabase/querying/expressions/tokenizer/lezer.js,
frontend/src/metabase/query_builder/components/NativeQueryEditor/CodeMirrorEditor/language.ts,
frontend/src/metabase/static-viz/constants/char-sizes.ts,
frontend/src/metabase/visualizations/lib/mapping_codes.ts,
frontend/*/mocks/*,
modules/drivers/mongo/src/metabase/driver/mongo/operators.clj,
src/metabase/analyze/classifiers/name.clj,
src/metabase/util/currency.cljc,
ignore-words = .codespellignore

View File

@@ -1,23 +0,0 @@
---
name: Epic (Internal Use)
about: This issue is used to track a feature/project that may span several days
to weeks to implement. This is used by the core Metabase team to project plan
and manage work.
title: "[Epic] Title"
labels: ".Epic"
---
### Links
- [product doc](LINK_TO_PRODUCT_DOC)
- [technical design doc](LINK_TO_TECHNICAL_DESIGN_DOC)
- [testing plan](LINK_TO_TESTING_PLAN)
- feature branch: `BRANCH_NAME`
- issue links: _related issues, if any_
### Implementation Plan
```[tasklist]
#### Milestone 1
- [ ] _Add tasks here_
```

View File

@@ -7,16 +7,13 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**What problem will this feature request solve?**
**Describe the solution you'd like**
**Describe the solution you'd like.**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**How does this feature request impact you?**
For example, is it a minor inconvenience, or does it block important work?
**How important is this feature to you?**
**Additional context**
**Additional information**
Add any other context or screenshots about the feature request here.

View File

@@ -1,14 +0,0 @@
---
name: Task (Internal Use)
about: This issue is used to track a smaller feature/task that does not require
being broken down to milestones. This is used by the core Metabase team to
project plan and manage work.
labels: ".Task"
---
**Context**
- issue links: _related issues if any_

View File

@@ -25,3 +25,4 @@ _Upload a demo video or before/after screenshots if sensible or remove the secti
### Checklist
- [ ] Tests have been added/updated to cover changes in this PR
- [ ] If adding new Loki tests: they pass [stress testing](https://github.com/metabase/metabase/actions/workflows/loki-stress-test-flake-fix.yml)

View File

@@ -0,0 +1,19 @@
name: Maximize disk space
description: Removes unused stuff from GH runner
runs:
using: "composite"
steps:
- name: Maximize build space
shell: bash
run: |
echo "-- Dotnet"
sudo rm -rf /usr/share/dotnet
echo "-- Android"
sudo rm -rf /usr/local/lib/android
echo "-- Haskell"
sudo rm -rf /opt/ghc
echo "-- CodeQL"
sudo rm -rf /opt/hostedtoolcache/CodeQL
df -h

View File

@@ -13,54 +13,21 @@ inputs:
required: false
description: Suffix to append to the test logs artifact name to avoid conflicts in matrix jobs
outputs:
quarantine-skipped:
description: Whether the driver test was skipped due to quarantine
value: ${{ steps.check-driver.outputs.skip }}
runs:
using: "composite"
steps:
- name: Check if driver is ignored
id: check-driver
shell: bash
run: |
# If DRIVERS env var is not set, proceed with tests
if [ -z "$DRIVERS" ]; then
echo "No DRIVERS env var set. Proceeding with tests."
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Fetch CI config and check if driver should be skipped
curl -sSf -o ci-test-config.json https://raw.githubusercontent.com/metabase/ci-test-config/refs/heads/master/ci-test-config.json || true
# Check if the driver is in the ignored list (gracefully handle missing/invalid config)
IS_IGNORED=$(jq --arg driver "$DRIVERS" '.ignored.drivers // [] | contains([$driver])' ci-test-config.json 2>/dev/null || echo "false")
if [ "$IS_IGNORED" = "true" ]; then
echo "⚠️ Driver '$DRIVERS' is in the ignored list. Skipping tests."
echo "View the ignored list here: https://github.com/metabase/ci-test-config/blob/master/ci-test-config.json"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Driver '$DRIVERS' is not ignored. Proceeding with tests."
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Prepare back-end environment
if: steps.check-driver.outputs.skip != 'true'
uses: ./.github/actions/prepare-backend
- name: Prepare front-end environment
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
if: ${{ inputs.build-static-viz == 'true' }}
uses: ./.github/actions/prepare-frontend
- name: Build static viz frontend
if: ${{ steps.check-driver.outputs.skip != 'true' && inputs.build-static-viz == 'true' }}
if: ${{ inputs.build-static-viz == 'true' }}
run: yarn build-static-viz
shell: bash
env:
MB_EDITION: ee
- name: Test database driver
if: steps.check-driver.outputs.skip != 'true'
run: clojure -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test ${{ inputs.test-args }}
shell: bash
id: run-test
@@ -73,7 +40,7 @@ runs:
# As per the documentation: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#steps-context
# test.outcome gives us access to the result before `continue-on-error` overrides it and sets it to 'success'.
# We want test reports even if the test step gets cancelled (e.g. times out), hence we match anything that is not a 'success'.
if: ${{ steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true' }}
if: ${{ steps.run-test.outcome != 'success' }}
with:
path: "target/junit/**/*_test.xml"
name: JUnit Test Report ${{ inputs.junit-name }}
@@ -82,7 +49,7 @@ runs:
list-tests: failed
fail-on-error: false
- name: Upload Logs on Failure
if: always() && steps.run-test.outcome != 'success' && steps.check-driver.outputs.skip != 'true'
if: always() && steps.run-test.outcome != 'success'
uses: actions/upload-artifact@v4
with:
name: ${{ format('test-logs-{0}{1}', github.job, (inputs.logs-artifact-suffix == '' || inputs.logs-artifact-suffix == null) && '' || format('-{0}', inputs.logs-artifact-suffix)) }}

317
.github/scripts/generate-llms-txt.js vendored Normal file
View File

@@ -0,0 +1,317 @@
const fs = require("fs");
const path = require("path");
const REPO = "metabase/metabase";
const OUTPUT_FILE = "llms.txt";
// Sections to generate llms-<name>-full.txt for.
// Each section can define a preamble (introductory content before the main docs).
const LLMS_FULL_TO_GENERATE = [
{
section: "embedding",
getPreamble: (branch) =>
aboveVersion(branch, 57) ? getModularEmbeddingGotchaNotes() : "",
},
];
/**
* List directory recursively to get markdown files
*
* @param {string} docsPath - full path to docs directory
* @returns {Array} list of markdown file objects with fullPath and relativePath
*/
function getMarkdownFilesList(docsPath) {
if (!fs.existsSync(docsPath)) {
console.error(`Error: Docs directory '${docsPath}' does not exist`);
process.exit(1);
}
const entries = fs.readdirSync(docsPath, {
recursive: true,
withFileTypes: true,
});
return entries
.filter((entry) => {
return (
entry.isFile() &&
// Only markdown files
entry.name.endsWith(".md") &&
// Ignore README.md
entry.name !== "README.md" &&
// Ignore node_modules and hidden directories
!entry.parentPath
.split(path.sep)
.some((dir) => dir === "node_modules" || dir.startsWith("."))
);
})
.map((entry) => {
const fullPath = path
.join(entry.parentPath, entry.name)
.replace(/\\/g, "/");
const relativePath = path
.relative(docsPath, fullPath)
.replace(/\\/g, "/");
return { fullPath, relativePath };
});
}
/**
* Generate llms.txt file with links to all markdown files in docs/
*
* @param {string} branch - branch name (e.g. 'master' or 'release-x.58.x')
* @param {Array} files - list of all markdown file objects
* @param {Array<Object>} sectionsConfig - list of section config objects to generate complete references for
*/
function generateLlmsTxt(branch, files, sectionsConfig) {
const baseUrl = `https://raw.githubusercontent.com/${REPO}/refs/heads/${branch}`;
const filesToExtract = files
// Files in embedding/sdk/api/snippets does not exist in the repo.
.filter(
(file) => !file.relativePath.startsWith("embedding/sdk/api/snippets"),
);
const entries = filesToExtract.map((file) => {
const content = fs.readFileSync(file.fullPath, "utf8");
const title = extractTitle(content, file.relativePath);
const url = `${baseUrl}/docs/${file.relativePath}`;
return { title, url, path: file.relativePath };
});
// Generate table of contents entries
const tableOfContents = entries.map(
(entry) => `- [${entry.title}](${entry.url})`,
);
// Generate complete references entries
const completeReferences = sectionsConfig
.filter((config) =>
files.some((file) => file.relativePath.startsWith(`${config.section}/`)),
)
.map((config) => {
const capitalized =
config.section.charAt(0).toUpperCase() + config.section.slice(1);
return `- [${capitalized} - Complete Reference](${baseUrl}/llms-${config.section}-full.txt)`;
});
// Generate llms.txt content using template literal
const content = `# Metabase Documentation
Metabase - The simplest, fastest way to get business intelligence and analytics to everyone in your company.
## Table of Contents
${tableOfContents.join("\n")}
## Complete References
These files are very large and are around 90,000 tokens. Do not use by default unless the context window is huge or RAG is supported in your editor.
${completeReferences.join("\n")}
`;
fs.writeFileSync(OUTPUT_FILE, content, "utf8");
console.log(
`✅ Generated ${OUTPUT_FILE} with ${entries.length} markdown files`,
);
console.log(` Branch: ${branch}`);
console.log(` Base URL: ${baseUrl}`);
}
/**
* Generate llms-full.txt files for specified sections
*
* @param {string} branch - branch name (e.g., 'master', 'release-x.58.x')
* @param {Array} files - list of all markdown file objects
* @param {Array<Object>} sectionsConfig - list of section config objects to generate full files for
*/
function generateLlmsFullFiles(branch, files, sectionsConfig) {
const baseUrl = `https://raw.githubusercontent.com/${REPO}/refs/heads/${branch}`;
sectionsConfig.forEach((config) => {
generateLlmsFullTxt(branch, config, files, baseUrl);
});
}
/**
* LLMs are likely to pay attention to the very first lines.
*
* We add the most important context for LLMs to avoid
* confusion and pitfalls like out-of-date APIs in trained data.
*
* @returns {string} Markdown formatted gotcha notes
*/
function getModularEmbeddingGotchaNotes() {
return `> **Important Version Notes**
>
> Watch out for these deprecated props and gotchas for Metabase 57 onwards, for modular embedding:
>
> 1. \`config\` prop on MetabaseProvider no longer exist - it is replaced by \`authConfig\`.
> 2. \`authProviderUri\` field no longer exist.
> 3. \`jwtProviderUri\` optional field only exists in v58+. This is used to make JWT auth faster by skipping the \`GET /auth/sso\` discovery request. Not needed for initial implementation.
> 4. \`fetchRequestToken\` is not needed by default. This is only used to customize how the SDK fetches the request token.
> 5. Numeric IDs must be integers not strings, e.g. \`dashboardId={1}\`. When the ID is retrieved from the URL and it is numeric, convert it to an integer via \`parseInt\` before passing it to the SDK. IDs can also be strings for entity ids, so you should not parse all IDs as numbers if entity ids are also to be expected.
`;
}
/**
* Generate llms-full.txt file for a specific section
* @param {string} branch - The branch name
* @param {Object} config - Section config object with section name and optional getPreamble function
* @param {Array} files - Array of all markdown file objects
* @param {string} baseUrl - Base URL for raw GitHub links
*/
function generateLlmsFullTxt(branch, config, files, baseUrl) {
const { section, getPreamble } = config;
// Filter docs for this section
const sectionFiles = files.filter((file) =>
file.relativePath.startsWith(`${section}/`),
);
if (sectionFiles.length === 0) {
console.log(` Skipping llms-full.txt for ${section} (no files found)`);
return;
}
// Build content
const sectionCapitalized = section.charAt(0).toUpperCase() + section.slice(1);
const header = `# Metabase ${sectionCapitalized} - Complete Reference for AI agents
> Table of contents: ${baseUrl}/${OUTPUT_FILE}
`;
// Add preamble if provided (e.g., version-specific notes, warnings)
const preamble = getPreamble ? getPreamble(branch) : "";
// Concatenate all documents
const docs = sectionFiles
.map((file) => readAndCleanMarkdown(file.fullPath))
.join("\n");
// Write llms-full.txt file at repo root
const content = header + preamble + "\n" + docs;
const llmsFullPath = `llms-${section}-full.txt`;
fs.writeFileSync(llmsFullPath, content, "utf8");
console.log(
`✅ Generated ${llmsFullPath} with ${sectionFiles.length} documents`,
);
}
/**
* Extract title from markdown file frontmatter or filename.
*
* We try to extract the title in this order: 1) YAML frontmatter
* 2) first H1 heading, and 3) the Markdown file name.
*
* Example Markdown and frontmatter format:
* ```markdown
* ---
* title: "Getting Started"
* description: "A guide"
* ---
* # Getting Started
* ```
*
* @param {string} content - file content
* @param {string} filePath - relative file path
* @returns {string} title of content
*/
function extractTitle(content, filePath) {
// Try to extract title from YAML frontmatter
// First, match against the YAML frontmatter delimiter `---`
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
if (frontmatterMatch) {
// Then, match against the title property
// Example: `title: "My Page"`
const frontmatter = frontmatterMatch[1];
const titleMatch = frontmatter.match(/^title:\s*(.+)$/m);
// Remove quotes if present
if (titleMatch) {
return titleMatch[1].trim().replace(/^["']|["']$/g, "");
}
}
// Try to extract title from first H1 heading
// Example: `# My Page`
const h1Match = content.match(/^#\s+(.+)$/m);
if (h1Match) {
return h1Match[1].trim();
}
// Fallback to filename, e.g. `my-page.md`
const filename = path.basename(filePath, ".md");
// Convert kebab-case or snake_case to Title Case
// `my-page.md` would become "My Page"
return filename
.split(/[-_]/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
/**
* Read and clean markdown file content
*
* @param {string} filePath - full path to the markdown file
* @returns {string} cleaned markdown content with separator
*/
function readAndCleanMarkdown(filePath) {
let content = fs.readFileSync(filePath, "utf8");
// Strip YAML frontmatter
content = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
// Strip Jekyll/Liquid template syntax
content = content.replace(/\{%.*?%\}/g, "");
content = content.replace(/\{\{.*?\}\}/g, "");
return `${content.trim()}\n\n---\n`;
}
/**
* Check if branch version is above target version
* @param {string} branch - Branch name (e.g., 'master', 'release-x.58.x')
* @param {number} targetVersion - Target version number
* @returns {boolean}
*/
function aboveVersion(branch, targetVersion) {
if (branch === "master") {
return true;
}
// Parse version from release branch like "release-x.58.x" -> 58
const match = branch.match(/release-x\.(\d+)\.x/);
if (!match) {
return false;
}
const version = parseInt(match[1], 10);
return version >= targetVersion;
}
// Main execution
if (require.main === module) {
const args = process.argv.slice(2);
const docsPath = args[0] || "docs";
const branch = args[1] || "master";
const resolvedDocsPath = path.resolve(docsPath);
const markdownFiles = getMarkdownFilesList(resolvedDocsPath);
// Sort all files upfront for consistent ordering across all generated files
markdownFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
generateLlmsTxt(branch, markdownFiles, LLMS_FULL_TO_GENERATE);
generateLlmsFullFiles(branch, markdownFiles, LLMS_FULL_TO_GENERATE);
}

4
.github/team.json vendored
View File

@@ -93,7 +93,9 @@
"alxnddr",
"bpander",
"andsalves",
"gantoreno"
"gantoreno",
"kevinkelleher12",
"undrfined"
]
},
{

View File

@@ -95,6 +95,8 @@ jobs:
Use the docs-review skill to review all changed documentation files for compliance with the Metabase writing style guide.
IMPORTANT: Only review markdown files (.md and .mdx). Skip any .txt files or other non-markdown files.
DO NOT post any comments confirming that something is correct - only post comments for style violations or concerns.
claude_args: |

View File

@@ -3,17 +3,21 @@ name: Codespell
on:
push:
branches:
- 'master'
- 'release-**'
- "master"
- "release-**"
pull_request:
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: codespell-project/actions-codespell@v1
- uses: actions/checkout@v4
- uses: codespell-project/actions-codespell@v2
with:
# keep this list in sync with the list in `mage/src/mage/codespell.yml`
# keep this list in sync with the list in `mage/src/mage/codespell.clj`
path: >-
docs
enterprise
frontend
modules/drivers
src

View File

@@ -8,7 +8,7 @@ on:
target_branch:
description: "Target branch for documentation update"
required: false
default: "release-x.57.x"
default: "release-x.58.x"
type: string
jobs:
@@ -16,6 +16,6 @@ jobs:
name: Call documentation update for release branch
uses: ./.github/workflows/docs-generate-base.yml
with:
target_branch: ${{ inputs.target_branch || 'release-x.57.x' }}
target_branch: ${{ inputs.target_branch || 'release-x.58.x' }}
secrets:
automation_token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}

View File

@@ -12,12 +12,52 @@ concurrency:
cancel-in-progress: true
jobs:
# This job will determine if the tier 3 driver tests should be skipped.
# It will always run first.
# This job determines which driver tests should run based on PR context.
# All decision logic is consolidated in mage driver-decisions.
determine-driver-skips:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check-branch-and-files-changed.outputs.skip }}
# All driver run decisions (from mage driver-decisions)
h2-should-run: ${{ steps.driver-decisions.outputs.h2-should-run }}
athena-should-run: ${{ steps.driver-decisions.outputs.athena-should-run }}
bigquery-should-run: ${{ steps.driver-decisions.outputs.bigquery-should-run }}
clickhouse-should-run: ${{ steps.driver-decisions.outputs.clickhouse-should-run }}
databricks-should-run: ${{ steps.driver-decisions.outputs.databricks-should-run }}
druid-should-run: ${{ steps.driver-decisions.outputs.druid-should-run }}
druid-jdbc-should-run: ${{ steps.driver-decisions.outputs.druid-jdbc-should-run }}
mongo-should-run: ${{ steps.driver-decisions.outputs.mongo-should-run }}
mongo-ssl-should-run: ${{ steps.driver-decisions.outputs.mongo-ssl-should-run }}
mongo-sharded-cluster-should-run: ${{ steps.driver-decisions.outputs.mongo-sharded-cluster-should-run }}
mysql-mariadb-should-run: ${{ steps.driver-decisions.outputs.mysql-mariadb-should-run }}
oracle-should-run: ${{ steps.driver-decisions.outputs.oracle-should-run }}
postgres-should-run: ${{ steps.driver-decisions.outputs.postgres-should-run }}
presto-jdbc-should-run: ${{ steps.driver-decisions.outputs.presto-jdbc-should-run }}
redshift-should-run: ${{ steps.driver-decisions.outputs.redshift-should-run }}
snowflake-should-run: ${{ steps.driver-decisions.outputs.snowflake-should-run }}
sparksql-should-run: ${{ steps.driver-decisions.outputs.sparksql-should-run }}
sqlite-should-run: ${{ steps.driver-decisions.outputs.sqlite-should-run }}
sqlserver-should-run: ${{ steps.driver-decisions.outputs.sqlserver-should-run }}
vertica-should-run: ${{ steps.driver-decisions.outputs.vertica-should-run }}
# Per-driver quarantine conflict flags (set when driver is quarantined but has file changes)
athena-quarantine-conflict: ${{ steps.driver-decisions.outputs.athena-quarantine-conflict }}
bigquery-quarantine-conflict: ${{ steps.driver-decisions.outputs.bigquery-quarantine-conflict }}
clickhouse-quarantine-conflict: ${{ steps.driver-decisions.outputs.clickhouse-quarantine-conflict }}
databricks-quarantine-conflict: ${{ steps.driver-decisions.outputs.databricks-quarantine-conflict }}
druid-quarantine-conflict: ${{ steps.driver-decisions.outputs.druid-quarantine-conflict }}
druid-jdbc-quarantine-conflict: ${{ steps.driver-decisions.outputs.druid-jdbc-quarantine-conflict }}
mongo-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-quarantine-conflict }}
mongo-ssl-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-ssl-quarantine-conflict }}
mongo-sharded-cluster-quarantine-conflict: ${{ steps.driver-decisions.outputs.mongo-sharded-cluster-quarantine-conflict }}
mysql-mariadb-quarantine-conflict: ${{ steps.driver-decisions.outputs.mysql-mariadb-quarantine-conflict }}
oracle-quarantine-conflict: ${{ steps.driver-decisions.outputs.oracle-quarantine-conflict }}
postgres-quarantine-conflict: ${{ steps.driver-decisions.outputs.postgres-quarantine-conflict }}
presto-jdbc-quarantine-conflict: ${{ steps.driver-decisions.outputs.presto-jdbc-quarantine-conflict }}
redshift-quarantine-conflict: ${{ steps.driver-decisions.outputs.redshift-quarantine-conflict }}
snowflake-quarantine-conflict: ${{ steps.driver-decisions.outputs.snowflake-quarantine-conflict }}
sparksql-quarantine-conflict: ${{ steps.driver-decisions.outputs.sparksql-quarantine-conflict }}
sqlite-quarantine-conflict: ${{ steps.driver-decisions.outputs.sqlite-quarantine-conflict }}
sqlserver-quarantine-conflict: ${{ steps.driver-decisions.outputs.sqlserver-quarantine-conflict }}
vertica-quarantine-conflict: ${{ steps.driver-decisions.outputs.vertica-quarantine-conflict }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -26,29 +66,26 @@ jobs:
run: |
# Fetch the base branch to ensure its history is available for diffing
git fetch --depth=1 origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}
# Only run this if it's a pull request and the base ref exists
- name: Check if driver tests should run
id: check-branch-and-files-changed
- name: Determine which drivers should run
id: driver-decisions
run: |
IS_MASTER_OR_RELEASE="${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}"
echo "Is master or release: ${IS_MASTER_OR_RELEASE}"
if [[ "${IS_MASTER_OR_RELEASE}" == "true" ]]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "Merging into master or a release branch. All driver tests will run."
else
echo "Checking for changes in relevant modules..."
if ./bin/mage can-skip-driver-tests "${{ github.event.pull_request.base.ref }}"; then
echo 'skip=true' >> $GITHUB_OUTPUT
else
echo 'skip=false' >> $GITHUB_OUTPUT
fi
fi
# All decision logic is in mage - it analyzes module dependencies,
# branch context, labels, and file changes to decide what runs.
MAGE_ARGS=(
--git-ref="${{ github.event.pull_request.base.ref || 'master' }}"
--is-master-or-release="${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}"
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
--skip="${{ inputs.skip }}"
)
# First call: show human-readable output for debugging
./bin/mage -driver-decisions "${MAGE_ARGS[@]}"
# Second call: output only key=value lines for GITHUB_OUTPUT
./bin/mage -driver-decisions "${MAGE_ARGS[@]}" --github-output-only >> $GITHUB_OUTPUT
shell: bash
be-tests-athena-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.athena-should-run == 'true' || needs.determine-driver-skips.outputs.athena-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -65,6 +102,16 @@ jobs:
MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY: ${{ secrets.MB_ATHENA_TEST_WITHOUT_GET_TABLE_METADATA_SECRET_KEY }}
name: Athena
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.athena-quarantine-conflict == 'true' }}
run: |
echo "::error::Athena driver is quarantined but you changed files in modules/drivers/athena/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Athena tests, add the label: break-quarantine-athena"
echo ""
echo "If you merge without testing, you risk breaking the Athena driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Athena driver
id: test-driver
@@ -76,7 +123,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -88,7 +135,7 @@ jobs:
be-tests-bigquery-cloud-sdk-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.bigquery-should-run == 'true' || needs.determine-driver-skips.outputs.bigquery-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -116,6 +163,16 @@ jobs:
MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING: ${{ secrets.MB_BIGQUERY_CLOUD_SDK_TEST_SERVICE_ACCOUNT_JSON_ROUTING }}
name: BigQuery ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.bigquery-quarantine-conflict == 'true' }}
run: |
echo "::error::BigQuery driver is quarantined but you changed files in modules/drivers/bigquery-cloud-sdk/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run BigQuery tests, add the label: break-quarantine-bigquery"
echo ""
echo "If you merge without testing, you risk breaking the BigQuery driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -131,7 +188,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -149,7 +206,7 @@ jobs:
be-tests-clickhouse-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.clickhouse-should-run == 'true' || needs.determine-driver-skips.outputs.clickhouse-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -173,6 +230,16 @@ jobs:
MB_CLICKHOUSE_TEST_NGINX_PORT: 8127
name: Clickhouse ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.clickhouse-quarantine-conflict == 'true' }}
run: |
echo "::error::ClickHouse driver is quarantined but you changed files in modules/drivers/clickhouse/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run ClickHouse tests, add the label: break-quarantine-clickhouse"
echo ""
echo "If you merge without testing, you risk breaking the ClickHouse driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -203,7 +270,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -221,7 +288,7 @@ jobs:
be-tests-druid-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.druid-should-run == 'true' || needs.determine-driver-skips.outputs.druid-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -236,6 +303,16 @@ jobs:
CLUSTER_SIZE: nano-quickstart
name: Druid (Legacy)
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.druid-quarantine-conflict == 'true' }}
run: |
echo "::error::Druid driver is quarantined but you changed files in modules/drivers/druid/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Druid tests, add the label: break-quarantine-druid"
echo ""
echo "If you merge without testing, you risk breaking the Druid driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Druid driver
id: test-driver
@@ -247,7 +324,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -259,7 +336,7 @@ jobs:
be-tests-druid-jdbc-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.druid-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.druid-jdbc-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -274,6 +351,16 @@ jobs:
CLUSTER_SIZE: nano-quickstart
name: Druid (JDBC)
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.druid-jdbc-quarantine-conflict == 'true' }}
run: |
echo "::error::Druid JDBC driver is quarantined but you changed files in modules/drivers/druid-jdbc/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Druid JDBC tests, add the label: break-quarantine-druid-jdbc"
echo ""
echo "If you merge without testing, you risk breaking the Druid JDBC driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Druid JDBC driver
id: test-driver
@@ -285,7 +372,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -297,7 +384,7 @@ jobs:
be-tests-databricks-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -312,6 +399,16 @@ jobs:
MB_DATABRICKS_TEST_OAUTH_SECRET: ${{ secrets.MB_DATABRICKS_JDBC_TEST_OAUTH_SECRET }}
name: Databricks
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
run: |
echo "::error::Databricks driver is quarantined but you changed files in modules/drivers/databricks/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Databricks tests, add the label: break-quarantine-databricks"
echo ""
echo "If you merge without testing, you risk breaking the Databricks driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Databricks driver
id: test-driver
@@ -323,7 +420,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -335,7 +432,7 @@ jobs:
be-tests-databricks-multi-catalog-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.databricks-should-run == 'true' || needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -351,6 +448,16 @@ jobs:
MB_DATABRICKS_TEST_OAUTH_SECRET: ${{ secrets.MB_DATABRICKS_JDBC_TEST_OAUTH_SECRET }}
name: Databricks (Multi-Catalog)
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.databricks-quarantine-conflict == 'true' }}
run: |
echo "::error::Databricks driver is quarantined but you changed files in modules/drivers/databricks/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Databricks tests, add the label: break-quarantine-databricks"
echo ""
echo "If you merge without testing, you risk breaking the Databricks driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Databricks driver with multi-catalog
id: test-driver
@@ -362,7 +469,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -373,7 +480,8 @@ jobs:
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
be-tests-h2:
if: ${{ !inputs.skip }}
needs: determine-driver-skips
if: ${{ needs.determine-driver-skips.outputs.h2-should-run == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -391,7 +499,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -403,7 +511,7 @@ jobs:
be-tests-mysql-mariadb:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.mysql-mariadb-should-run == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -521,7 +629,7 @@ jobs:
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -539,7 +647,7 @@ jobs:
be-tests-mongo:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.mongo-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -576,6 +684,16 @@ jobs:
MONGO_INITDB_ROOT_PASSWORD: metasample123
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.mongo-quarantine-conflict == 'true' }}
run: |
echo "::error::MongoDB driver is quarantined but you changed files in modules/drivers/mongo/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run MongoDB tests, add the label: break-quarantine-mongo"
echo ""
echo "If you merge without testing, you risk breaking the MongoDB driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -591,7 +709,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -609,7 +727,7 @@ jobs:
be-tests-mongo-ssl:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.mongo-ssl-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-ssl-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -627,6 +745,16 @@ jobs:
MB_TEST_MONGO_REQUIRES_SSL: true
name: "${{ matrix.version.name }} (SSL)"
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.mongo-ssl-quarantine-conflict == 'true' }}
run: |
echo "::error::MongoDB SSL driver is quarantined but you changed files in modules/drivers/mongo/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run MongoDB SSL tests, add the label: break-quarantine-mongo-ssl"
echo ""
echo "If you merge without testing, you risk breaking the MongoDB SSL driver!"
exit 1
- uses: actions/checkout@v4
- name: Spin up Mongo docker container
run: >-
@@ -663,7 +791,7 @@ jobs:
:only-tags [:mb/driver-tests]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -675,7 +803,7 @@ jobs:
be-tests-mongo-sharded-cluster-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.mongo-sharded-cluster-should-run == 'true' || needs.determine-driver-skips.outputs.mongo-sharded-cluster-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -688,6 +816,16 @@ jobs:
- "27017:17017"
name: MongoDB Sharded Cluster
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.mongo-sharded-cluster-quarantine-conflict == 'true' }}
run: |
echo "::error::MongoDB Sharded Cluster driver is quarantined but you changed files in modules/drivers/mongo/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run MongoDB Sharded Cluster tests, add the label: break-quarantine-mongo-sharded-cluster"
echo ""
echo "If you merge without testing, you risk breaking the MongoDB Sharded Cluster driver!"
exit 1
- uses: actions/checkout@v4
- name: Test MongoDB driver (Sharded cluster)
id: test-driver
@@ -698,7 +836,7 @@ jobs:
:only metabase.driver.mongo.sharded-cluster-test
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -710,7 +848,7 @@ jobs:
be-tests-oracle:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.oracle-should-run == 'true' || needs.determine-driver-skips.outputs.oracle-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -761,6 +899,16 @@ jobs:
- "1521:${{ matrix.version.port }}"
name: ${{ matrix.version.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.oracle-quarantine-conflict == 'true' }}
run: |
echo "::error::Oracle driver is quarantined but you changed files in modules/drivers/oracle/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Oracle tests, add the label: break-quarantine-oracle"
echo ""
echo "If you merge without testing, you risk breaking the Oracle driver!"
exit 1
- uses: actions/checkout@v4
- name: Test ${{ matrix.version.name }}
id: test-driver
@@ -773,7 +921,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -785,7 +933,7 @@ jobs:
be-tests-postgres:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.postgres-should-run == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -876,7 +1024,7 @@ jobs:
:exclude-tags '[ ${{ matrix.job.exclude-tag }} ${{ env.__ADDITIONAL_EXCLUDED_TAG__ }}]'
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -894,7 +1042,7 @@ jobs:
be-tests-presto-jdbc-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.presto-jdbc-should-run == 'true' || needs.determine-driver-skips.outputs.presto-jdbc-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -917,6 +1065,16 @@ jobs:
env:
JAVA_TOOL_OPTIONS: "-Xmx2g"
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.presto-jdbc-quarantine-conflict == 'true' }}
run: |
echo "::error::Presto JDBC driver is quarantined but you changed files in modules/drivers/presto-jdbc/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Presto JDBC tests, add the label: break-quarantine-presto-jdbc"
echo ""
echo "If you merge without testing, you risk breaking the Presto JDBC driver!"
exit 1
- uses: actions/checkout@v4
- uses: ./.github/actions/await-port
with:
@@ -953,7 +1111,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -965,7 +1123,7 @@ jobs:
be-tests-redshift-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.redshift-should-run == 'true' || needs.determine-driver-skips.outputs.redshift-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -1010,6 +1168,16 @@ jobs:
MB_REDSHIFT_TEST_PASSWORD: ${{ secrets.MB_REDSHIFT_TEST_PASSWORD }}
name: ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.redshift-quarantine-conflict == 'true' }}
run: |
echo "::error::Redshift driver is quarantined but you changed files in modules/drivers/redshift/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Redshift tests, add the label: break-quarantine-redshift"
echo ""
echo "If you merge without testing, you risk breaking the Redshift driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -1027,7 +1195,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: ${{ !matrix.job.skip && !cancelled() && steps.test-driver.outputs.quarantine-skipped != 'true' }}
if: ${{ !matrix.job.skip && !cancelled() }}
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1045,7 +1213,7 @@ jobs:
be-tests-snowflake-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.snowflake-should-run == 'true' || needs.determine-driver-skips.outputs.snowflake-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
@@ -1083,6 +1251,16 @@ jobs:
MB_SNOWFLAKE_TEST_RSA_ROLE_TEST_DB: RSA_ROLE_TEST_DB
name: Snowflake ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.snowflake-quarantine-conflict == 'true' }}
run: |
echo "::error::Snowflake driver is quarantined but you changed files in modules/drivers/snowflake/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Snowflake tests, add the label: break-quarantine-snowflake"
echo ""
echo "If you merge without testing, you risk breaking the Snowflake driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -1098,7 +1276,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.job.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1116,7 +1294,7 @@ jobs:
be-tests-sparksql-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.sparksql-should-run == 'true' || needs.determine-driver-skips.outputs.sparksql-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -1129,6 +1307,16 @@ jobs:
- "10000:10000"
name: Spark SQL
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.sparksql-quarantine-conflict == 'true' }}
run: |
echo "::error::SparkSQL driver is quarantined but you changed files in modules/drivers/sparksql/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run SparkSQL tests, add the label: break-quarantine-sparksql"
echo ""
echo "If you merge without testing, you risk breaking the SparkSQL driver!"
exit 1
- uses: actions/checkout@v4
- name: Test Spark driver
id: test-driver
@@ -1140,7 +1328,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1152,7 +1340,7 @@ jobs:
be-tests-sqlite-ee:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.sqlite-should-run == 'true' || needs.determine-driver-skips.outputs.sqlite-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -1160,6 +1348,16 @@ jobs:
DRIVERS: sqlite
name: SQLite
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.sqlite-quarantine-conflict == 'true' }}
run: |
echo "::error::SQLite driver is quarantined but you changed files in modules/drivers/sqlite/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run SQLite tests, add the label: break-quarantine-sqlite"
echo ""
echo "If you merge without testing, you risk breaking the SQLite driver!"
exit 1
- uses: actions/checkout@v4
- name: Test SQLite driver
id: test-driver
@@ -1171,7 +1369,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1183,7 +1381,7 @@ jobs:
be-tests-sqlserver:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.sqlserver-should-run == 'true' || needs.determine-driver-skips.outputs.sqlserver-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
@@ -1222,6 +1420,16 @@ jobs:
MSSQL_MEMORY_LIMIT_MB: 1024
name: ${{ matrix.version.name }} ${{ matrix.job.name }}
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.sqlserver-quarantine-conflict == 'true' }}
run: |
echo "::error::SQL Server driver is quarantined but you changed files in modules/drivers/sqlserver/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run SQL Server tests, add the label: break-quarantine-sqlserver"
echo ""
echo "If you merge without testing, you risk breaking the SQL Server driver!"
exit 1
- uses: actions/checkout@v4
- name: Setup python-runner
if: ${{ matrix.job.needs-python-runner == true }}
@@ -1237,7 +1445,7 @@ jobs:
logs-artifact-suffix: ${{ matrix.version.name }}
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}
@@ -1294,7 +1502,7 @@ jobs:
login-to-ecr:
needs: determine-driver-skips
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
@@ -1324,7 +1532,7 @@ jobs:
be-tests-vertica-ee:
needs: [determine-driver-skips, login-to-ecr]
if: ${{ !inputs.skip && !(needs.determine-driver-skips.outputs.skip == 'true') }}
if: ${{ needs.determine-driver-skips.outputs.vertica-should-run == 'true' || needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -1340,6 +1548,16 @@ jobs:
password: ${{ needs.login-to-ecr.outputs.docker_password }}
name: Vertica
steps:
- name: Fail on quarantine conflict
if: ${{ needs.determine-driver-skips.outputs.vertica-quarantine-conflict == 'true' }}
run: |
echo "::error::Vertica driver is quarantined but you changed files in modules/drivers/vertica/"
echo ""
echo "Your changes will NOT be tested because the driver is currently quarantined."
echo "To run Vertica tests, add the label: break-quarantine-vertica"
echo ""
echo "If you merge without testing, you risk breaking the Vertica driver!"
exit 1
- uses: actions/checkout@v4
- name: Make plugins directory
run: mkdir plugins
@@ -1353,7 +1571,7 @@ jobs:
:exclude-tags [:mb/transforms-python-test]
- name: Upload Test Results
uses: ./.github/actions/upload-test-results
if: always() && steps.test-driver.outputs.quarantine-skipped != 'true'
if: always()
with:
input-path: ./target/junit/
output-name: ${{ github.job }}

View File

@@ -76,19 +76,15 @@ jobs:
timeout-minutes: 60
name: Stress test E2E flake fix
env:
DISPLAY: ""
MB_RUN_MODE: e2e
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
TERM: xterm
TZ: US/Pacific # to make Node match the instance tz
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
MB_SNOWPLOW_AVAILABLE: true
MB_SNOWPLOW_URL: "http://localhost:9090" # Snowplow micro
MB_EDITION: ${{ inputs.mb_edition }}
CYPRESS_ENABLE_NETWORK_THROTTLING: ${{ inputs.enable_network_throttling }}
TERM: xterm
TZ: US/Pacific # to make node match the instance tz
steps:
- uses: actions/checkout@v4
with:
@@ -114,7 +110,6 @@ jobs:
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend

View File

@@ -29,6 +29,15 @@ on:
required: false
type: number
env:
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
TERM: xterm
TZ: US/Pacific # to make Node match the instance tz
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
JOB_NAME: ${{ inputs.name }}
jobs:
e2e-tests:
runs-on: ${{ inputs.runner }}
@@ -36,21 +45,12 @@ jobs:
name: e2e-tests-${{ inputs.name }}-${{ inputs.edition }}
env:
MB_EDITION: ${{ inputs.edition }}
DISPLAY: ""
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
JOB_NAME: ${{ inputs.name }}
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
CYPRESS_PULL_REQUEST_ID: ${{ github.event.pull_request.number || '' }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- uses: actions/checkout@v4
with:
@@ -113,6 +113,7 @@ jobs:
run: node e2e/runner/run_cypress_ci.js snapshot
- name: Run auto-split Cypress tests on ${{ inputs.name }}
id: split-e2e
if: ${{ !inputs.specs }}
env:
SPLIT: ${{ inputs.total_chunks }}
@@ -136,6 +137,7 @@ jobs:
continue-on-error: true
- name: Run Tagged EE Cypress tests on ${{ inputs.name }}
id: tagged-e2e
if: ${{ inputs.specs }}
shell: bash
env:
@@ -168,7 +170,8 @@ jobs:
- name: Upload Cypress Artifacts upon failure
uses: actions/upload-artifact@v4
if: failure()
if: |
!cancelled() && (steps.split-e2e.outcome == 'failure' || steps.tagged-e2e.outcome == 'failure')
with:
name: cypress-recording-${{ inputs.name }}-${{ inputs.edition }}
path: |
@@ -178,10 +181,12 @@ jobs:
- name: Upload Failed Tests
uses: actions/upload-artifact@v4
if: failure()
if: |
!cancelled() && (steps.split-e2e.outcome == 'failure' || steps.tagged-e2e.outcome == 'failure')
with:
name: failed-tests-${{ inputs.name }}-${{ inputs.edition }}
path: ./cypress/test-results
if-no-files-found: ignore
- name: Extract chunk-specific changed timings
id: extract-timings

View File

@@ -3,6 +3,12 @@ name: Embedding SDK Component Tests
on:
workflow_call:
env:
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
TERM: xterm
TZ: US/Pacific # to make Node match the instance tz
MB_EDITION: "ee"
jobs:
sdk-component-tests:
name: "SDK component tests (React ${{ matrix.react-version }})"
@@ -13,17 +19,12 @@ jobs:
react-version: [18, 19]
fail-fast: false
env:
MB_EDITION: ee
DISPLAY: ""
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
# Example: you can get `CYPRESS_FOO` with `Cypress.env("FOO")`
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- uses: actions/checkout@v4
@@ -149,8 +150,6 @@ jobs:
runs-on: ubuntu-22.04
timeout-minutes: 45
env:
MB_EDITION: ee
DISPLAY: ""
MB_RUN_MODE: "e2e"
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
# Any env starting with `CYPRESS_` will be available to all Cypress tests via `Cypress.env()`
@@ -159,9 +158,6 @@ jobs:
CYPRESS_MB_STARTER_CLOUD_TOKEN: ${{ secrets.STAGING_MB_STARTER_CLOUD_TOKEN }}
CYPRESS_MB_PRO_CLOUD_TOKEN: ${{ secrets.STAGING_MB_PRO_CLOUD_TOKEN }}
CYPRESS_MB_PRO_SELF_HOSTED_TOKEN: ${{ secrets.STAGING_MB_PRO_SELF_HOSTED_TOKEN }}
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title || github.event.head_commit.message || github.event.head.sha }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
steps:
- name: Check out the code from the previous commit

View File

@@ -3,6 +3,14 @@ name: "[NOT REQUIRED] SDK Compatibility tests"
on:
workflow_call:
env:
DISPLAY: "" # https://github.com/cypress-io/cypress/issues/4034
TERM: xterm
TZ: US/Pacific # to make Node match the instance tz
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
MB_EDITION: ee
jobs:
get-sample-apps-data:
runs-on: ubuntu-22.04
@@ -100,15 +108,9 @@ jobs:
name: e2e-sample-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
env:
MB_EDITION: ee
DISPLAY: ""
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
MB_RUN_MODE: e2e
METASTORE_DEV_SERVER_URL: ${{ secrets.METASTORE_DEV_SERVER_URL }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
SAMPLE_APP_BRANCH_NAME: ${{ needs.get-sample-apps-data.outputs.branch_name }}
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
@@ -117,6 +119,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/maximize-disk-space
# We have to use the latest version that does not have this issue https://github.com/docker/compose/pull/12752
- name: Set up Docker Compose
uses: docker/setup-compose-action@v1
@@ -237,14 +241,7 @@ jobs:
timeout-minutes: 45
name: e2e-host-app-${{ matrix.test_suite }}-tests-${{ matrix.environment }}-bundle
env:
MB_EDITION: ee
DISPLAY: ""
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
HASH: ${{ github.event.pull_request.head.sha || github.sha }}-${{ github.run_attempt }}
CYPRESS_MB_ALL_FEATURES_TOKEN: ${{ secrets.STAGING_MB_ALL_FEATURES_TOKEN }}
TZ: US/Pacific # to make node match the instance tz
CYPRESS_CI: true
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write

198
.github/workflows/llms-txt-generate.yml vendored Normal file
View File

@@ -0,0 +1,198 @@
name: Generate llms.txt file
on:
workflow_call:
inputs:
version:
description: 'Metabase version (e.g. v0.57.0)'
type: string
required: true
branch:
description: 'Target branch (e.g. release-x.57.x)'
type: string
required: true
workflow_dispatch:
inputs:
version:
description: 'Metabase version (e.g. v0.57.0)'
type: string
required: true
branch:
description: 'Target branch (e.g. release-x.57.x)'
type: string
required: true
permissions:
contents: write
pull-requests: write
jobs:
check-version:
name: Check if minor or major release
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
should_generate: ${{ steps.check.outputs.should_generate }}
target_branch: ${{ steps.check.outputs.target_branch }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Check version
id: check
run: |
VERSION="${{ inputs.version }}"
BRANCH="${{ inputs.branch }}"
# Extract version number (e.g., v0.57.0 -> 0.57.0)
VERSION_NUM="${VERSION#v}"
# Count dots to determine version type
# Metabase format: v0.MAJOR.MINOR (3 segments) or v0.MAJOR.MINOR.PATCH (4 segments)
DOT_COUNT=$(echo "$VERSION_NUM" | tr -cd '.' | wc -c | tr -d ' ')
# Only proceed if this is a minor or major release (3 segments)
# Examples of minor/major releases: v0.57.8, v0.59.0
# Skip for patch releases (4 segments): v0.57.8.1
# Note: This is a safety check even though the workflow should only be called
# for minor/major releases, just in case the workflow is dispatched manually.
if [ "$DOT_COUNT" -eq 3 ]; then
echo "⏭️ This is a patch release ($VERSION). Skipping llms.txt generation."
echo "should_generate=false" >> $GITHUB_OUTPUT
else
echo "✅ This is a minor or major release ($VERSION). Proceeding with llms.txt generation for branch: $BRANCH"
echo "should_generate=true" >> $GITHUB_OUTPUT
fi
echo "target_branch=$BRANCH" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
generate-llms-txt:
name: Generate llms.txt
needs: check-version
if: needs.check-version.outputs.should_generate == 'true'
runs-on: ubuntu-22.04
timeout-minutes: 15
steps:
- name: Get the generate-llms-txt.js node script via sparse checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
ref: ${{ github.ref }}
sparse-checkout: |
.github/scripts
path: workflow-scripts
- name: Checkout target branch for docs
uses: actions/checkout@v4
with:
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
ref: ${{ needs.check-version.outputs.target_branch }}
path: metabase
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Generate llms.txt
id: generate
run: |
node workflow-scripts/.github/scripts/generate-llms-txt.js metabase/docs ${{ needs.check-version.outputs.target_branch }}
- name: Sanity check generated files
run: |
if [ ! -f "llms.txt" ]; then
echo "❌ Error: llms.txt was not generated"
exit 1
fi
if [ ! -f "llms-embedding-full.txt" ]; then
echo "❌ Error: llms-embedding-full.txt was not generated"
exit 1
fi
# Check that llms.txt contains URLs with the expected GitHub raw URL prefix
URL_COUNT=$(grep -o "https://raw.githubusercontent.com/metabase/metabase/refs/heads/${{ needs.check-version.outputs.target_branch }}/docs" llms.txt | wc -l)
if [ "$URL_COUNT" -lt 10 ]; then
echo "❌ Error: llms.txt appears to be malformed. Found only $URL_COUNT URLs (expected at least 10)"
exit 1
fi
echo "✅ Both llms.txt and llms-embedding-full.txt were generated successfully"
echo "✅ llms.txt contains $URL_COUNT documentation URLs"
- name: Set up git configuration
working-directory: metabase
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
BRANCH_NAME="llms-txt/auto-update-${{ needs.check-version.outputs.target_branch }}-$(date +%Y%m%d-%H%M%S)"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
echo "Branch name will be: $BRANCH_NAME"
- name: Copy generated files to metabase repo
run: |
cp llms.txt metabase/
cp llms-embedding-full.txt metabase/
- name: Create Pull Request
id: create_pr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
path: metabase
branch: ${{ env.BRANCH_NAME }}
base: ${{ needs.check-version.outputs.target_branch }}
commit-message: |
Update llms.txt and llms-full.txt (${{ needs.check-version.outputs.target_branch }})
Regenerated llms.txt to include all markdown files from the docs/ directory.
Also generated llms-full.txt files for the embedding section.
🤖 Generated automatically by llms-txt-generate workflow
Co-Authored-By: GitHub Actions <actions@github.com>
committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
title: "[chore] Update llms.txt for ${{ needs.check-version.outputs.target_branch }}"
body: |
## Summary
This PR contains automatically generated updates to `llms.txt` and `llms-embedding-full.txt` files, triggered by a minor or major release.
## Changes
- The `llms.txt` file has been regenerated to include all markdown files from the `docs/` directory.
- The `llms-embedding-full.txt` file has been generated for the embedding section (concatenated documentation for RAG).
## Generation
These files were generated using:
- Script: `.github/scripts/generate-llms-txt.js`
- Branch: `${{ needs.check-version.outputs.target_branch }}`
- Base URL: `https://raw.githubusercontent.com/metabase/metabase/refs/heads/${{ needs.check-version.outputs.target_branch }}`
Please review the changes and merge if they look correct.
🤖 Generated automatically by the `llms-txt-generate` workflow.
Co-Authored-By: GitHub Actions <actions@github.com>
labels: |
Type:Documentation
no-backport
add-paths: |
llms.txt
llms-embedding-full.txt
- name: Auto approve PR
if: steps.create_pr.outputs.pull-request-number != ''
working-directory: metabase
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr review "${{ steps.create_pr.outputs.pull-request-number }}" --approve --body "Auto-approved by llms-txt-generate workflow"
- name: Auto merge PR
if: steps.create_pr.outputs.pull-request-number != ''
working-directory: metabase
env:
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
run: |
gh pr merge "${{ steps.create_pr.outputs.pull-request-number }}" --auto --squash

View File

@@ -0,0 +1,114 @@
name: Loki Visual Stress Test Flake Fix
on:
workflow_dispatch:
inputs:
stories_filter:
description: "Regex pattern to filter stories (e.g. 'DataGrid' or 'static-viz|viz')"
type: string
required: true
burn_in:
description: "Number of times to run the test (e.g. 50)"
type: string
required: true
default: "50"
jobs:
workflow-summary:
name: Stress test inputs
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Generate workflow summary
run: |
echo '**Inputs:**' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '- `branch`: ${{ github.ref_name }}' >> $GITHUB_STEP_SUMMARY
echo '- `stories_filter`: ${{ inputs.stories_filter }}' >> $GITHUB_STEP_SUMMARY
echo '- `burn_in`: ${{ inputs.burn_in }}' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
stress-test-loki:
runs-on: ubuntu-latest
timeout-minutes: 60
name: Stress test Loki visual tests
services:
docker:
image: docker:19.03.12
options: --privileged
ports:
- 2376:2376
env:
DOCKER_TLS_CERTDIR: /certs
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/docker-certs:/certs/client
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Prepare frontend environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Compile CLJS
run: NODE_ENV=development yarn build-pure:cljs
- name: Build Storybook (once)
run: yarn build-storybook
- name: Stress-test Loki "${{ inputs.stories_filter }}" ${{ inputs.burn_in }} times
run: |
BURN_IN=${{ inputs.burn_in }}
STORIES_FILTER='${{ inputs.stories_filter }}'
echo "Running Loki visual tests $BURN_IN times for stories matching: $STORIES_FILTER"
echo ""
FAILED=0
for i in $(seq 1 $BURN_IN); do
echo "=========================================="
echo "Run $i of $BURN_IN"
echo "=========================================="
if yarn test-visual:loki \
--reactUri file:./storybook-static \
--verboseRenderer \
--storiesFilter "$STORIES_FILTER"; then
echo "✅ Run $i passed"
else
echo "❌ Run $i FAILED"
FAILED=$((FAILED + 1))
mkdir -p .loki/failures/run-$i
cp -r .loki/difference/* .loki/failures/run-$i/ 2>/dev/null || true
fi
echo ""
done
echo "=========================================="
echo "Summary: $((BURN_IN - FAILED))/$BURN_IN passed, $FAILED failed"
echo "=========================================="
if [ $FAILED -gt 0 ]; then
echo "::error::$FAILED out of $BURN_IN runs failed"
exit 1
fi
- name: Generate Visual Report on Failure
if: failure()
run: yarn test-visual:loki-report
- name: Upload Artifact
id: artifact-upload-step
if: failure()
uses: actions/upload-artifact@v4
with:
name: loki-stress-test-report
include-hidden-files: true
path: .loki/
if-no-files-found: ignore

View File

@@ -424,6 +424,19 @@ jobs:
}
});
# updates the llms.txt asynchronously.
# release does not need to wait for this PR to be merged,
# because the llms.txt is in the main git repo.
trigger-update-llms-txt:
# do not trigger llms.txt update on automatic patch releases
if: ${{ !inputs.auto }}
needs: add-extra-tags
uses: ./.github/workflows/llms-txt-generate.yml
secrets: inherit
with:
version: ${{ inputs.version }}
branch: ${{ github.ref_name }}
draft-release-notes:
if: ${{ !inputs.auto }}
needs: add-extra-tags

View File

@@ -3,15 +3,15 @@ name: "Snyk"
on:
push:
branches:
- 'master'
- 'release-**'
- "master"
- "release-**"
paths:
- '**/deps.edn'
- '**/package.json'
- '.github/workflows/snyk.yml'
- '.github/scripts/write-poms.sh'
- "**/deps.edn"
- "**/package.json"
- ".github/workflows/snyk.yml"
- ".github/scripts/write-poms.sh"
schedule:
- cron: '0 5 * * *'
- cron: "0 5 * * *"
jobs:
# Generate pom.xml files for all Maven projects
@@ -45,26 +45,6 @@ jobs:
file: yarn.lock
package-manager: yarn
category: main-frontend-yarn
- name: Embedding SDK Template
file: bin/embedding-sdk/templates/yarn.lock
package-manager: yarn
category: embedding-sdk-template
- name: E2E Angular 20 Host App
file: e2e/embedding-sdk-host-apps/angular-20-host-app/package-lock.json
package-manager: npm
category: e2e-angular-host
- name: E2E Next 15 App Router
file: e2e/embedding-sdk-host-apps/next-15-app-router-host-app/package-lock.json
package-manager: npm
category: e2e-next-app-router
- name: E2E Next 15 Pages Router
file: e2e/embedding-sdk-host-apps/next-15-pages-router-host-app/package-lock.json
package-manager: npm
category: e2e-next-pages-router
- name: E2E Vite 6 Host App
file: e2e/embedding-sdk-host-apps/vite-6-host-app/package-lock.json
package-manager: npm
category: e2e-vite-host
- name: Release Helper
file: release/yarn.lock
package-manager: yarn

View File

@@ -15,8 +15,8 @@ jobs:
BRANCH_NAME: update-translations-${{ github.run_id }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
GH_TOKEN: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
@@ -107,7 +107,7 @@ jobs:
- name: Auto approve PR
uses: juliangruber/approve-pull-request-action@v1
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
github-token: ${{ github.token }}
number: ${{ steps.create_pr.outputs.pr_number }}
- name: Enable Pull Request Automerge

View File

@@ -16,5 +16,5 @@ jobs:
- uses: actions/checkout@v4
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- run: clojure -T:whitespace-linter lint
- run: clojure -T:whitespace-linter lint :whitespace-linter/progress-bar false
name: Run Whitespace Linter

3
.gitignore vendored
View File

@@ -21,7 +21,8 @@
/*.mv.db
/*.trace.db
/.babel_cache
/.env
/.env*
!/.env.example
/.envrc
/.lein-env
/.lein-failures

View File

@@ -1,6 +1,3 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn precommit
# Run the formatter
@@ -9,4 +6,4 @@ yarn precommit
# ATM, you can manually run ./bin/cljfmt_staged.sh if you want to format locally staged files and inspect after.
# ./bin/cljfmt_staged.sh
./hooks/pre-commit.nocommit
./hooks/pre-commit.nocommit

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -120,8 +120,8 @@ function CssVariables() {
Theming-specific CSS variables.
These CSS variables are not part of the core design system colors.
**/
--mb-color-bg-dashboard: var(--mb-color-bg-white);
--mb-color-bg-dashboard-card: var(--mb-color-bg-white);
--mb-color-bg-dashboard: var(--mb-color-background-primary);
--mb-color-bg-dashboard-card: var(--mb-color-background-primary);
}
/* For Embed frame questions to render properly */

View File

@@ -97,7 +97,7 @@ This repository contains the source code for both the Open Source edition of Met
See [LICENSE.txt](./LICENSE.txt) for details.
Unless otherwise noted, all files © 2025 Metabase, Inc.
Unless otherwise noted, all files © 2026 Metabase, Inc.
## Community

408
bb.edn
View File

@@ -1,15 +1,15 @@
{:min-bb-version "1.12.212"
;; we put path as bin, and everything is in the ./mage subdirectory,
;; so the namespaces are mage.cli, mage.format, etc.
:paths ["mage/src" "mage/test" "bin/lint-migrations-file/src"]
:deps {dev.weavejester/cljfmt {:mvn/version "0.15.3"} ;; run cljfmt from bb
io.github.paintparty/bling {:mvn/version "0.8.8"} ;; printing bells and whistles
medley/medley {:mvn/version "1.3.0"} ;; utilities
metosin/malli {:mvn/version "0.17.0"} ;; data validation
mvxcvi/puget {:mvn/version "1.3.4"} ;; pretty printing
org.babashka/json {:mvn/version "0.1.6"} ;; json parsing
table/table {:mvn/version "0.5.0"} ;; table printing
}
:paths ["mage/src" "mage/test" "bin/lint-migrations-file/src"]
:deps {dev.weavejester/cljfmt {:mvn/version "0.15.3"} ;; run cljfmt from bb
io.github.paintparty/bling {:mvn/version "0.8.8"} ;; printing bells and whistles
medley/medley {:mvn/version "1.3.0"} ;; utilities
metosin/malli {:mvn/version "0.17.0"} ;; data validation
mvxcvi/puget {:mvn/version "1.3.4"} ;; pretty printing
org.babashka/json {:mvn/version "0.1.6"} ;; json parsing
table/table {:mvn/version "0.5.0"} ;; table printing
}
:tasks
{:requires [[mage.cli :as cli]
[mage.color :as c]]
@@ -21,214 +21,220 @@
(cons 'do body))))
alias
{:doc "Installs 'mage' alias and autocomplete"
:examples [["./bin/mage alias" "Prints instructions to install the 'mage' alias and autocomplete"]
["./bin/mage alias install fish" "Installs mage alias for fish shell"]
["./bin/mage alias print zsh" "Prints shell script to put into .zshrc for zsh users"]]
:requires [[mage.alias :as alias]]
{:doc "Installs 'mage' alias and autocomplete"
:examples [["./bin/mage alias" "Prints instructions to install the 'mage' alias and autocomplete"]
["./bin/mage alias install fish" "Installs mage alias for fish shell"]
["./bin/mage alias print zsh" "Prints shell script to put into .zshrc for zsh users"]]
:requires [[mage.alias :as alias]]
:arg-schema [:or
[:tuple [:enum {:name "shell-type"} "bash" "zsh" "fish"]]
[:tuple [:enum {:name "mode"} "install" "print"]
[:enum {:name "shell-type"} "bash" "zsh" "fish"]]]
:task (task! (alias/instructions parsed))}
[:enum {:name "shell-type"} "bash" "zsh" "fish"]]]
:task (task! (alias/instructions parsed))}
cljfmt-staged
{:doc "Runs cljfmt on staged files"
{:doc "Runs cljfmt on staged files"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-staged" "Format staged files"]
["./bin/mage cljfmt-staged -c" "Check staged files"]]
:options [["-c" "--force-check" "Check staged files"]]
:task (task! (format/staged parsed))}
:options [["-c" "--force-check" "Check staged files"]]
:task (task! (format/staged parsed))}
cljfmt-files
{:doc "Runs cljfmt on the given files/directories"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-files src/metabase/events.clj" "Format events.clj"]
["./bin/mage cljfmt-files src" "Format all files in src"]
["./bin/mage cljfmt-files -c src" "Check all files in src"]]
:options [["-c" "--force-check" "Check staged files"]]
{:doc "Runs cljfmt on the given files/directories"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-files src/metabase/events.clj" "Format events.clj"]
["./bin/mage cljfmt-files src" "Format all files in src"]
["./bin/mage cljfmt-files -c src" "Check all files in src"]]
:options [["-c" "--force-check" "Check staged files"]]
:arg-schema [:sequential [:string {:name "file-or-directory"
:description "Files or directories to format."}]]
:task (task! (format/files parsed))}
:task (task! (format/files parsed))}
cljfmt-all
{:doc "Runs cljfmt on all (clojure) files"
{:doc "Runs cljfmt on all (clojure) files"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-all" "Format all files"]]
:options [["-c" "--force-check" "Check staged files"]]
:task (task! (format/all parsed))}
:options [["-c" "--force-check" "Check staged files"]]
:task (task! (format/all parsed))}
cljfmt-updated
{:doc "Runs cljfmt on all (Clojure) files relative to a git ref (default HEAD)"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-updated" "Format updated files relative to HEAD"]
["./bin/mage cljfmt-updated master" "Format updated files relative to master"]]
:options [["-c" "--force-check" "Check staged files"]]
{:doc "Runs cljfmt on all (Clojure) files relative to a git ref (default HEAD)"
:requires [[mage.format :as format]]
:examples [["./bin/mage cljfmt-updated" "Format updated files relative to HEAD"]
["./bin/mage cljfmt-updated master" "Format updated files relative to master"]]
:options [["-c" "--force-check" "Check staged files"]]
:arg-schema [:or [:tuple] [:tuple [:string {:name "git-ref" :description "Git ref to compare against"}]]]
:task (task! (format/updated parsed))}
:task (task! (format/updated parsed))}
kondo
{:doc "Runs Kondo against a file, directory, or everything we usually lint"
{:doc "Runs Kondo against a file, directory, or everything we usually lint"
:examples [["./bin/mage kondo" "run Kondo against everything we usually lint"]
["./bin/mage kondo src/metabase/db.clj" "run Kondo against a file"]
["./bin/mage kondo src/metabase/db.clj src/metabase/config.clj" "run Kondo against 2 files"]
["./bin/mage kondo src/metabase/api/macros" "run Kondo against a directory"]]
:requires [[mage.kondo :as kondo]]
:task (task! (kondo/kondo (:arguments parsed)))}
:task (task! (kondo/kondo (:arguments parsed)))}
kondo-updated
{:doc "Runs kondo against files changed compared to a git ref"
{:doc "Runs kondo against files changed compared to a git ref"
:examples [["./bin/mage kondo-updated" "run Kondo on files with changes relative to HEAD"]
["./bin/mage kondo-updated master" "run Kondo on files with changes relative to master"]]
:requires [[mage.kondo :as kondo]]
:task (task! (kondo/kondo-updated (:arguments parsed)))}
:task (task! (kondo/kondo-updated (:arguments parsed)))}
check
{:doc "Checks whether we can compile all source files (and finds circular dependencies)"
{:doc "Checks whether we can compile all source files (and finds circular dependencies)"
:examples [["./bin/mage check"]]
:requires [[mage.check]]
:task (task! (mage.check/check (:arguments parsed)))}
:task (task! (mage.check/check (:arguments parsed)))}
codespell
{:doc "Check for misspellings in the source code."
{:doc "Check for misspellings in the source code."
:examples [["./bin/mage codespell"]]
:requires [[mage.codespell]]
:task (task! (mage.codespell/codespell (:arguments parsed)))}
:task (task! (mage.codespell/codespell (:arguments parsed)))}
codespell-interactive
{:doc "Check for misspellings in the source code, and interactively prompt to fix them."
{:doc "Check for misspellings in the source code, and interactively prompt to fix them."
:examples [["./bin/mage codespell-interactive"]]
:requires [[mage.codespell]]
:task (task! (mage.codespell/codespell-interactive (:arguments parsed)))}
:task (task! (mage.codespell/codespell-interactive (:arguments parsed)))}
start-maildev
{:doc "Start Maildev"
{:doc "Start Maildev"
:examples [["./bin/mage start-maildev -h" "print help for start-maildev"]]
:requires [[mage.start-maildev :as start-maildev]]
:task (task! (start-maildev/start-maildev!))}
:task (task! (start-maildev/start-maildev!))}
start-python-runner
{:doc "Start python-runner container with internal Moto S3 server for Python transforms"
:examples [["./bin/mage start-python-runner" "start python-runner with internal S3"]]
:requires [[mage.start-python-runner :as start-python-runner]]
:task (task! (start-python-runner/start-python-runner!))}
start-db
{:doc "Start a db on a default port in docker"
:examples [["./bin/mage start-db postgres latest" "start the latest postgres db we support"]
["./bin/mage start-db mysql oldest" "start the oldest mysql db we support"]]
:requires [[mage.start-db :as start-db]]
{:doc "Start a db on a default port in docker"
:examples [["./bin/mage start-db postgres latest" "start the latest postgres db we support"]
["./bin/mage start-db mysql oldest" "start the oldest mysql db we support"]]
:requires [[mage.start-db :as start-db]]
:arg-schema [:tuple
[:enum {:name "db"} :postgres :mysql :mariadb :mongo]
[:enum {:name "relative-version"} :oldest :latest]]
:db-info {:postgres {:ports {:oldest 5432 :latest 5433} :eol-url "https://endoflife.date/api/postgres.json"}
:mysql {:ports {:oldest 3308 :latest 3309} :eol-url "https://endoflife.date/api/mysql.json"}
:mariadb {:ports {:oldest 3306 :latest 3307} :eol-url "https://endoflife.date/api/mariadb.json"}
:mongo {:ports {:oldest 27017 :latest 27018} :eol-url "https://endoflife.date/api/mongodb.json"}}
:usage-fn start-db/usage
:task (task!
(let [[db version] (:arguments parsed)]
(start-db/start-db (:db-info (current-task)) db version)))}
:db-info {:postgres {:ports {:oldest 5432 :latest 5433} :eol-url "https://endoflife.date/api/postgres.json"}
:mysql {:ports {:oldest 3308 :latest 3309} :eol-url "https://endoflife.date/api/mysql.json"}
:mariadb {:ports {:oldest 3306 :latest 3307} :eol-url "https://endoflife.date/api/mariadb.json"}
:mongo {:ports {:oldest 27017 :latest 27018} :eol-url "https://endoflife.date/api/mongodb.json"}}
:usage-fn start-db/usage
:task (task!
(let [[db version] (:arguments parsed)]
(start-db/start-db (:db-info (current-task)) db version)))}
nrepl
{:doc "Starts the babashka nrepl: helpful for mage development"
{:doc "Starts the babashka nrepl: helpful for mage development"
:requires [[babashka.nrepl.server :as nrepl.server]
[mage.color :as c]]
:examples [["./bin/mage nrepl" "Starts the nrepl server"]
["./bin/mage nrepl --port 50506" "Starts the nrepl server"]
["./bin/mage nrepl -p 50506" "Starts the nrepl server"]]
:options [["-p" "--port PORT" "optionally provide a port to run the nrepl server on" :default 1667 :parse-fn Integer/parseInt]]
:task (task!
(let [{:keys [port]} (:options parsed)]
(spit ".nrepl-port" port)
(nrepl.server/start-server! {:port port})
(deref (promise))))}
:options [["-p" "--port PORT" "optionally provide a port to run the nrepl server on" :default 1667 :parse-fn Integer/parseInt]]
:task (task!
(let [{:keys [port]} (:options parsed)]
(spit ".nrepl-port" port)
(nrepl.server/start-server! {:port port})
(deref (promise))))}
lint-migrations
{:doc "Lint migrations files"
:requires [[lint-migrations-file]
[babashka.process :as p]]
:examples [["./bin/mage lint-migrations" "Lint all migrations and print report"]]
:task (do (cli/parse! (current-task))
(p/shell "bin/pre-lint-migrations-file.sh")
(lint-migrations-file/-main))}
{:doc "Lint migrations files"
:requires [[lint-migrations-file]
[babashka.process :as p]]
:examples [["./bin/mage lint-migrations" "Lint all migrations and print report"]]
:task (do (cli/parse! (current-task))
(p/shell "bin/pre-lint-migrations-file.sh")
(lint-migrations-file/-main))}
jar-download
{:doc "Download (and optionally run) jar for a metabase version or branch"
:examples [["./bin/mage jar-download 50" "Download the latest enterprise version of release 50 to ~/path/to/my/metabase/jars"]
["./bin/mage jar-download 1.45.2 -r" "Download and run metabase_1.45.2.jar"]
["./bin/mage jar-download 1.45.2 -d ~/path/to/my/jars" "Download metabase_1.45.2.jar to ~/path/to/my/jars, deleting if it exists"]
["./bin/mage jar-download master --run --port 3001" "Download and run the latest master branch jar on port 3001"]
["JARS=~/my-stuff ./bin/mage jar-download 1.45.2" "Download metabase_1.45.2.jar to ~/my-stuff"]
["./bin/mage jar-download v1.56.0-beta -r" "Download and run metabase_1.56.0-beta.jar"]]
:requires [[mage.jar-download :as jar-download]]
:options [["-r" "--run" "run the new jar after downloading it"]
["-e" "--env-file FILE" "you can optionally provide a lein-env file to specify the environment to use when running the jar."]
["-d" "--delete" "delete the old jar if found, by default does not re-download it"]
["-p" "--port PORT" "you can optionally provide a port to run the jar on, if not, we use a sane port."]]
{:doc "Download (and optionally run) jar for a metabase version or branch"
:examples [["./bin/mage jar-download 50" "Download the latest enterprise version of release 50 to ~/path/to/my/metabase/jars"]
["./bin/mage jar-download 1.45.2 -r" "Download and run metabase_1.45.2.jar"]
["./bin/mage jar-download 1.45.2 -d ~/path/to/my/jars" "Download metabase_1.45.2.jar to ~/path/to/my/jars, deleting if it exists"]
["./bin/mage jar-download master --run --port 3001" "Download and run the latest master branch jar on port 3001"]
["JARS=~/my-stuff ./bin/mage jar-download 1.45.2" "Download metabase_1.45.2.jar to ~/my-stuff"]
["./bin/mage jar-download v1.56.0-beta -r" "Download and run metabase_1.56.0-beta.jar"]]
:requires [[mage.jar-download :as jar-download]]
:options [["-r" "--run" "run the new jar after downloading it"]
["-e" "--env-file FILE" "you can optionally provide a lein-env file to specify the environment to use when running the jar."]
["-d" "--delete" "delete the old jar if found, by default does not re-download it"]
["-p" "--port PORT" "you can optionally provide a port to run the jar on, if not, we use a sane port."]]
:arg-schema [:or
[:tuple [:string {:name "version"}]]
[:tuple [:string {:name "version"}] [:string {:name "path"}]]]
:usage-fn (fn [_]
(-> "Version can have the following forms:"
(str "\n -" (c/green "50") " for the latest enterprise version of release 50")
(str "\n -" (c/green "1.45.2") " for a specific version")
(str "\n -" (c/green "master") " for the latest jar created for master")
(str "\n -" (c/green "any-branch-with-a-pr") " for the latest uberjar created for that branch")
(str "\n -" (c/green "1.56.0-beta") " for a specific beta version")
(str "\n\nnote: If you get an error about 'Invalid or corrupt jarfile', run this command again with --delete.")))
:task (task! (jar-download/jar-download parsed))}
:usage-fn (fn [_]
(-> "Version can have the following forms:"
(str "\n -" (c/green "50") " for the latest enterprise version of release 50")
(str "\n -" (c/green "1.45.2") " for a specific version")
(str "\n -" (c/green "master") " for the latest jar created for master")
(str "\n -" (c/green "any-branch-with-a-pr") " for the latest uberjar created for that branch")
(str "\n -" (c/green "1.56.0-beta") " for a specific beta version")
(str "\n\nnote: If you get an error about 'Invalid or corrupt jarfile', run this command again with --delete.")))
:task (task! (jar-download/jar-download parsed))}
setup-stats-repl
{:doc "Connect to the stats repl"
:examples [["./bin/mage connect-stats-repl" "Connects to the stats repl"]]
:requires [[mage.stats-repl :as stats-repl]]
:task (stats-repl/connect (cli/parse! (current-task)))}
:task (stats-repl/connect (cli/parse! (current-task)))}
notify
{:doc "Send notification for a comment (that already exists)"
{:doc "Send notification for a comment (that already exists)"
:requires [[mage.be-dev :refer [nrepl-eval]]]
:options [["-c" "--comment-id ID" "Notify about this comment id"]
["-p" "--port PORT" "Port to use for the task, defaults to value in .nrepl-port"]]
:options [["-c" "--comment-id ID" "Notify about this comment id"]
["-p" "--port PORT" "Port to use for the task, defaults to value in .nrepl-port"]]
:examples [["./bin/mage notify -c 1234" "Notify about comment with id 1234"]
["./bin/mage notify --comment-id 5678 --port 59498" "Notify about comment with id 5678 on nrepl port 59498"]]
:task (let [opts (:options (cli/parse! (current-task)))
:task (let [opts (:options (cli/parse! (current-task)))
;; note: no quoting -> expect it to be int
code (format "(notify-comment-id! %d)" (parse-long (:comment-id opts)))]
(if (:port opts)
(nrepl-eval "metabase.comments.core" code (:port opts))
(nrepl-eval "metabase.comments.core" code)))}
code (format "(notify-comment-id! %d)" (parse-long (:comment-id opts)))]
(if (:port opts)
(nrepl-eval "metabase.comments.core" code (:port opts))
(nrepl-eval "metabase.comments.core" code)))}
run-tests
{:doc "Quickly run tests locally. Pass dirs/files as arguments to run them."
:examples [["./bin/mage run-tests" "launch interactive test picker"]
["./bin/mage run-tests test/metabase/db" "run tests in test/metabase/db"]
["./bin/mage run-tests test/metabase/logger test/metabase/api_keys" "run tests in test/metabase/logger and src/metabase/api_keys"]
["./bin/mage run-tests test/metabase/config_test.clj" "run all tests in the metabase.config-test namespace"]]
{:doc "Quickly run tests locally. Pass dirs/files as arguments to run them."
:examples [["./bin/mage run-tests" "launch interactive test picker"]
["./bin/mage run-tests test/metabase/db" "run tests in test/metabase/db"]
["./bin/mage run-tests test/metabase/logger test/metabase/api_keys" "run tests in test/metabase/logger and src/metabase/api_keys"]
["./bin/mage run-tests test/metabase/config_test.clj" "run all tests in the metabase.config-test namespace"]]
;; TODO: add option to select files as well as dirs from fzf with styles
:options [["-s" "--selecting STYLE" "Style of test selection" :default "all"]
:options [["-s" "--selecting STYLE" "Style of test selection" :default "all"]
;; all, file, dir
["-p" "--port PORT" "Port to use for the task, defaults to value in .nrepl-port"]]
["-p" "--port PORT" "Port to use for the task, defaults to value in .nrepl-port"]]
:arg-schema [:sequential
{:details "If none are given, you will be prompted to select directories recursively from test/ and enterprise/backend/test/"}
[:string {:name "file-or-dir" :description "Files or directories (recursive) to test"}]]
:requires [[mage.quick-test-runner :as qtr]]
:task (task! (qtr/go parsed))}
:requires [[mage.quick-test-runner :as qtr]]
:task (task! (qtr/go parsed))}
affected-modules
{:doc "Which backend modules have changes?"
{:doc "Which backend modules have changes?"
:examples [["./bin/mage affected-modules master" "prints changes compared to `master`"]
["./bin/mage affected-modules"]]
:arg-schema [:or [:tuple] [:tuple [:string {:name "git-ref"}]]]
:requires [[mage.modules]]
:task (task! (mage.modules/cli-print-affected-modules (:arguments parsed)))}
:task (task! (mage.modules/cli-print-affected-modules (:arguments parsed)))}
can-skip-driver-tests
{:doc "Can we skip driver tests for this branch?"
{:doc "Can we skip driver tests for this branch?"
:examples [["./bin/mage can-skip-driver-tests master" "Returns zero (success) if we can skip driver tests based on changes related to `master`"]
["./bin/mage can-skip-driver-tests" "Returns zero (success) if we can skip driver tests based on changes? (defaults to master)"]]
:requires [[mage.modules]]
:task (task! (mage.modules/cli-can-skip-driver-tests (:arguments parsed)))}
:task (task! (mage.modules/cli-can-skip-driver-tests (:arguments parsed)))}
ls
{:doc "List all mage public and private tasks"
{:doc "List all mage public and private tasks"
:examples [["./bin/mage ls" "List all tasks"]]
:requires [[mage.util :as u]]
:task (task! (u/print-tasks parsed))}
:task (task! (u/print-tasks parsed))}
analytics:setup
{:doc "Set up usage analytics development mode"
@@ -267,18 +273,51 @@
:examples [["./bin/mage doctor" "Run all health checks"]]
:requires [[mage.doctor :as doctor]]
:task (task! (doctor/doctor!))}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Private Tasks:
;; These tasks are more for internal tooling, e.g. for use from git hooks etc.
;; - hidden from `./bin/mage` listing and `bb tasks`, these all - start with a `-`
-driver-decisions
{:doc "Determine which driver tests should run based on PR context. Outputs decisions for all drivers."
:examples [["./bin/mage -driver-decisions --git-ref=master --is-master-or-release=false --pr-labels= --skip=false" "Determine driver decisions for a PR"]
["./bin/mage -driver-decisions --pr-labels=ci:all-cloud-drivers" "Run all cloud drivers via label"]
["./bin/mage -driver-decisions --github-output-only" "Output only GITHUB_OUTPUT format (for CI)"]]
:usage-fn
(fn [_]
(->>
["# How we decide to run a driver's tests. For each driver, we check these conditions in order (first match wins):"
" |----------|-------------------------------------|--------|-------------------------------------------|"
" | Priority | Condition | Result | Reason |"
" |----------|-------------------------------------|--------|-------------------------------------------|"
" | 1 | Global skip (no backend changes) | SKIP | workflow skip (no backend changes) |"
" | 2 | Driver is :h2 | RUN | H2 always runs |"
" | 3 | Driver is quarantined | SKIP | driver is quarantined |"
" | 3(a) | ...and has break-quarantine-X label | RUN | anti-quarantine label present |"
" | 4 | On master or release branch | RUN | master/release branch |"
" | 5 | Cloud driver + ci:all-cloud-drivers | RUN | ci:all-cloud-drivers label |"
" | 6 | Cloud driver + its files changed | RUN | driver files changed |"
" | 7 | Cloud driver, no relevant changes | SKIP | no relevant changes for cloud driver |"
" | 8 | Driver deps affected by changes | RUN | driver module affected by shared code |"
" | 9 | Self-hosted driver, not affected | SKIP | driver module not affected |"
" |----------|-------------------------------------|--------|-------------------------------------------|"
""] (str/join "\n")))
:options [[nil "--git-ref REF" "The git ref to compute changed modules against"]
[nil "--is-master-or-release MASTER_OR_RELEASE"]
[nil "--pr-labels PR_LABELS" "Comma delimited labels on the pr"]
[nil "--skip SKIP"]
[nil "--github-output-only" "Only output GITHUB_OUTPUT format (no human-readable output)"]]
:requires [[clojure.string :as str]
[mage.modules]]
:task (task! (mage.modules/-main parsed))}
-install-merge-drivers
{:doc "Installs custom git merge drivers for YAML migration files."
:requires [[mage.util :as u]
[clojure.string :as str]]
:task (task!
(let [git-dir (u/sh "git rev-parse --git-dir")
(let [git-dir (u/sh "git rev-parse --git-common-dir")
git-config-file (str git-dir "/config")
the-include (str "\n"
"[include]\n"
@@ -293,7 +332,7 @@
:requires [[clojure.string :as str]
[mage.util :as u]]
:task (task!
(let [git-dir (u/sh "git rev-parse --git-dir")
(let [git-dir (u/sh "git rev-parse --git-common-dir")
git-config-file (str git-dir "/config")
the-include (str "\n"
"[include]\n"
@@ -313,38 +352,38 @@
[:string {:name "base" :description "ancestor's version (base)"}]
[:string {:name "ours" :description "current version (ours)"}]
[:string {:name "theirs" :description "other branch's version (theirs)"}]
[:int {:name "conflict-marker-size" :description "conflict marker size" :min 0}]
[:int {:name "conflict-marker-size" :description "conflict marker size" :min 0}]
[:string {:name "file-path" :description "file path"}]]
:requires [[mage.merge-yaml-migrations]]
:task (task! (mage.merge-yaml-migrations/-main parsed))}
-play
{:doc "Play sounds from mage.sound"
:examples [["./bin/mage -play success" "play success sound"] ["./bin/mage -play s" "play success sound"]
["./bin/mage -play error" "play error sound"] ["./bin/mage -play e" "play error sound"]
["./bin/mage -play ping" "play ping sound"] ["./bin/mage -play p" "play ping sound"]
["./bin/mage -play warning" "play warning sound"] ["./bin/mage -play w" "play warning sound"]]
:requires [[mage.sound :as sound]]
{:doc "Play sounds from mage.sound"
:examples [["./bin/mage -play success" "play success sound"] ["./bin/mage -play s" "play success sound"]
["./bin/mage -play error" "play error sound"] ["./bin/mage -play e" "play error sound"]
["./bin/mage -play ping" "play ping sound"] ["./bin/mage -play p" "play ping sound"]
["./bin/mage -play warning" "play warning sound"] ["./bin/mage -play w" "play warning sound"]]
:requires [[mage.sound :as sound]]
:arg-schema [:tuple [:enum "s" "success" "e" "error" "p" "ping" "w" "warning"]]
:task (task! (case (first (:arguments parsed))
("s" "success") (sound/success)
("e" "error") (sound/error)
("p" "ping") (sound/ping)
("w" "warning") (sound/warning)))}
:task (task! (case (first (:arguments parsed))
("s" "success") (sound/success)
("e" "error") (sound/error)
("p" "ping") (sound/ping)
("w" "warning") (sound/warning)))}
-token-scan
{:doc "Security token scanner for detecting leaked API keys and secrets"
:examples [["./bin/mage -token-scan deps.edn bb.edn" "Scan specific files"]
["./bin/mage -token-scan -v deps.edn" "Scan with verbose output"]
["./bin/mage -token-scan -a" "Scan all files in the project"]]
:requires [[mage.token-scan :as token-scan]]
:options [["-a" "--all-files" "Scan all files in the project"]
["-v" "--verbose" "Verbose output"]]
{:doc "Security token scanner for detecting leaked API keys and secrets"
:examples [["./bin/mage -token-scan deps.edn bb.edn" "Scan specific files"]
["./bin/mage -token-scan -v deps.edn" "Scan with verbose output"]
["./bin/mage -token-scan -a" "Scan all files in the project"]]
:requires [[mage.token-scan :as token-scan]]
:options [["-a" "--all-files" "Scan all files in the project"]
["-v" "--verbose" "Verbose output"]]
:arg-schema [:sequential [:string {:description "Files to scan"}]]
:task (task! (token-scan/run-scan parsed))}
:task (task! (token-scan/run-scan parsed))}
-repl
{:doc "Eval a string of clojure code in the backend dev server"
{:doc "Eval a string of clojure code in the backend dev server"
:requires [[babashka.process :as process]
[mage.color :as c]
[mage.be-dev :refer [nrepl-eval]]]
@@ -356,16 +395,16 @@
"Evaluate a keyword inside a namespace"]]
:options [["-n" "--namespace NAMESPACE" "Namespace to use for the task"]
["-p" "--port PORT" "Port to use for the task, defaults to value in .nrepl-port"]]
:task (let [parsed (cli/parse! (current-task))
code (first (:arguments parsed))
port (or (:port (:options parsed))
(slurp ".nrepl-port")
(throw (ex-info (c/red "No port specified, and no .nrepl-port file found.") {:babashka/exit 1})))
nns (or (:namespace (:options parsed)) "user")]
(nrepl-eval nns code port))}
:task (let [parsed (cli/parse! (current-task))
code (first (:arguments parsed))
port (or (:port (:options parsed))
(slurp ".nrepl-port")
(throw (ex-info (c/red "No port specified, and no .nrepl-port file found.") {:babashka/exit 1})))
nns (or (:namespace (:options parsed)) "user")]
(nrepl-eval nns code port))}
-check-readable
{:doc "Check that code is readable parens a file"
{:doc "Check that code is readable parens a file"
:requires [[mage.readability-check :as readability]]
:arg-schema [:or
[:tuple [:string {:name "file"
@@ -379,60 +418,59 @@
"Check file for readability"]
["./bin/mage -check-readable dev/src/dev.clj 300"
"Check top level form containing line 300 for readability"]]
:task (let [{:keys [arguments]} (cli/parse! (current-task))]
(apply readability/check arguments))}
:task (let [{:keys [arguments]} (cli/parse! (current-task))]
(apply readability/check arguments))}
;; Uncomment this if you want to play with the example calculator task:
#_#_
-example-calculator
{;; `:doc` is a short docstring for the task, will be listed in `./bin/mage -h` and other help info.
#_#_-example-calculator
{;; `:doc` is a short docstring for the task, will be listed in `./bin/mage -h` and other help info.
;; Keep these short, they need to fit on one line in the output from `./bin/mage -h`.
:doc "The example calculator's task docstring"
:doc "The example calculator's task docstring"
;; examples get printed out when you pass -h or --help.
:examples [["./bin/mage -example-calculator 1 + 99" "evaluates to 100"]
:examples [["./bin/mage -example-calculator 1 + 99" "evaluates to 100"]
["./bin/mage -example-calculator 100 - 99" "evaluates to 1"]]
;; The task is the actual code that runs when you run the task.
:task (task!
(println "Parsed input:") (u/pp parsed)
(let [{:keys [arguments data]} parsed
[a op b] arguments]
(println a (name op) b "=" (c/blue (({:+ + :- -} op) a b)))))
:task (task!
(println "Parsed input:") (u/pp parsed)
(let [{:keys [arguments data]} parsed
[a op b] arguments]
(println a (name op) b "=" (c/blue (({:+ + :- -} op) a b)))))
;; (optional) `:require` lazily libraries for just your task:
:requires [[mage.color :as c]
:requires [[mage.color :as c]
[mage.util :as u]]
;; (optional) `:options` are passed to [[clojure.tools.cli/parse-opts]].
;; See: https://clojure.github.io/tools.cli/index.html#clojure.tools.cli/parse-opts
:options [["-n" "--negate" "Negate the result of the calculation"]
["-a" "--also-add N" "Add N to the result of the calculation"]]
:options [["-n" "--negate" "Negate the result of the calculation"]
["-a" "--also-add N" "Add N to the result of the calculation"]]
;; (optional) `:arg-schema` is a malli schema for the arguments passed to the task, after the options
:arg-schema [:tuple [:int {:name "number-a"}] [:enum {:name "operation"} :+ :-] [:int {:name "number-b"}]]
:arg-schema [:tuple [:int {:name "number-a"}] [:enum {:name "operation"} :+ :-] [:int {:name "number-b"}]]
;; (optional) `:usage-fn` is a function called with the current-task map.
;; Returns a string containing extra-detailed usage information.
:usage-fn (fn [{:keys [doc] :as _task}]
:usage-fn (fn [{:keys [doc] :as _task}]
(str "Optional extra usage information, for explicit callouts.\n"
"Reversed docstring: '" (c/green (apply str (reverse doc))) "'"))
;; (optional) Any other keys are completely allowed. Put things to lookup or that you want to be easy to change
;; here. These will be returned from `(current-task)`. See: start-db for an example of using `:ports` to define a
;; top-level map of ports. If something should be easily accessible in the task, put it here.
:data {:a [:b :c] :b [:d]}}
:data {:a [:b :c] :b [:d]}}
-test {:doc "run all mage tests"
-test {:doc "run all mage tests"
:requires [[clojure.string :as str]
[clojure.test]
[mage.core-test] ;; loads the actual tests
[mage.util :as u]]
:arg-schema [:sequential [:string {:name "test-matching-pattern"}]]
:task (let [{:keys [arguments]} (task! parsed)
pattern (if (seq arguments)
(str "^mage\\..*(" (str/join "|" arguments) ").*")
"^mage\\..*")
_ (u/debug ["test regex matching pattern" pattern])
results (clojure.test/run-all-tests (re-pattern pattern))]
(if (zero? (+ (:fail results) (:error results)))
(println (c/green "All mage tests passed!"))
(do
(println (c/red "Some mage tests failed."))
(u/exit 1))))}
:task (let [{:keys [arguments]} (task! parsed)
pattern (if (seq arguments)
(str "^mage\\..*(" (str/join "|" arguments) ").*")
"^mage\\..*")
_ (u/debug ["test regex matching pattern" pattern])
results (clojure.test/run-all-tests (re-pattern pattern))]
(if (zero? (+ (:fail results) (:error results)))
(println (c/green "All mage tests passed!"))
(do
(println (c/red "Some mage tests failed."))
(u/exit 1))))}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Personal Tasks:
@@ -441,14 +479,14 @@
;; `./bin/mage` listing.
-escherize-pr-summary
{:doc "Print a day's PR summary for the metabase/metabase repo"
{:doc "Print a day's PR summary for the metabase/metabase repo"
:requires [[mage.escherize.pr-summary :as eps]]
:examples [["./bin/mage -escherize-pr-summary | pbcopy" "Copy your daily"]]
:task (task! (eps/summarize-prs parsed))}
:task (task! (eps/summarize-prs parsed))}
-escherize-fix-whitespace
{:doc "Removes all lines like '^<space>+$' from files in glob pattern."
:requires [[mage.escherize.fix-whitespace :as fix-whitespace]]
:examples [["mage -escherize-fix-whitespace '**/*.clj{,c,s}'" "Removes all lines like ' ' from all clj files"]]
:arg-schema [:sequential [:string {:name "glob-pattern" :description "Glob pattern to match files"}]]
:task (task! (fix-whitespace/run-clean parsed))}}}
:task (task! (fix-whitespace/run-clean parsed))}}}

View File

@@ -47,7 +47,15 @@
:merge-map ::connection-property-map))
(s/def ::merge-map (spell/keys :req-un [::merge]))
(s/def ::container-style-type #{"grid" "component"})
(s/def ::container-style (s/cat :type ::container-style-type :value string?))
(s/def ::fields (s/coll-of ::connection-property))
(s/def ::group-map (spell/keys :req-un [::container-style ::fields]))
(s/def ::group-property (spell/keys :req-un [::group]))
(s/def ::group ::group-map)
(s/def ::connection-property (s/or :merge-with ::merge-map
:group-with ::group-property
:property-name ::raw-property-name-ref
:property-map (s/merge (spell/keys :req-un [::name]) ::connection-property-map)))

View File

@@ -1,711 +0,0 @@
// mb-proxy: Caddy Admin API helper for per-branch Metabase routing.
// Example usage:
//
// go run ./cmd/mb-proxy add --host my-branch.metabase.localhost --frontend 3001 --backend 3002
// go run ./cmd/mb-proxy add --host my-branch.metabase.localhost --backend 4000
// go run ./cmd/mb-proxy remove --host my-branch.metabase.localhost
// go run ./cmd/mb-proxy list
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
const (
defaultAdminURL = "http://127.0.0.1:2019"
)
type registry struct {
Entries map[string]registryEntry `json:"entries"`
}
type registryEntry struct {
Host string `json:"host"`
Server string `json:"server"`
Frontend string `json:"frontend,omitempty"`
Backend string `json:"backend,omitempty"`
Updated string `json:"updated"`
}
type caddyClient struct {
baseURL string
client *http.Client
}
type httpServer struct {
Listen []string `json:"listen"`
}
func main() {
if len(os.Args) < 2 {
fail(errors.New("expected subcommand: add | remove | list"))
}
fmt.Fprintf(os.Stderr, "[mb-proxy] subcommand=%s\n", os.Args[1])
switch os.Args[1] {
case "add":
addCmd(os.Args[2:])
case "remove":
removeCmd(os.Args[2:])
case "list":
listCmd(os.Args[2:])
case "ports":
portsCmd(os.Args[2:])
default:
fail(fmt.Errorf("unknown subcommand %q (expected add | remove | list)", os.Args[1]))
}
}
func addCmd(args []string) {
fs := flag.NewFlagSet("add", flag.ExitOnError)
var host, frontend, backend, admin, server, stateDir string
fs.StringVar(&host, "host", "", "Fully-qualified host (e.g. feature-x.metabase.localhost)")
fs.StringVar(&frontend, "frontend", "", "Frontend/WebSocket port (optional; requires --backend)")
fs.StringVar(&backend, "backend", "", "Backend port (required)")
fs.StringVar(&admin, "admin", envOrDefault("MB_PROXY_ADMIN", defaultAdminURL), "Caddy Admin API base URL (http://127.0.0.1:2019 or unix:///path)")
fs.StringVar(&server, "server", os.Getenv("MB_PROXY_SERVER"), "Caddy HTTP server name (optional; auto-detected if empty)")
fs.StringVar(&stateDir, "state-dir", os.Getenv("MB_PROXY_STATE_DIR"), "State directory (optional; auto-resolved if empty)")
fs.Parse(args)
if host == "" {
fail(errors.New("--host is required"))
}
if backend == "" {
fail(errors.New("--backend is required"))
}
if frontend != "" && backend == "" {
fail(errors.New("--frontend requires --backend"))
}
stateDir, err := resolveStateDir(stateDir)
if err != nil {
fail(err)
}
fmt.Fprintf(os.Stderr, "[mb-proxy] state_dir=%s\n", stateDir)
if err := os.MkdirAll(stateDir, 0o700); err != nil {
fail(fmt.Errorf("create state dir: %w", err))
}
unlock, err := lockState(stateDir)
if err != nil {
fail(err)
}
defer func() {
fmt.Fprintf(os.Stderr, "[mb-proxy] unlocking state\n")
unlock()
}()
reg, err := loadRegistry(stateDir)
if err != nil {
fail(err)
}
client, err := newCaddyClient(admin)
if err != nil {
fail(err)
}
serverName, err := resolveServerName(client, server)
if err != nil {
fail(err)
}
routes, err := client.getRoutes(serverName)
if err != nil {
fail(err)
}
newRoute := buildRoute(host, frontend, backend)
existingIdx := findRouteIndexForHost(routes, host)
if existingIdx >= 0 {
fmt.Fprintf(os.Stderr, "[mb-proxy] updating route host=%s server=%s index=%d\n", host, serverName, existingIdx)
if err := client.putRoute(serverName, existingIdx, newRoute); err != nil {
fail(err)
}
fmt.Printf("Updated route for %s on server %s (index %d)\n", host, serverName, existingIdx)
} else {
fmt.Fprintf(os.Stderr, "[mb-proxy] creating route host=%s server=%s\n", host, serverName)
if err := client.postRoute(serverName, newRoute); err != nil {
fail(err)
}
fmt.Printf("Added route for %s on server %s\n", host, serverName)
}
reg.Entries[host] = registryEntry{
Host: host,
Server: serverName,
Frontend: frontend,
Backend: backend,
Updated: time.Now().Format(time.RFC3339),
}
if err := saveRegistry(stateDir, reg); err != nil {
fail(err)
}
}
func removeCmd(args []string) {
fs := flag.NewFlagSet("remove", flag.ExitOnError)
var host, admin, server, stateDir string
fs.StringVar(&host, "host", "", "Fully-qualified host to remove")
fs.StringVar(&admin, "admin", envOrDefault("MB_PROXY_ADMIN", defaultAdminURL), "Caddy Admin API base URL")
fs.StringVar(&server, "server", os.Getenv("MB_PROXY_SERVER"), "Caddy HTTP server name (optional)")
fs.StringVar(&stateDir, "state-dir", os.Getenv("MB_PROXY_STATE_DIR"), "State directory (optional)")
fs.Parse(args)
if host == "" {
fail(errors.New("--host is required"))
}
stateDir, err := resolveStateDir(stateDir)
if err != nil {
fail(err)
}
fmt.Fprintf(os.Stderr, "[mb-proxy] state_dir=%s\n", stateDir)
if err := os.MkdirAll(stateDir, 0o700); err != nil {
fail(fmt.Errorf("create state dir: %w", err))
}
unlock, err := lockState(stateDir)
if err != nil {
fail(err)
}
defer func() {
fmt.Fprintf(os.Stderr, "[mb-proxy] unlocking state\n")
unlock()
}()
reg, err := loadRegistry(stateDir)
if err != nil {
fail(err)
}
client, err := newCaddyClient(admin)
if err != nil {
fail(err)
}
serverName := server
if serverName == "" {
if entry, ok := reg.Entries[host]; ok && entry.Server != "" {
serverName = entry.Server
}
}
serverName, err = resolveServerName(client, serverName)
if err != nil {
fail(err)
}
routes, err := client.getRoutes(serverName)
if err != nil {
fail(err)
}
idx := findRouteIndexForHost(routes, host)
if idx >= 0 {
fmt.Fprintf(os.Stderr, "[mb-proxy] deleting route host=%s server=%s index=%d\n", host, serverName, idx)
if err := client.deleteRoute(serverName, idx); err != nil {
fail(err)
}
fmt.Printf("Removed route for %s (index %d) from server %s\n", host, idx, serverName)
} else {
fmt.Printf("No route found for %s on server %s (nothing to remove)\n", host, serverName)
}
delete(reg.Entries, host)
if err := saveRegistry(stateDir, reg); err != nil {
fail(err)
}
if len(reg.Entries) == 0 {
if err := stopCaddy(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to stop Caddy (no hosts remaining): %v\n", err)
} else {
fmt.Println("Stopped Caddy (no hosts remaining)")
}
}
}
func listCmd(args []string) {
fs := flag.NewFlagSet("list", flag.ExitOnError)
var stateDir string
fs.StringVar(&stateDir, "state-dir", os.Getenv("MB_PROXY_STATE_DIR"), "State directory (optional)")
fs.Parse(args)
stateDir, err := resolveStateDir(stateDir)
if err != nil {
fail(err)
}
fmt.Fprintf(os.Stderr, "[mb-proxy] state_dir=%s\n", stateDir)
reg, err := loadRegistry(stateDir)
if err != nil {
fail(err)
}
if len(reg.Entries) == 0 {
fmt.Println("No hosts registered.")
return
}
for host, entry := range reg.Entries {
target := entry.Backend
if target == "" {
target = "(none)"
}
fmt.Printf("%s -> %s (server=%s", host, target, entry.Server)
if entry.Frontend != "" {
fmt.Printf(", frontend=%s", entry.Frontend)
}
fmt.Printf(", updated=%s)\n", entry.Updated)
}
}
func portsCmd(args []string) {
fs := flag.NewFlagSet("ports", flag.ExitOnError)
var count int
fs.IntVar(&count, "count", 1, "Number of free ports to return")
fs.Parse(args)
if count <= 0 {
fail(errors.New("--count must be >= 1"))
}
ports, err := getFreePorts(count)
if err != nil {
fail(err)
}
fmt.Println(strings.Join(ports, " "))
}
func getFreePorts(n int) ([]string, error) {
var listeners []net.Listener
var ports []string
for i := 0; i < n; i++ {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
for _, l := range listeners {
_ = l.Close()
}
return nil, fmt.Errorf("get free port: %w", err)
}
listeners = append(listeners, ln)
_, portStr, err := net.SplitHostPort(ln.Addr().String())
if err != nil {
for _, l := range listeners {
_ = l.Close()
}
return nil, fmt.Errorf("parse port: %w", err)
}
if _, err := strconv.Atoi(portStr); err != nil {
for _, l := range listeners {
_ = l.Close()
}
return nil, fmt.Errorf("invalid port: %w", err)
}
ports = append(ports, portStr)
}
for _, l := range listeners {
_ = l.Close()
}
return ports, nil
}
func resolveStateDir(override string) (string, error) {
if override != "" {
return override, nil
}
if v := os.Getenv("MB_PROXY_STATE_DIR"); v != "" {
return v, nil
}
if v := os.Getenv("XDG_STATE_HOME"); v != "" {
return filepath.Join(v, "mb-proxy"), nil
}
if runtime.GOOS == "darwin" {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Application Support", "mb-proxy"), nil
}
if home, err := os.UserHomeDir(); err == nil {
return filepath.Join(home, ".local", "state", "mb-proxy"), nil
}
return filepath.Join(os.TempDir(), "mb-proxy"), nil
}
func lockState(stateDir string) (func(), error) {
lockPath := filepath.Join(stateDir, "registry.lock")
deadline := time.Now().Add(5 * time.Second)
staleAfter := 30 * time.Second
fmt.Fprintf(os.Stderr, "[mb-proxy] acquiring lock at %s\n", lockPath)
for {
err := os.Mkdir(lockPath, 0o700)
if err == nil {
fmt.Fprintf(os.Stderr, "[mb-proxy] lock acquired\n")
return func() { _ = os.Remove(lockPath) }, nil
}
if !errors.Is(err, os.ErrExist) {
return nil, fmt.Errorf("lock state: %w", err)
}
// Handle stale locks: if the lock dir is old, remove it and retry.
info, statErr := os.Stat(lockPath)
if statErr == nil {
if time.Since(info.ModTime()) > staleAfter {
fmt.Fprintf(os.Stderr, "[mb-proxy] removing stale lock (age=%s)\n", time.Since(info.ModTime()))
_ = os.Remove(lockPath)
continue
}
}
if time.Now().After(deadline) {
return nil, errors.New("timed out waiting for registry lock")
}
time.Sleep(100 * time.Millisecond)
}
}
func loadRegistry(stateDir string) (registry, error) {
path := filepath.Join(stateDir, "registry.json")
r := registry{Entries: map[string]registryEntry{}}
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return r, nil
}
return r, fmt.Errorf("read registry: %w", err)
}
if len(data) == 0 {
return r, nil
}
if err := json.Unmarshal(data, &r); err != nil {
return r, fmt.Errorf("parse registry: %w", err)
}
if r.Entries == nil {
r.Entries = map[string]registryEntry{}
}
return r, nil
}
func saveRegistry(stateDir string, r registry) error {
path := filepath.Join(stateDir, "registry.json")
tmp := path + ".tmp"
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return fmt.Errorf("serialize registry: %w", err)
}
if err := os.WriteFile(tmp, data, 0o600); err != nil {
return fmt.Errorf("write registry: %w", err)
}
return os.Rename(tmp, path)
}
func envOrDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func newCaddyClient(admin string) (*caddyClient, error) {
if admin == "" {
admin = defaultAdminURL
}
if strings.HasPrefix(admin, "unix://") || strings.HasPrefix(admin, "unix:") {
socket := strings.TrimPrefix(admin, "unix://")
socket = strings.TrimPrefix(socket, "unix:")
tr := &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socket)
},
}
return &caddyClient{baseURL: "http://unix", client: &http.Client{Transport: tr, Timeout: 5 * time.Second}}, nil
}
return &caddyClient{baseURL: strings.TrimRight(admin, "/"), client: &http.Client{Timeout: 5 * time.Second}}, nil
}
func (c *caddyClient) get(path string) (*http.Response, error) {
req, err := http.NewRequest("GET", c.baseURL+path, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
}
func (c *caddyClient) postJSON(path string, body any) (*http.Response, error) {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", c.baseURL+path, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return c.client.Do(req)
}
func (c *caddyClient) putJSON(path string, body any) (*http.Response, error) {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PUT", c.baseURL+path, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return c.client.Do(req)
}
func (c *caddyClient) delete(path string) (*http.Response, error) {
req, err := http.NewRequest("DELETE", c.baseURL+path, nil)
if err != nil {
return nil, err
}
return c.client.Do(req)
}
func ensureSuccess(resp *http.Response, body []byte) error {
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
return fmt.Errorf("caddy admin responded %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
func (c *caddyClient) getServers() (map[string]httpServer, error) {
resp, err := c.get("/config/apps/http/servers")
if err != nil {
return nil, fmt.Errorf("fetch servers: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusNotFound {
return map[string]httpServer{}, nil
}
if err := ensureSuccess(resp, body); err != nil {
return nil, err
}
var servers map[string]httpServer
if err := json.Unmarshal(body, &servers); err != nil {
return nil, fmt.Errorf("parse servers: %w", err)
}
return servers, nil
}
func (c *caddyClient) getRoutes(server string) ([]map[string]any, error) {
resp, err := c.get("/config/apps/http/servers/" + server + "/routes")
if err != nil {
return nil, fmt.Errorf("fetch routes: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusNotFound {
return []map[string]any{}, nil
}
if err := ensureSuccess(resp, body); err != nil {
return nil, err
}
var routes []map[string]any
if len(body) == 0 {
return []map[string]any{}, nil
}
if err := json.Unmarshal(body, &routes); err != nil {
return nil, fmt.Errorf("parse routes: %w", err)
}
return routes, nil
}
func (c *caddyClient) postRoute(server string, route map[string]any) error {
resp, err := c.postJSON("/config/apps/http/servers/"+server+"/routes", route)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return ensureSuccess(resp, body)
}
func (c *caddyClient) putRoute(server string, idx int, route map[string]any) error {
resp, err := c.putJSON(fmt.Sprintf("/config/apps/http/servers/%s/routes/%d", server, idx), route)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return ensureSuccess(resp, body)
}
func (c *caddyClient) deleteRoute(server string, idx int) error {
resp, err := c.delete(fmt.Sprintf("/config/apps/http/servers/%s/routes/%d", server, idx))
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusNotFound {
return nil
}
return ensureSuccess(resp, body)
}
func resolveServerName(client *caddyClient, override string) (string, error) {
if override != "" {
return override, nil
}
servers, err := client.getServers()
if err != nil {
return "", err
}
if len(servers) == 0 {
return "", errors.New("no HTTP servers configured in Caddy; start Caddy with your base config first")
}
for name, srv := range servers {
for _, ln := range srv.Listen {
if strings.Contains(ln, ":80") {
return name, nil
}
}
}
for name := range servers {
return name, nil
}
return "", errors.New("no HTTP server found")
}
func findRouteIndexForHost(routes []map[string]any, host string) int {
for i, r := range routes {
matchVal, ok := r["match"]
if !ok {
continue
}
matchSlice, ok := matchVal.([]any)
if !ok {
continue
}
for _, m := range matchSlice {
mm, ok := m.(map[string]any)
if !ok {
continue
}
hostVal, ok := mm["host"]
if !ok {
continue
}
hostSlice, ok := hostVal.([]any)
if !ok {
continue
}
for _, h := range hostSlice {
if hs, ok := h.(string); ok && hs == host {
return i
}
}
}
}
return -1
}
func buildRoute(host, frontend, backend string) map[string]any {
var innerRoutes []map[string]any
// Preflight (OPTIONS)
innerRoutes = append(innerRoutes, map[string]any{
"match": []map[string]any{{"method": []string{"OPTIONS"}}},
"handle": []map[string]any{
corsHeaders(true),
{"handler": "static_response", "status_code": 204},
},
"terminal": true,
})
// Websocket route to frontend if provided
if frontend != "" {
innerRoutes = append(innerRoutes, map[string]any{
"match": []map[string]any{{
"header": map[string]any{
"Connection": []string{"*Upgrade*"},
"Upgrade": []string{"websocket"},
},
}},
"handle": []map[string]any{
corsHeaders(false),
reverseProxyHandler(frontend),
},
"terminal": true,
})
}
// Default route to backend
innerRoutes = append(innerRoutes, map[string]any{
"handle": []map[string]any{
corsHeaders(false),
reverseProxyHandler(backend),
},
})
return map[string]any{
"match": []map[string]any{{"host": []string{host}}},
"handle": []map[string]any{{"handler": "subroute", "routes": innerRoutes}},
"terminal": true,
}
}
func corsHeaders(preflight bool) map[string]any {
set := map[string]any{
"Access-Control-Allow-Origin": []string{"{http.request.header.origin}"},
"Access-Control-Expose-Headers": []string{"Authorization"},
"Access-Control-Allow-Credentials": []string{"true"},
"Vary": []string{"Origin"},
}
if preflight {
set["Access-Control-Allow-Methods"] = []string{"GET, POST, PUT, PATCH, DELETE"}
set["Access-Control-Max-Age"] = []string{"3600"}
}
return map[string]any{
"handler": "headers",
"response": map[string]any{
"set": set,
},
}
}
func reverseProxyHandler(portOrAddress string) map[string]any {
target := portOrAddress
if !strings.Contains(target, ":") {
target = "127.0.0.1:" + target
}
return map[string]any{
"handler": "reverse_proxy",
"upstreams": []map[string]any{
{"dial": target},
},
}
}
func fail(err error) {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
func stopCaddy() error {
cmd := exec.Command("caddy", "stop")
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("caddy stop: %w (%s)", err, strings.TrimSpace(string(out)))
}
return nil
}

View File

@@ -4,10 +4,7 @@
;; !! PLEASE KEEP THESE ORGANIZED ALPHABETICALLY !!
;; !! AND ADD A COMMENT EXPLAINING THEIR PURPOSE !!
;; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{amalloy/ring-buffer {:mvn/version "1.3.1" ; fixed length queue implementation, used in log buffering
: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
{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.27"} ; Portable filesystem operations
bigml/histogram {:mvn/version "5.0.0" ; Histogram data structure
:exclusions [junit/junit]}
@@ -104,7 +101,8 @@
org.bouncycastle/bcprov-jdk18on]}
metabase/throttle {:mvn/version "1.0.2"} ; Tools for throttling access to API endpoints and other code pathways
methodical/methodical {:mvn/version "1.0.127"} ; drop-in replacements for Clojure multimethods and adds several advanced features
metosin/malli {:mvn/version "0.20.0"} ; Data-driven Schemas for Clojure/Script and babashka
metosin/malli {:git/url "https://github.com/metabase/malli" ; Data-driven Schemas for Clojure/Script and babashka
:git/sha "b4a2d78647a049870b316753d7aae7fb4ade3ae5"} ; patched to resolve an issue causing OOM
net.cgrand/macrovich {:mvn/version "0.2.2"} ; utils for writing macros for both Clojure & ClojureScript
net.clojars.wkok/openai-clojure {:mvn/version "0.23.0"
:exclusions [lambdaisland/uri]} ; OpenAI

View File

@@ -313,13 +313,28 @@
[k (into (sorted-set) (map :namespace) v)]))
(group-by :depends-on-namespace (external-usages deps module-symb)))))
(defn externally-used-namespaces
"All namespaces from a module that are used outside that module."
([module-symb]
(externally-used-namespaces (dependencies) module-symb))
(defn module-friends
"`:friends` of the module from the Kondo config -- these are allowed to freely use all namespaces in the module, not
just `:api` ones.
([deps module-symb]
(into (sorted-set) (map :depends-on-namespace) (external-usages deps module-symb))))
(module-friends (kondo-config) 'lib)
;; => #{query-processor}"
[kondo-config module-symb]
(get-in kondo-config [module-symb :friends]))
(declare kondo-config)
(defn externally-used-namespaces-ignoring-friends
"All namespaces from a module that are used outside that module, excluding usages by `:friends` of the module."
([module-symb]
(externally-used-namespaces-ignoring-friends (dependencies) (kondo-config) module-symb))
([deps kondo-config module-symb]
(let [friends (module-friends kondo-config module-symb)]
(into (sorted-set)
(comp (remove #(contains? friends (:module %)))
(map :depends-on-namespace))
(external-usages deps module-symb)))))
(defn module-dependencies
"Build a graph of module => set of modules it directly depends on."
@@ -412,12 +427,12 @@
(defn generate-config
"Generate the Kondo config that should go in `.clj-kondo/config/modules/config.edn`."
([]
(generate-config (dependencies)))
(generate-config (dependencies) (kondo-config)))
([deps]
([deps kondo-config]
(into (sorted-map)
(map (fn [[module uses]]
[module {:api (externally-used-namespaces deps module)
[module {:api (externally-used-namespaces-ignoring-friends deps kondo-config module)
:uses uses}]))
(module-dependencies deps))))
@@ -447,12 +462,13 @@
(kondo-config-diff (dependencies)))
([deps]
(-> (ddiff/diff
(update-vals (kondo-config) #(dissoc % :team))
(generate-config deps))
ddiff/minimize
kondo-config-diff-ignore-any
ddiff/minimize)))
(let [kondo-config (kondo-config)]
(-> (ddiff/diff
(update-vals kondo-config #(dissoc % :team :friends))
(generate-config deps kondo-config))
ddiff/minimize
kondo-config-diff-ignore-any
ddiff/minimize))))
(defn print-kondo-config-diff
"Print the diff between how the config would look if regenerated with [[generate-config]] versus how it looks in

View File

@@ -142,7 +142,7 @@
clojure -M:migrate rollback id \"v40.00.001\" ;; rollback to a specific migration with id
clojure -M:migrate rollback last-deployment ;; rollback the last deployment
clojure -M:migrate status ;; print the latest migration id
clojure -M:migrate reset-checksums. ;; sets the checkums to what they would be if migrated from the current changelog"
clojure -M:migrate reset-checksums. ;; sets the checksums to what they would be if migrated from the current changelog"
[& args]
(let [[cmd & migration-args] args]

View File

@@ -22,7 +22,6 @@
[toucan2.tools.before-update :as t2.before-update]
[toucan2.util :as t2.util]))
(def changes*
"An atom to store all the changes of models that we currently track."
(atom {}))
@@ -79,7 +78,7 @@
:update
(into {} (t2/changes row-or-instance))
(into {} row-or-instance))
clean-change)
clean-change)
path [(t2/table-name model) action]]
;; ideally this should be debug, but for some reasons this doesn't get logged
(on-change path change-info)
@@ -96,7 +95,7 @@
[;; will be better if we could use after-insert to get the inserted id, but toucan2 doesn't define a multimethod for after-insert
[#'t2.before-insert/before-insert :after :insert ::t2.before-insert/before-insert]
[#'t2.before-update/before-update :after :update ::t2.before-update/before-update]
;; we do :before aux-method instead of :after for delete bacause the after method has input is number of affected rows
;; we do :before aux-method instead of :after for delete because the after method has input is number of affected rows
[#'t2.before-delete/before-delete :before :delete ::t2.before-delete/before-delete]])
(defn- track-one!

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -19,6 +19,7 @@ To set up Metabot, see [Metabot settings](./settings.md).
Metabot can help you to:
- [AI exploration](#ai-exploration).
- [Create a chart using the query builder](#how-metabot-uses-the-query-builder) from a natural language query.
- [Generate SQL in the native editor](../questions/native-editor/writing-sql.md) from natural language. (Currently, only SQL is supported.)
- [Edit SQL directly in the native editor](#inline-sql-editing).
@@ -101,16 +102,29 @@ When you get an error in a SQL query, you can click the **Have Metabot fix it**
You can ask Metabot to generate or edit code in the SQL editor, without using the [chat sidebar](#the-metabot-chat-sidebar):
1. Open the [SQL editor](../questions/native-editor/writing-sql.md).
2. Press Cmd+; on Mac or Ctrl+; on Windows.
2. Press Cmd+shift+i on Mac or Ctrl+shift+i on Windows.
3. Describe the SQL you want to generate or modify.
4. Review the generated code and accept or reject it.
Once you start typing or editing your SQL, you can continue using the keyboard shortcut to refine your code.
## Navigating after Metabot creates a chart
If Metabot creates a query or takes you to a new item but you want to return to the previous screen, you can navigate using your browser's back button.
You can also save any chart that Metabot creates to a dashboard or collection.
## AI exploration
![AI exploration](./images/ai-exploration.png)
Use **AI exploration** to start a new conversation with Metabot when you don’t already have a chart, question, or query to work from.
Open it from **+ New → AI exploration**, then ask a question or pick one of the suggested prompts.
When answering questions in AI exploration, Metabot searches a limited set of content instead of your entire Metabase. Admins can set a **Collection for natural language querying** to scope AI exploration to a specific collection and its subcollections. You can still refer to content outside that scope by explicitly @-mentioning it in your prompt. See [Metabot AI settings](../ai/settings.md) for more details.
## Giving feedback on Metabot responses
When you hover over Metabot's responses, you'll see options to give feedback.

View File

@@ -36,6 +36,14 @@ Admins on Pro and Enterprise plans can tell Metabot to only work with [models](.
Restricting Metabot to verified models and metrics (and only models and metrics) helps Metabot produce more reliable answers, since you know someone has at least vetted the data Metabot can use.
## Collection for natural language querying
Select a collection (including its subcollections) to limit which collections Metabot searches during [AI exploration](../ai/metabot.md#ai-exploration).
This setting only affects conversations started from **+ New → AI exploration**.
People can still @-mention items outside of this collection when prompting in AI exploration. Metabot can also see the person's current context (for example, Metabot will know about the dashboard they're currently viewing, even if the dashboard is outside the selected collection).
## When embedding Metabot, you can pick a collection for Metabot to have access to
When embedding Metabot in your app, you can select a collection for Metabot:

File diff suppressed because one or more lines are too long

View File

@@ -106,6 +106,7 @@ config:
bcc-enabled: true
breakout-bin-width: 10.0
breakout-bins-num: 8
can-run-adhoc-query-check-threshold: 250
check-for-updates: true
config-from-file-sync-databases: true
custom-formatting: {}
@@ -161,7 +162,7 @@ config:
index-update-thread-count: 2
install-analytics-database: true
jdbc-data-warehouse-max-connection-pool-size: 15
jdbc-networkout-timeout-ms: 1800000
jdbc-network-timeout-ms: 1800000
jwt-attribute-email: email
jwt-attribute-firstname: first_name
jwt-attribute-groups: groups
@@ -221,6 +222,7 @@ config:
redirect-all-requests-to-https: false
remote-sync-auto-import: false
remote-sync-auto-import-rate: 5
remote-sync-check-changes-cache-ttl-seconds: 60
remote-sync-task-time-limit-ms: 300000
report-timezone: null
reset-token-ttl-hours: 48

View File

@@ -293,6 +293,13 @@ When using the default binning strategy for a field of type Coordinate (such as
When using the default binning strategy and a number of bins is not provided, this number will be used as the default.
### `MB_CAN_RUN_ADHOC_QUERY_CHECK_THRESHOLD`
- Type: integer
- Default: `250`
Maximum number of cards to compute can_run_adhoc_query for. When the number of cards exceeds this threshold, can_run_adhoc_query will return true for all cards without computing actual permissions. Set to 0 to always compute permissions. This only affects how cards are displayed in the query builder and does not affect actual permission enforcement.
### `MB_CHECK_FOR_UPDATES`
- Type: boolean
@@ -340,6 +347,7 @@ Whether or not the use of custom GeoJSON is enabled.
- Type: boolean
- Default: `false`
- [Exported as](../installation-and-operation/serialization.md): `custom-homepage`.
- [Configuration file name](./config-file.md): `custom-homepage`
Pick one of your dashboards to serve as homepage. Users without dashboard access will be directed to the default homepage.
@@ -348,6 +356,7 @@ Pick one of your dashboards to serve as homepage. Users without dashboard access
- Type: integer
- Default: `null`
- [Exported as](../installation-and-operation/serialization.md): `custom-homepage-dashboard`.
- [Configuration file name](./config-file.md): `custom-homepage-dashboard`
ID of dashboard to use as a homepage.
@@ -810,7 +819,7 @@ Change this to a higher value if you notice that regular usage consumes all or c
For setting the maximum,
see [MB_APPLICATION_DB_MAX_CONNECTION_POOL_SIZE](#mb_application_db_max_connection_pool_size).
### `MB_JDBC_NETWORKOUT_TIMEOUT_MS`
### `MB_JDBC_NETWORK_TIMEOUT_MS`
- Type: integer
- Default: `1800000`
@@ -1360,6 +1369,14 @@ Whether to automatically import from the remote git repository. Only applies if
If remote-sync-type is :read-only and remote-sync-auto-import is true, the rate (in minutes) at which to check for updates to import. Defaults to 5.
### `MB_REMOTE_SYNC_CHECK_CHANGES_CACHE_TTL_SECONDS`
- Type: integer
- Default: `60`
- [Configuration file name](./config-file.md): `remote-sync-check-changes-cache-ttl-seconds`
Time-to-live in seconds for the remote changes check cache. Default is 60 seconds.
### `MB_REMOTE_SYNC_TASK_TIME_LIMIT_MS`
- Type: integer

View File

@@ -1,8 +1,8 @@
---
title: Dashboard subscriptions
redirect_from:
- /docs/latest/users-guide/dashboard-subscriptions
- /docs/latest/enterprise-guide/dashboards-subscriptions
- /docs/latest/users-guide/dashboard-subscriptions
- /docs/latest/enterprise-guide/dashboards-subscriptions
---
# Dashboard subscriptions
@@ -79,7 +79,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. If you don't select any questions, Metabase won't send any attachments.
The attached files will include up to 1 048 575 rows by default (Excel's row limit for spreadsheets). If you're self-hosting Metabase, you can adjust this row limit by setting the environment variable [`MB_ATTACHMENT_ROW_LIMIT`](../configuring-metabase/environment-variables.md#mb_attachment_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 1 048 575 rows by default (Excel's row limit for spreadsheets). If you're self-hosting Metabase, you can adjust this row limit by setting the environment variable [ `MB_ATTACHMENT_ROW_LIMIT` ](../configuring-metabase/environment-variables.md#mb_attachment_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.
### Send only attachments (no charts)
@@ -139,6 +139,10 @@ See [Notification permissions](../permissions/notifications.md).
See [Remove Metabase branding from exports](../questions/exporting-results.md#remove-metabase-branding-from-exports).
## Subscriptions in embedded dashboards exclude links
Since people viewing embedded dashboards likely don't have direct access to your Metabase, Metabase will omit the links to Metabase items in subscriptions sent from embedded dashboards. That way people don't get broken links in their email. See [embedded dashboards](../embedding/components.md).
## Further reading
- [Alerts](../questions/alerts.md)

View File

@@ -17,7 +17,6 @@ Currently, editable tables are available for the following databases:
- PostgreSQL
- MySQL
- Metabase Sample Database
## Setting up editable tables

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