Compare commits

...

404 Commits

Author SHA1 Message Date
Ryan Laurie
945e8d3aaf move settings components where they belong
# Conflicts:
#	frontend/src/metabase/admin/tools/components/ToolsApp.tsx
2026-04-20 17:03:07 -06:00
Alex Yarosh
33d9cfb809 docs: data studio updates (#72641)
* data studio updates

* Optimized 9 images, saved 652.1 KB (47.2% smaller)

* links

* link

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* Update docs/data-studio/dependencies/replace-data-sources.md

* typo

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
2026-04-20 13:37:16 +00:00
github-automation-metabase
5a0ecdede4 Auto-update documentation for master (#72812)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-20 06:25:25 -07:00
Nicola Mometto
608cc81d21 GDGT-2310: use db-routing's own db-routing-enabled? (#72865)
* GDGT-2310: use db-routing's own db-routing-enabled?

* chore: test

* chore: remove import

* fix: test on ee enabled

* update test
2026-04-20 14:12:30 +01:00
Kevin Kelleher
6637fff38f Fix duplicated dimensions in Metrics Viewer tab picker and incorrect 'Multiple dimensions' label (#72637) 2026-04-20 08:46:36 -04:00
Nemanja Glumac
b3e64a7ccd Fix chart drill broken e2e test (#72864) 2026-04-20 12:11:30 +02:00
Nemanja Glumac
de2a5af435 Delete e2e reproduction for #57132 (#72862) 2026-04-20 11:49:40 +02:00
Timofey Kachalov
95c2dd47ec Update SDK version in package.template.json to 0.61.0-alpha (#72861) 2026-04-20 13:39:00 +04:00
Fraser
73e58e2135 Move api to shared (#72856) 2026-04-20 21:36:07 +12:00
metabase-bot[bot]
0141d69411 [chore] Update E2E test timings with recent data (#72835)
Update E2E test timings with averaged data

Collected timing data from 7 days of test runs.
All specs with timing data were included (simple averages).

🤖 Generated automatically by update-e2e-timings workflow

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <actions@github.com>
2026-04-20 11:30:00 +02:00
Fraser
0f03828392 Simplify click actions (#72849) 2026-04-20 20:36:00 +12:00
Fraser
700315244f Move Text viz into dashboard (#72848) 2026-04-20 20:35:47 +12:00
Fraser
f0b01b187b Move card slowness into viz types (#72846) 2026-04-20 20:35:33 +12:00
Fraser
cbe82473d6 Pass qb into viz via props (#72844) 2026-04-20 20:35:21 +12:00
Nick Fitzpatrick
f7ead91137 Reduce bleeding-edge token usage (#72613)
* comment out all occurances of bleeding edge to see which ones are actually needed

* default to pro-self-hosted

* enable transforms where needed

* final adjustments?
2026-04-20 14:05:10 +05:30
Ngoc Khuat
655825076b Guard transform runner from being poisoned by unrunable transforms (#72149) 2026-04-20 08:21:33 +00:00
Aleksandr Lesnenko
0c493fd36e Prettier -> oxfmt (#70825)
* oxfmt config

* run oxfmt
2026-04-19 19:22:04 -04:00
appleby
0f819c0a9b BOT-1285 Restrict metabase managed AI to anthropic and claude-sonnet-4-6 (#72324) 2026-04-19 13:23:38 -07:00
Oleksandr Yakushev
fc7812a0e6 [match] Add :and syntax to match-lite (#70010) 2026-04-19 23:00:23 +03:00
Alexander Polyankin
82e868df4c Clarify permissions for fetching database metadata (#72837)
* small api fixes

* fix
2026-04-19 13:40:44 -04:00
Alexander Polyankin
be849d8ba6 Cleanup /api/database/metadata (#72831)
simplify
2026-04-18 19:33:00 -04:00
appleby
da05483604 BOT-1321 BOT-1230 Switch to AiUsageLog for token usage reporting in metabot-stats (#72662)
* BOT-1321 BOT-1230 Switch to AiUsageLog for token usage reporting in metabot-stats

The AiUsageLog table includes usage not only for chat conversations, but non-chat LLM usage like the example question
generator (a.k.a suggested prompts), so use it for reporting metabot-stats metering values to harbormaster.

This also has the advantage that we use the same table both for billing and ai usage limits enforcement.

* Add some test of example question generator token usage in metabot-stats

* Move metabot-stats tests into EE
2026-04-18 03:19:32 +00:00
Maksym Yakubych
c6babc336d fix: remove -tiered add-on on disconnect (#72807) 2026-04-18 01:58:54 +00:00
Aleksandr Lesnenko
3379b1be9f model to transform e2e specs (#72589) 2026-04-17 21:24:17 -04:00
appleby
ddc4e3604b BOT-1321 Add ai_usage_log migration to v60 (#72651)
* BOT-1321 Backport ai_usage_log migration to v60

* Add ai_proxied column to ai_usage_log table
2026-04-17 17:33:59 -07:00
Maksym Yakubych
1a5ba0a0a4 fix: cancel managed ai add-on when disconnecting (#72782) 2026-04-17 22:12:30 +00:00
appleby
fa3e7d34d4 Fix broken link fragment in guest-embedding.md (#72815)
lychee docs --config ./.lychee/config.toml --offline
Issues found in 1 input. Find details below.

[docs/embedding/guest-embedding.md]:
   [ERROR] /metabase/docs/embedding/guest-embedding.md#to-turn-off-a-locked-parameter-pass-an-empty-array-as-its-value | Cannot find fragment: Fragment not found in document. Check if fragment exists or page structure
2026-04-17 22:12:04 +00:00
Andrei Chernykh
16dd3b3fad Add spacing above buttons in new dashboard modal (#72755)
* Add spacing above buttons in New Dashboard modal (UXW-3739)

* Clean up CreateDashboardModal tests
2026-04-17 13:15:39 -07:00
Jeff Bruemmer
6586ca393e Docs locked params (#72657) 2026-04-17 20:11:12 +00:00
Jeff Bruemmer
272bcd4abc docs - MCP settings update (#72744) 2026-04-17 12:51:27 -07:00
Jeff Bruemmer
ac3d0d0c59 docs - various edits (#72185) 2026-04-17 12:50:53 -07:00
Alexander Polyankin
996d6b2140 Fix db filter in metadata endpoint (#72791)
* fix db filter

* fix filter order
2026-04-17 15:30:21 -04:00
Tyler Plude
119889d16f BOT - Debug opt-in helper for agent request & response JSON (#72780)
dump agent req to json
2026-04-17 11:47:55 -07:00
Anderson
6539732003 Show the MCP URL in the admin panel (#72737)
* Show the MCP URL in the admin panel

* Use CopyTextInput component
2026-04-17 15:39:56 -03:00
Robert Kosara
7dbdbe100c Docs: Fix dependency graph image (#72789)
Fix dependency graph image
2026-04-17 11:01:14 -07:00
Maksym Yakubych
bda6c83a12 fix: token stats (#72777) 2026-04-17 16:36:03 +00:00
Rodrigo López Dato
d3850dc628 Hide process UUID in log viewer if there is only one process (#67152) 2026-04-17 13:19:09 -03:00
Uladzimir Havenchyk
315d513f3f chore: cleanup resolutions (#72030) 2026-04-17 18:57:50 +03:00
Sloan Sparger
cb67a8ff47 [Metabot] Remove indicator for events (#71788)
* remove indicator for events

* fix unit tests
2026-04-17 15:50:55 +00:00
Nemanja Glumac
c16ec07edc Update the Sample Database by shifting CREATED_AT by three years (#72701)
* Shift CREATED_AT by three years

* Update tests

* Fix e2e group 8

* Fix e2e group 11

* Fix e2e group 12

* Fix e2e group 14

* Fix e2e group 47

* Fix e2e group 38

* Fix e2e group 32

* Fix e2e group 35

* Fix e2e group 36

* Fix e2e group 44

* Fix e2e group 42

* Fix repro 15331

* Fix e2e group 5

* Fix moar tests

* Fix more tests

* Moooooar

* Fix the timeline test

* Fix "extract shorcuts" (group 26)

* Fix extract actions (group 22)

* Fix e2e group 19

* Attempt to fix embedding tests
2026-04-17 08:53:33 -06:00
metamben
1e17223288 Clamp :max-results-bare-rows with pivot-limit if it's set (#72369)
Closes #72157
2026-04-17 17:37:49 +03:00
dpsutton
a497238a75 Improve checker (#72747)
* correctly analyze native queries

need to include aliases in column names. consider `select f.name as
field_name, t.name as table_name ...`. The columns referenced are both
`name`, but because of the aliases (field_name, and table_name) they are
disambiguated. The bug is we were using name and not the selected name

* fix sql wrapper issues:

```
 -- PASS before fix, PASS after
  SELECT datum FROM (SELECT 1 AS datum) DQ

  -- FAIL before fix (false positive: missing-column "datum"), PASS after
  SELECT datum AS b FROM (SELECT 1 AS datum) DQ

  -- FAIL before fix (same bug, multiple references), PASS after
  SELECT datum AS b, datum - 1 AS d FROM (SELECT 1 AS datum) DQ

  -- Real-world case: dim_dates transform had 1 false positive error, now 0
  SELECT TO_CHAR(datum, 'yyyymmdd')::INT AS date_dim_id,
         datum AS date_actual,
         EXTRACT(EPOCH FROM datum) AS epoch,
         datum - DATE_TRUNC('quarter', datum)::DATE + 1 AS day_of_quarter
  FROM (SELECT '2000-01-01'::DATE + SEQUENCE.DAY AS datum
        FROM GENERATE_SERIES(0, 20000) AS SEQUENCE (DAY)) DQ

  -- Real-world case: self_hosted_daily_stats had 47 false positive errors, now 0
  -- (CTE-defined columns like latest_month_above_goal_alerts were flagged
  --  as missing on the source table)
  WITH stats AS (
    SELECT ..., SUM(CASE WHEN x THEN v END) AS latest_month_alerts, ...
    FROM source_table
  )
  SELECT ls.latest_month_alerts  -- was falsely flagged as missing
  FROM stats ls
```

issues propagating out from ctes where there was an alias involved

* skip validation for queries with table-type template tags

Queries like `SELECT * FROM {{table_one}}` where `table_one` is a
table-type template tag can't be meaningfully validated — the actual
table (and therefore its columns) is dynamic and unknown at check time.

`native-result-metadata` already skipped these, but `validate-native-query`
did not, causing a syntax error when the unresolved table-id path vector
(e.g. `["DB" "public" "my_table"]`) was substituted into the SQL as a
placeholder name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* move function before declaration

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 09:22:56 -05:00
Filipe Silva
9571b2b4c3 fix: fix transform run metering aggregation (#72632)
* fix: fix transform run metering aggregation

Part of https://linear.app/metabase/issue/CLO-5307/investigate-double-billing-on-basic-advanced-plan-change

* fix: comment about transforms metering context
2026-04-17 14:00:53 +01:00
Mihael Konjević
b966e69c6f Metabot mbql improvements (#71524)
* Add agent-lib module for structured MBQL program repair, validation, and evaluation

Introduces the `agent-lib` module (team: Metabot), which provides the
core pipeline for processing structured MBQL programs produced by the AI
agent: deterministic repair/normalization of malformed operator forms,
schema validation, and safe evaluation against a trusted runtime.

Key components:
  - repair: idempotent normalization of operator forms, stage boundaries,
    and top-level operations
  - validate: schema and semantic validation with structured error reporting
  - eval: sandboxed evaluation of validated programs against lib/MBQL
  - runtime: trusted bindings exposing lib query-introspection helpers
  - capabilities/catalog: enumerates available operators, sources, and
    field types for the agent
  - mbql_integration: bridges agent-lib's structured programs to lib's
    MBQL query building API

The module is registered in clj-kondo config with `:uses #{lib util}`
and as a `:friend` of `lib` (for two internal functions not yet in the
public API: `lib.expression/maybe-resolve-expression` and
`lib.ref/expression-ref-name`).

* Wire up agent-lib for MBQL construction, remove synthetic field IDs

Replace the old construct_notebook_query approach (3 query types, synthetic
field IDs like `t42-3`, rigid filter schemas) with agent-lib's unified
program format (`{source, operations}`). Programs use real database field
IDs and support richer capabilities: expressions, joins, post-aggregation
filtering via `append-stage`, and custom calculations.

Key changes:

- construct.clj: rewritten to call `agent-lib/evaluate-program` instead of
  the old filters.clj pipeline. Tool now accepts `{program: {source, operations}}`
  instead of `{query: {query_type, source, filters, ...}}`

- agent-api construct-query endpoint: translates the existing REST request
  format into programs and evaluates via agent-lib

- Entity details, field stats, table utils: all switched from synthetic
  index-based field IDs (`t42-3`) to real database field IDs (integers)

- filters.clj: deleted — no longer needed by any caller

- Prompts: construct_notebook_query.md completely rewritten with program
  format documentation, operations reference, and usage patterns

- System prompts: removed outdated limitation references (no joins, no
  field calculations)

* Refine construct_notebook_query tool schema and fix metric breakouts

Tool schema now uses the source_entity/context-ref pattern: `source_entity`
at the top level selects the primary data source, while the program always
uses `{"type": "context", "ref": "source"}`. This separates entity selection
from program structure and enables `referenced_entities` for additional
context.

Strip default metric breakouts when the agent provides explicit ones.
Metrics include a default time dimension breakout in their query definition.
When the program contains explicit breakout operations, these now replace
(not augment) the metric defaults via `lib/remove-all-breakouts`.

Include `display` in question URL hash so the frontend renders the correct
visualization type (line, bar, pie, etc.) instead of defaulting to table.

Rewrite construct_notebook_query.md prompt with comprehensive rules:
context-ref pattern, single-transform-per-operation rule, temporal
extraction helpers, multi-value filter guidance, breakout ordering rules,
grouped ratio examples, join retry guidance, and field projection rules.

* Fix evaluation context and time-interval keyword coercion

Evaluation context improvements:
- Map "model" source type to "dataset" (matching agent-lib's
  context-allowed-ids which checks for "card" or "dataset")
- Compute surrounding tables for all source types, not just tables,
  enabling join context for models/questions/metrics
- Compute and pass measure-ids to the evaluation context, enabling
  measure ID validation in agent-lib's operator validator

Keywordize time-interval amount values ("current", "last", "next")
before passing to lib/filter, which expects keyword enum values
(:current, :last, :next). Integer amounts pass through unchanged.

* Fix tests

* Fix Eastwood warnings, update tests for real field IDs and new tool schema

* Fix cljfmt indentation in entity_details.clj

* Temp fix for transform table issues

* Build Uberjar on push to branch

* Tighten metabot prompt guidance for MBQL queries

- Add MBQL intro sentence to the construct_notebook_query tool prompt
- Drop references to the retired metabot query DSL and the redundant
  `relative-date` alias
- Correct the permission-error join guidance: implicit refs hit the same
  permission check, so surface the error instead of retrying
- Add a heuristic for choosing pie-chart vs calculated-percentage expressions
  in the percentage_question example
- Update the agent_api reference to describe field/segment/measure refs
  using the actual request-body keys instead of internal MBQL forms

* Use shared ::field-id schema in agent_api

Defines `::field-id` reusing `::lib.schema.id/field` (pos-int? with API
decoding) and a string branch for expression/aggregation aliases, then
replaces the repeated `[:or :int :string]` field-id shapes across request
and response schemas with that single alias.

Also tightens the `body->program` helper by removing blank lines inside
the `let`/`cond->` per the no-blank-lines-in-def-forms style rule and
fixes the `construct-query*` docstring to say MBQL 5 rather than pMBQL.

* Use vars for agent-lib capability bindings

Converts every catalog `:binding lib/foo` to `:binding #'lib/foo` so that
REPL inspection of a capability entry shows the var name instead of an
opaque function object.

Also aligns all seven catalog files on the standard `(def capabilities
"docstring" …)` form instead of the `^{:doc …}` metadata shape, adds a
namespace docstring to `catalog/aggregation.clj` explaining that the
catalog is the runtime source of truth while `construct_notebook_query.md`
is hand-curated prompt guidance, and brings the join / top-level prompt
notes in line with the corrected permission-error fallback guidance.

* Document the unsupported/repair split in agent-lib

Adds a namespace docstring to `capabilities.unsupported` explaining that
this catalog is prompt-time guidance distinct from the runtime repair
layer: some entries (temporal extractions, `count-if`, `variance`, etc.)
are silently rewritten before validation so the agent never sees an
error, while others (`avg-where`) have no safe mechanical rewrite and
only surface as retry guidance when the model gets it wrong.

* Simplify agent-lib coercion helpers

Rewrites `quarter-label->number` as a plain `case` dispatch over the
trimmed, upper-cased input instead of threading through a regex, and
makes `direction-string?` actually return a boolean so it matches its
`?`-suffixed name contract. Adjusts the direction-string test to assert
`true?`/`false?` instead of `some?`/`nil?`.

* Derive agent-lib aggregation operator names from lib hierarchy

Replaces the hard-coded set of aggregation helper names in
`literals/aggregation-operator-names` with a derivation from
`(descendants @lib.hierarchy/hierarchy ::aggregation-clause-tag)` so new
aggregation clauses added to the lib schema are picked up automatically.
Filters out `:aggregation` (the abstract wrapper tag) and `:offset`
(a windowed aggregation that also appears as an expression — keeping it
out of this set preserves the existing `aggregation-form?` semantics so
the repair layer still defers offset-inside-expression to lib.core).

* Inline private structured-helper-symbols in agent-lib syntax

`structured-helper-symbols` was a private single-use alias for
`capabilities/helper-symbols`. Inlining it at its one call site in
`recognized-operator-symbols` removes the indirection without changing
behavior. `top-level-operation-symbols` is left as-is because it is
consumed by other namespaces.

* Increase max allowed count for modules triggering drivers tests

* Make agent-lib operator repair idempotent for degenerate args

Several binary/ternary nested operators (`in`, `not-in`, `=`, `!=`, `is`,
`is-not`, `between`, `with-temporal-bucket`, `percentile`,
`with-join-conditions`) used to unconditionally destructure their args
and rebuild the form at a fixed arity, so a degenerate input like
`["in"]` would be padded to `["in" nil nil]`. When that same operator
was embedded inside `case`, the case normalizer would then classify the
3-element form as a `[pred then else]` triple and rewrap it into a
branches vector on the next pass — adding another `nil` each time
`repair-program` was called.

The `repair-is-idempotent` fuzz property caught this intermittently in
CI (~20% flake rate); a 10k-iteration local run now passes cleanly.
Each affected operator now returns its args unchanged when it does not
have enough of them to form a canonical shape, leaving rejection to
downstream validation instead of synthesizing a malformed canonical
form. A deterministic regression test pins the `["case" [op]]` case
for every affected operator.

* Scope agent-lib runtime metadata to entities the program references

Walk the validated program for referenced table/field/card/metric/measure
ids and load only those tables (and their fields) into the runtime, instead
of materializing every table in the database on each evaluate-program call.
Field/card/measure ids are resolved via lib.metadata/bulk-metadata so each
class collapses to a single SQL query regardless of count.

* Change how docstrings are attached to defs

* Drop unused agent-lib unsupported-rewrite-catalog

* Expose agent-lib programs directly in agent_api v2 endpoints

Replace the legacy POST /v1/construct-query and /v1/query endpoints —
which spoke a flat filters/aggregations/group_by body shape that an
in-process adapter translated into an agent-lib program — with new
POST /v2/construct-query and /v2/query endpoints whose request body is
the agent-lib program itself. The flat shape only ever exposed the
lowest-common-denominator subset of agent-lib's capabilities; programs
unlock joins, expressions, multi-stage queries, and the full operator
catalog for any HTTP or MCP client. Repair and validation continue to
happen inside agent-lib, so the loose-schema benefit of accepting raw
programs is free.

The request schema reuses metabase.agent-lib.schema/program-schema
(now re-exported from metabase.agent-lib.core), with a thin restriction
that the top-level source must be table/card/dataset/metric — context
and nested program sources are reserved for in-process callers. The
new endpoints derive the source-entity for evaluation from the program
itself via a new program-source->source-entity helper on
metabase.metabot.tools.construct, leaving execute-program's signature
unchanged so the slackbot caller is untouched. MCP picks up the new
shape automatically because its tool manifest is generated from
defendpoint metadata.

* Keep agent_api v2 program request schema MCP-friendly

The :and+:fn wrapper around agent-lib/program-schema confused the MCP
tools manifest's flatten-root-schema pass — it couldn't extract a clean
object root for construct_query and query, so their inputSchemas lost
their "type": "object" field and the tools became unusable through MCP.

Drop the :fn predicate from ::program-request and instead enforce the
top-level source-type restriction imperatively inside
evaluate-program-for-execution. The schema is now just program-schema,
which the manifest flattens cleanly into a single object. Update the
MCP execute-query test to send the v2 program shape.

* Make agent responses less chatty during work

Strip step-by-step narration from system prompts so the agent works
silently through tool calls, then presents results with a brief
query-translation sentence explaining how the question mapped to the query.

* Clean up prompt contradictions, redundancy, and misplaced guidance

- Fix identity/tone contradictions with brevity style ("conversational" →
  "concise, action-oriented", "expert data analyst" → "query builder")
- Remove banned narration pattern from example text
- Remove redundant "Data Source Types" section from NLQ prompt
- Scope proactive helpfulness to ambiguous/partial results only
- Move percentage question guidance from <why> blocks in examples to
  proper query construction sections in both prompts
- Fix chatty entity linking examples in NLQ prompt

* Fix tests

* Fix modules config

* Restore base_type and related field metadata in tool result columns

Keep the real-field-ID redesign and new :type enum, but restore
:base_type, :effective_type, :semantic_type (qualified), and
:coercion_strategy.

Also update construct-notebook-query-tool test to use the new
{:source_entity, :program} schema after the rebase.

* Remove model/Transform cleanup hotfix from database.clj
2026-04-17 14:04:24 +02:00
Nick Fitzpatrick
c2a0e3615d Uxw 3765 goal line on waterfall chart (#72650)
* feat: Add goal line to waterfall chart

* adjusting e2e test

* add storybook test for static viz

* adding loki screenshot, finxing e2e

---------

Co-authored-by: Brad W <bradleo99@gmail.com>
2026-04-17 10:12:09 +05:30
Nick Fitzpatrick
24d7e73495 update copy in Enable Transforms Page based on plan (#72625) 2026-04-17 10:11:39 +05:30
Aleksandr Lesnenko
3564f8662e import json metadata (#72729) 2026-04-17 01:07:47 +00:00
Kevin Kelleher
00497ab9f4 Fix Metrics Viewer "No compatible dimensions" error (#72731) 2026-04-16 19:21:33 -04:00
Andrei Chernykh
3619ec2e85 Added a new team member (#72647) 2026-04-16 15:34:10 -07:00
dpsutton
bcec823dba Better checker output (#72734)
* human and json output

* drop --format flag and JSON output

Removed for now — the human format is what we actually want, and we'll
add structured output (JSON or otherwise) when there's a real consumer
asking for it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* remove classloader/plugins calls

These belong to the driver-loading PR (#72732), not this output-formatting
branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:10:58 -05:00
Rodrigo López Dato
10972d2b35 Preserve original cause in serdes load errors when app DB tx is aborted (#72725)
* Preserve original cause in serdes load errors when app DB tx is aborted

path-error-data does a SELECT via serdes/load-find-local to populate
:local-id on the error map. When it is called from the catch block in
load-one!, the outer load transaction has often already been poisoned by
the real failure, so that SELECT throws "current transaction is aborted,
commands ignored until end of transaction block" and prevents ex-info
from being constructed. The original cause is lost and support engineers
see the aborted-tx symptom instead of the actual SQL error.

Wrap the lookup so a failure leaves :local-id nil and still lets us
chain the real cause.

* Update enterprise/backend/src/metabase_enterprise/serialization/v2/load.clj

Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>

* Don't run starting from empty app DB

---------

Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
2026-04-16 20:24:25 +00:00
dpsutton
e2fa24df94 plugins loaded for checker (#72732) 2026-04-16 15:00:24 -05:00
Maksym Yakubych
d495e0391c fix: do not disable agent api and mcp when not configured (#72730) 2026-04-16 19:56:18 +00:00
Maksym Yakubych
a734b119aa fix: allow use of legacy metabase AI (#72568) 2026-04-16 19:55:56 +00:00
dpsutton
74f715fa72 Concise schema source (#72667)
* concise format

shaves about a minutes off!

before:

```
clj -M:dev:drivers:ee -m metabase.core.bootstrap --mode checker --export ~/projects/work/stats-remote-sync --schema-dir /tmp/metadata/metadata/databases

took 1m55s
```

now with new format:

```
clj -M:dev:drivers:ee -m metabase.core.bootstrap --mode checker --export ~/projects/work/stats-remote-sync --schema-dir /Users/dan/projects/work/exports-root/metadata/new-format

took 58s
```

new format is one JSON file per database, loaded entirely into memory:

```javascript
{
  "name": "Sample Database",
  "engine": "h2",
  "tables": [
    {
      "name": "PRODUCTS",
      "display_name": "Products",
      "database_schema": "PUBLIC",
      "fields": [
        {"name": "ID", "base_type": "type/BigInteger", "semantic_type": "type/PK", "database_type": "BIGINT"},
        ...
      ]
    },
    ...
  ]
}
```

The CLI auto-detects the format: if --schema-dir contains .json files,
it uses concise; otherwise falls back to the serdes YAML format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* document SchemaSource and AssetsSource contracts

Each resolve-* method now documents the exact map shape it should return,
keys marked as required vs optional. The ns docstring lays out the broader
contract: sources return snake_case maps matching the serialized form,
provider.clj does all conversion to lib metadata format (kebab-case,
keywords, defaults, ID resolution, MBQL normalization).

Documenting this now (alongside adding the concise format) so future source
authors — REST API, git, LLM, app DB via toucan2 — know exactly what shape
to produce.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* concise format: single file with three flat lists

The concise schema format is now a single JSON file with three flat lists
keyed by integer IDs:

  {"databases": [{"id": 1, "name": "Sample Database", "engine": "h2"}, ...],
   "tables":    [{"id": 3, "db_id": 1, "name": "PRODUCTS", "schema": "PUBLIC"}, ...],
   "fields":    [{"id": 12, "table_id": 3, "name": "ID",
                  "base_type": "type/BigInteger", "semantic_type": "type/PK",
                  "database_type": "BIGINT"}, ...]}

At load time we join tables.db_id → databases.id and fields.table_id →
tables.id to build path-keyed indexes, so the rest of the checker still
sees portable references like [db-name schema table].

Format selection is now explicit via --schema-format (serdes|concise)
rather than guessed. Default remains serdes for backwards compatibility.

  java -jar metabase.jar --mode checker \
    --export /path/to/export \
    --schema-dir /path/to/metadata.json \
    --schema-format concise

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* loudly error when --schema-dir doesn't match --schema-format

Pointing --schema-format serdes (the default) at a single JSON file used
to silently produce a schema source with zero databases, which then
showed up as a flood of unresolved-reference errors. Same in reverse for
concise format pointed at a directory.

Now the CLI checks the path shape against the format and exits with a
helpful error suggesting the right --schema-format value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* rename format/concise to format/concise-schema

Mirrors the serdes-schema vs serdes-assets split — the namespace name now
makes clear which side of the protocol divide it implements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* use cheshire instead of clojure.data.json

clojure.data.json is available at dev time but not bundled into the
production jar, causing a FileNotFoundException when running the checker
from a built artifact. cheshire is a hard dep already on the classpath.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* use metabase.util.json instead of cheshire directly

Project convention is to go through metabase.util.json, which wraps
cheshire and adds custom encoders/decoders. Stream-decode via reader
to avoid slurping the whole file into a string before parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:08:45 -05:00
Ben Grabow
a0d8cc309d Ghy 3359/embedded postgres dev/test driver (#72635)
* Add embedded-postgres dep

* Basic integrant lifecycle wrapper around embedded pg
2026-04-16 12:29:48 -05:00
Edward Paget
6f03c07e98 Bump Bouncy Castle to 1.84 (#72712)
Updates the top-level pin from 1.82 to 1.84 and adds matching pins to
the Snowflake and Databricks driver deps.edn files so the bundled
driver JARs pick up the patched version during security scans.
2026-04-16 12:23:29 -05:00
Ben Grabow
ee847893ff Ghy 3370/remote sync phantom diffs (#72495)
* Use natural sort order for all nested model types.

* Update serialization baseline with new expected sort order
2026-04-16 11:37:38 -05:00
Edward Paget
737056cb93 Switch Athena driver from uber-jar to thin jar with pinned deps (#72539)
* Downgrade all netty pins to 4.2.9.Final

Netty 4.2.12 introduced a breaking change in HttpObjectEncoder that
causes AWS SDK netty-nio-client to fail with "unexpected message type:
LastHttpContent" errors. Revert to 4.2.9.Final which is compatible
with the AWS SDK while still being in the 4.2.x line.

Closes: #72532

* Switch Athena driver from uber-jar to thin jar with pinned deps

Use athena-jdbc-thin instead of the bundled uber-jar so transitive
deps are visible to security scanners. Pin netty at 4.2.12.Final to
match root deps.edn. Add athena-streaming from our private maven
repo (not available on Maven Central).
2026-04-16 11:32:55 -05:00
Edward Paget
a5a132c663 Publish both uber-jar and thin jar for Athena JDBC driver (#72480)
Update athena.bb to download and publish both the existing
with-dependencies uber-jar (athena-jdbc) and the lean thin jar
(athena-jdbc-thin) from AWS's Athena downloads bucket. The thin
jar enables pinning transitive deps for security scanning.
2026-04-16 11:16:18 -05:00
Aleksandr Lesnenko
bf1ddbe794 data source replacement e2e specs (#72409)
* data source replacement tests

* source replacement e2e sql (#72636)
2026-04-16 11:10:10 -04:00
Braden Shepherdson
57271be747 Fix a new broken test that snuck in while Snowflake was quarantined (#72703)
Fixes DEV-1818.
2026-04-16 15:04:11 +00:00
Alexander Polyankin
6a31828228 /api/database/metadata endpoint (#72683) 2026-04-16 10:44:17 -04:00
Braden Shepherdson
1437d04c8d [Viz] Row charts pass raw values to drill-thru, not formatted strings (#72659)
Fixes QUE2-97.

Row charts were passing formatting strings like "(empty)" as the `value`
in the drill-thru context map, rather than the proper value `null`. That
breaks at least the `underlying-records` drill and perhaps others too,
when the formatted string is different from the value's usual
representation.
2026-04-16 10:35:22 -04:00
Kevin Kelleher
fd2a3e0705 Metrics Viewer design improvements (#72252) 2026-04-16 10:07:57 -04:00
dpsutton
113ee31093 Split source (#72666)
* rename serdes to serdes assets in prep of split

* split serdes source into serdes-schema and serdes-assets

The previous commit renamed serdes to serdes-assets. This commit
extracts the schema (database/table/field) resolution into its own
serdes-schema namespace and source type (SerdesSchemaSource), keeping
assets (cards, dashboards, segments, etc.) in serdes-assets.

This is a no-op refactor — the checker behaves identically. The split
prepares for a new schema format that uses a single file instead of
the 500k-file directory tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* kondo lints

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:07:01 -05:00
Kevin Kelleher
bbc6ca6a04 Metrics Viewer UX tweaks (#72553) 2026-04-16 07:35:34 -04:00
Romeo Van Snick
e0acf707bd Reduce metabase/utils violations by moving notifications helpers into shared (#72510)
* Move Question.alertType to utils/notifications as getAlertType

* Move metabase/utils/notifications to metabase/notifications

* Disable alert message in embedding sdk differently

* Fix type
2026-04-16 12:59:13 +02:00
Romeo Van Snick
2221a978a8 Move export formats to common instead of utils and merge duplicate definitions (#72611)
* Move export formats to common instead of metabase/utils/urls

* Fix types
2026-04-16 12:58:46 +02:00
Romeo Van Snick
2b9a4b6f9e Clean up metabase/utils/text-measurer (#72619)
* Move MeasureText types to the utils file that uses them

* Fix imports
2026-04-16 12:58:31 +02:00
Romeo Van Snick
821b0529fd Move uploads module into status where it is used (#72621) 2026-04-16 12:57:47 +02:00
lbrdnk
2d50c382ae Support questions in read_resource (#72469)
* Add question instructions into read_resource template

* Extend fetch to question

* support question entity in tools/field-values

* extend get-table-details to work also with questions

* refactor get-table-details usage

* refactor get-table-details tests

* Adjust fetch-model-or-card-resource to work with questions

* drop old get-table-details

* Adjust tests

* Extend question->xml with fields support

* Update llm representations question template with fields

* whitespace

* Update /v1/table/:id

* Update src/metabase/metabot/tools/entity_details.clj

Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>

* Adjust test

---------

Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>
2026-04-16 08:38:12 +02:00
Anderson
c42e6a7312 Show tooltip on disabled databases when creating transforms (#70987)
* Show tooltip on disabled databases when creating transforms

* Fix issues / Use better approach

* Add e2e tests / Remove unneeded prop

* Address review comments

* Fix test expectations to match validateDatabase messages

* Fix e2e test assertions to match validateDatabase messages
2026-04-16 01:23:27 -03:00
Fraser
3f89706bb7 Move dashboard-only components into dashboard (#72595) 2026-04-16 12:14:52 +12:00
Edward Paget
665c3bc807 deps: jackson-core 2.21.2 and httpcore5-h2 5.3.5 in databricks driver (#72474)
Pin jackson-core 2.21.2 and httpcore5-h2 5.3.5 in databricks driver
2026-04-15 17:07:08 -07:00
da
76f41437cf Add installation of Astral and Python3 pip (#72645)
* Add installation of Astral and Python3 pip

If you build Metabase with Docker it will fail unless uv and pip are installed

* Update Dockerfile to remove python3-pip installation

Removed installation of python3-pip from Dockerfile.
2026-04-15 20:41:20 -03:00
Edward Paget
961e63a622 deps: jackson-core 2.21.2 in snowflake, bigquery, and redshift drivers (#72468)
Pin jackson-core 2.21.2 in snowflake, bigquery, and redshift drivers

Each driver pulls a vulnerable jackson-core transitively:
- snowflake: 2.18.4.1 via snowflake-jdbc-thin
- bigquery: 2.18.2 via google-cloud-bigquery
- redshift: 2.17.2 via aws-java-sdk-core
2026-04-15 16:34:31 -07:00
Alexander Polyankin
ea831ed8d5 Revert "database mcp tool" (#72664)
* Revert "Fix database tools perf (#72633)"

This reverts commit 1526a1b26e.

* Revert "Fix database tools scope (#72617)"

This reverts commit 1523499820.

* Revert "database agent api (#71852)"

This reverts commit 5034e186b7.
2026-04-15 23:38:05 +01:00
bryan
9a2d08aec6 Implement current-user-table-privileges for Oracle and SQL Server to fix N+1 query (#72043) 2026-04-15 16:20:04 -04:00
Aleksandr Lesnenko
c76d1aa35d Allow metrics with numeric names (#71870)
* support metrics with numeric names

(cherry picked from commit 48cd551625792d9997206bb492e2e5ba21e0e0f6)

# Conflicts:
#	frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.tsx

* e2e test

(cherry picked from commit aebcd2a92eed6c818e32fb28e1fd5d47f7d8c7bb)

* Fix styling

* UXW-3294 fix e2e

* Revert not needed changes for dimensions mapping

* Fix issue with metric added to expression without dropdown list

* Fix e2e

* Fix e2e

* Review fixes

---------

Co-authored-by: Denis Berezin <denis.berezin@metabase.com>
2026-04-15 20:11:42 +00:00
Timofey Kachalov
a3b1e81cd8 Use fetch-artifact action for SDK bundle size checking to find artifact in other workflows (#72592) 2026-04-15 23:03:11 +04:00
Riley Thompson
430df2c389 Apply temporal bucketing on expressions (#72567)
* apply temporal bucketing to expression

* improve test

* fix test with formatted-rows

* format dates

* use normal driver select
2026-04-15 18:39:42 +00:00
Nick Fitzpatrick
65f313d35c fixing flakey tests (#72540)
flakey test fixes
2026-04-15 23:56:53 +05:30
Alexander Polyankin
1526a1b26e Fix database tools perf (#72633) 2026-04-15 18:01:08 +00:00
Romeo Van Snick
4730f8aa92 Move metabase/utils/click-behavior helpers into metabase/dashboard (#72512)
* Move metbabase/utils/click-behavior into metabase/dashboard

* Fix test name to avoid confusion
2026-04-15 19:39:37 +02:00
Timothy S. Dean
800db3ff13 GHY-3339: serdes export should drop most result_metadata (#72560)
**Simplify `result_metadata` serialization for serdes export**

For non-models, drop `result_metadata` entirely on export (it is recomputed by the QP on import anyway). For models, export only `:name` and the keys in `lib/model-preserved-keys` (used to match and apply model column overrides), discarding internal `lib/` and `qp/` properties that are unused after import. This reduces export noise and avoids needing to generate full metadata when editing cards externally.
2026-04-15 10:44:48 -06:00
Nicola Mometto
0c5b412af1 fix: re-evaluate active advisories even if they've been acked (#72610) 2026-04-15 17:18:42 +01:00
lbrdnk
6d5721c585 Fix create_chart tool and chart extraction (#72566)
* Fix create chart links

* Fix chart extraction

* Use pseudo-card fn

* Adjust test

* Integration test

* Instruction formatting

* Adjust chart link resolution

* Adjust test

* Adjust test

* Update test

* Adjust test
2026-04-15 17:29:43 +02:00
Eric Normand
92df3ec3e9 Fix null effective_type for sample database fields (GHY-3367) (#72571)
* Set effective_type for sample database fields (GHY-3367)

Every field entry in resources/sample-content.edn was missing :effective_type,
so CreateSampleContentV2 inserted metabase_field rows with null effective_type
on fresh installs. Default effective_type to match base_type for all 71 fields.

* Test that sample-db fields have non-null effective_type (GHY-3367)
2026-04-15 09:55:32 -05:00
Nathan Voxland
1eebfd0c37 Reduce memory usage of metabase.sync on postgres (#72128) 2026-04-15 09:24:32 -05:00
Alexander Polyankin
1523499820 Fix database tools scope (#72617)
* Fix database tools scope

* fix
2026-04-15 10:21:31 -04:00
Sébastien
f32612daa7 Updated UI for Security Center (#72518)
Co-authored-by: Nicola Mometto <nicola.mometto@metabase.com>
2026-04-15 15:13:11 +02:00
Timofey Kachalov
80671e6643 Convert embedding SDK shared files to TypeScript (#72521) 2026-04-15 16:06:27 +04:00
github-automation-metabase
e66fff337a Use targeted fetch instead of full clone (#71350)
perf: replace full clone with targeted fetch in containerize jobs
2026-04-15 12:43:07 +02:00
YB
fd7d198955 GDGT-232 fix: dashboard subscriptions should work even if Slack channels are renamed (#72223)
Dashboard subscriptions store Slack channels by name (e.g. "#alerts"). Slack channel IDs are immutable, but names are not. Renaming a channel breaks all subscriptions targeting it with just the name.

This PR fixes this by storing the immutable Slack channel ID alongside the display name at subscription creation time. At send time, we prefer the ID over the name - this maintains backwards compatibility with legacy subscriptions. The UI remains entirely name-based - users never see or interact with channel IDs.
2026-04-15 17:47:35 +08:00
Alexander Polyankin
5034e186b7 database agent api (#71852)
* database api

* add tests

* add docs

* perms

* fixes
2026-04-15 05:07:37 -04:00
Nick Fitzpatrick
270e297f8b Force color on select item hover in question picker (#72353)
* Force color on select item hover in question picker

* move select item color into class instead of c prop
2026-04-15 14:35:51 +05:30
Romeo Van Snick
7619844a88 Colocate data_grid tests with code (#72513) 2026-04-15 07:20:59 +02:00
Romeo Van Snick
7de6c4db9d Colocate nativeDrillFeedback with component that uses it (#72514) 2026-04-15 07:18:50 +02:00
Romeo Van Snick
c24ea41f56 Move memoizeClass to metabase/utils (#72511) 2026-04-15 07:17:21 +02:00
dpsutton
e16a8d8a07 Deref schema model from a protocol not field access (#72588)
it's internal but we don't need to use field access on the type. I don't
want to export the type, etc. Just make a debug protocol and this won't
be exported so it's not for anyone else.

Before:

```
serdes-test=>
Reflection warning, metabase_enterprise/checker/format/serdes_test.clj:286:20 - reference to field schema_model can't be resolved.
Reflection warning, metabase_enterprise/checker/format/serdes_test.clj:292:20 - reference to field schema_model can't be resolved.
Reflection warning, metabase_enterprise/checker/format/serdes_test.clj:298:20 - reference to field schema_model can't be resolved.
Reflection warning, metabase_enterprise/checker/format/serdes_test.clj:315:20 - reference to field schema_model can't be resolved.
nil
```

After:

```
serdes-test=> (clojure.test/run-tests)

Testing metabase-enterprise.checker.format.serdes-test

Ran 28 tests containing 87 assertions.
0 failures, 0 errors.
{:test 28, :pass 87, :fail 0, :error 0, :type :summary}
```
2026-04-15 02:16:10 +00:00
Maksym Yakubych
b395d97868 feat: redesign AI settings (#72041)
* feat: redesign AI settings

* fix: invalidate llm proxy token cache

* fixup! fix: invalidate llm proxy token cache

* fix: review comments
2026-04-14 23:03:04 +00:00
Noah Moss
f869ac0811 Version slack secret and expire linked accounts on rotation (#72559)
* version slack keys and expire on rotation

* refactor around module boundaries

* fix upgrade path

* fix module config test
2026-04-14 18:00:23 -04:00
Nathan Voxland
e6c13bd0e0 Reduce memory usage of fingerprinting postgres (#72133) 2026-04-14 16:51:58 -05:00
Edward Paget
587b41e307 Grant admin access to Helping Hand support user (#72478)
fetch-or-create-support-user! now sets is_superuser=true on all paths
(new user, reactivation, and existing active user). On grant revocation,
is_superuser is set back to false. This ensures the support user has
full admin access as promised by the grant access dialog.

Fixes: UXW-3725
2026-04-14 21:28:54 +00:00
Alexander Polyankin
cf1ef102e7 Bridge the gap between agent/mcp field types and representations field types format (#72547)
bridge the gap between agent/mcp field types and representations field types
2026-04-14 17:26:24 -04:00
dpsutton
d4a861b3c9 Checker module (#70866)
* Checker module

```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Finding tests took 80.0 ms.
Running 10 tests
Running before-run hooks...

10/10   100% [==================================================]  ETA: 00:00

Ran 10 tests in 0.645 seconds
39 assertions, 0 failures, 0 errors.
{:test 10, :pass 39, :fail 0, :error 0, :type :summary, :duration 644.51825, :single-threaded 10}
Ran 0 tests in parallel, 10 single-threaded.
Finding and running tests took 762.0 ms.
```

The number of public functions here is too high but will come down soon.

```
❯ tree export-dir -d -L 2
export-dir
├── collections
│   ├── BFCwHpVZqKMKqth3wVbGF_automatically_generated_dashboards
│   ├── HyB3nRtqb7pBPhFG26evI_examples
│   ├── cards
│   ├── hFKj55TbZN8uDDTNPiZgf_repro1
│   ├── transforms
│   ├── uj8O1_VYi4rPa4Y8ZGb_S_demo_day
│   └── wUkEfoXgg3dBTNCN2egsh_native_queries
├── databases
│   ├── Internal Metabase Database
│   ├── Sample Database
│   └── pg sample-dataset
├── python-libraries
└── transforms
    ├── transform_jobs
    └── transform_tags

17 directories
```

and

```
❯ time clj -X:dev:drivers:ee nocommit.ci/cli :export '"export-dir"' :output '"/tmp/from-cli-with-error.txt"'
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-13 14:26:57,899 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
2026-03-13 14:27:04,139 INFO util.encryption :: Saved credentials encryption is DISABLED for this Metabase instance. 🔓
 For more information, see https://metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html
clj -X:dev:drivers:ee nocommit.ci/cli :export '"export-dir"' :output   34.10s user 1.68s system 228% cpu 15.634 total
```

the output is a big edn map:

```clojure
{"fdkSuB8zWEbjEq_vWMaI-"
 {:card-id 1,
  :name "Heavy-Duty Silk Chair trend",
  :entity-id "fdkSuB8zWEbjEq_vWMaI-",
  :file
  "export-dir/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/fdkSuB8zWEbjEq_vWMaI-_heavy_duty_silk_chair_trend.yaml",
  :refs
  {:tables ["Sample Database.PUBLIC.ORDERS"], :fields ["Sample Database.PUBLIC.ORDERS.CREATED_AT"], :source-cards []},
  :unresolved nil,
  :bad-refs #{}}
  ...
;; card with unknown field
"rvxR7dn4wrKT9_7MHhy7i"
 {:card-id 222,
  :name "Distinct User ID",
  :entity-id "rvxR7dn4wrKT9_7MHhy7i",
  :file "export-dir/collections/uj8O1_VYi4rPa4Y8ZGb_S_demo_day/cards/rvxR7dn4wrKT9_7MHhy7i_distinct_user_id.yaml",
  :refs
  {:tables
   ["pg sample-dataset.public.products" "pg sample-dataset.public.people" "pg sample-dataset.public.demo_transform"],
   :fields [],
   :source-cards []},
  :unresolved [{:type :field, :path ["pg sample-dataset" "public" "demo_transform" "user_iddddd"]}],
  :bad-refs #{}}
  ;; card with unknown table:
 "srEGaobVwDUI177CowuLy"
 {:card-id 226,
  :name "Orders + People + Products model for real by Total",
  :entity-id "srEGaobVwDUI177CowuLy",
  :file
  "export-dir/collections/uj8O1_VYi4rPa4Y8ZGb_S_demo_day/cards/srEGaobVwDUI177CowuLy_orders___people___products_model_for_real_by_total.yaml",
  :refs
  {:tables ["pg sample-dataset.public.begone" "pg sample-dataset.public.products" "pg sample-dataset.public.people"],
   :fields ["pg sample-dataset.public.begone.total"],
   :source-cards []},
  :unresolved [{:type :table, :path ["pg sample-dataset" "public" "begoneeee"]}],
  :bad-refs #{}}
```

* handle foreign key refs in metadata, schemaless dbs like sqlite

checker=> (write-results! (check "export-dir-2") "/tmp/realized.txt")
Results written to: /tmp/realized.txt

```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker

Ran 14 tests in 0.936 seconds
58 assertions, 0 failures, 0 errors.
{:test 14, :pass 58, :fail 0, :error 0, :type :summary, :duration 936.312333, :single-threaded 14}
Ran 0 tests in parallel, 14 single-threaded.
Finding and running tests took 1.1 s.
All tests passed.
```

* yaml structural checks:

```shell
❯ clj -X:ee metabase-enterprise.checker.structural/cli :export '"/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based"'
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-17 17:16:18,363 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
Structural validation complete:
  Valid files: 444
  Invalid files: 1

Errors:
  /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/-1mRtcrAiZoe-GjYrQFHX_order_value_distribution_by_category.yaml (card)
    #ordered/map ([:name ["missing required key"]])
```

* report errors

```
❯ clj -X:ee metabase-enterprise.checker.structural/cli :export '"/Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based"'
Structural validation complete:
  Valid files: 444
  Invalid files: 1

Errors:

  /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based/collections/HyB3nRtqb7pBPhFG26evI_examples/cards/-1mRtcrAiZoe-GjYrQFHX_order_value_distribution_by_category.yaml (card)
    - Missing required key 'name' - found 'nname' which may be a typo
```

```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 208.0 ms.
Running 22 tests
Running before-run hooks...

22/22   100% [==================================================]  ETA: 00:00

Ran 22 tests in 0.948 seconds
104 assertions, 0 failures, 0 errors.
{:test 22, :pass 104, :fail 0, :error 0, :type :summary, :duration 947.840541, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.2 s.
All tests passed.
Running after-run hooks...
```

* Start to extract format from checker; add readme

ideally the runner persists and we can change the backends that power
it. Now that things are largely working on serdes, i'm extracting the
specifics about the format from the checker stuff so we can begin to
swap them out to simpler formats

* Refactor checker to use MetadataSource protocol

  Introduce protocol-based architecture for the checker module:

  - Add `source.clj` with `MetadataSource` protocol for entity resolution
  - Refactor `checker.clj` to use source protocol instead of global state
    - Session state is now per-check, not global (no more reset-state!)
    - Source and enumerators passed in, enabling different backends
  - Update `format/serdes.clj` to implement MetadataSource
    - Add `make-source`, `check`, `check-cards`, `make-enumerators`
    - High-level API now lives here, not in checker.clj
  - Update tests to use new API
    - In-memory sources via `reify` for edge case testing
    - No fixture cleanup needed between tests
  - Add DESIGN.md documenting two-protocol architecture:
    - EntityResolver: "does this reference exist?"
    - FolderLayout: "where are files in this directory?" (future)
  - Update README.md with current architecture and usage

  This prepares for supporting multiple file formats (serdes, simple LLM-friendly)
  and multiple backends (files, memory, app-db) with the same checker core.

```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests with options {:mode :cli/local, :namespace-pattern #"^(?:(?:metabase.*)|(?:hooks\..*))", :exclude-directories [".clj-kondo/src" "classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000, :exclude-tags [:metabot-v3/e2e], :module enterprise/checker, :only ("enterprise/backend/test/metabase_enterprise/checker")}
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 196.0 ms.
Running 22 tests
Running before-run hooks...

22/22   100% [==================================================]  ETA: 00:00

Ran 22 tests in 0.976 seconds
110 assertions, 0 failures, 0 errors.
{:test 22, :pass 110, :fail 0, :error 0, :type :summary, :duration 976.301375, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.2 s.
All tests passed.
Running after-run hooks...
```

* proper cli entries

```
❯ clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/stats-remote-sync --output /tmp/output.edn
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-19 12:27:47,656 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
2026-03-19 12:27:52,987 INFO util.encryption :: Saved credentials encryption is DISABLED for this Metabase instance. 🔓
 For more information, see https://metabase.com/docs/latest/operations-guide/encrypting-database-details-at-rest.html
Results written to: /tmp/output.edn
Card Check Results
==================
Total cards: 2
  OK: 0
  Errors: 2
  Unresolved refs: 0
  Issues: 0

Failures:
---------

=== Account [beKpAz4TM1cmVN_0HiAvi] ===
  Card ID: 1
  UNRESOLVED REFERENCES:
    - database: Analytics Data Warehouse
    ...
```

and structural:

```
❯ clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based --output /tmp/output.edn
Results written to: /tmp/output.edn
Card Check Results
==================
Total cards: 83
  OK: 83
  Errors: 0
  Unresolved refs: 0
  Issues: 0
```

```
❯ clj -X:dev:test:ee:ee-dev :module enterprise/checker
Running tests with options {:mode :cli/local, :namespace-pattern #"^(?:(?:metabase.*)|(?:hooks\..*))", :exclude-directories [".clj-kondo/src" "classes" "dev" "enterprise/backend/src" "local" "resources" "resources-ee" "src" "target" "test_config" "test_resources"], :test-warn-time 3000, :exclude-tags [:metabot-v3/e2e], :module enterprise/checker, :only ("enterprise/backend/test/metabase_enterprise/checker")}
Running tests in ("enterprise/backend/test/metabase_enterprise/checker")
Looking for test namespaces in directory enterprise/backend/test/metabase_enterprise/checker
Finding tests took 228.0 ms.
Running 22 tests
Running before-run hooks...

22/22   100% [==================================================]  ETA: 00:00

Ran 22 tests in 1.076 seconds
110 assertions, 0 failures, 0 errors.
{:test 22, :pass 110, :fail 0, :error 0, :type :summary, :duration 1075.697958, :single-threaded 22}
Ran 0 tests in parallel, 22 single-threaded.
Finding and running tests took 1.3 s.
All tests passed.
Running after-run hooks...
```

* whoops forgot to add new files

* hybrid format concise db schema, serdes assets

* clean up models.serialization

two things we are cleaning up:

1. protocol

there should be a real protocol that we can implement, rather than
knowing which five dynamic vars we need to override/bind a bunch of
dynamic vars, let's have a real protocol and a way to swap it out.

```
;; for yaml
(def ^:private checker-import-resolver
  (reify resolve/SerdesImportResolver
    (import-fk        [_ eid model]            (import-fk eid model))
    (import-fk-keyed  [_ portable model field] (import-fk-keyed portable model field))
    (import-user      [_ _email]               nil) ; checker doesn't resolve users
    (import-table-fk  [_ path]                 (import-table-fk path))
    (import-field-fk  [_ path]                 (import-field-fk path))))
```

note: future work will put the file system state inside of this and not
as some dynamic var spread throughout the code. But one step at a time.

Now to convert a dataset query we have:
```clojure
(defn- convert-dataset-query
  "Convert a dataset query from serdes format, resolving paths to IDs."
  [query]
  (when query
    (let [db-name (:database query)
          db-id (when (string? db-name) (resolve-db-name db-name))
          query (if db-id (assoc query :database db-id) query)]
      (binding [resolve/*import-resolver* checker-import-resolver]
        (resolve/import-mbql query)))))
```

instead of this:

```clojure
(defn- convert-dataset-query
  "Convert a dataset query from serdes format, resolving paths to IDs."
  [query]
  (when query
    (let [db-name (:database query)
          db-id (when (string? db-name) (resolve-db-name db-name))
          query (if db-id (assoc query :database db-id) query)]
      (binding [serdes/*import-field-fk* import-field-fk
                serdes/*import-table-fk* import-table-fk
                serdes/*import-fk* import-fk
                serdes/*import-fk-keyed* import-fk-keyed]
        (serdes/import-mbql query)))))
```

And in the future it will hopefully be something like this:

```clojure
(defn- convert-dataset-query
  "Convert a dataset query from serdes format, resolving paths to IDs."
  [yaml-store query]
  (when query
    (let [db-name (:database query)
          db-id (when (string? db-name) (resolve-db-name db-name))
          query (if db-id (assoc query :database db-id) query)]
      (binding [resolve/*import-resolver* (checker-import-resolver yaml-store)]
        (resolve/import-mbql query)))))
```

2. not require toucan in the checker.
Before this change, we had to require `metabase.models.serialization`
which brought in `[toucan2.core :as t2]`, models.interface, all kinds of
database specific stuff. We now have the protocol and can implement it
against the yaml store rather than the db. Hopefully startup is faster
now.

```
clj -M:ee -m metabase.core.bootstrap --mode checker --checker cards --export /Users/dan/projects/work/yaml-checked-files-v1/exports/sqlite-based-mixed-versions --output /tmp/mixed.output
Results written to: /tmp/mixed.output
Card Check Results
==================
Total cards: 83
  OK: 83
  Errors: 0
  Unresolved refs: 0
  Issues: 0
```

has gone from 11s to 9s. Small wins, but ~10% savings.

Also, it seems like the vast majority of time is spent in stuff we don't
care about which generates these logs:

```
Warning: environ value /Users/dan/.sdkman/candidates/java/current for key :java-home has been overwritten with /Users/dan/.sdkman/candidates/java/25.0.2-tem
2026-03-20 12:15:32,319 INFO metabase.util :: Maximum memory available to JVM: 8.0 GB
```

* clean up, build file interpreter rather than imperative

* lenient mode

writes out a manifest file of fields that are used

* lenient checker

* add serdes workflow and yaml-edit skill files, fix bootstrap fallthrough

Two new Claude skill files for the serdes demo workflow:
- serdes-workflow: export/validate/import pipeline with exact commands
- serdes-yaml-edit: editing YAML with portable refs, self-healing loop,
  checker error interpretation, and reference lookup guide

Fix bootstrap.clj to return false when no --mode flag is present,
allowing normal Metabase startup (import/export commands) to proceed.

* allow for separate db schema directory

checker: --schema-dir, --errors-only, sentinel IDs for unresolved refs

  --schema-dir lets database schemas live in a separate directory from the
  export. The directory is treated as the databases dir directly (contains
  db entries, not a databases/ subdirectory).

  --errors-only outputs just errors to stdout in a concise format for LLM
  consumption — no summary, no status lines.

  Unresolved field/table refs now get sentinel IDs instead of nil, so
  lib/query can construct the query and lib/find-bad-refs can flag the
  missing refs. Previously these blew up with "should be a positive int,
  got: nil".

  Without --lenient, missing schemas now throw instead of silently falling
  back to lenient mode.

  Want me to go ahead and commit with that?

* validate native sql

* filter false-positive :missing-column errors from SQL validation

Schema-less databases (e.g., SQLite) store tables with nil schema, but
sql-tools/validate-query assumes the driver's default schema ("public")
for unqualified table references. This mismatch causes valid columns to
be reported as missing.

Cross-check :missing-column errors against the store and suppress any
where the column actually exists for that database.

* extract the notion of the store

* down to just a single format

* cleanup, rename some args

* rename to semantic

* dashboard checker, extract a bit of common semantic checks

* check dashboard references

* extra schemas to bespoke file so we can move to a public repo

* Remove schema checker

we are semantic only. Now no reason to use a mode param

* Simplify checker module: remove formats, add dashboard/document, norm

  Major refactoring of the checker module:

- Remove format abstraction: Delete concise, lenient, and hybrid
formats. Serdes is the only format. Delete format/hybrid.clj,
format/concise.clj, format/lenient.clj and their tests.

- Remove structural checker: Schemas moved to separate repo. Delete
structural.clj, schemas.clj, structural_test.clj.

- Rename checker.clj → semantic.clj: The "cards" checker is now the
"semantic" checker. CLI no longer has a --checker flag.

- Simplify store: Replace enumerator thunks with direct file index. Add
fields-by-table reverse index for O(1) table→fields lookup.

- Remove requiring-resolve: checker.clj (now semantic.clj) directly
requires format.serdes. No circular dependency indirection.

- schema-dir is required: No auto-detection. Export dir and schema dir
are always separate arguments.

- Add CompositeSource: In source.clj, delegates db/table/field to one
source and cards to another.

- Index entities by model field: Collections tree indexed by reading
serdes/meta model from YAML content rather than path patterns. Supports
both traditional serdes layout and the new flat layout.

- Dashboard validation: Validate dashcard card_id refs, dashboard_tab_id refs, and grid bounds (24-column grid).

- Document validation: Walk ProseMirror content tree, validate cardEmbed card refs.

- collection_id and dashboard_id validation: Any entity with these fields is checked against the index.

- Duplicate entity_id detection: Detected at index build time, reported with file paths.

- MBQL normalization pipeline: keywordize string operators → resolve
portable refs → legacy normalize. Known temporal unit strings
keywordized. Malli enforcement disabled during checking so lib/query
accepts valid clauses the schema doesn't enumerate.

- Disambiguate internal naming: Accumulator atom renamed from unresolved
to !failures. Qualified keys renamed to ::resolution-failures and
::missing-database.

* segment and measure support

* compile sql, handle native query snippet

* implement transforms checks

* using binding to prevent running t2 middleware

* fixup merge failures

* directory agnostic schema dir; sql parsing fixes

realized we weren't keeping around db information. now we are so we
don't have to bind driver information

```diff
-            compiled    (binding [driver/*driver* driver
-                                  qp.i/*skip-middleware-because-app-db-access* true]
+            compiled    (binding [qp.i/*skip-middleware-because-app-db-access* true]
```

* use lib for native check

* parse transform sql, store tests, fixups

* rename check-cards → check-entities; add direct native validation tests

check-cards was misleading — it validates cards, dashboards,
collections, documents, transforms, and more. Renamed to check-entities
with an updated docstring that documents the three parameters.

Added 6 direct tests for native.clj functions (extract-sql-refs,
validate-native-sql) covering valid queries, missing columns, syntax
errors, unknown databases, and nil db-name.

* remove bespoke native checking, use deps analysis

* compute result metadata for processing:

change:

before:
```
=== Native Query - Card and Snippet References [HiBFSt0BNx5s5MxVDLLKB] ===
  Card ID: 94
  Tables: Sample Database.PUBLIC.ORDERS
  Fields: Sample Database.PUBLIC.ORDERS.CREATED_AT
  Source Cards: Basic Aggregations
  NATIVE SQL ERRORS:
    - {:type :missing-column, :name "total_revenue", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
    - {:type :missing-column, :name "created_attt", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
    - {:type :missing-column, :name "cooount", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
  Status: NATIVE SQL ERRORS
```

after:
```
=== Native Query - Card and Snippet References [HiBFSt0BNx5s5MxVDLLKB] ===
  Card ID: 94
  Tables: Sample Database.PUBLIC.ORDERS
  Fields: Sample Database.PUBLIC.ORDERS.CREATED_AT
  Source Cards: Basic Aggregations
  NATIVE SQL ERRORS:
    - {:type :missing-column, :name "created_attt", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
    - {:type :missing-column, :name "cooount", :source-entity-type :card, :source-entity-id "h5F2EjHsRd73Dqqh8sAtd"}
  Status: NATIVE SQL ERRORS
```

The total_revenue issue is related to not knowing that the card's
aggregation creates this column. Before it didnt' recognize it. Now with
metadata it does recognize it

* cleanup a bit

- println -> output! to silence the warnings and suppress in one place
- -main -> entrypoint so it doesn't appear as a proper entrypoint for
  the jar, we still go through bootstrap and dispatch

* some dumentation on checker shape

* cljfmt files

* fixup

* include resolver arg

* fix readme and docstrings

* clean things up, introduce provider

provider is what wraps a store into a metadata provider. it's very
qp. the store is what cares about how to load assets and schema from
disk (or anywhere else?)

* e2e tests, report all checked items, roadmap and readme

* fix lint errors

* Fix dependencies module dependency

was using deps.analysis for it's check-entity multimethod. I at first
put an entry in deps.core that passes through to this, but deps.core
brings in t2 which i don't want.

Future work should split this namespace so it has some protocol of how
to load up it's information for deps analysis and the checker can figure
out how to adapt it's store and index for these purposes. Then all the
t2 stuff goes away and we're golden

* sort the api namespaces

* fmt fixes

metabase on  yaml-backed-metadata-provider [$?] via 🥟 v1.3.8 via ☕ v25.0.2 on ☁️  metabase-query
❯ bin/mage cljfmt-files enterprise/backend/test/metabase_enterprise/checker
Reformatted enterprise/backend/test/metabase_enterprise/checker/semantic_test.clj

metabase on  yaml-backed-metadata-provider [$!?] via 🥟 v1.3.8 via ☕ v25.0.2 on ☁️  metabase-query
❯ bin/mage cljfmt-files enterprise/backend/src/metabase_enterprise/serialization/cmd.clj
Reformatted enterprise/backend/src/metabase_enterprise/serialization/cmd.clj

* Checker: unify entity checking, split protocols, extract provider

  Major refactoring of the checker module for clarity and consistency:

  - Split MetadataSource into SchemaSource (databases/tables/fields) and
    AssetsSource (cards/snippets/transforms/segments/dashboards/collections/
    documents/measures). All entity types now go through source protocols
    and the store — no more direct YAML file loading fallbacks.

  - Extract provider.clj from semantic.clj. The provider adapts the store
    into a lib MetadataProvider: reference resolution, MBQL normalization,
    data conversion. semantic.clj is now just validation + orchestration.

  - Replace custom keywordize-mbql-operators (~40 lines) with lib/normalize.

  - Unify entity checking: cards are no longer special-cased. check-entity
    dispatches by kind for all entity types. check-one works for any entity,
    not just cards. check-entities defaults to checking everything.

  - Store sources are now private. Provider creates resolvers internally.
    resolve-ref uses store load functions instead of reaching through to
    source protocols directly.

  - Add dashboard/collection/document/measure to source protocols, store
    load functions, and test helpers. Tests use in-memory sources throughout
    — no more temp YAML files.

  - Add e2e tests against test_resources/serialization_baseline with shape
    assertions for joins, snippets, measures/metrics/segments, and
    transitive ref propagation.

  - Add forward-declare wrapper for deps.analysis/check-entity.

  - Track snippets, measures, metrics, segments in card :refs, propagating
    transitively through source-card chains.

  - All non-card entities now appear in results (not just failures).

* handle multiple databases

* cleanup entrypoint a bit

* Checker: lazy on-demand indexing for schema directories

Index only database YAMLs (~40 files) at startup instead of parsing all
563k YAML files. Tables, fields resolved on-demand from directory structure.
Segments/measures indexed only from export dir, not schema dir.

Key changes:
- SchemaSource protocol gains enumeration methods (fields-for-table,
  tables-for-database, all-table-paths, etc.)
- db-name->dir mapping handles slugified directory names
- Provider scoped to current database (tables-for-database not all-table-paths)
- Store delegates to source instead of maintaining field index

Setup time: 64s → ~100ms for 40 databases, 22k tables, 541k fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Checker: handle table-type template tags, lightweight table listing

- Native queries with {{table_one}} (type: table) template tags now get
  placeholder substitution instead of passing through as raw mustache
  syntax that SQLGlot can't parse
- Filter out validation errors referencing table placeholder names
- Skip result-metadata for table-tag queries (columns unknown at check time)
- Provider uses ensure-table-id! for table enumeration (path-derived
  metadata, no YAML parse) instead of load-table! (full YAML parse)
- Remove debug instrumentation from semantic.clj

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Checker: update docs to reflect on-demand indexing architecture

- DESIGN.md: rewrite to reflect current architecture (SchemaSource/AssetsSource
  protocols, on-demand field/table resolution, db-name->dir mapping, parallelism
  readiness, performance characteristics)
- CHECKER_RESULTS.md: add analysis of 177-entity run (17 true errors, 2 remaining
  bugs, 1 fixed template tag bug)
- Fix stale docstrings in store.clj and serdes.clj

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Checker: consolidate docs into README, remove standalone md files

Move architecture, performance, and on-demand indexing docs into
the existing README. Remove DESIGN.md and CHECKER_RESULTS.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Checker: fix test helpers for new SchemaSource protocol methods

- Add fields-for-table, all-field-paths, all-database-names,
  all-table-paths, tables-for-database to test helper reify
- Update index assertion tests to check via source protocol
  instead of pre-indexed table/field counts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Merge branch 'master' into yaml-backed-metadata-provider

* remove bashing in place, unused function

* Checker: single-atom schema model with two-level lazy resolution

Replace the three separate cache atoms (indexed-schemas,
table-cache,field-cache) with a single schema-model atom that owns all
database, table, and field knowledge. Separate from the assets-index
which holds cards, dashboards, segments, etc.

Schema model shape:
  {db-name {:db-file "path"
            :schemas {schema ::not-indexed
                             | {table {:table-file "path"
                                       :fields ::not-indexed | {field "path"}}}}}}

Two levels of laziness:
- Schema → tables: populated when any table in that schema is resolved,
  or when tables-for-database is called. Walks the schema's tables/
  directory and reads each table's name: from YAML via fast regex.
- Table → fields: populated when resolve-field or fields-for-table is
  called for that specific table. Walks the table's fields/ directory
  and reads each field's name: from YAML via fast regex.

Schema name resolution handles the case where directory names differ
from real entity names (e.g. H2: dir=public, real=PUBLIC). This is
isolated in resolve-schema-name as the single point to change when
schema resolution improves.

Also: store paths as strings not Files, fix exists? for databases (now
checked via schema source not assets index), remove dead
has-card-template-tags?.

* inline util lower-case-en

don't want to pull in all the stuff so i just use the definition inline
here

---------

Co-authored-by: Bryan Maass <bryan.maass@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:10:01 -05:00
Jeff Bruemmer
a89d93d1f3 docs - file-based development (#72496) 2026-04-14 21:04:32 +00:00
Jeff Bruemmer
2f56929881 docs - serdes updates (#72391) 2026-04-14 20:34:57 +00:00
Cam Saul
1af01d8489 Pick random host when testing Redshift from MB_REDSHIFT_TEST_HOSTS (#72498)
* Pick random host when testing Redshift from MB_REDSHIFT_TEST_HOSTS

* Debugging

* Update CI config

* Oops

* Simplify connection details

* Kicc CI
2026-04-14 19:46:17 +00:00
Edward Paget
618d6f93dc Fix OIDC SSRF: validate all discovery-document URLs before HTTP requests (#72534)
* Fix OIDC SSRF: validate all discovery-document URLs before HTTP requests

The token_endpoint extracted from the OIDC discovery document was used
for HTTP POST requests without validating the host against
oidc-allowed-networks. A compromised OIDC provider could return a
token_endpoint pointing at internal hosts, causing Metabase to POST
client credentials to an attacker-chosen URL.

Centralizes all OIDC HTTP requests through a new metabase.sso.oidc.http
namespace that validates URLs against oidc-allowed-networks before every
request. Also adds defense-in-depth validation in get-endpoint so URLs
are checked at extraction time from the discovery document.

* Log blocked OIDC requests at WARN level for easier debugging

Adds an explicit log/warnf in validate-url! so SSRF blocks are visible
at the point of origin rather than only as wrapped causes downstream.
2026-04-14 13:40:28 -05:00
Edward Paget
0ac48cb972 Downgrade all netty pins to 4.2.9.Final (#72546)
Netty 4.2.12 introduced a breaking change in HttpObjectEncoder that
causes AWS SDK netty-nio-client to fail with "unexpected message type:
LastHttpContent" errors. Revert to 4.2.9.Final which is compatible
with the AWS SDK while still being in the 4.2.x line.

Closes: #72532
2026-04-14 15:39:31 -03:00
Nick Fitzpatrick
d93f7fecde Remove theme transition from dashbaord header, tabs, and parameters panel (#72370) 2026-04-14 23:40:44 +05:30
Braden Shepherdson
eb61194d47 [QP] Correctly handle "struct" fields on Athena and similar drivers (#71917)
Fixes QUE2-34.

These are nested fields (`:parent_id` is set) but not JSON nesting, so
the mechanism is slightly different from `:nfc_path`. This change should
include the necessary metadata on `:field` refs inserted by field filters
to correctly handle struct fields.
2026-04-14 13:48:19 -04:00
Mihael Konjević
828e1bf20c Align embedding profile with NLQ profile tools and system prompt (#72448)
* Align embedding profile with NLQ profile tools and system prompt

The embedding_next profile was a stripped-down version with only 3 tools
and a simpler prompt. This aligns it with the NLQ profile so embedded
metabot behaves identically: same system prompt content (kept as a
separate file for future divergence), same tool set (nlq-search,
read-resource, construct-notebook-query, navigate-user, create-chart,
edit-chart).

* Remove obsolete embedding_next tools test

This test was asserting the old tool set (including
list_available_data_sources). Coverage is now provided by
embedding-next-matches-nlq-tools-test in profiles_test.
2026-04-14 19:28:43 +02:00
Nick Fitzpatrick
2b5b417b1f Center buttons vertically in CreateOrEditQuestionAlertModal footer (#72366)
* center buttons vertically in CreateOrEditQuestionAlertModal footer

* Fix alert modal footer button sizing mismatch (#71945)

- Replace ActionButton with a Mantine Button for the Done/Save changes button
- Wire loading state to isCreating and isUpdating from RTK Query mutations
- Swap the wrapping div for a Flex with align="center" and gap="sm"
- Add align="center" to the outer footer Flex so all three buttons stay consistently sized and vertically centered at all viewport widths
- Remove throw result.error which only existed to drive ActionButton's internal error display

* handle error states

---------

Co-authored-by: Alexander <aquezadawa@gmail.com>
2026-04-14 22:38:45 +05:30
Rodrigo López Dato
82b07d4b28 Make CLJS dev port configurable in CSP via MB_CLJS_DEV_PORT (#72530)
The frontend dev port was already configurable via MB_FRONTEND_DEV_PORT,
but the CLJS REPL port (shadow-cljs) was still hardcoded to 9630. This
causes CSP violations when running multiple Metabase instances with
dynamic port allocation.
2026-04-14 13:14:31 -03:00
Anderson
175846d975 Address a few AI Controls UI follow-up items (#72482)
* Implement a few AI Controls UI follow-ups

* Fix token input suffix (pluralization)
2026-04-14 15:48:18 +00:00
metamben
52257c531f Fix BigQuery tests broken by #71771 (#72484) 2026-04-14 11:46:23 -04:00
Anderson
dc2bfa264b Address review comments from pr #69874 (#72500) 2026-04-14 11:41:20 -03:00
Noah Moss
af3da5c6b7 Strip bloat fields from :tool-output; add logging/metrics around stored metabot message size (#72388)
* strip bloat fields from :tool-output; add logging/metrics around stored message size

* switch to actually counting encoded byte size for prometheus

* address feedback

* clarify comment
2026-04-14 08:55:21 -04:00
Nick Fitzpatrick
50e490de81 Typescript common components (#72288)
* Entity Menu stuff, Expanding Content

* LastEditInfoLabel, ListItem

* buncha test files

* DebounceFrame, more spec files

* type adjustments
2026-04-14 16:11:27 +05:30
Nick Fitzpatrick
2d4279d596 Re-add new dashboard button in move modal and collection picker when dashboards are selectable (#72363) 2026-04-14 16:11:04 +05:30
Ngoc Khuat
64af0de83f GDGT-2144 Require transform target schema on schema-aware DBs (#72220) 2026-04-14 10:27:02 +00:00
Fraser
3403b34d00 Move store types and mocks to shared tier (#71400) 2026-04-14 22:06:00 +12:00
Fraser
d1fc89abff Update enforced modules (#72520) 2026-04-14 09:49:59 +00:00
Fraser
bcc9b9a15d Fix lib-css outgoing violations (#72437) 2026-04-14 21:34:08 +12:00
Sébastien
e21a007fbc Fixes ordering issue in parameters (#72083)
Co-authored-by: Braden Shepherdson <Braden.Shepherdson@gmail.com>
Co-authored-by: metamben <103100869+metamben@users.noreply.github.com>
2026-04-14 10:21:05 +02:00
Ngoc Khuat
109257d6bc Don't send alerts to deactivated users (GDGT-1927) (#72444) 2026-04-14 14:59:30 +07:00
Fraser
522f803837 Fix breadcrumbs story violation (#72333)
Fix breadcrumps story violation
2026-04-14 18:31:30 +12:00
Fraser
2d26466250 Resolve MultiAutocomplete translation plugin (#72228) 2026-04-14 17:50:59 +12:00
Tyler Plude
70263943b5 [BOT-1316] Improve native agent API error logging (#72499)
* improve native agent error logging

* fix kondo
2026-04-13 22:16:18 -07:00
Anderson
25015ba318 [AI Controls] Show validation error for group limits above instance limit (#72205)
* Rename natural language query page to AI exploration page

* Show validation error for group limits above instance limit
2026-04-13 20:09:30 -03:00
Fraser
c417b95a44 Selectively enforce module boundaries (#72436) 2026-04-14 10:04:00 +12:00
github-automation-metabase
d493249403 Auto-update documentation for master (#72493)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-13 17:25:59 -04:00
Alexander Polyankin
baf1ebad78 fix table-id serialization (#72489) 2026-04-13 21:15:09 +00:00
bryan
1c92ac2bbc Fix models to transforms for transform output tables created with nil schema (#72139) 2026-04-13 14:42:06 -06:00
github-automation-metabase
a04458ffd8 Auto-update documentation for master (#72398)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-13 16:18:15 -04:00
Timothy S. Dean
d07c7a73ff Skip t2 hooks when updating dashboard's last-viewed-at (#72479)
Skip t2 hooks when updating dashboard last-viewed-at.
2026-04-13 14:01:57 -06:00
bryan
a0e764069c GHY-3378: Block source replacement when sandboxing policies are affected (#72473) 2026-04-13 13:33:32 -06:00
Nick Fitzpatrick
ee9e271af6 Update Checkbox Styling (#72375)
* Update Checkbox Styling

* fixing select all checkboxes

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-04-13 23:39:04 +05:30
Edward Paget
297421c564 deps: bump clickhouse transitive deps (#72470)
Fix ClickHouse driver security vulnerabilities

- Pin httpcore5-h2 to 5.3.5 to fix DoS vulnerability
- Replace discontinued org.lz4:lz4-java 1.8.0 with maintained fork
  at.yawk.lz4:lz4-java 1.10.2 to fix out-of-bounds read and
  sensitive data exposure vulnerabilities
2026-04-13 12:54:07 -05:00
Edward Paget
141ea91664 deps: bump jackson and related (#72460)
* Pin jackson-core/databind 2.21.2, bump clj-otel to 0.2.10, and bump httpcore5 to 5.3.5

Fixes transitive vulnerabilities:
- jackson-core 2.21.1 resource exhaustion (via cheshire)
- jackson-databind 2.13.3 DoS (via snowplow)
- kotlin-stdlib 1.8.21 info exposure (via clj-otel → okhttp, resolved by bumping clj-otel)
- httpcore5-h2 5.3.4 DoS

* Sort jackson-core/databind deps alphabetically in deps.edn
2026-04-13 12:26:22 -05:00
Edward Paget
f677b4524e deps: bump all log4j deps to 2.25.4 (#72461)
Bump all log4j deps to 2.25.4 to fix certificate validation and log injection vulnerabilities
2026-04-13 10:34:43 -05:00
John Swanson
dd59e7545a Safer conversion of the Data Analysts group (#71032)
Currently, stats has 5 "Data Analysts (converted)" groups. As far as I
can tell, this shouldn't happen in the happy path: after the database is
set up, we check for the necessary premium features before converting
the "special" Data Analysts group into a renamed "normal" group and
creating a new Data Analysts group.

But this is happening - intermittently - so I think we just need to be
more defensive here. If some transient network issue or something causes
us to temporarily be unsure what features are enabled, then normally we
just say "nope, no features here" and that's fine. In this case we need
to be sure, and should only do the Data Analysts swap if Metastore has
*told us* that the feature is not present.

So, this adds:

- a `:canonical? false` flag, when the result is not canonical

- and a function, `canonically-has-feature?` that returns `true` if the
  feature is canonically present, `false` if the feature is canonically
  absent, or `nil` if the feature's presence cannot be canonically
  determined

Then, when we start up, we check the feature using this function.

If the feature is canonically present, we do nothing.

If the feature is canonically absent, we do the swap.

If neither is true (network blip or some other issue caused us to not
receive a valid response from the metastore), we fire off a future that
will wait for a canonical response and then do the swap if necessary.
2026-04-13 08:22:50 -07:00
John Swanson
904a701e71 Skip cache auto-refresh attempts for routed dbs (#70082)
* Skip cache auto-refresh attempts for routed dbs

Currently we pollute the logs here, because we can't do anonymous
queries against a routed DB.

Just skip cards that are against routed databases to avoid failing
deeper down.

* Import :model/DatabaseRouter into enterprise/cache module
2026-04-13 08:22:04 -07:00
Edward Paget
a450c7db99 deps: bump zookeeper and guava in hivelike (#72454)
* Bump Zookeeper 3.8.4→3.8.6 and Curator 5.7.1→5.9.0 in hive-like driver

Fixes CVE-2026-24308 (config data leaked to INFO logs) and
CVE-2026-24281 (hostname verification bypass via PTR spoofing).

* Pin Guava 33.5.0-jre in hive-like driver to fix transitive 22.0 vulnerabilities

hive-standalone-metastore-common pulls Guava 22.0 transitively, which has
three known Snyk advisories (info disclosure, deserialization, insecure
temp files). Exclude it from the metastore dep and pin 33.5.0-jre directly,
matching the root deps.edn version.

* Pin Guava 33.5.0-jre in hive-like driver to fix transitive 22.0 vulnerabilities

hive-standalone-metastore-common pulls Guava 22.0 transitively, which has
known security advisories (info disclosure, deserialization, insecure
temp files). Exclude it from the metastore dep and pin 33.5.0-jre directly,
matching the root deps.edn version.
2026-04-13 10:19:52 -05:00
Anderson
5e6eb552e3 Use partial settings update when switching branches during Remote Sync conflict (#69874) 2026-04-13 10:15:08 -05:00
Alexander Polyankin
416a8cc63f Fix streaming with serdes (#72457)
* fix streaming

* fix comment

* fix
2026-04-13 10:23:32 -04:00
Nick Fitzpatrick
e1438f4c42 Update PATH_WITH_COLLAPSED_NAVBAR to not close on all routes with question (#72374) 2026-04-13 18:16:21 +05:30
Kevin Kelleher
027a2bede1 Time series axis label improvements (#70814) 2026-04-13 08:25:15 -04:00
Ngoc Khuat
10997b1090 Stop aborting Redshift test runs on concurrent schema-drop races (GDGT-2126) (#72441) 2026-04-13 11:09:51 +00:00
Nicola Mometto
3c8b941dce seed advisories on first run (#72446) 2026-04-13 10:42:13 +00:00
Fraser
9d68e94b06 Fix shared-common outgoing violations (#72438) 2026-04-13 20:46:21 +12:00
Fraser
8ea71a40e7 Move disable command palette hook into ui (#72432)
Move disable command palette hook into ui
2026-04-13 20:45:45 +12:00
Ngoc Khuat
7998a40a63 Show transform inspector as upsell when feature is not enabled (#71341) 2026-04-13 07:21:11 +00:00
Alexander Polyankin
28f1915be3 Trivial serdes export fixes for metadata (#72434) 2026-04-13 00:55:58 -04:00
Alexander Polyankin
011bbd3f98 Revert "Fix serdes perf on export" (#72416)
* Revert "Fix serdes breaking on IDs for db/table/fields for non-existent entit…"

This reverts commit 8b051f242f.

* Revert "Fix serdes export perf with database metadata - id lookup (#72326)"

This reverts commit e90c0ea24e.
2026-04-11 21:15:14 +01:00
Uladzimir Havenchyk
d95a444446 chore: do not transpile cljs in tests (#72248) 2026-04-11 20:25:59 +03:00
Edward Paget
84ebef5e3e Migrate hive-like driver from hive-jdbc$standalone to hive-jdbc (#72379)
Replace the standalone uberjar (which bundles and relocates dependency
versions with published security advisories) with the regular
hive-jdbc artifact plus explicit sub-module and third-party dependency
declarations at safe versions.

The published hive-jdbc 4.2.0 POM is broken (HIVE-28455) so all Hive
sub-modules must be declared manually. Each has exclusions to prevent
pulling in the massive Hadoop/Hive transitive dependency tree (YARN,
Jetty, Jersey, Spring, Shibboleth, etc.).

Key version pins for security scanning:
- Netty 4.2.12.Final (was bundled at 4.1.127.Final)
- commons-io 2.18.0 (CVE-2024-47554)
- commons-codec 1.17.1, commons-logging 1.3.5
- httpclient 4.5.14, httpcore 4.4.16

Fixes: GHY-1756:
2026-04-11 09:04:51 -03:00
Uladzimir Havenchyk
cb21657273 deps: upgrade lodash and lodash-es (#72336) 2026-04-11 08:20:14 -03:00
Ryan Laurie
04a520b658 Pin axios versoin (#72405)
* pin axios versoin

* pin axios version
2026-04-10 23:09:11 +00:00
Tyler Plude
257365531a [BOT-1260] Stub workflow for testing GH benchmark integrations (#72401)
stub workflow
2026-04-10 16:02:52 -07:00
Alexander Polyankin
8b051f242f Fix serdes breaking on IDs for db/table/fields for non-existent entities (#72399)
* fix broke FK fields

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2026-04-10 15:15:56 -07:00
John Swanson
e88f6252d8 Don't document jwt-configured-and-enabled (#72244) 2026-04-10 15:14:46 -07:00
Denis Berezin
4c217bdb3c Metrics Math UX (#70733)
* feat: metric math - backend only

Squashed commit of lib_metric AST, compile, plan, filter, projection, and related backend changes.
Frontend and JS-export changes have been removed.

* support for constants

* fix tests

* kondo

* whitespace

* tests

* UXW-3264 Add metric math test UX

* Polishing, address edge cases

* FE changes to work with constants

Code may be bad but it seemed pretty simple to add support here.

* Add text view

(cherry picked from commit e7ad8536860ece87a84608b42a2e7a70ebc99ca0)

# Conflicts:
#	frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.module.css
#	frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.tsx
#	frontend/src/metabase/metrics-viewer/pages/MetricsViewerPage/MetricsViewerPage.tsx
#	frontend/src/metabase/metrics-viewer/utils/url-serialization.ts

* Add support for constants

* Add unit tests

* Small updates

* WIP - added syntax highlighting, validation

* Add syntax highlight and basic validation

* Metric viewer can use metrics based on cards

[UXW-3260: Metrics from models/table questions are not viewable in the Metrics viewer](https://linear.app/metabase/issue/UXW-3260/metrics-from-modelstable-questions-are-not-viewable-in-the-metrics)

* Add commit changes flow

* Allow using the same metric multiple times

* Temp

* Fixed reload after adding a new metric from dropdown

* Fix issue with single expression

* Polished version with metric text tokens

* scalar results tab

* show no compatible dimensions error rather than blank screen

* fix types

* Fix adding breakouts

* fix scalar and map breakouts

* rename to totals tab and allow adding back in

* scalar tab cleanup

* fix default tabs not reappearing

* fix error flashing

* UXW-3382 Fix metric names with commas and other symbols (#71318)

* UXW-3382 Fix metric names with commas and other symbols

* UXW-3382 Fix issue with searching metric with comma in it's name

* Self review

* allow adding tabs for expressions

* fix deserializing measures from URL

* add index to serialized metrics and expressions to preserve order on refresh

* fix tests

* UXW-3380 Fix metric dropdown to filter only self

* don't move cursor with arrow up/down when popover is open

* handle order of operations properly

* handle non-numeric metric error

* Allow metric math with non-exact temporal field matches (#71183)

* Allow metric math with non-exact temporal field matches

We can now do metric math between two metrics that both have temporal
dimensions, even if those temporal dimensions aren't identical! 🎉

* cljfmt

* fix clj tests

* UXW-3413 Support break out the same metric multiple ways (#71390)

* UXW-3413 Support break out the same metric multiple ways

* UXW-3413 Fix issues the last same metric instance overrides breakout, fix colors

* Fix removing breakout after changing formula

* Self review

* Review comments

* Remove rename that breaks IDE navigation

* Filtering specific instances of metrics (#71640)

* fix be tests

* llm review

* fix be test

* support negative numbers in metrics viewer expressions (#71790)

* support negative numbers

* review

* slight refac

* llm review - don't clear dimension filter on dimension change

* cleanup console.log

* revert accidental changes to pie files

* fix unit tests

* revert changes with invalid css variables

* remove unused CSS classes with bad CSS vars

* fix e2e tests

* Fix Metric Math URL serialization (#71769)

* reconile state on expressions change (#71795)

* add metric math e2e tests

* wip

* Revert "wip"

This reverts commit dadde5a90c.

* UXW-3292 Add dimension controls for every metric instance (#71791)

* UXW-3292 Add dimension controls for every metric instance

* Add expressions dimensions controls, but it doesn't really apply to the series

* Add fixes to pick default dimension, and support dimensions for expressions

* Add e2e tests

* Refactor

* Fix issue with not visible series

* Fix issue with missing MetricControls

* Fix issue selected metric dimension being preserve after reload

* Fix e2e

* Update dimension projection serialization for expressions

* Address some review comments

* Added more unit tests

* Self review

* Self review

* fix test, try to fix flaky metric identities

* revert failed attempt to fix flaky test

* Reuse reconciliation logic from MetricSearchInput (#72111)

---------

Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>

* Ensure we can always parse formulas (#72112)

* ensure we can always parse formulas

* review comments

* Revert changes to e2e to wait for metrics load

* Fix after merge

* fix modifiedDefinitions indexing for expressions

* update invalid expression handling (#71934)

* fix types in tests after merge

* fix test

* Review fixes

* more test fixes

* try to fix flaky test again

* Series refactor (#72060)

* add dimension pill icon for expressions

* Fix styles

* Fix styles

* Fixes after merge

* Review fixes

* Review fixes

* Fix e2e tests

---------

Co-authored-by: Edward Paget <ed.paget@gmail.com>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>
Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com>
2026-04-10 15:01:21 -07:00
appleby
95b6569503 BOT-1231 Remove user-intent tracking (#72199)
* BOT-1231 Remove user-intent tracking

https://metaboat.slack.com/archives/C0AP8MCUE4U/p1774895471205839?thread_ts=1774891049.263789&cid=C0AP8MCUE4U

* Remove trailing whitespace

* Remove unused require

* Remove unused defs
2026-04-10 14:43:17 -07:00
da
ed21fcad5a Update cheshire version to 6.2.0 (#72279)
* Update cheshire version to 6.2.0

https://github.com/dakrone/cheshire/commits/master/

patches the jackson-core dep issue

* Make JSON error test resilient to parser version changes

Use regex matching instead of exact string equality so the test
doesn't break when the JSON parser reports a different column offset.

---------

Co-authored-by: Edward Paget <ed.paget@gmail.com>
2026-04-10 15:49:38 -05:00
Edward Paget
e4d5acd850 deps: bump com.taoensso/nippy (#72295)
* deps: bump com.taoensso/nippy

resolves a reported issue with a transitive dependency

* Update deps.edn
2026-04-10 15:49:15 -05:00
John Swanson
f57e7585cc Fix metrics on models with custom column dims (#72164)
* Fix metrics on models with custom column dims

Just like with joins, a metric with a source-card needs to be compiled
to a two-stage query rather than querying the table directly, so that we
can query custom columns.

* Fix metrics on models with custom column dims

Just like with joins, a metric with a source-card needs to be compiled
to a two-stage query rather than querying the table directly, so that we
can query custom columns.
2026-04-10 12:41:06 -07:00
Timofey Kachalov
5ea5206719 feat(sdk,embed-js): Auto-scroll to the auto-opened dropdown for Join Step on touch devices (#71965) 2026-04-10 23:27:47 +04:00
appleby
ce78ecf14e BOT-1289 Send metabot-usage metering for both yesterday and today (#72378)
Send metering for today's usage under a new :metabot-rolling-usage key with corresponding :metabot-rolling-usage-date.

All other keys only send yesterday's values for backwards compat.

Keys are omitted if there is no usage for the corresponding day.
2026-04-10 11:37:40 -07:00
bryan
fa1f458a3d Fix flaky dataset-already-loaded?-test on Redshift (#72123) 2026-04-10 12:33:11 -06:00
appleby
d0570b318d BOT-1292 Do not convert metering values to string in send-metering-events! (#72377) 2026-04-10 11:05:39 -07:00
Alexander Polyankin
e90c0ea24e Fix serdes export perf with database metadata - id lookup (#72326)
* wip

* wip

* fixes

* fixes

* fix

* fix

* handle millions of fields

* fix

* fixes
2026-04-10 10:45:30 -07:00
Nick Fitzpatrick
59246fc093 dnd folder (#72264) 2026-04-10 21:54:53 +05:30
Ben Grabow
959a7a0e39 Ghy 3343/fix key sorting (#72192)
* Fix sorting of maps nested under double-layers of sequences

* Re-export after fixing nested map sorting

* Descend into all sequential nodes

* Re-export instance analytics collection

* Test against expected yaml string
Test that sorting is idempotent
2026-04-10 11:04:50 -05:00
Phoomparin Mano
4548c37b08 Fix flaky E2E test in connection impersonation step scenarios (#72341)
* fix flaky e2e

* disable next button until permission group query resolves
2026-04-10 22:10:57 +07:00
Eric Normand
5d1d7d4a49 GHY-3232: Make dependency graph calculation async on entity updates (#71037)
* Make broken dependency analysis async

This change make the synchronous (in the API request) part very simple and fast: Just mark the current entity stale,
trigger an async task, and return.

The async task will do the actual analysis and mark its direct dependents stale. The async task loop will then pick
those up and continue from there. In this way, all transient dependents will be analyzed as well.

Updated tests also to make sure chains of dependents do get updated.

* Async dependency backfill analysis

* Fix async dependency backfill: test fixes and error handling

- Fix NPE in mark-stale! when no existing entries found
- Add error handling in mark-stale-and-trigger! for empty test DBs
- Let dependency computation errors propagate for retry handling
- Fix downstream transform tests to persist target changes to DB
- Fix delayed-retry test scope for with-redefs
- Add run-backfill! calls to dependency model integration tests
- Remove with-empty-h2-app-db! from filtered-graph-dependencies-test

* Add DependencyStatus to model->namespace resolution map

Fixes failing all-models-are-accounted-for-test.

* Add backfill calls to replacement api tests

Events no longer synchronously compute dependencies, so tests
need to run the backfill before asserting on dependency data.

* Revert "Add backfill calls to replacement api tests"

This reverts commit 68b2835beb.

* Fix CI failures: add backfill calls, serialization exclusions, shared test helper

- Only process entities WITH dependency_status entries (inner-join
  instead of left-join) to avoid computing deps for entities that
  were never event-triggered
- Add DependencyStatus to serialization excluded-models and entity-id
  exclusion set
- Add deps.test/synchronously-run-backfill! shared helper in test_util
- Use shared helper consistently across all test files
- Add events/publish-event! calls in tests that create entities via
  mt/with-temp without firing dependency events
- Add backfill calls in replacement, usages, and workspace tests

* Remove trailing blank line in backfill_test.clj

* Add DependencyStatus to copy-test models-to-exclude

* Fix workspace test failures: run backfill before graph persistence

The workspace graph is cached when ws-done! runs. With async dep
computation, the backfill must run before ws-done! so that upstream
dependency edges exist when the graph is persisted.

Also add backfill call in validation_test which creates transforms
via mt/with-temp outside of create-resources!.

* Guard segment-delete dependency row delition

* Fix TOCTOU races in dependency_status and missing feature gate on segment-delete

- Replace select-then-insert patterns in mark-stale! and upsert-status! with
  app-db/update-or-insert! which handles cross-database conflict retry with
  savepoints (H2, MySQL, PostgreSQL).
- Add missing (when (premium-features/has-feature? :dependencies) ...) guard
  on ::segment-delete event handler, matching all other delete handlers.

* Use app-db.core instead of app-db.query to fix kondo module boundary warning

* Refactor backfill to use multimethods for per-entity-type behavior

- Move dashboard hydration from backfill case statement to hydrate-for-deps
  multimethod in dependency_status.clj, using standard t2/hydrate [:dashcards :series]
- Convert upstream-deps:* public functions to calculate-deps multimethod in
  calculation.clj with logic in defmethods, update all callers
- Extract post-deps-cleanup! multimethod in backfill.clj for transform-specific
  downstream dep cleanup
- Remove custom :series-card-ids hydration; extract IDs from existing :series
  hydration in calculate-deps :dashboard

* Move retry/broken state into dependency_status table, add Malli schema wrapper, migration fixes

- Add fail_count, next_retry_at, terminal columns to dependency_status table
- Remove in-memory ConcurrentHashMap retry tracking from backfill.clj
- record-failure! and clear-failure! persist state to DB, surviving restarts
- mark-stale! resets retry state so modified entities get a fresh chance
- instances-for-dependency-calculation filters terminal/not-yet-retryable in SQL
- Uses Java time (not DB time) for retry comparison so mt/with-clock works in tests
- Rename calculate-deps multimethod to calculate-deps*, add mu/defn wrapper with
  Malli schema validation on return value
- Add comment in events.clj explaining two separate stale tracking systems
  (dependency_status vs analysis_finding)
- Migrate ALL entities (not just version > 0), marking never-analyzed as stale
- Rename migration file to 20260330 for fresh DB compatibility
- Remove unused DashboardCard/DashboardCardSeries from module imports
- Add DependencyStatus to module exports

* Remove DependencyStatus from module exports — only used internally

* Add tests for error logging and graceful degradation in backfill

- backfill-error-logging-test: verifies that when calculate-deps throws,
  the entity remains stale, no deps are created, and fail_count is recorded
- backfill-partial-batch-failure-test: verifies that a failure on one entity
  doesn't prevent other entities in the batch from being processed

* Shorten comment separator lines to stay under 120 char limit

* Clean up dependency_status rows on entity delete, fold clear-failure into upsert-status

- All 8 delete handlers now also delete the dependency_status row to prevent
  orphaned rows accumulating over time
- upsert-status! now resets fail_count and next_retry_at, eliminating the
  need for a separate clear-failure! call and closing a gap where
  has-pending-retries? could loop indefinitely

* Remove trailing blank line in dependency_status.clj

* Harden error handling: catch failures in record-failure! path, fold clear-failure into upsert-status

- Wrap record-failure! and logging in a nested try/catch so a DB error
  while recording the failure doesn't abort the rest of the batch
- upsert-status! now clears fail_count and next_retry_at, eliminating
  the separate clear-failure! function and closing a gap where
  has-pending-retries? could loop on a processed entity

* kick ci

* Remove DependencyStatus from serialization models list

dependency_status is transient state (stale flags, retry counts) that
gets recomputed by the backfill job. No need to export/import it.

* Fix runner test: add synchronously-run-backfill! after card-create events

With async deps, the runner test needs to run backfill before attempting
source swaps that depend on the dependency graph being populated.

* kick ci

* Add DependencyStatus to serialization excluded-models list

The every-model-is-supported-test requires every Toucan model to be
accounted for in either exported, inlined, or excluded models.
DependencyStatus is transient state and should never be serialized.

* Fix replacement api test: add synchronously-run-backfill! after card-create events

* Improve docstring

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

* Improve docstring

* Use immediate dependents instead of transitive for all event handlers

- Transform run, sync complete, table/field metadata update handlers now
  mark only immediate dependents as stale, letting the entity-check job
  propagate in waves rather than computing the full transitive tree upfront
- Add mark-all-immediate-dependents-stale! for batch immediate-dependents
- Add missing trigger-entity-check-job! calls to table/field metadata handlers

* Add wave propagation test, fix check-entities! loop to drain all stale entities

- Add wave-propagation-via-entity-check-test: verifies that marking immediate
  dependents stale propagates through the entity-check job loop in waves
  (grandparent change → parent re-analyzed → child marked stale → child re-analyzed)
- Fix check-entities! loop condition: continue while has-stale-entities? is true,
  not only when the batch was full. analyze-and-propagate! creates new stale
  entities during processing that need to be picked up in subsequent iterations.

* Fix nested deftest: move query-transform-update-handles-downstream-dependencies-test to top level

Was accidentally nested inside python-transform-update-handles-downstream-dependencies-test
due to a missing closing paren. Now runs as its own test (18 tests, was 17).

* Add test for mark-stale! failure resilience in event handlers

Verifies that when mark-stale! throws (e.g., DB failure), the event
handler catches the error and doesn't propagate it to the API caller.

* Restore deleted analysis propagation tests adapted for async model

Adds back 15 tests that were removed when switching to async deps:
- Transaction rollback test: verify analyze-and-propagate! rolls back on failure
- Card update triggers native cards: wave propagation through entity-check job
- Card update stops on transforms: propagation boundary test with waves
- Transform update/run: with/without existing analyses, native transforms
- Segment update: with/without existing analyses, propagation to dependents
- Table metadata update: marks immediate dependents stale, no-analysis resilience
- Field metadata update: marks immediate dependents stale, no-analysis resilience

Tests adapted for async model: events mark stale, entity-check job propagates.

* Increase e2e dependency wait timeout from 10s to 30s

With async dependency computation, the backfill and entity-check jobs
need time to process entities. The 10s timeout was too short for e2e
tests that create multiple entities and wait for analysis findings.

* Move dependency_status migration from 060 to 061, update changeset IDs

The cross-branch migration test requires migrations to be in the correct
version directory. Since this PR targets v61, move the migration there
and update all changeset IDs from v60 to v61.

* Move dependency_status migration back to 060/, keep updated date

* Add preConditions with MARK_RAN to all dependency_status changesets

Databases that already ran the old v60.2026-03-17 changesets (from the
previous migration filename) will skip these changesets instead of
failing with checksum or duplicate errors.

* Revert preConditions — old changesets were never deployed

* Add preConditions onFail: MARK_RAN to all dependency_status changesets

* Revert "Add preConditions onFail: MARK_RAN to all dependency_status changesets"

This reverts commit f9f7ef1645.

* Add failing test: wave propagation through non-analyzable entities

Demonstrates that mark-immediate-dependents-stale! followed by
check-entities! doesn't reach cards that depend on non-analyzable
intermediaries (tables). The chain transform → table → card breaks
because the wave stops at the table (not analyzable).

* Fix wave propagation through non-analyzable entities, add 1s trigger delay

Two fixes:
- mark-supported-dependents-stale! now looks through non-analyzable entities
  (e.g., tables) to reach analyzable dependents beyond them. Previously the
  wave stopped at tables, so cards depending on a transform's output table
  were never re-analyzed when the transform changed.
- trigger-backfill-job! and trigger-entity-check-job! now use a 1-second
  delay instead of 0. Event handlers run inside Toucan2 after-update hooks
  (within a transaction), and delay-0 triggers fired the Quartz job before
  the transaction committed, so the job saw no stale entities.

* Remove trailing blank line in findings_test.clj

* Add waitForGraphDependencies helper for e2e tests

Polls /api/ee/dependencies/graph until the dependency data is available,
similar to waitForBreakingDependencies and waitForUnreferencedEntities.
Needed because dependency computation is now async.

* Add waitForGraphDependencies to visitGraphForEntity in e2e graph tests

Polls the graph API until dependency edges exist before navigating to
the graph page. This accounts for async dependency computation.
Also removes accidental .only() left from local testing.

* Remove test-mode delay-0 for backfill job init

The backfill job was starting with delay 0 in test mode, which caused
it to run during e2e snapshot creation. This monopolized the connection
pool and caused the data-studio-metrics e2e test to timeout when trying
to load the entity picker. Now uses the normal production initial delay.
Backend tests are unaffected — they call synchronously-run-backfill!
directly without relying on the Quartz scheduler.

* Appease the kondo

* Remove config from enterprise/dependencies module uses

The dependency module no longer uses the config module.

* Wait for async dependency computation in e2e tests

Dependencies are now computed asynchronously, so tests that check the
dependency graph need to poll until edges are populated before asserting.

* Wait for async dependency computation in unreferenced list e2e tests

Add waitForUnreferencedEntities calls before visiting the unreferenced
entities page to ensure async dependency computation has completed.

* Add backfill-status endpoint and use waitForBackfillComplete in e2e tests

Add GET /api/ee/dependencies/backfill-status endpoint that returns whether
dependency backfill has pending work. Replace per-entity graph polling in
e2e tests with a single waitForBackfillComplete() helper that polls this
endpoint, simplifying async dependency waits across all test files.

* Reload page before navigating to dependencies tab to refresh cache

---------

Co-authored-by: Timothy S. Dean <timothy.dean@metabase.com>
2026-04-10 09:47:44 -05:00
Braden Shepherdson
352798c484 [QP] Handle Snowflake tables with UUID columns more gracefully (#72034)
An upstream Snowflake JDBC driver issue causes an exception to be thrown
because the UUID column type is not present in an enum.

This adds logic to fall back to the more generic `SELECT *` sync mode if
this error occurs during a sync.
2026-04-10 13:35:32 +00:00
lbrdnk
e392e85ab4 Cleanup Metabot tools feature sentinels (#72113)
* Remove snippets sentinel

* Add appropriate feature to python transform tools

* Adjust oss transform tools sentinels

* Remove remnants of ee-feature-available?

* Drop features-transforms and adjust profiles code

* Use feature-transforms

* Adjust test

* Tests

* tests

* Ensure python tools not available when basic transforms are not available
2026-04-10 14:03:08 +02:00
Nicola Mometto
07325c8057 Security Center (#71661)
* security center data model (#71660)

* add security_center model

* wip: migration

* kondo

* Advisory security endpoints (#71704)

* Security center admin page and advisory list (#71707)

* Use :admin-security-center feature flag and restore premium-handler on route

* Reuse notification system components for security center channel config (#71723)

* Security center settings (#71807)

* feat: add security-center recipients settings

* feat(FE): add security-center settings

* tests

* feat(FE): hook to settings

* Security center matching engine (#71747)

* upgrade banner (#71800)

* added banner (#71816)

* moved the security center into the top bar (#71821)

* fixed an e2e test

* Gate Security Center for non-trial subscriptions (#71799)

* Gate Security Center for non-trial subscriptions

Trial subscriptions should not have access to Security Center to
prevent abuse via easily-obtainable trial enterprise tokens.

Adds `is-trial?` and `assert-not-trial` to the premium features
token check, and applies a `+require-non-trial` middleware to the
security-center route that returns 402 for trial tokens.

* Handle check-token exceptions in is-trial?

Fail open (return false) if the token status check throws, matching
the same pattern used by *token-features*. Prevents a 500 when
MetaStore is unreachable.

* Use token-status setting instead of check-token directly

Follow the existing pattern used by billing and cloud-add-ons.

* Use -token-status instead of the setting getter

* Gate Security Center for non-trial subscriptions

Trial subscriptions should not have access to Security Center to
prevent abuse via easily-obtainable trial enterprise tokens.

Adds is-trial? and security-center-enabled? to premium features
token check, and a +check-not-trial middleware on the security
center API routes that returns 402 for trial tokens.

* Security center notification (#71814)

* feat: add last_notified_at column

* feat: add security-advisory-match event

* feat: security advisory email template

* feat: add slack renderer

* feat: notify

* email fixes

* kondo

* fix: hydrate

* chore: quartz

* test

* send notification sync

* fix: guard against no smtp config

* fix: bcc

* more tests

* only on has feature

* switch to util

* move url builder

* feat: use all-admin-recipients

* Security center: fetch and scheduled evaulation (#71830)

* addressed stas feedback

* all-admins and recipients are no longer exclusive (#71895)

* Fix read only connection for security center (#71898)

* added sync button (#71905)

* Security center unify tasks + CI green (#71894)

* pull notify task into sync task

* make CI green

* chore: exclude security-advisory

* fmt

* require settings in init

* kondo

* set wor

* fixes

* exclude is-hosted

* check-enabled with open-api wrap

* change error

* missing

* fix: emails

* fix use an user-id

* fixes

* init test-users

* enable on dev

* rollback to enforce no writes

* fix: add index

* index before fk

* may not error

* Security center test notification (#71915)

* test notifications

Closes GDGT-2174

* test

* fix

* addressed PR feedback (#71919)

* fix parsing published_at timestamp durign fetch

* fix fetch test to use string published_at matching real API response

The test was using #t tagged literals for published_at which bypassed
the string→timestamp coercion path, masking the bug we just fixed.

* added tests for recipients settings (#71963)

* make recipients non nullable (#71967)

* Treat error match_status as affected (fail-open) (#71961)

* treat error match_status as affected (fail-open)

Per the matching engine spec, when a matching query fails, the advisory
should be treated as potentially affected. The isAffected check was only
looking for "active" status, missing the "error" case.

* fix type errors in createAdvisory mock

Add return type annotation so string literals narrow to the Advisory
type instead of widening to string when spread with Partial<Advisory>.

* Security center: force sync endpoint (#71899)

* Security center cursor (#71969)

* feat: add updated_at

* feat: cursor on updated_at

* feat: since tests

* fix fetch tests

* appease the linter

* fix test

* remove seed

* small fixes

* fix

* feature token properly propagates (#71970)

* added polling for sync button (#71971)

* fix

* fix

* fix

* Add Snowplow tracking for Security Center (#71972)

* Add Snowplow tracking for Security Center

Track 3 events using the existing simple_event schema:
- security_center_page_viewed (FE, on page mount)
- security_advisory_acknowledged (BE, with severity)
- security_advisory_notification_sent (BE, with channel/result/trigger)

* Derive notification channels from handlers instead of config

Instead of re-checking configured-channels at tracking time, derive
the channel list from the notification's actual handlers.

* Move last_notified_at update inside try block

* add :admin-security-center to excluded features in stats test

* kondo

* update schema (#72031)

* hook full feature logic to premium features exposed to the FE (#72026)

* hook full feature logic to premium features exposed to the FE

* tests

* fix test

* chore: error in FE

* fix

* fix e2e test

* lint

* tighten schemas

* fix hydration

* chore: defensively check-superuser

* tighter schemas

* fix test

* chore: simpler schema as we'd need to duplicate with :api/regex

* e2e test should only run on ee

* fix: recipients default (#72035)

* Add testing endpoint for security advisories (#71977)

* fix model boundary violations for SecurityAdvisory

* relax schema

* move to 45 (#72073)

* chore: migrations in 001 file for backporting to 45

* mark-ran preconds

* reorder

* consolidate

* add set null

* Mobile improvements (#72071)

* move to 55

* move severity->repeat-days into an overridable setting

Closes GDGT-2208

* reformat

* typo

* deal with nits

* switch to select-fn->fn

* explicit map building

* more nits

* align

* nit

* add not-empty

* use custom executor instead of future

* use semver4j

* fix test

* restrict queries to selects (#72162)

* reorder

* Security center no h2 (#72215)

* disable security-center for h2 appdbs

* tests

* fix last-checked at, store in setting

* fix tests

* enable for e2e tests

* fix merge conflicts

* Revert "Security center no h2 (#72215)"

This reverts commit 41dc93794d.

* fix: persist last-synced-at in a setting

* fix test

* disable on h2 (enabled for tests) (#72263)

* UI feedback on Security Center (#72214)

* fixed imports

* chore: max 2 mins for advisory query

* kondo

* chore: use sql-jdbc.execute

* kondo

---------

Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com>
Co-authored-by: Sébastien <sebastien@metabase.com>
2026-04-10 12:28:48 +01:00
Mahatthana (Kelvin) Nomsawadi
a57cff4dd4 Fix drilling Metabot quesitons hides drilled questions (#72233)
* fix(sdk): push adhoc question to internal navigation stack

* Add tests

---------

Co-authored-by: Nicolò Pretto <info@npretto.com>
2026-04-10 11:20:00 +01:00
Chris Truter
9b86604078 Throttle FE error API (#72232) 2026-04-10 08:35:02 +00:00
Romeo Van Snick
773692eaf3 Move metabase/lib to metabase/utils and move domain-related utils to other modules (#71467)
* Rename metabase/lib to metabase/utils

* Move metabase/lib to metabase/utils

* Move getCrumbs to getCollectionBreadcrumbs in common/utils

* Fold compat into react-compat

* Remove composeEventHandlers as it is too simple

* Move fields types to metabase/common/utils/fields

* Always go through getCspNonce

* Move jwt helpers into jwt file

* Remove unused scroll helpers

* Use helper function instead of constant

* Remove unneeded removeAllChildren helper

* Move data url helper to separate file

* Move lib tests inline with modules

* Remove unused clipPathReference

* Remove unused getMainElement helper

* Move canDownloadResults to metabase/common

* Merge metabase/utils/engine and metabase/databases/utils/engine

* Clean up fetchWithTimeout

* Move formatting into formatting folder

* Split up formatting tests into formatting folder

* Allow null for date_style

* Remove need for eslint comments

* Remove unused i18n-debug package

* Move iframe utils into metabase/utils/iframe

* Remove unneeded getObjectValues helepr

* Move sortObject into metabase/utils/object

* Import delay from the delay module

* Rename utils/security to utils/password

* Remove unused foreignKeyCountsByOriginTable helper

* Move fields helpers to metabase/common/utils

* Move entities helpers to metabase/entities

* Move passwordComplexityDescription out of Settings

* Remove unused string utils

* Move tests to have same name as module under test

* Merge time-dayjs tests

* Move timelines to metabase/common/utils

* Use crypto.randomUUID

* Replace copy() with builtin structuredClone

* Remove single-use isEmpty helper

* Remove unused propertiesEqual helper

* Move version helpers to metabase/utils/version

* Move numberToWord into passwords file where it is used

* Remove simple _.isEqual wrapper

* Remove isEEBuild wrapper

* Fix new lib imports after merge

* Fix missing import

* Fix whitespace linter exclusions

* Remove utils/utils imports

* Fix import

* Fix noop imports

* Fix more aliases

* Fix formatting

* Fix lint

* Allow utils/promise in embedding tests

* Fix imprts after merge

* Fix lint

* Remove delay confusion

* Fix lint

* Revert to clone implementation

* Add fallback for crypto.randomUUID

* Fix lint

* Revert jspdf

* Do not coerce nonce to empty string

* Fix types

* Fix lint
2026-04-10 06:42:38 +00:00
Romeo Van Snick
32aedee1c7 Move InsightsUpsellTab to metabase/common/components/upsell (#72155) 2026-04-10 07:07:27 +02:00
Sébastien
8b4f96092a Rename "New exploration" to "New question" in <metabase-browser> (#72145) 2026-04-10 06:44:36 +02:00
Fraser
8322959c37 Make simple events extensible (#71631) 2026-04-10 16:25:25 +12:00
Aleksandr Lesnenko
6b5ce715dc fix breakouts axis split in metrics viewer (#72141)
* fix breakouts axis split in metrics viewer

* post-rebase fixes
2026-04-10 02:03:04 +00:00
Aleksandr Lesnenko
1b31cc6521 add stack series button to metrics viewer (#71694)
* add stack series button to metrics viewer

* change ui

* better approach

* update spec

* review
2026-04-10 00:59:27 +00:00
Cam Saul
2fd986a3f7 Support FKs for SQLite in tests (#72142)
* Support FKs for SQLite in tests

* Kondo

* 🔧

* Code cleanup

* Test fix 🔧
2026-04-09 17:26:10 -07:00
Copilot
12a66b2abe Add ESLint rule to restrict analytics imports to analytics files (#66780)
* Add ESLint rule to restrict analytics imports to analytics files

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

# Conflicts:
#	.eslintrc.js
#	frontend/lint/eslint-plugin-metabase/rules/no-analytics-import-outside-analytics-files.js

* Fix test regex patterns to match full error message

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

* formatting

* add lint rule

* fix use-table-crud

* fix violations

* remove dead exports

* type fixes

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: iethree <30528226+iethree@users.noreply.github.com>
Co-authored-by: Ryan Laurie <iethree@gmail.com>
2026-04-09 17:33:09 -06:00
Fraser
113f908408 Clean ui theming violations (#71629) 2026-04-10 11:01:29 +12:00
Edward Paget
4a2a143e15 deps: bump redshift to latest release (#72294) 2026-04-09 19:25:14 -03:00
Timofey Kachalov
fafb054e57 feat(sdk,embed-js): Replace PointerSensor with MouseSensor and add TouchSensor for improved drag-and-drop support (#72032) 2026-04-10 02:09:56 +04:00
Timofey Kachalov
5b53f1f8dc fix(sdk,embed-js): Don't close ChartSettingsWidgetPopup on touch events (#71979) 2026-04-10 02:08:39 +04:00
da
17f0cbe480 Update commons-lang3 version to 3.20.0 (#72278)
https://commons.apache.org/proper/commons-lang/changes.html#a3.20.0
2026-04-09 18:20:11 -03:00
da
c3bffc2610 Update Netty dependencies to version 4.2.12.Final (#72274)
https://github.com/netty/netty/releases
2026-04-09 18:17:45 -03:00
Edward Paget
3392486525 Enforce session timeout server-side with last_active_at tracking (#71866)
* Enforce session timeout server-side with last_active_at tracking

Add a last_active_at column to core_session and use it to enforce
session inactivity timeouts on the server side (behind the
session-timeout-config premium feature). Activity updates are throttled
via an atomic compare-and-set cache in session.core to avoid a DB write
on every request. The session cleanup task prunes idle sessions and
expired cache entries.

* Move EE-dependent session timeout tests to enterprise test file

Session timeout tests that require the :session-timeout-config premium
feature fail in OSS mode because enable-session-timeout-config? checks
config/ee-available? which is always false without enterprise code.

* Remove trailing blank lines from test files
2026-04-09 15:48:18 -05:00
Edward Paget
71e5ad58dc Fix nested WHERE clause in remove-unsynced! for TransformTag (#72286)
Fix nested WHERE clause in remove-unsynced! for TransformTag (UXW-3710)

Mixing a {:where ...} HoneySQL map with kv-pair conditions in t2/delete!
caused toucan2 to misinterpret the map as a condition key, producing invalid
SQL with a WHERE inside a WHERE. Convert other-conditions into the {:where}
clause directly and remove unreachable scoped-path code that had the same bug.
2026-04-09 15:47:40 -05:00
Alex Yarosh
a96f3bc9d5 docs: Update links in CONTRIBUTING.md to latest version (#72300)
Update links in CONTRIBUTING.md to latest version
2026-04-09 16:35:49 -04:00
Alexander Polyankin
46e35e97ba Fix serialization export perf with no content models (#72290) 2026-04-09 12:26:36 -07:00
Cam Saul
629c10e733 Replace 'MLv2' with 'Lib' or 'MBQL 5' in docstrings and function names (#72121)
Replace usages of 'MLv2' with 'Lib' or 'MBQL 5'
2026-04-09 12:26:17 -07:00
metamben
5d0d3c47c0 Enable commented out drill-thru tests (#71845)
Closes QUE2-140
2026-04-09 21:54:05 +03:00
metamben
5f5d472fe7 Use locale independent formatting to get the day of the week (#71620)
* Use locale independent formatting to get the day of the week

Closes #57794

* Use MBQL 5 in the test
2026-04-09 21:53:49 +03:00
Aleksandr Lesnenko
afe5a73498 Increase stacked series gap, make it dynamic (#72118)
* increase stacked series gap, make it dynamic

* Update Loki Snapshots

* review

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-04-09 13:59:32 -04:00
github-automation-metabase
b9e2e96185 Update translations 2026-04-07 (#72070)
* Update translation sources 2026-04-07

* Update translations

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-04-09 17:47:56 +00:00
Timothy S. Dean
8d5bd8ebcb Don't trigger result-metadata write based on non-default parameters (#72007)
If you have a parameterized card with computed columns, there is no Field from which to pull fingerprints, and the query-processor runs a fingerprint process based on the results of the query. Because the query is parameterized, the result is specific to whatever random parameter was used. `record-metadata!` saw this difference and updated the result-metadata on the card.

The fix: we will no longer persist `result_metadata` when non-default parameterized values are present.
2026-04-09 11:41:51 -06:00
Kevin Kelleher
2e71a55212 Fix colors in metrics viewer (#71874) 2026-04-09 17:19:16 +00:00
Edward Paget
fccf343325 Fix dashboard PUT response (#72173)
Extract shared `apply-card-permission-filters` helper used by both GET and
PUT paths to ensure consistent card visibility filtering. Also fix
`set-download-perms-on-dashcards` to handle empty dataset_query maps
gracefully.

Fixes: UXW-3571
2026-04-09 11:11:18 -05:00
Nicolò Pretto
2a8821b6a7 Address lack of continuity after admin completes database connection and x-ray dashboard steps (#72072)
* wip modal

* change button text

* polish

* update copy
2026-04-09 18:04:36 +02:00
John Swanson
6c65769bc5 fix n+1s in db, table, group creation (#72087)
* fix n+1s in db, table, group creation

this batches permissions changes to fix cluster lock timeouts with large numbers of groups, database, and tables.

When we created a new group, table, or database, we obtained O(NxM) fine-grained cluster locks, and did NxM separate inserts into data permissions.

This does batch operations instead. Obtains a single cluster-wide shared lock for batch operations (which actually might not be necessary at all for database and group creation - think it's still needed for creating a table though), then does the insert in batches.

Reduces the total number of queries dramatically:

- group creation: scaling with NxM -> ~10

- db creation: scaling with NxM -> ~10

- table creation: scaling with NxM -> ~8

* Fixup tests, improve perf more

* comment cleanup

* fix test

* remove dead code + unnecessary api epxansion

* Fix cluster lock contention

This is a fairly big change on its own, but it's fairly straightforward
and an additional performance improvement that should help with
concurrent processes trying to modify permissions at the same time.

Essentially, I realized that currently our cluster lock system provides
exactly one type of lock, an exclusive lock. But that's not the only
type of potential lock! Both Postgres and MariaDB/MySQL support locking
a row as *shared*. This conflicts with exclusive locks (`FOR UPDATE`)
but does not conflict with other shared locks.

This way, we can design a two-level lock system in which:

- most operations require:
  - a *shared* lock on `::batch-permissions-update`, and
  - an *exclusive* lock on `::batch-permissions-update-<db>`

- some operations require only an *exclusive* lock on
  `::batch-permissions-update`.

As one example, modifying permissions with the graph API requires an
exclusive lock on `::batch-permissions-update`. We don't want sync
adding a table at the same time that someone is modifying permissions,
e.g. moving from granular -> coalesced or vice versa.

On the other hand, sync itself just requires a *shared* lock on
`::batch-permissions-update`, because it can happily coexist with other
syncs that are for different databases. But they'll all take exclusive
locks on `::batch-permissions-update-<db>`, so any permissions changes
they're making to the database will be serialized.
2026-04-09 08:37:14 -07:00
Kamil Mielnik
42c4a2f3e9 Remove accidental semicolon in JSX (#72242) 2026-04-09 15:18:59 +00:00
Chris Truter
58916bb3ce Fix module update method to insert missing :model-X entries (#72247) 2026-04-09 17:10:20 +02:00
lbrdnk
50e9fe7a7a Adjust format-native-query to accept legacy mbql (#72217)
* Adjust format-native-query to accept legacy mbql

* Add test

* Restore permissive extraction form

* assertion

* Docstring
2026-04-09 16:53:58 +02:00
Ngoc Khuat
d6ccee4d52 GDGT-2143 Use subscription creator's perms for attachment check (#72156) 2026-04-09 14:25:36 +00:00
Aleksandr Lesnenko
d3ba36d66d Change config of js execution context for static viz (#72196)
* static viz bundle should be sandboxed

* update color selector

* split execution contexts
2026-04-09 14:05:01 +00:00
Maksym Yakubych
3b995f986f fix: sql edit tools creating new questions instead of editing (#70255) 2026-04-09 14:03:44 +00:00
Nathan Voxland
6a7e27195f Fix: allow moving dashboard questions to their parent collection (#71550)
Allow moving dashboard questions to their parent dashboard's collection

The MoveModal's isSameDestination() was comparing the question's
collection_id to the target collection, blocking the move. But for
dashboard questions, the real "location" is the dashboard — moving to the
collection is a valid operation that detaches the question from the
dashboard. Added \!movingItem.dashboard_id to the collection check.
2026-04-09 19:26:47 +05:30
Nathan Voxland
dc38720b20 Add keyboard shortcut (g s) to navigate to Data Studio (#71328)
* Add keyboard shortcut (g s) to navigate to Data Studio

Closes UXW-3155

* adding permissions check, e2e test coverage

* e2e adjustment

---------

Co-authored-by: Nick Fitzpatrick <nickfitz.582@gmail.com>
2026-04-09 19:25:03 +05:30
Timofey Kachalov
6078e48144 feat(sdk,embed-js): Improve EntityPicker mobile UX (#71815) 2026-04-09 17:54:23 +04:00
Denis Berezin
605968c6e9 Fix styling for chart png download in Firefox (#72198) 2026-04-09 16:24:59 +03:00
Uladzimir Havenchyk
77fca900be deps: upgrade yaml (#71988)
chore: upgrade yaml
2026-04-09 14:57:30 +03:00
Nick Fitzpatrick
f31336e113 Uxw 3547 add analytics events for metrics viewer and metrics homepage (#72091)
* Adding events

* tests

* remove page view events

* re-adding comment since it's relevant again
2026-04-09 16:40:29 +05:30
Phoomparin Mano
75582f7983 fix(sdk-bundle): only render SDK components when EE plugins are loaded (#70737)
* only render children if plugin is ready

* fix: wait for ee plugins to initialize before rendering children

* add unit tests for race condition

* fix: avoid module cache and always re-dispatch plugin ready

* fix: initialize EE plugins synchronously during render instead of useEffect

Moves plugin initialization out of useEffect into the render body,
eliminating the extra frame delay. Uses reduxStore.dispatch directly
following the same pattern as use-init-data-internal.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:23:37 +07:00
Chris Truter
e54b1d968c Delete agent APIs for Workspaces (#71827) 2026-04-09 08:02:24 +00:00
Chris Truter
398840c0a1 OAuth token cleanup task (#72081) 2026-04-09 09:33:58 +02:00
Chris Truter
70552d31d8 Add OAuth and MCP throttling (#71884) 2026-04-09 09:33:45 +02:00
Timofey Kachalov
a2d99db064 Mock i18n FE translations to use EN texts (#72044) 2026-04-09 10:58:24 +04:00
da
f09bc46311 Fixes the find and replace menu from dissapearing (#71780)
Fixes the find and remove menu from dissapearing
2026-04-08 21:36:36 -03:00
appleby
80af5a2b55 BOT-1282 Report combined in + out tokens in :metabot-usage metering (#72186)
* BOT-1282 Report combined in + out tokens in :metabot-usage metering

* Add some additional tests for :metabot-usage reporting
2026-04-08 22:21:56 +00:00
github-automation-metabase
7382721f95 Auto-update documentation for master (#72195)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-08 17:35:05 -04:00
Maksym Yakubych
ee7663ccd1 feat: metabase as an AI provider (#71655) 2026-04-08 20:39:30 +00:00
Uladzimir Havenchyk
a5cf9c43c5 bundle: exclude lazy loaded libs from vendor bundle again (#72061) 2026-04-08 23:02:42 +03:00
bryan
db672b4043 Copy model metadata overrides to Fields during models-to-transforms (#72129) 2026-04-08 13:51:19 -06:00
Aleksandr Lesnenko
bc41b0d2c3 remove data studio link from the metrics viewer (#72122)
* remove data studio link from the metrics viewer

* update spec
2026-04-08 19:44:31 +00:00
Edward Paget
910a4b3e09 Require authentication for eid-translation API endpoints (#72174)
The /api/eid-translation routes were unauthenticated despite only being
used by frontend routes behind the IsAuthenticated guard. Wrap with +auth
and remove the stale "public facing" comment.
2026-04-08 13:41:01 -05:00
John Swanson
a224d29129 Don't allow pausing JWT if it's on via env var (#69386)
* Don't allow pausing JWT if it's on via env var

This separate the `jwt-enabled` setting into two parts:

- `jwt-enabled`, which is settable via an env var, and means "do I want
JWT to run or not"?

- `jwt-enabled-and-configured`, which is not settable at all, and simply
computes `(and (jwt-enabled) (jwt-configured))`. This one means "JWT is
running".

This way, if you set MB_JWT_ENABLED via the env, we don't give you the
option to pause or disable JWT anymore. If it's configured, it's on.

In some ways `MB_JWT_ENABLED` feels useless here, when set alone - it's
not really doing anything other than blocking the user from pausing JWT.
But I think in context it makes sense: you want to be able to configure
JWT solely via the env, but if you do that partially, it makes sense
that we just don't let you change the env-configured bits.

* kondo

* fix
2026-04-08 11:37:17 -07:00
Jeff Bruemmer
2867945fd4 docs - fix anchor links (#72176)
fix anchor links
2026-04-08 13:23:26 -04:00
technomancy
657669dcc5 Implement table privileges support for SQLite. (#71950)
* Implement table privileges support for SQLite.

This is based on code from https://github.com/metabase/metabase/pull/57000
but breaks it down into more manageable chunks.

* Set SQLite insert/update/delete privileges to true.

Unclear why the original PR hard-coded these to false; SQLite has no
permissions system which could prevent writes.

Getting this wrong doesn't affect anything in the backend; it's not
used anywhere outside the frontend, so I suppose it's somewhat
unrealistic to expect it to cause a test failure.
2026-04-08 10:22:03 -07:00
John Swanson
177a6515b7 Query parameter cleanup (#72048)
* Normalize query parameters in preprocessing

* Filter metadata results by user permissions

* Simplify permission checking logic

* Use server-controlled query context

* Validate query parameters at API boundaries

* Code review improvements

* Fixed failing test

* Fixed failing test

* Revert SQL driver to read persisted-info from query object

Instead of having the SQL driver load persisted-info from the metadata
provider, retain the old behavior where it reads :persisted-info/native
from the query map (set by fetch-source-query middleware).

---------

Co-authored-by: Edward Paget <ed.paget@gmail.com>
Co-authored-by: Nathan Voxland <nathan@metabase.com>
2026-04-08 12:04:28 -05:00
Alexander Polyankin
25969cb31e Fix source replacement with null effective_type (#72124) 2026-04-08 16:29:14 +00:00
Ben Grabow
6d45a39d94 Ghy-3343 Re-export instance analytics content (#72063)
* Re-export instance analytics in new serdes format
2026-04-08 11:00:42 -05:00
Alex Yarosh
97f95e40cd docs: more typos (#72168)
more typos
2026-04-08 11:49:12 -04:00
Nick Fitzpatrick
53a13043ae Hide Regenerate Key button if form field is readonly (#72105)
* Hide Regenerate Key button if form field is readonly

* adjust unit test
2026-04-08 20:59:07 +05:30
Edward Paget
241181c7f8 Add README for built-in MCP server (#71834)
Documents the MCP server capabilities, endpoint, client setup
(Claude Code, Claude Desktop, Cursor), OAuth authentication,
scopes, available tools, JSON-RPC methods, and architecture.
2026-04-08 10:11:22 -05:00
John Swanson
5dde87ebf7 Fix search filters for published tables (#71323)
When:

- a table was published,
- a user had data permissions for that table, and
- the user did NOT have permissions on the collection the table was
  published to

Then the search endpoint would not return that table, even though it's
queryable and viewable via data permissions.

This fixes the filtering, so that the table is discoverable via search.
2026-04-08 08:04:15 -07:00
Anderson
31d3ebe6fb [AI Controls] Add tooltip explaining most-permissive group access (#72132)
Add tooltip explaining most-permissive group access
2026-04-08 11:59:18 -03:00
Ngoc Khuat
9d50b86f16 Fix validation in public dashboard action endpoint (#72088) 2026-04-08 13:21:47 +00:00
YB
fdd2d558ba GDGT-26 Fix discard-setting-changes helper for settings that use init (#72069)
The issue reported that discard-setting-changes is broken for settings that use :init (e.g. query-analysis-native-disabled). The report predicted that calling the setting getter inside the macro would return nil, and that the value would be corrupted afterward.

do-with-discarded-setting-changes! used setting/read-setting to capture the "original" value before the thunk. read-setting binds *disable-init* to true, so for settings whose value comes from an :init function (and which haven't been initialized yet), it returns nil. This nil is then passed as both:

(outer capture) The "temporary" value to set during the thunk (via do-with-temporary-setting-value!)
(inner capture) The value to restore in the finally block (via the :skip-init? flag)
The key fix here is to use setting/get instead of setting/read-setting. We also remove the :skip-init? flag since it was there to match the semantics of read-setting and suppress the init call.
2026-04-08 17:23:40 +08:00
Romeo Van Snick
fc07abb93c Fix race condition in test and reset mocks (#72150) 2026-04-08 11:23:27 +02:00
Chris Truter
8a869177f4 Standardize where models live (#72096) 2026-04-08 08:06:38 +00:00
Romeo Van Snick
ed21122ef5 Content translations for parameters values without custom labels (#72025)
* Translate parameter values too

* Allow translating arrays of values

* Add e2e test for translations without label

* Fix types
2026-04-08 07:36:55 +02:00
Anderson
c989e01952 Metabot AI Usage Limits page (#71699)
* Implement static UI for Usage controls page

* Added initial ai-controls tables

* Set sidenav menu items / Add placeholders for new pages

* Implement Metabot Customization page UI

* - Added metabot/permissions GET and PUT apis
- Added settings for metabot name/tone/icon

* Fixed migration ids

* fixup! bad merge

* Add :scope metadata to all Agent Loop tools and create metabase.metabot.scope namespace

- Create metabase.metabot.scope with *current-user-scope* (defaults to #{}),
  scope-matches? with hierarchical wildcard support, and unrestricted set
- Add :scope to all 38 Agent Loop tool-fns with specific action scopes
  (e.g. "agent:sql:create", "agent:viz:edit") — wildcards are grant-side only
- Propagate :scope through wrap-tools-with-state in tools.clj
- :capabilities remain unchanged (orthogonal data-permission gating)
- Add tests for scope-matches?, default scope, and stub

Done: tool-scope/phase-1-tool-scope-declarations

* Add scope-based authorization middleware for Agent Loop tools

- Filter tools by scope in get-tools-for-profile (profiles.clj)
- Check scope before tool invocation in wrap-tools-with-state (tools.clj),
  returning a generic denial message (scope details logged, not leaked)
- Bind *current-user-scope* to unrestricted in run-agent-loop (temporary
  until phase 4 wires real permission resolution)
- Add scope enforcement tests covering filtering, denial, wildcards,
  no-scope passthrough, and default empty scope behavior

Done: tool-scope/phase-2-authorization-middleware

* Gate profile system prompts by metabot permission types

- Add *current-user-metabot-permissions* dynamic var to scope.clj
- Bind all-yes permissions in run-agent-loop (temporary, phase 4 wires real resolution)
- Derive has_sql_generation, has_nql, has_query_tools, has_other_tools
  boolean flags from permissions and pass to Selmer template context
- Gate internal.selmer sections: critical constraints, verification protocol,
  query building steps, SQL construction, SQL anti-patterns, SQL examples,
  NLQ guidance, dashboard routing, and query-focused common scenarios
- General sections (identity, context grounding, communication style, search
  example, entity linking) remain ungated for all permission levels
- 7 tests, 43 assertions covering all permission combinations

Done: tool-scope/phase-3-profile-prompt-gating

* Wire user-metabot-perms->scopes to metabot_permissions data model

- Implement resolve-user-permissions: looks up user's groups, queries
  MetabotPermissions, resolves most-permissive value across groups
- Implement real user-metabot-perms->scopes: maps :yes permissions to
  wildcard scope sets, always includes always-granted-scopes
- Replace temporary unrestricted/all-yes bindings in run-agent-loop with
  real permission resolution from api/*current-user-id*
- Superusers get scope/unrestricted and all-yes-permissions
- Permission resolution done once at reify construction, not per-reduce
- Agent API scope binding deferred (tracked in rdm task agent-api-scope-binding)
- 38 new test assertions covering permission→scope mapping, single-group,
  multi-group most-permissive-wins, and default scenarios

Done: tool-scope/phase-4-permissions-to-scopes

* Update permissions expected request format

* Include metabot and semantic-search tool options on the backend

* Connect access controls UI to the backend

* Wire up the backend for the Metabot Customize page

* Polish access controls page / Create MetabotSystemPromptsPage

* Added metabot-system-prompt

* Connect System prompt page to the backend

* Remove model selection + clean up

* Use custom metabot name

* Use metabot-icon in the UI + added metabot-show-illustrations toggle

* Removed metabase custom prompt

* Add defscope macro, scope registry, and api-scope module

Introduce a `defscope` macro that registers scopes with translatable
descriptions in a global registry, replacing inline scope strings across
all tool and endpoint metadata with typed var references.

Key changes:
- New `metabase.api-scope.core` module with defscope, scope registry,
  scope-matches?, and public API (all-scopes, scope-description)
- All 25 agent scopes declared via defscope in metabase.metabot.scope
- 3 EE workspace scopes declared in metabase-enterprise.workspaces.scope
- validate-tool-var! rejects tools with unregistered scopes at startup
- defendpoint accepts var references for :scope metadata (not just strings)
- enforce-scope logs dev-time warnings for unregistered endpoint scopes
- MCP scope enforcement now handled by defendpoint middleware (removed
  redundant pre-dispatch check); enforce-authentication preserves existing
  token-scopes on synthetic requests
- Scope enforcement error messages no longer leak required scope names
- Consolidated duplicate scope-matching logic in mcp/tools.clj

Done: tool-scope/phase-5-defscope-registry

* Make semantic_queries optional in search tool schemas

When semantic search is not enabled, the feature gate strips
semantic_queries from the tool definition sent to the LLM, but Malli
validation still required it — causing an "Invalid input" error.

* Add missing agent:query scope declaration

The agent API query endpoint requires "agent:query" but it was never
registered via defscope, only its sub-scopes were.

* Block agent profiles by permission and add prompt gating to slackbot

Add profile-level permission checks in run-agent-loop that throw 403
when users lack required permissions for specialized profiles (:sql,
:nlq, :transforms_codegen, :document-generate-content). Add explicit
denial messages to internal.selmer and slackbot.selmer templates when
features are disabled, directing users toward available capabilities
like search instead.

* Enforce per-group metabot on/off switch and remove semantic-search permission

Add base permission/metabot check in check-metabot-access! that blocks
all agent profiles when the group-level metabot permission is :no.
Remove permission/metabot-semantic-search from the permissions model
and frontend. Fix permissions API test request body format and update
all-perm-types to match current permission set.

Done: task/enforce-metabot-group-onoff

* Integrate custom system prompts into internal, SQL, and NLQ profiles

Read the metabot-system-prompt admin setting in build-system-message-content
and pass it as a custom_instructions Selmer variable. Add {% if %} blocks
at the end of internal, sql-querying-only, and natural-language-querying-only
templates to render custom instructions when set. Use |safe filter to
avoid HTML-escaping admin-provided content.

Done: task/integrate-custom-system-prompts

* Added AI usage backend APIs and tables

* Add new system prompts pages

* Update the ai feature access page

* Update side nav for Usage controls menu item + Clean up

* Make the page headings margin top consistent

* Update Customization page / Remove metabot-tone

* Add default metabot permissions for magic user groups

Insert default metabot permission rows (all yes/medium) for the four
magic groups: Administrators, All Users, Data Analysts, and All Tenant
Users. Also fix missing remarks on group_id column in metabot_group_limit
table to pass migration linter.

* Add GET /api/metabot/permissions/user-permissions endpoint

Return the current user's resolved metabot permissions (most permissive
across all their groups). Superusers get all-yes permissions. Response
uses simplified key names without the permission/ namespace prefix.

Done: frontend-ai-permission-gating/phase-1-backend-user-permissions-endpoint

* Add useUserMetabotPermissions hook for granular AI permission checks

Add RTK Query endpoint for GET /api/metabot/permissions/user-permissions
and a useUserMetabotPermissions() hook that returns granular permission
booleans (canUseMetabot, canUseSqlGeneration, canUseNlq, canUseOtherTools)
combining the global metabot-enabled setting with per-user group permissions.

Done: frontend-ai-permission-gating/phase-2-frontend-permissions-hook

* Gate metabot UI entry points with granular user permissions

Replace useMetabotEnabledEmbeddingAware with useUserMetabotPermissions
in 6 primary metabot UI entry points: sidebar button (canUseMetabot),
AI exploration menu (canUseNlq), Fix SQL button and inline prompt
(canUseSqlGeneration), chart analysis and data studio buttons
(canUseMetabot). Update tests to mock the permissions API endpoint.

Done: frontend-ai-permission-gating/phase-3-gate-ui-entry-points

* Migrate remaining components from useMetabotEnabledEmbeddingAware to useUserMetabotPermissions

Replace useMetabotEnabledEmbeddingAware with useUserMetabotPermissions
in all remaining metabot UI components: Metabot shell, MetabotQueryBuilder
(canUseNlq), MetabotQuickLinks, AppBarLarge, CodeMirrorEditor
(canUseSqlGeneration), query builder context registration, MetabotEmbed,
and CommandSuggestion. Add permissions API mock to shared test utils and
all affected test files. SelectEmbedExperienceStep intentionally kept on
the old hook (uses forceEmbedding for embedding feature config check).

Done: frontend-ai-permission-gating/phase-4-migrate-remaining-embedding-aware-uses

* Remove usage limits menu item

* Fix migration to work with mysql

* Fix clj-kondo warnings on feat/ai-controls branch

- Add api-scope to mcp module and users to metabot module allowed deps
- Add response schema to /user-permissions defendpoint
- Suppress aliased-namespace-symbol warnings on potemkin import-vars
- Remove unused make-tool-var helper and prefix unused bindings with _
- Rename make-tool-var to make-tool-var! (uses alter-meta!)

* Address code review findings from clojure-review

- Add response schemas to GET / and PUT / permissions endpoints
- Add ^:parallel to 22 pure tests across 3 test files
- Move check-metabot-access! guard out of let binding for clarity
- Fix indentation, alignment, :refer :all, and stale kondo ignores

* Route agent API scope references through metabase.metabot.core

Re-export scope vars from metabase.metabot.core so external modules
use the public API namespace instead of metabot.scope directly.

* Address PR review comments

- Fix indent in api-scope module config
- Re-export workspace scopes from workspaces.core instead of using
  workspaces.scope directly in agent_api/workspace.clj
- Extract wrap-with-scope-check into its own function in tools.clj
- Fix comment indentation in core_test.clj

* Add 'Settings and general limits' section on Usage limits page

* Add 'Group and tenant limits' section

* Tighten user-permissions response schema with exact permission fields

Use keyword keys and enum values matching the defined permission types
instead of loose [:map-of :string :string]. Also fix simplify-permissions
to produce keyword keys/values instead of strings.

* Separate foreign keys and indexes into distinct migration steps

Extract inline foreign key constraints from createTable into their own
changesets so FKs can be dropped independently of indexes/unique
constraints. Affects metabot_permissions, metabot_instance_limit, and
metabot_group_limit tables.

* Added api_usage_log tracking table and populate it through usage

* Add api-scope to driver-affecting-overrides

api-scope is a simple scope-registry utility that only depends on util.
It was triggering driver tests via its enterprise/workspaces dependent.

* Connect the backend API / Remove dollar limit type

* Initial llm limit checks

* Fix CI failures from AI controls + master merge

- Add MetabotPermissions/GroupLimit/InstanceLimit to copy entities
- Fix module config: sort api-scope, remove extraneous APIs/uses,
  add PermissionsGroup to metabot model-imports
- Fix MCP scope-matches? returning false instead of nil on non-match
- Remove :capabilities gate from autogen dashboard tool
- Bind scope to unrestricted in profiles capability tests
- Update prompt gating tests to match master's template wording
- Update prompts test to match metabot_name template variable
- Update scope resolution tests for migration defaults (yes/medium)

* Remove usage limit code from AI controls branch

Usage limits (instance, tenant, and group) are being developed on a
separate branch. Remove all related backend code: models, API endpoints,
settings, migrations, and tests.

Done: ai-controls-to-enterprise/phase-1-remove-usage-limits

* Fix copy entities: deduplicate MetabotPrompt, move AI models to core section

* Move permission resolution and MetabotPermissions model to enterprise

Introduce the :ai-controls premium feature flag and restructure metabot
permission resolution for the OSS/EE split:

- Register enable-ai-controls? in premium_features/settings.clj
- Convert resolve-user-permissions to defenterprise (OSS returns defaults)
- Create EE implementation with full DB-backed group resolution
- Move MetabotPermissions Toucan2 model registration to EE namespace
- Keep pure permission type definitions and defaults in OSS
- Split tests: OSS verifies defaults, EE verifies group-based resolution

Done: ai-controls-to-enterprise/phase-2-defenterprise-permission-resolution

* Move permission admin APIs to enterprise with input validation

Move GET/PUT /api/metabot/permissions admin endpoints to
/api/ee/ai-controls/permissions, gated by :ai-controls feature.
Keep user-permissions endpoint in OSS. Add schema validation on
PUT perm_type/perm_value fields, fix 402 feature-gate test to use
correct assertion pattern, and add ^:parallel to all test defs.

* Move AI controls pages into new plugin

* Add error toast for instance limit update

* Remove unused permission/metabot-model (model size) feature

The metabot-model permission (large/medium/small) was defined
speculatively but never wired into model selection and had no UI.
Remove it from permission definitions, defaults, API schemas,
migration seed data, frontend types, and all tests.

Done: ai-controls-to-enterprise/phase-4-move-model-loading

* Gate metabot customization settings behind :ai-controls feature

Add :feature :ai-controls to branding settings (metabot-name,
metabot-icon, metabot-show-illustrations) and system prompt settings
(metabot-chat-system-prompt, metabot-nlq-system-prompt,
metabot-sql-system-prompt). Without the feature, reads return defaults
and writes are rejected.

Done: ai-controls-to-enterprise/phase-5-move-customization-settings

* Refactor MetabotPermissions: move model to OSS, replace deprecated APIs

- Move Toucan2 model registration from enterprise to
  metabase.metabot.models.metabot-permissions (OSS)
- Replace mdb/update-or-insert! with t2/exists?+update!/insert!
- Replace user/group-ids with a subquery join through
  PermissionsGroupMembership
- Update model resolution to point to OSS namespace

* Move MetabotPermissions model to EE, fix test failures for ai-controls feature

- Move MetabotPermissions Toucan2 model registration from OSS to
  enterprise (metabase-enterprise.metabot.models.metabot-permissions)
- Move permission defs (metabot-permissions, perm-types, perm-type-defaults)
  into metabase.metabot.scope alongside related permission/scope code
- Add :ai-controls to snowplow stats ping and session token features test
- Add MetabotPermissions to serialization excluded-models and entity-id
  not-exported lists
- Update serialization baseline with new metabot settings
- Fix modules config: add api routes, model imports/exports for
  enterprise/metabot; remove stale users dep from metabot module

* Fix kondo warnings in settings_test: use true?, remove ^:parallel

* Add usage limit models and API as enterprise-only endpoints

Restore the usage limit code (instance, tenant, and group limits) that
was removed in 2db9f363, now scoped under enterprise modules and the
/api/ee/ai-controls/usage/ route prefix with :ai-controls feature gate.

* Address clojure-review findings: per-type perm validation, privacy, metadata

- Validate perm_value per perm_type in PUT endpoint (not just flat enum)
- Make format-native-query private
- Add ^{:added "0.61.0"} to enable-ai-controls?
- Add clarifying comment on enforce-scope dev-time warning

* - Add AI usage limits enforcement, logging, and cleanup
- Rename metabot-limit-type to metabot-limit-unit
- Add scheduled task to trim ai_usage_log rows older than 3 months

* Move ai controls RTK endpoints to enterprise folder

* Fix input debounced state issue

* Clean up on Group and tenant limits tabs

* - Add AI usage limits enforcement, logging, and cleanup
- Rename metabot-limit-type to metabot-limit-unit
- Add scheduled task to trim ai_usage_log rows older than 3 months

* Cleaned up ai_controls migrations

* Merged from ai-controls

* Fix CI test failures caused by ai-controls permission system

The new check-metabot-access! gate in run-agent-loop rejects
non-superusers whose permissions default to :no. Honor pre-bound
*current-user-metabot-permissions* so tests can bind permissions
directly. Also fix scope error message expectation, gate custom
prompt settings behind :ai-controls feature, increase poll timeout
for closing-connection test, and split large deftests into smaller
parallel tests.

* Move EE-dependent permissions test to enterprise, ignore metabot_permissions in H2 dump comparison

The user-permissions test that creates :model/MetabotPermissions belongs
in EE tests since the model is EE-only. The metabot_permissions table is
created by migration in all editions but only included in H2 dumps when
EE is available, so add it to the ignored tables in the dump comparison
utility.

* Fixed namespace reference

* Make cache ttl be 10 seconds

* Fix FE test failures by mocking metabot user-permissions endpoint

The new useUserMetabotPermissions hook calls /api/metabot/permissions/user-permissions
in many component trees. Tests that render these components were failing due to
unmocked API calls. Added setupUserMetabotPermissionsEndpoint helper and called it
in each affected test's setup.

* Mark update-permissions-test as non-parallel to fix MariaDB CI failures

* Fix prettier formatting in use-user-metabot-permissions test

* Fix TypeScript errors: missing settings defaults, stale token feature refs, type mismatches

* Fix eslint: remove unused createMockTokenFeatures imports, fix import ordering

* Compute token max in millions of tokens

* If any groups have a null limit, return null as the limit

* Improve state handling on group and limits components

* Add unit tests

* Fix metabot/new quicklink / don't call user-permissions if metabot is globally disabled

* Remove test flakiness

* Address PR review comments: remove scope re-exports, use enums, split tests

- Remove potemkin re-exports from metabot.scope; consumers now use
  api-scope directly for scope-matches?, unrestricted, registered-scope?,
  all-scopes, and scope-description
- Use proper malli enums for perm_type/perm_value in EE permissions
  response schema instead of bare :keyword
- Sort required-feature->message alphabetically in EE routes
- Fix api-scope indent in kondo config
- Delete unused keep-me comment in EE metabot routes
- Split large deftests into focused tests in permissions_test,
  scope_resolution_test, and profiles_test
- Remove unnecessary blank lines between testing blocks in api_test

* Exclude new AI usage limit models from serialization and update module config

Add AiUsageLog, MetabotGroupLimit, MetabotInstanceLimit, and
MetabotPermissions to serialization excluded-models, entity-id
not-exported, and copy-test exclusion lists. Update enterprise/metabot
module config with routes API, app-db/users dependencies, and
PermissionsGroup model import. Add new metabot settings to
serialization baseline.

* Fix kondo warnings: sort namespace requires alphabetically

* Fix namespace require ordering in agent core

* Fix add missing init module

* Fix module config for AI usage limit models

Update enterprise/metabot module: remove stale app-db and users deps,
add AiUsageLog model import. Export AiUsageLog from metabot module.
Remove stale internal-stats.metabot from internal-stats API.

* Add e2e tests for basic ai controls

* Fix serialization baseline: rename metabot-limit-type to metabot-limit-unit

* Fix e2e tests

* Add e2e tests for ai usage limits

* Polish tests

* Fix e2e tests

* Fix bug on custom quota-reached message input

* Fix issues with auto-saving and debounced requests

* Address further review comments

* Set max limit number on inputs

* Fix some tests

* Fix flaky MetabotFeatureAccessPage test by awaiting permission state

* Fix unit test

* Add anthropic api mock to test group quota limit more properly

* Remove stale metabot dependency from testing-api module config

* Fix nql→nlq typo in permission type and OSS default permissions

The permission type `permission/metabot-nql` was misspelled throughout
the codebase (should be `nlq` for Natural Language Query). Also fixed
the prompt file reference `nql_search.md` → `nlq_search.md` which was
a runtime bug, and updated the OSS fallback for resolve-user-permissions
to return all-yes (permissive) instead of all-no defaults.

* Move DB query helpers from permissions API into MetabotPermissions model

Move permissions-for-group and all-permissions from the API namespace
into the model namespace where they belong as data-access functions.

* Fix flaky resolve-user-permissions-nil-test

Return all-yes-permissions for nil user-id in both OSS and EE paths
(no user means no restrictions). Wrap test with with-premium-features
to make it deterministic regardless of token presence.

* Fix defenterprise-schema nil return schema causing OSS test failures

defenterprise-schema for log-ai-usage! had no return schema, which
produced nil and crashed malli validation in OSS mode. This broke the
report-token-usage-xf transducer pipeline, preventing snowplow events
from firing in downstream transducers.

* Renamed "conversations" to "messages"

* Add tenant limits e2e test / Address a few other review comments

* Fix more review comments

* Clean up on other pages to reuse new hook

* Move translation maps outside their functions / Fix config.edn issue

* Do not reset limits when switching limit types

* Fix e2e test

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>
Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
Co-authored-by: Edward Paget <ed.paget@gmail.com>
2026-04-07 22:13:53 -03:00
Ryan Laurie
0b237d0918 Fix release permissions (#72140)
* more permission constriction

* more permission constriction
2026-04-07 17:07:57 -06:00
Ryan Laurie
b1819f54f4 update release workflow permissions (#72138) 2026-04-07 16:59:49 -06:00
Timothy S. Dean
69a5020367 Adjust CLAUDE.md backend test instructions (#72136) 2026-04-07 16:58:48 -06:00
Ryan Laurie
e01f66612a update release workflow permissions (#72137) 2026-04-07 16:33:15 -06:00
github-automation-metabase
69a776ea2d Auto-update documentation for master (#72131)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-07 17:23:02 -04:00
Oleksandr Solovyov
3671d3bd80 metabot: ability to make requests via ai proxy w/o having api key configured (#71714)
* metabot: ability to make requests via ai proxy w/o having api key configured

* modernize logic: BYOK is more important than ai proxy

* dry error messages and tests

* metabot token reporting

* byok setting

* track that ai-proxied messages are marked in the db

* only count ai_proxied in tokens reported

* fix ci concerns

* metabase/ as indicator of requests going through ai-proxy

* Port resolve-auth and test fixes from #71655

* Add claude-list-models-auth-preferences-test similar to claude-auth-preferences

* Add conventional -test suffix to auth-preferences tests

* BOT-1262 Add :feature :metabase-ai-provider guard to llm-proxy-base-url defsetting

Rather than moving the defsetting into ee add a :feature :metabase-ai-provider guard and leave it in oss.

* Fix trailing whitespace

* Fix logic in llm-provider-configured?

Logic now matches metabase.metabot.self.core/resolve-auth, i.e. no proxy fallback if llm-metabot-provider is unconfigured.

* Remove unused func

* Add more tests for metabase proxied models

Add tests for:
- validate-metabot-provider!
- /list-models endpoint provider resolution for both direct and metabase/ prefix
- slackbot streaming ai-proxy? propagation to store-message!

* Add metabot.provider-util namespace for parsing model-and-provider strings

And use it instead of ad-hoc parsing with str/split in various places.

* Remove unused metabot.core/routes

Re-exported from metabot.api/routes but nothing references it.

* Prefer with-redefs over mt/with-temporary-setting-values in tests to override env vars too

Specifically for tests that set llm-anthropic-api-key to ignore any value set in the env.

* Fix token usage reporting for openai

OpenAI returns token usage in :input_tokens and :output_tokens, map those back to out internal :promptTokens and
:completionTokens, respectively and also drop any nested token usage *_details that get returned to prevent an
exception in accumulate-usage-xf when attempting to (merge-with +).

    java.lang.ClassCastException: class clojure.lang.PersistentArrayMap cannot be cast to class java.lang.Number (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')
	at clojure.lang.Numbers.add(Numbers.java:155)
	at clojure.core$_PLUS_.invokeStatic(core.clj:994)
	at clojure.core$_PLUS_.invoke(core.clj:986)
	at clojure.core$merge_with$merge_entry__6053.invoke(core.clj:3087)
	at clojure.core$reduce1.invokeStatic(core.clj:946)

* Review feedback: use str/replace-first

* Review feedback: ensure metabot-provider-schema matches supported-metabot-providers

---------

Co-authored-by: Mike Appleby <86076+appleby@users.noreply.github.com>
2026-04-07 13:25:37 -07:00
Tyler Plude
742b2190cd [BOT-1272] Fix missing metabot-id in streaming request (#72114)
pass metabot id to native streaming; unit test
2026-04-07 13:22:05 -07:00
Alex Yarosh
9b76829304 docs: the great detypoing (#72100)
* typos

* embedding
2026-04-07 13:15:59 -04:00
Jeff Bruemmer
0ffb9c891b docs - clarify one dashboard per page (#72098)
* clarify one dashboard per page

* questions
2026-04-07 13:14:34 -04:00
lbrdnk
a0a787ea54 Convert legacy into mbql5 on API level (#71944)
* Convert legacy into mbql5 on API level

* tests

* idempotence

* whitespace

* Review comments
2026-04-07 19:08:10 +02:00
Nathan Voxland
9f7c0c3a27 Search indexing memory and CPU improvements (#71999) 2026-04-07 11:30:01 -05:00
Cam Saul
4ae75fc5e6 Replace leftover usages of 'pMBQL' with 'MBQL 5' (#72066)
* Replace the term 'pMBQL' with 'MBQL 5'

* Restore deleted file
2026-04-07 09:29:27 -07:00
John Swanson
b1734f09ae No "login from new device" emails for to_session (#69854)
* No "login from new device" emails for `to_session`

Prevent the /api/sso/to_session endpoint from sending "login from new
device" emails. This endpoint is used for programmatic JWT-to-session
token exchange, so new device notifications are noise. Login history is
still recorded for audit purposes.

* kondo fix

* fix tests
2026-04-07 09:10:17 -07:00
Jeff Bruemmer
84329d9f08 Fix typo in settings documentation (#72074) 2026-04-07 11:30:04 -04:00
Timofey Kachalov
acfdee530b fix(sdk,embed-js): Fix wrong join on dropdown items styles (#71993) 2026-04-07 19:20:18 +04:00
Nick Fitzpatrick
d029b634ae Metric Viewer Menu Dividers (#71906) 2026-04-07 17:34:38 +05:30
Nick Fitzpatrick
122500f230 Update strings in metric view (#71912) 2026-04-07 17:34:27 +05:30
Nick Fitzpatrick
277d629317 Update Measure Icon in Data Studio and Metric Explorer (#71924) 2026-04-07 17:34:17 +05:30
Nick Fitzpatrick
05a0cd9db2 Hide the separator in breakout menu if not needed (#71937) 2026-04-07 17:34:05 +05:30
YB
12e3515671 GDGT-1431 Delete notifications orphaned from deleted cards (#71897)
When a card with an alert is permanently deleted, the Notification record and its Quartz cron trigger survive.
The trigger keeps firing and crashing every time, clogging logs and task history indefinitely.

The root cause is that Card's before-delete hook cleans up ParameterCards, ModerationReviews, and Revisions — but not Notifications. The notification_card table has a deleteCascade FK to report_card, so the NotificationCard payload row gets cascade-deleted, but the Notification row itself survives with a dangling payload_id.

There's 2 fixes in this PR:

Add notification cleanup to Card's before-delete hook. Materializes notification IDs first because Notification's own before-delete deletes the NotificationCard, which would invalidate a delete that depends on a subquery by the time the actual DELETE executes.

For notifications orphaned before this fix is deployed, guard in send-notification-sync! checks if the NotificationCard payload still exists before hydration. If not, it deletes the notification and throws a clean error.
We fully delete rather than deactivate (setting :active false) because an orphaned notification with a deleted card has no value — its handlers, subscriptions, and recipients would just be dead rows. This matches what happens when users manually delete an alert via delete-alert-and-notify!.
2026-04-07 10:02:06 +08:00
technomancy
69c17552f7 Add technomancy (me) to slack and GH team map json. (#71698)
Add technomancy (me) to slack map json file and github team map.

This is one of the steps of the onboarding guide for day 1.

[ci skip]
2026-04-06 16:36:27 -07:00
github-automation-metabase
3dc0f3eb08 Auto-update documentation for master (#72064)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-06 17:19:12 -04:00
Uladzimir Havenchyk
fcde592db4 deps: upgrade markdown-it (#71984) 2026-04-06 23:59:54 +03:00
Uladzimir Havenchyk
05fd40e2fb deps: upgrade dompurify (#71986) 2026-04-06 23:35:06 +03:00
bryan
351e4be6c8 Bundle airgap public key in standard Metabase JAR (#72046) 2026-04-06 19:39:54 +00:00
bryan
3cba66ca73 Guard export-viz-click-behavior-link against nil link models (#72003) 2026-04-06 15:54:46 +00:00
John Swanson
85de150a57 Increase debounce delay to 500ms (#70083)
[UXW-3154: Increase debounce delay for search API calls](https://linear.app/metabase/issue/UXW-3154/increase-debounce-delay-for-search-api-calls)

The current delay (300ms) is a bit too short. Increasing it to 500ms
should still be a snappy UX but send fewer requests while the user is
typing.
2026-04-06 15:16:11 +00:00
Braden Shepherdson
d4821e78e3 [MBQL lib] Add user docs for :fields lists (#71444) 2026-04-06 14:02:19 +00:00
Uladzimir Havenchyk
b34aa1c692 chore: upgrade qs (#70785)
deps: upgrade qs
2026-04-06 14:49:53 +03:00
Uladzimir Havenchyk
27e2412d69 deps: upgrade jspdf (#71987) 2026-04-06 13:04:36 +03:00
Uladzimir Havenchyk
f414178f3f deps: upgrade diff (#71981) 2026-04-06 13:03:19 +03:00
Uladzimir Havenchyk
492c1f3ae6 deps: upgrade lodash and lodash-es (#71982) 2026-04-06 13:02:57 +03:00
Uladzimir Havenchyk
5489604670 chore: replace vulnerable underscore with a util (#70784)
* chore: replace vulnerable underscore with a util

* missed another function

* remove underscore
2026-04-06 12:56:55 +03:00
metamben
cdcf995f5f Use effective/base type info for casts if database-type is missing (#71771)
* Use effective/base type info for casts if database-type is missing

Closes #68065
Closes #71551
2026-04-06 09:52:19 +03:00
Romeo Van Snick
9db030e42c Fix flaky test in unreferenced entries (#71134)
* Fix flaky test

* Fix flaky test

* Fix flaky test

* Add reference for table too
2026-04-05 09:20:57 +02:00
Anderson
672bd07e35 Metabot AI Controls (#71387)
* Implement static UI for Usage controls page

* Added initial ai-controls tables

* Set sidenav menu items / Add placeholders for new pages

* Implement Metabot Customization page UI

* - Added metabot/permissions GET and PUT apis
- Added settings for metabot name/tone/icon

* Fixed migration ids

* fixup! bad merge

* Add :scope metadata to all Agent Loop tools and create metabase.metabot.scope namespace

- Create metabase.metabot.scope with *current-user-scope* (defaults to #{}),
  scope-matches? with hierarchical wildcard support, and unrestricted set
- Add :scope to all 38 Agent Loop tool-fns with specific action scopes
  (e.g. "agent:sql:create", "agent:viz:edit") — wildcards are grant-side only
- Propagate :scope through wrap-tools-with-state in tools.clj
- :capabilities remain unchanged (orthogonal data-permission gating)
- Add tests for scope-matches?, default scope, and stub

Done: tool-scope/phase-1-tool-scope-declarations

* Add scope-based authorization middleware for Agent Loop tools

- Filter tools by scope in get-tools-for-profile (profiles.clj)
- Check scope before tool invocation in wrap-tools-with-state (tools.clj),
  returning a generic denial message (scope details logged, not leaked)
- Bind *current-user-scope* to unrestricted in run-agent-loop (temporary
  until phase 4 wires real permission resolution)
- Add scope enforcement tests covering filtering, denial, wildcards,
  no-scope passthrough, and default empty scope behavior

Done: tool-scope/phase-2-authorization-middleware

* Gate profile system prompts by metabot permission types

- Add *current-user-metabot-permissions* dynamic var to scope.clj
- Bind all-yes permissions in run-agent-loop (temporary, phase 4 wires real resolution)
- Derive has_sql_generation, has_nql, has_query_tools, has_other_tools
  boolean flags from permissions and pass to Selmer template context
- Gate internal.selmer sections: critical constraints, verification protocol,
  query building steps, SQL construction, SQL anti-patterns, SQL examples,
  NLQ guidance, dashboard routing, and query-focused common scenarios
- General sections (identity, context grounding, communication style, search
  example, entity linking) remain ungated for all permission levels
- 7 tests, 43 assertions covering all permission combinations

Done: tool-scope/phase-3-profile-prompt-gating

* Wire user-metabot-perms->scopes to metabot_permissions data model

- Implement resolve-user-permissions: looks up user's groups, queries
  MetabotPermissions, resolves most-permissive value across groups
- Implement real user-metabot-perms->scopes: maps :yes permissions to
  wildcard scope sets, always includes always-granted-scopes
- Replace temporary unrestricted/all-yes bindings in run-agent-loop with
  real permission resolution from api/*current-user-id*
- Superusers get scope/unrestricted and all-yes-permissions
- Permission resolution done once at reify construction, not per-reduce
- Agent API scope binding deferred (tracked in rdm task agent-api-scope-binding)
- 38 new test assertions covering permission→scope mapping, single-group,
  multi-group most-permissive-wins, and default scenarios

Done: tool-scope/phase-4-permissions-to-scopes

* Update permissions expected request format

* Include metabot and semantic-search tool options on the backend

* Connect access controls UI to the backend

* Wire up the backend for the Metabot Customize page

* Polish access controls page / Create MetabotSystemPromptsPage

* Added metabot-system-prompt

* Connect System prompt page to the backend

* Remove model selection + clean up

* Use custom metabot name

* Use metabot-icon in the UI + added metabot-show-illustrations toggle

* Removed metabase custom prompt

* Add defscope macro, scope registry, and api-scope module

Introduce a `defscope` macro that registers scopes with translatable
descriptions in a global registry, replacing inline scope strings across
all tool and endpoint metadata with typed var references.

Key changes:
- New `metabase.api-scope.core` module with defscope, scope registry,
  scope-matches?, and public API (all-scopes, scope-description)
- All 25 agent scopes declared via defscope in metabase.metabot.scope
- 3 EE workspace scopes declared in metabase-enterprise.workspaces.scope
- validate-tool-var! rejects tools with unregistered scopes at startup
- defendpoint accepts var references for :scope metadata (not just strings)
- enforce-scope logs dev-time warnings for unregistered endpoint scopes
- MCP scope enforcement now handled by defendpoint middleware (removed
  redundant pre-dispatch check); enforce-authentication preserves existing
  token-scopes on synthetic requests
- Scope enforcement error messages no longer leak required scope names
- Consolidated duplicate scope-matching logic in mcp/tools.clj

Done: tool-scope/phase-5-defscope-registry

* Make semantic_queries optional in search tool schemas

When semantic search is not enabled, the feature gate strips
semantic_queries from the tool definition sent to the LLM, but Malli
validation still required it — causing an "Invalid input" error.

* Add missing agent:query scope declaration

The agent API query endpoint requires "agent:query" but it was never
registered via defscope, only its sub-scopes were.

* Block agent profiles by permission and add prompt gating to slackbot

Add profile-level permission checks in run-agent-loop that throw 403
when users lack required permissions for specialized profiles (:sql,
:nlq, :transforms_codegen, :document-generate-content). Add explicit
denial messages to internal.selmer and slackbot.selmer templates when
features are disabled, directing users toward available capabilities
like search instead.

* Enforce per-group metabot on/off switch and remove semantic-search permission

Add base permission/metabot check in check-metabot-access! that blocks
all agent profiles when the group-level metabot permission is :no.
Remove permission/metabot-semantic-search from the permissions model
and frontend. Fix permissions API test request body format and update
all-perm-types to match current permission set.

Done: task/enforce-metabot-group-onoff

* Integrate custom system prompts into internal, SQL, and NLQ profiles

Read the metabot-system-prompt admin setting in build-system-message-content
and pass it as a custom_instructions Selmer variable. Add {% if %} blocks
at the end of internal, sql-querying-only, and natural-language-querying-only
templates to render custom instructions when set. Use |safe filter to
avoid HTML-escaping admin-provided content.

Done: task/integrate-custom-system-prompts

* Added AI usage backend APIs and tables

* Add new system prompts pages

* Update the ai feature access page

* Update side nav for Usage controls menu item + Clean up

* Make the page headings margin top consistent

* Update Customization page / Remove metabot-tone

* Add default metabot permissions for magic user groups

Insert default metabot permission rows (all yes/medium) for the four
magic groups: Administrators, All Users, Data Analysts, and All Tenant
Users. Also fix missing remarks on group_id column in metabot_group_limit
table to pass migration linter.

* Add GET /api/metabot/permissions/user-permissions endpoint

Return the current user's resolved metabot permissions (most permissive
across all their groups). Superusers get all-yes permissions. Response
uses simplified key names without the permission/ namespace prefix.

Done: frontend-ai-permission-gating/phase-1-backend-user-permissions-endpoint

* Add useUserMetabotPermissions hook for granular AI permission checks

Add RTK Query endpoint for GET /api/metabot/permissions/user-permissions
and a useUserMetabotPermissions() hook that returns granular permission
booleans (canUseMetabot, canUseSqlGeneration, canUseNlq, canUseOtherTools)
combining the global metabot-enabled setting with per-user group permissions.

Done: frontend-ai-permission-gating/phase-2-frontend-permissions-hook

* Gate metabot UI entry points with granular user permissions

Replace useMetabotEnabledEmbeddingAware with useUserMetabotPermissions
in 6 primary metabot UI entry points: sidebar button (canUseMetabot),
AI exploration menu (canUseNlq), Fix SQL button and inline prompt
(canUseSqlGeneration), chart analysis and data studio buttons
(canUseMetabot). Update tests to mock the permissions API endpoint.

Done: frontend-ai-permission-gating/phase-3-gate-ui-entry-points

* Migrate remaining components from useMetabotEnabledEmbeddingAware to useUserMetabotPermissions

Replace useMetabotEnabledEmbeddingAware with useUserMetabotPermissions
in all remaining metabot UI components: Metabot shell, MetabotQueryBuilder
(canUseNlq), MetabotQuickLinks, AppBarLarge, CodeMirrorEditor
(canUseSqlGeneration), query builder context registration, MetabotEmbed,
and CommandSuggestion. Add permissions API mock to shared test utils and
all affected test files. SelectEmbedExperienceStep intentionally kept on
the old hook (uses forceEmbedding for embedding feature config check).

Done: frontend-ai-permission-gating/phase-4-migrate-remaining-embedding-aware-uses

* Remove usage limits menu item

* Fix migration to work with mysql

* Fix clj-kondo warnings on feat/ai-controls branch

- Add api-scope to mcp module and users to metabot module allowed deps
- Add response schema to /user-permissions defendpoint
- Suppress aliased-namespace-symbol warnings on potemkin import-vars
- Remove unused make-tool-var helper and prefix unused bindings with _
- Rename make-tool-var to make-tool-var! (uses alter-meta!)

* Address code review findings from clojure-review

- Add response schemas to GET / and PUT / permissions endpoints
- Add ^:parallel to 22 pure tests across 3 test files
- Move check-metabot-access! guard out of let binding for clarity
- Fix indentation, alignment, :refer :all, and stale kondo ignores

* Route agent API scope references through metabase.metabot.core

Re-export scope vars from metabase.metabot.core so external modules
use the public API namespace instead of metabot.scope directly.

* Address PR review comments

- Fix indent in api-scope module config
- Re-export workspace scopes from workspaces.core instead of using
  workspaces.scope directly in agent_api/workspace.clj
- Extract wrap-with-scope-check into its own function in tools.clj
- Fix comment indentation in core_test.clj

* Tighten user-permissions response schema with exact permission fields

Use keyword keys and enum values matching the defined permission types
instead of loose [:map-of :string :string]. Also fix simplify-permissions
to produce keyword keys/values instead of strings.

* Separate foreign keys and indexes into distinct migration steps

Extract inline foreign key constraints from createTable into their own
changesets so FKs can be dropped independently of indexes/unique
constraints. Affects metabot_permissions, metabot_instance_limit, and
metabot_group_limit tables.

* Add api-scope to driver-affecting-overrides

api-scope is a simple scope-registry utility that only depends on util.
It was triggering driver tests via its enterprise/workspaces dependent.

* Fix CI failures from AI controls + master merge

- Add MetabotPermissions/GroupLimit/InstanceLimit to copy entities
- Fix module config: sort api-scope, remove extraneous APIs/uses,
  add PermissionsGroup to metabot model-imports
- Fix MCP scope-matches? returning false instead of nil on non-match
- Remove :capabilities gate from autogen dashboard tool
- Bind scope to unrestricted in profiles capability tests
- Update prompt gating tests to match master's template wording
- Update prompts test to match metabot_name template variable
- Update scope resolution tests for migration defaults (yes/medium)

* Remove usage limit code from AI controls branch

Usage limits (instance, tenant, and group) are being developed on a
separate branch. Remove all related backend code: models, API endpoints,
settings, migrations, and tests.

Done: ai-controls-to-enterprise/phase-1-remove-usage-limits

* Fix copy entities: deduplicate MetabotPrompt, move AI models to core section

* Move permission resolution and MetabotPermissions model to enterprise

Introduce the :ai-controls premium feature flag and restructure metabot
permission resolution for the OSS/EE split:

- Register enable-ai-controls? in premium_features/settings.clj
- Convert resolve-user-permissions to defenterprise (OSS returns defaults)
- Create EE implementation with full DB-backed group resolution
- Move MetabotPermissions Toucan2 model registration to EE namespace
- Keep pure permission type definitions and defaults in OSS
- Split tests: OSS verifies defaults, EE verifies group-based resolution

Done: ai-controls-to-enterprise/phase-2-defenterprise-permission-resolution

* Move permission admin APIs to enterprise with input validation

Move GET/PUT /api/metabot/permissions admin endpoints to
/api/ee/ai-controls/permissions, gated by :ai-controls feature.
Keep user-permissions endpoint in OSS. Add schema validation on
PUT perm_type/perm_value fields, fix 402 feature-gate test to use
correct assertion pattern, and add ^:parallel to all test defs.

* Move AI controls pages into new plugin

* Remove unused permission/metabot-model (model size) feature

The metabot-model permission (large/medium/small) was defined
speculatively but never wired into model selection and had no UI.
Remove it from permission definitions, defaults, API schemas,
migration seed data, frontend types, and all tests.

Done: ai-controls-to-enterprise/phase-4-move-model-loading

* Gate metabot customization settings behind :ai-controls feature

Add :feature :ai-controls to branding settings (metabot-name,
metabot-icon, metabot-show-illustrations) and system prompt settings
(metabot-chat-system-prompt, metabot-nlq-system-prompt,
metabot-sql-system-prompt). Without the feature, reads return defaults
and writes are rejected.

Done: ai-controls-to-enterprise/phase-5-move-customization-settings

* Refactor MetabotPermissions: move model to OSS, replace deprecated APIs

- Move Toucan2 model registration from enterprise to
  metabase.metabot.models.metabot-permissions (OSS)
- Replace mdb/update-or-insert! with t2/exists?+update!/insert!
- Replace user/group-ids with a subquery join through
  PermissionsGroupMembership
- Update model resolution to point to OSS namespace

* Move MetabotPermissions model to EE, fix test failures for ai-controls feature

- Move MetabotPermissions Toucan2 model registration from OSS to
  enterprise (metabase-enterprise.metabot.models.metabot-permissions)
- Move permission defs (metabot-permissions, perm-types, perm-type-defaults)
  into metabase.metabot.scope alongside related permission/scope code
- Add :ai-controls to snowplow stats ping and session token features test
- Add MetabotPermissions to serialization excluded-models and entity-id
  not-exported lists
- Update serialization baseline with new metabot settings
- Fix modules config: add api routes, model imports/exports for
  enterprise/metabot; remove stale users dep from metabot module

* Fix kondo warnings in settings_test: use true?, remove ^:parallel

* Address clojure-review findings: per-type perm validation, privacy, metadata

- Validate perm_value per perm_type in PUT endpoint (not just flat enum)
- Make format-native-query private
- Add ^{:added "0.61.0"} to enable-ai-controls?
- Add clarifying comment on enforce-scope dev-time warning

* Cleaned up ai_controls migrations

* Fix CI test failures caused by ai-controls permission system

The new check-metabot-access! gate in run-agent-loop rejects
non-superusers whose permissions default to :no. Honor pre-bound
*current-user-metabot-permissions* so tests can bind permissions
directly. Also fix scope error message expectation, gate custom
prompt settings behind :ai-controls feature, increase poll timeout
for closing-connection test, and split large deftests into smaller
parallel tests.

* Move EE-dependent permissions test to enterprise, ignore metabot_permissions in H2 dump comparison

The user-permissions test that creates :model/MetabotPermissions belongs
in EE tests since the model is EE-only. The metabot_permissions table is
created by migration in all editions but only included in H2 dumps when
EE is available, so add it to the ignored tables in the dump comparison
utility.

* Fix FE test failures by mocking metabot user-permissions endpoint

The new useUserMetabotPermissions hook calls /api/metabot/permissions/user-permissions
in many component trees. Tests that render these components were failing due to
unmocked API calls. Added setupUserMetabotPermissionsEndpoint helper and called it
in each affected test's setup.

* Mark update-permissions-test as non-parallel to fix MariaDB CI failures

* Fix prettier formatting in use-user-metabot-permissions test

* Fix TypeScript errors: missing settings defaults, stale token feature refs, type mismatches

* Fix eslint: remove unused createMockTokenFeatures imports, fix import ordering

* Add unit tests

* Fix metabot/new quicklink / don't call user-permissions if metabot is globally disabled

* Remove test flakiness

* Address PR review comments: remove scope re-exports, use enums, split tests

- Remove potemkin re-exports from metabot.scope; consumers now use
  api-scope directly for scope-matches?, unrestricted, registered-scope?,
  all-scopes, and scope-description
- Use proper malli enums for perm_type/perm_value in EE permissions
  response schema instead of bare :keyword
- Sort required-feature->message alphabetically in EE routes
- Fix api-scope indent in kondo config
- Delete unused keep-me comment in EE metabot routes
- Split large deftests into focused tests in permissions_test,
  scope_resolution_test, and profiles_test
- Remove unnecessary blank lines between testing blocks in api_test

* Fix kondo warnings: sort namespace requires alphabetically

* Fix namespace require ordering in agent core

* Add e2e tests for basic ai controls

* Fix e2e tests

* Fix issues with auto-saving and debounced requests

* Address further review comments

* Fix some tests

* Fix flaky MetabotFeatureAccessPage test by awaiting permission state

* Fix nql→nlq typo in permission type and OSS default permissions

The permission type `permission/metabot-nql` was misspelled throughout
the codebase (should be `nlq` for Natural Language Query). Also fixed
the prompt file reference `nql_search.md` → `nlq_search.md` which was
a runtime bug, and updated the OSS fallback for resolve-user-permissions
to return all-yes (permissive) instead of all-no defaults.

* Move DB query helpers from permissions API into MetabotPermissions model

Move permissions-for-group and all-permissions from the API namespace
into the model namespace where they belong as data-access functions.

* Fix flaky resolve-user-permissions-nil-test

Return all-yes-permissions for nil user-id in both OSS and EE paths
(no user means no restrictions). Wrap test with with-premium-features
to make it deterministic regardless of token presence.

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>
Co-authored-by: Edward Paget <edpaget@users.noreply.github.com>
Co-authored-by: Edward Paget <ed.paget@gmail.com>
2026-04-03 16:42:29 -07:00
Alex Yarosh
c05bc7d17b docs: stacked series and sticky columns (#71952)
* v60 viz improvements

* Optimized 4 images, saved 715.3 KB (43.9% smaller)

* Apply suggestions from code review

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

* Update docs/questions/visualizations/line-bar-and-area-charts.md

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

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
2026-04-03 22:19:50 +00:00
github-automation-metabase
eb3ef366ad Auto-update documentation for master (#72004)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-03 21:16:12 +00:00
Edward Paget
d1d42b9a22 Fix missing :lib/uuid in join metric projection tests (#72000)
The two tests added in #71741 were missing required :lib/uuid keys
in their projection maps, causing API schema validation failures.
2026-04-03 20:03:00 +00:00
John Swanson
c49a43c8fe support expr with multiple instances of same metric (#71930)
support expr with multiple instances of same metric
2026-04-03 09:57:23 -08:00
John Swanson
1f90a2172f Fix metric explorer for metrics with joins (#71741) 2026-04-03 09:56:45 -08:00
Nemanja Glumac
cad0841958 Fix Trivy sarif failing to upload (#71994) 2026-04-03 17:02:22 +00:00
Nemanja Glumac
69fad10cda Fix unsafe inline expression interpolation in CI workflows (#71991)
* fix: extract 20 unsafe expressions from run blocks to env vars

Move ${{ }} expressions used in shell `run:` blocks into step-level
`env:` mappings to prevent script injection. Affected workflows:
athena, drivers, e2e-stress-test, frontend-unit-test-stress-test,
perf-test, pr-env, pr-env-destroy, release-embedding-sdk, repro-bot,
tag-for-release.

This is part 2 of a split from #71610 as requested.

* fix: use C-style for loop for burn_in variable expansion

Bash brace expansion ({1..N}) happens before variable expansion, so
{1..$VAR} doesn't expand as a range. Use a C-style for loop instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: extract inline PGPASSWORD secret to env var

The PR_ENV_DB_PASSWORD was left inline in run blocks while other
secrets in the same steps were already extracted to env mappings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename REF_NAME env var to IS_MASTER_OR_RELEASE in drivers.yml

The variable holds a boolean expression result, not a ref name. The
new name matches the CLI flag it's passed to (--is-master-or-release).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: extract remaining inline workflow_dispatch inputs to env vars

Extracts spec, grep, burn_in, version, and issue_number inputs that
were still interpolated directly in run blocks. Also simplifies env
var names by dropping the redundant INPUT_ prefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: place env block before run block in athena.yml

Consistent with the convention used in all other steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Chris Nyhuis <cnyhuis@vigilantnow.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:35:30 +02:00
Mahatthana (Kelvin) Nomsawadi
e82a4d027c Add token renewal mechanism for guest embeds (#71075)
* An empty commit for the integration branch

* Extract JWT utilities into dedicated lib/jwt.ts module (#71515)

* Extract JWT utilities into dedicated lib/jwt.ts module

Moves `isJWT`, `decodeJwt`, `extractResourceIdFromJwtToken`, and `redactJwt`
from `lib/utils.ts` into a new `lib/jwt.ts`. Improves JWT decoding with safer
UTF-8 handling via `TextDecoder`. Updates all import sites.

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

* Rename utils.unit.spec.ts to jwt.unit.spec.ts after moving extractResourceIdFromJwtToken

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

* Remove redactJwt and show full error strings in JWT error messages

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

* Fix AI slop

* Fix unit test

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix SdkError too wide (#71569)

* Extract JWT utilities into dedicated lib/jwt.ts module

Moves `isJWT`, `decodeJwt`, `extractResourceIdFromJwtToken`, and `redactJwt`
from `lib/utils.ts` into a new `lib/jwt.ts`. Improves JWT decoding with safer
UTF-8 handling via `TextDecoder`. Updates all import sites.

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

* Rename utils.unit.spec.ts to jwt.unit.spec.ts after moving extractResourceIdFromJwtToken

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

* Remove redactJwt and show full error strings in JWT error messages

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

* Fix AI slop

* Fix unit test

* Add Storybook stories for SdkError component

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

* Fix mage error

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add token renewal infrastructure for guest embeds (#71516)

* Add token renewal infrastructure for guest embeds

Implements the full token renewal mechanism across three layers:

- embed.ts: adds _callGuestTokenProvider(), _fetchInitialGuestToken(), and
  _refreshGuestToken() to MetabaseEmbedElement; fetches tokens from
  guestEmbedProviderUri and serves refresh requests via postMessage

- request-session-token.ts + Redux store (auth.ts, guest-embed.ts): SDK-side
  postMessage protocol for requestGuestTokenRefresh / submitRefreshedGuestToken;
  getOrRefreshGuestSession thunk checks JWT expiry and refreshes transparently

- Plugin hooks (api.ts, embedding-sdk.ts, api.js, override-requests-for-embeds):
  wires refresh handler into the API request pipeline; adds x-metabase-guest-embed
  header and :entityIdentifier URL substitution

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

* clean up AI slop

* Fix another AI slop

* Fix more slop

* fix embed wizard parameter not affecting the preview for 1 render

* Fix string custom-context not working

* Address review feedback

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Wire token renewal into dashboard/question components and add E2E tests (#71517)

* Wire token renewal into dashboard/question components and add E2E tests

- SdkDashboard and SdkQuestionProvider now dispatch setInitialGuestToken on
  mount, storing the raw JWT in Redux so the request interceptor can refresh it
  transparently when it expires
- use-load-question and use-common-dashboard-params updated for guest embed mode
- SdkIframeEmbedRoute: new route component for the iframe embed SDK
- guest-token-refresh.cy.spec.ts: comprehensive E2E Cypress suite covering
  token expiry, automatic refresh, error paths for both iframe and SDK modes

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

* Fix _fetchInitialGuestToken to omit dashboardId/questionId from settings

In the guest embed token flow the JWT already encodes the resource, so
dashboardId and questionId should not be forwarded to the iframe via
setSettings. Pass them as undefined to override the values that
_updateSettings merges in from this.properties.

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

* fix Kelvin slop

* Make E2E tests consistent

* Make test more complete

* add custom context assertions

* address review feedback

* Address review

* Fix failed E2E tests

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix that one test that's always failing

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:00:46 +01:00
Mahatthana (Kelvin) Nomsawadi
7f785d6c98 Fix flaky metrics-editing.cy.spec.js test (#71983)
Fix that one test that's always failing
2026-04-03 15:08:36 +01:00
Timofey Kachalov
207548a864 Handle multi-touch events to improve pinch-to-zoom functionality on mobile devices for visualizations (#71932) 2026-04-03 15:52:44 +04:00
Sébastien
2a7c8331be fix(sdk-bundle): <metabase-question/> in guest embed no longer calls pulseform input (#71885) 2026-04-03 11:17:04 +02:00
dagecko
c27db353ba fix: pin 87 unpinned actions to full commit SHAs (#71913)
Pin all third-party GitHub Actions to immutable 40-character commit
SHAs to prevent supply chain attacks via tag rewriting. Version
comments added for readability.
2026-04-03 10:47:55 +02:00
Timofey Kachalov
63d5eb845a fix(sdk,embed-js): Adjust disabled state opacity for EditableText component (#71836) 2026-04-03 12:44:34 +04:00
Eric Normand
cbc40606ef Block database routing when transforms already exist (#71931)
Prevent enabling database routing on a database that already has transforms, since running transforms on databases with DB routing is not supported
Backend: added validation check in PUT /api/ee/database-routing/router-database/:id that returns 400 if transforms exist for the database
Frontend: disables the routing toggle in the UI when transforms exist, with a descriptive message
Also wired through the existing but unused database-id filter param in the transforms list API
2026-04-03 00:07:14 +00:00
github-automation-metabase
a7ca6bbca8 Auto-update documentation for master (#71949)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-04-02 18:06:12 -04:00
Alex Yarosh
a63333ac04 docs: metrics (#71778)
* first draft

* Apply suggestions from code review

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

* viewer

* metrics edits

* metrics explorer

* data studio

* Update docs/data-modeling/metrics.md

* typo

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* edits

---------

Co-authored-by: Jeff Bruemmer <jeff.bruemmer@gmail.com>
2026-04-02 22:04:37 +00:00
John Swanson
93db322a84 Hide dimensions and filters without access in the metric explorer (#71692) 2026-04-02 13:50:36 -08:00
Alexander Polyankin
940c3e343f fix escape logic in serdes (#71940)
* fix escape logic in serdes

* fix

* fixes
2026-04-02 21:11:30 +00:00
Edward Paget
9f5aaf4a81 Add GitHub Action to auto-resolve backport conflicts with Claude (#71158)
* Add GitHub Action to auto-resolve backport conflicts with Claude

Adds a new workflow triggered by the `auto-resolve-conflicts` label (or
manual dispatch) that uses Claude Code to automatically resolve cherry-pick
conflicts on backport PRs. This eliminates the manual step of checking out
the branch, running backport.sh, and hand-resolving conflicts.

* Add kondo and typescript checks

* Add syntax validation and formatting to backport conflict resolver

- Prepare backend (JDK/Clojure) and frontend (Node/Bun) environments
- Run cljfmt and eslint --fix to auto-format resolved files
- Run clj-kondo and tsc to validate syntax before completing cherry-pick
- Claude loops on errors until files are clean before committing

* Make Claude max turns configurable in backport conflict resolver, default to 50

* Fix Bash permission pattern for cljfmt in backport conflict resolver

Use colon-star syntax (cljfmt:*) to match other Bash tool permission
patterns, fixing permission denial for ./bin/mage cljfmt-files.

* Detect yarn vs bun for older release branches in backport resolver

Release branches ≤58 use yarn, ≥59 use bun. Detect the package manager
by checking for bun.lock after checkout, then use it for the CLJS build
step, allowed_tools permissions, and Claude prompt instructions.

* Set NODE_VERSION=22 for claude-code-base-action step

The action internally runs setup-node with 18.x by default, overriding
the Node 22 installed by prepare-frontend. This caused yarn engine
check failures on older release branches. The action respects a
NODE_VERSION env var, so set it to 22.

* Improve backport resolver: detect max turns, add commit details to success message

- Bump default max turns from 50 to 75
- Detect when Claude exits due to max_turns exceeded and post a specific
  comment explaining the issue with a link to re-run the workflow
- Include a commit link and collapsible changed files list in the success comment
2026-04-02 13:45:21 -07:00
Oreon Lothamer
0eef3193ce Use assume role for AWS credentials in workflows (#71608) 2026-04-02 22:15:16 +02:00
Jeff Bruemmer
6dbb28bb3e docs - Fix formatting of Agent API link in documentation (#71941)
Fix formatting of Agent API link in documentation
2026-04-02 19:23:35 +00:00
Jeff Bruemmer
7034d54542 docs - upload edits (#71684) 2026-04-02 14:49:10 -04:00
Riley Thompson
bdda38ff17 Reject multi statement queries (#71573)
* reject multi statement queries

* add dialect

* update pr

* use multimethod

* mark driver tests

* remove join for clickhouse

* kebab case

* fix e2e test
2026-04-02 18:24:02 +00:00
Aleksandr Lesnenko
293b99e321 fix unnecessary data grid row remounting (#71877)
* fix unnecessary data grid row remounting

* spec
2026-04-02 18:21:26 +00:00
John Swanson
1c26f9d31c Fix document visibility in trash (#71903) 2026-04-02 09:17:25 -08:00
Phoomparin Mano
71b8317b65 Add MCP apps CORS settings page and settings (#71763)
* feat: add MCP apps CORS settings page at /admin/metabot/mcp

Add a new settings page where admins can enable CORS for popular MCP
app clients (Claude, ChatGPT, Cursor/VS Code) via checkboxes, and
add custom domains for self-hosted clients like LibreChat.

Backend: new `common-mcp-apps-cors-origins` (CSV) and
`custom-mcp-apps-cors-origins` (string) settings in mcp/settings.clj.
Security middleware merges these with embedding SDK origins in
`access-control-headers`. Special handling for vscode-webview:// prefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address design review — use toggles, fix casing, remove sublines

- Switch from Checkbox to Switch toggles for MCP client selection
- "MCP Apps" → "MCP apps" in sidebar and page title
- Remove redundant description sublines from client options

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract MCP CORS tests to separate security_mcp_test file

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add unit tests for mcp apps

* rename: common-mcp-apps-cors-origins → mcp-apps-cors-enabled-clients

The setting stores client keys (claude, vscode, chatgpt), not origins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* rename: custom-mcp-apps-cors-origins → mcp-apps-cors-custom-origins

Consistent mcp-apps-cors- prefix for both settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add mcp to driver-affecting-overrides

MCP settings are infrastructure, not driver logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add cors origins parsing improvements from @crisptrutski

Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>

* move mcp-apps-sandbox-origin? from security middleware to mcp.settings

Encapsulates MCP sandbox origin logic in the mcp module, uses condp
for extensibility when more non-standard schemes are added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add oauth-server to mage modules list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add mcp.core as public API, keep mcp.settings internal

External consumers (e.g. security middleware) now go through mcp.core
instead of reaching into mcp.settings directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* move application logic from mcp.settings to mcp.core

mcp.settings now only contains pure config (defsettings + cors-origins).
vscode-webview-enabled? and sandbox-origin? live in mcp.core.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* remove useEffect state sync from MCP apps settings

Local state is kept for optimistic UI updates, but the useEffect sync
from savedValue is unnecessary since these settings won't change from
an external source concurrently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* rename "MCP app clients" to "MCP clients" in descriptions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* rename "vscode" client key to "cursor-vscode"

Aligns the key with the label "Cursor and VS Code" across frontend,
backend domain mapping, and tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* expand test to toggle all MCP clients

Use findByLabelText instead of testIds, and verify each toggle
accumulates the enabled clients list correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add init for mcp

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Chris Truter <crisptrutski@users.noreply.github.com>
2026-04-02 16:29:09 +00:00
Sébastien
f01f753ff2 fixed translation issue with funnels (#71810) 2026-04-02 17:45:16 +02:00
Timofey Kachalov
a6453de48d feat(sdk,embed-js): Add touch device support for collection name description icon (#71762) 2026-04-02 19:42:56 +04:00
Aleksandr Lesnenko
a3ccc35af6 hides overview tab of the metrics pages if no dimensions (#71675)
* hides overview tab of the metrics pages if no dimensions

* hide caching tab if no write access to metric

* update specs based on latest changes from master
2026-04-02 10:28:56 -04:00
Chris Truter
d07b1772b8 Move table_transform_target_fk migrations from v60 to v61 (#71717) 2026-04-02 16:14:49 +02:00
Phoomparin Mano
05ec8451c5 Move CORS settings to OSS and show Security tab in Embedding (#71648)
* move the cors settings to oss

* add quick test for cors on oss

* test: update security middleware tests for OSS CORS behaviour

Add a dedicated test verifying CORS headers are sent when origins are
configured even without embedding features enabled (OSS path).

Update existing cases to reflect that removing the :embedding-sdk
feature flag from embedding-app-origins-sdk means the getter now
returns the actual stored value in all contexts (previously returned ""
without the premium feature active in tests).

Remove 16 redundant test cases where sdk=true with non-empty origins
produced identical results to sdk=false with non-empty origins — now
that CORS is enabled whenever origins are configured, the sdk flag is
only meaningful when origins are empty.

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

* restore tests

* fix: remove redundant enable flags from CORS check and add OSS CORS test

The enable-embedding-sdk and enable-embedding-simple flags are no longer
needed to gate CORS headers — having configured origins is sufficient,
and localhost is always allowed via the existing passthrough. This
addresses PR review feedback to simplify the logic and add missing
frontend test coverage for CORS settings in OSS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: update e2e tests for OSS/Starter CORS settings visibility

Security tab and CORS settings are now available on all plans,
not just EE. Update assertions to expect them to exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:05:52 +00:00
Chris Truter
6f850e24db Clean up workspace schema and user-name code (#71808)
* Clean up workspace schema and user-name code

1. Single source of truth for the prefix — "mb__isolation" is defined once in metabase.driver.util (private), not three places
2. Matching methods live next to the constant — workspace-isolated-schema? (in-memory) and workspace-isolated-schema-clause (HoneySQL) so callers don't need to know the prefix or construct patterns themselves
3. Removed the requiring-resolve hack in the redshift test
4. Removed duplicated name generation — enterprise workspaces/util had identical copies of isolation-namespace-name, isolation-user-name, and instance-uuid-slug; callers now use driver.u directly

Closes [GDGT-2157: redshift tests require enterprise alias](https://linear.app/metabase/issue/GDGT-2157/redshift-tests-require-enterprise-alias)

* Avoid dynamic string building

* Use escape characters since _ is a wildcard

* Tighten up a bit more

* Remove stale include
2026-04-02 08:17:52 -04:00
github-automation-metabase
c08d322874 Update translations 2026-04-01 (#71841)
* Update translation sources 2026-04-01

* Update translations

* skipping offending test

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Katya Voropaieva <110612633+KorowkaMu@users.noreply.github.com>
Co-authored-by: Sébastien <sebastien@metabase.com>
2026-04-02 11:48:13 +00:00
Chris Truter
33a9177696 Test to catch incorrect migration versions (#71743) 2026-04-02 11:12:44 +00:00
Stas Gavrylov
beed12ea36 GDGT-2089: Adjust viz settings sidebar styling (#71472)
* GDGT-2089: Adjust settings sidebar styling

* GDGT-2089: Fix PR comments. Adjust styling
2026-04-02 13:51:56 +03:00
Sébastien
23eaffd14a add new-native to mb-question doc (#71890) 2026-04-02 09:33:04 +00:00
Stas Gavrylov
96f8785d12 GDGT-2047: Add ability to proxy /api requests to remote server (#71090)
* Add ability to proxy /api requests to remote server

* Tighten security. Add tests

* Move proxy code to dev/. Add mention in docs
2026-04-02 11:48:20 +03:00
Nick Fitzpatrick
0642892b42 Adding Details tab to table section (#71722)
* Adding Details tab to table section

* e2e adjustments

* remove grow on tab list

* fix e2e tests
2026-04-02 12:42:57 +05:30
Timofey Kachalov
0336557161 feat(sdk,embed-js): Improve touch device support in dropdown components in Notebook Editor (#71562) 2026-04-02 11:12:47 +04:00
Timofey Kachalov
8c2ca60ccb Fix duplicated identifiers error (#71883)
Fix duplicated identifiers
2026-04-02 01:38:10 -04:00
Timofey Kachalov
a3340c17a6 feat(sdk,embed-js): Improve mobile range selection experience for visualizations (#71374) 2026-04-02 08:45:00 +04:00
Alexander Polyankin
bd82202177 Representations baseline (#71879)
* Omit UUIDs in SerDes MBQL 5 export unless required

* Test fixes 🔧

* Final test fixes 🔧

* cljfmt

* fixes

* fixes

* fx

* fixes

* es

* fix

---------

Co-authored-by: Cam Saul <github@camsaul.com>
2026-04-02 00:22:41 -04:00
Cam Saul
22a21845f1 Omit UUIDs in SerDes MBQL 5 export unless required (#71862)
* Omit UUIDs in SerDes MBQL 5 export unless required

* Test fixes 🔧

* Final test fixes 🔧

* cljfmt
2026-04-01 21:43:06 -04:00
metatyb
e75d0484e8 chore: add metatyb to team and github-slack-map (#71803) 2026-04-02 09:30:45 +08:00
Nemanja Glumac
43db728f71 Clean up two module violations in metabase api (#71786)
* Move DataSegregationStrategy type to metabase-types/api/permissions

This is a permissions concept, not an embedding one. Moving it to
metabase-types resolves a module boundary violation (basic/api importing
from shared/other) and breaks a circular dependency between metabase/api
and metabase/embedding/embedding-hub.


Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 00:39:48 +02:00
Nemanja Glumac
9537481bb2 Improve sync-fork workflow ergonomics (#71875) 2026-04-02 00:38:49 +02:00
Eric Normand
74a08a7a2d Fix usage_info IN clause exceeding 65K parameter limit (GHY-2413) (#71856)
- Fixes `GET /api/database/:id/usage_info` failing when a database has more than 65K tables
- The segment count query was passing all table IDs as parameters to an `IN` clause, which exceeds PostgreSQL's 65,535 parameter limit
- Replaced the `IN` clause with a subselect (`SELECT id FROM metabase_table WHERE db_id = ?`), eliminating the parameter explosion
- Removed the now-unnecessary `select-pks-set` call and `table-ids` argument from `database-usage-query`
2026-04-01 20:42:52 +00:00
Alex Yarosh
db1f2bbe75 Update description for logarithm value parameter (#71851) 2026-04-01 19:37:57 +00:00
Aleksandr Lesnenko
6d190f1f62 do not suggest city as a geo dimension for tabs with map viz (#71768)
* do not suggest city as a geo dimension for tabs with map viz

* update e2e spec
2026-04-01 19:32:56 +00:00
appleby
4ff8b2722e BOT-1223 Fix zero-arity tool call schemas for anthropic provider (#71618)
* BOT-1223 Fix zero-arity tool call schemas for anthropic provider

Anthropic is stricter than openrouter about schemas for tool calls with no arguments. A schema like `[:maybe :map]`
produces a JSON schema like `{"oneOf": [{"type": "object", ...}, {"type": "null"}]}` but Anthropic's API requires
input_schema to be `{"type": "object", ...}` at the top level. A oneOf with a null variant is invalid.

* Handle nil arguments in metabase.metabot.self.core/run-tool
2026-04-01 12:05:14 -07:00
Jeff Bruemmer
75befd8877 docs - links (#71853)
links

Frontload links to ref docs.
2026-04-01 14:04:00 -04:00
Filipe Silva
34a7791680 fix: accept metered transforms product types in cloud-add-ons endpoint (#71664) 2026-04-01 18:56:42 +01:00
Nemanja Glumac
6a72335427 Allow test quarantine in Metabase internal forks (#71837) 2026-04-01 19:15:37 +02:00
Nick Fitzpatrick
a86098bc57 Rename Data Structure to Tables (#71744) 2026-04-01 22:12:00 +05:30
Eric Normand
23410f5c24 Fix sync_schema silently dropping when executor is busy (GHY-3254) (#71756)
* Add failing test reproducing GHY-3254: sync_schema silently drops when executor is busy

The quick-task executor uses a single thread. If any task gets stuck
(e.g., a hanging JDBC connection), all subsequent manual sync requests
queue forever while the endpoint returns 200.

* Add per-task timeout to quick-task executor

Each task submitted to the quick-task executor now runs on a child
thread with a configurable timeout (*task-timeout-ms*, default 2h).
If a task exceeds the timeout, it is cancelled and the executor moves
on to the next queued task. This prevents a single stuck task from
blocking all subsequent manual sync requests.

The stuck thread's stack trace is logged for diagnostics.

* Use MB_QUICK_TASK_TIMEOUT_MINUTES env var for task timeout

Replace the dynamic var with a real env var (default 120 minutes).

* Use config/config-int for MB_QUICK_TASK_TIMEOUT_MINUTES

Read the env var via metabase.config.core instead of System/getenv,
consistent with how other config values are read in the codebase.
2026-04-01 11:39:44 -05:00
Timofey Kachalov
2aae5c688b feat(sdk,embed-js): Enhance getEventTarget to support TouchEvent for improved mobile compatibility when showing a drill popup (#71636) 2026-04-01 20:36:55 +04:00
Nick Fitzpatrick
6af2a17af7 Subtract scrollbar with from pivot table header when apropriate (#71654) 2026-04-01 21:39:41 +05:30
Nick Fitzpatrick
6c297776ee Skipping GridMapPdfExport loki test (#71833) 2026-04-01 21:38:01 +05:30
dpsutton
ee5d80d008 middleware to prevent middleware that hits app-db (#71775)
currently it is just two middleware: impersonation and db routing

this is necessary as we want to move into a world where we can compile a
query from yaml files without an app db file. These middleware are not
impportant for our purposes here.

This is also temporary work. This is slated to be fixed up in

[QUE2-488: Introduce an app-db protocol for QP
middleware](https://linear.app/metabase/issue/QUE2-488/introduce-an-app-db-protocol-for-qp-middleware)
2026-04-01 09:29:07 -05:00
Edward Paget
1038b9d7bd Remove metabot describe API, openai client, and unused deps (#71774)
The metabot describe endpoints (card and dashboard autodescription) are
no longer needed. This removes the API routes, task implementations,
OpenAI client wrapper, tests, and the now-unused openai-clojure and
lambdaisland/uri dependencies.
2026-04-01 08:46:13 -05:00
Nick Fitzpatrick
a4d498d247 add data image check to urlTransform (#71588)
* add data image check to urlTransform

* de-duplicate regex
2026-04-01 19:03:47 +05:30
Nick Fitzpatrick
3b4897a341 show error message on remote sync pull changes or branches error (#71668)
* show error message on remote sync pull changes or branches error

* test adjustments
2026-04-01 19:03:31 +05:30
Jeff Bruemmer
134065e95e docs - images and notes (#71773)
* images and notes

* Optimized 2 images, saved 118.8 KB (41.4% smaller)

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-04-01 13:01:48 +00:00
Nick Fitzpatrick
fdb8d17d50 Revert April Fools Joke (#71801)
* Add CI check

* Revert "Fix metabot padding (#71680)"

This reverts commit 5886b7b7c6.
2026-04-01 05:38:24 -06:00
Timofey Kachalov
efc354420f feat(SDK, EmbedJS): Add touch support for Popover targets to improve mobile usability (#71484) 2026-04-01 12:59:39 +04:00
Tyler Plude
878fc365a3 [BOT-1233] Fix get-table-details arg flag typos (#71679)
* fix get-table-details arg flags

* new unit test
2026-03-31 22:59:18 -07:00
Alexander Polyankin
9ada59a804 Serdes fixes for python transforms (#71735)
* fixes

* fixes
2026-03-31 21:43:55 -04:00
appleby
4544ff5e6b BOT-1221 Resolve metabase://transform links to new data-studio path (#71613) 2026-04-01 00:30:55 +00:00
appleby
734dba5964 BOT-1226 Only run metabot internal tasks when the llm-metabot-provider-lite is configured (#71667)
* BOT-1226 Only run metabot internal tasks when the llm-metabot-provider-lite is configured

The agent loop was already checking llm-metabot-internal-tasks-enabled? before running internal background tasks like
user intent classification. Switch the default value for llm-metabot-internal-tasks-enabled? from true to false and
update the :getter to check that the corresponding llm-metabot-provider-lite is configured.

* Review feedback: ensure llm-provider-configured? returns boolean
2026-03-31 23:10:11 +00:00
Cam Saul
b6a9236b1d Remove legacy MBQL conversion steps from SerDes (#71614)
* SerDes use MBQL 5 [WIP]

* Update serialization baselines

* Test fixes 🔧

* More test fixes 🔧

* Test fixes 🔧
2026-03-31 19:03:43 -04:00
github-automation-metabase
ef14caecf1 Auto-update documentation for master (#71781)
Update generated docs (master)

Co-authored-by: github-actions <github-actions@github.com>
2026-03-31 18:27:33 -04:00
appleby
9080614933 BOT-1199 ORDERS table link points to an invalid path (#71543)
* BOT-1199 Add table-specific link examples to the internal metabot prompt

* Fix resolve-table-link to resolve to valid ad-hoc question link

The docstring mentioned avoiding a db lookup for the database id, but a link like `/table/<id>` is not a valid
metabase link.

* Cache metabase.metabot.agent.links/resolve-table-link result for 10 minutes

* Remove regex guard around parse-long in resolve-table-link

parse-long already returns nil if the parse fails.

* Fix require for application-database-metadata-provider

* Fix resolve-entity-link-test for tables and split it out into a new test
2026-03-31 15:17:02 -07:00
Cam Saul
da8bc93939 Fix driver-related code scanning alerts (#71666) 2026-03-31 18:00:30 -04:00
Oleksandr Solovyov
9c23e9d35a [metabot] add repr for measures and segments (#71656)
* [metabot] add repr for measures and segments

* Wire up measures and segments correctly

* Improve measures and segments representation

---------

Co-authored-by: Mihael Konjevic <konjevic@gmail.com>
2026-03-31 21:20:17 +00:00
Sloan Sparger
686087f2ec [Metabot] Fix Admin AI sidebar disappearing (#71597)
reorganize page components so sidebar does disappear while loading metabot settings pages
2026-03-31 20:36:42 +00:00
Tyler Plude
e0ba625be4 [BOT-1247] Fix empty string content blocks in anthropic API (#71765)
init
2026-03-31 12:57:49 -07:00
Nemanja Glumac
9038c23075 Run cross-version script only on the main repo (#71754) 2026-03-31 21:55:10 +02:00
Nemanja Glumac
2e28c3f930 Ask for explicit input to override fork changes (#71753) 2026-03-31 21:52:30 +02:00
Fraser
0f42846b53 Clean UI utils and component violations (#71465) 2026-04-01 08:47:17 +13:00
Fraser
afc7fdbeb6 Add subset of files to app-misc module and make schema a lib module (#71700) 2026-04-01 08:46:56 +13:00
Noah Moss
adc3c025c5 Let Metabot write a brief response after using autogen_dashboard (#71758)
remove final-response? from autogen dashboard tool to allow for a brief description
2026-03-31 15:33:51 -04:00
Ryan Laurie
5886b7b7c6 Fix metabot padding (#71680)
* avante garde monetization

* fetch market prices

* limited fun

* obey the linter
2026-03-31 12:50:00 -06:00
Sloan Sparger
aa84a83fba [Metabot] Remove metabot v3 token feature (#71600)
remove metabot v3 token feature
2026-03-31 18:21:13 +00:00
Sloan Sparger
6c14871bae [Slackbot] Make slackbot auth req messages in channel an ephemeral message (#71603)
make slackbot auth req messages in channel an ephemeral message
2026-03-31 12:55:08 -05:00
Edward Paget
9e576f007b Fix git sync to remove stale YAML files from managed export directories (#71739)
Exports are always full, so any .yaml file in a managed directory that isn't
in the current write set should be removed. This fixes stale files persisting
after entity renames, moves between collections, or deletions.

The git source now accepts a set of managed top-level directories. During
write-files!, existing files in managed dirs not in the write set are cleaned
up automatically. This replaces the previous path-prefix-based collection
scoping and the explicit build-all-removal-paths machinery, which couldn't
handle renames or cross-collection moves.
2026-03-31 12:48:56 -05:00
Sloan Sparger
400a33e4ef [Metabot] Hide embedded settings if embedding is not available (#71594)
hide embedded metabot settings if embedding is not available
2026-03-31 12:17:12 -05:00
Noah Moss
201824c6b2 Remove capability check for create-autogenerated-dashboard-tool; clarify system prompt (#71589)
* fix autogen_dashboard tool by removing capability checck, clarify system prompt around dashboards

* fix tests

* fix test
2026-03-31 13:13:30 -04:00
Edward Paget
fa31def1bc Fix support access grant password reset failing for deactivated users (#71598)
* Fix support access grant password reset failing for deactivated users

When a support user was deactivated (e.g. after a previous grant expired),
creating a new grant would leave the user inactive. The password reset
token authentication would succeed, the :after method would consume the
token and change the password, but create-session! would then reject the
login because is_active was false. with-fallback would swallow this and
fall back to emailed-secret-password-reset which also failed, resulting
in an opaque "Invalid reset token" error with the token irreversibly
consumed.

Fix: fetch-or-create-support-user! now reactivates deactivated users.

Also adds tests for the deactivated user flow, pre-existing auth identity
cascade, with-fallback error swallowing behavior, and direct login! calls
for easier debugging.

* fixup! cljfmt
2026-03-31 11:00:45 -05:00
Nicola Mometto
c134074716 Dedupe slack channels (#71653)
dedupe slack channels
2026-03-31 16:39:30 +01:00
lbrdnk
237c3fe056 Remove stale archived_message instructions (#71715) 2026-03-31 16:17:00 +02:00
Nick Fitzpatrick
01af8eb57f updating styling for Database Destination Connection Modal (#71586) 2026-03-31 19:38:54 +05:30
Edward Paget
e60356ffd7 Fix notification send test retry config key (:max-attempts → :max-retries) (#71689)
The test overrode :max-attempts but diehard uses :max-retries, so the
override was silently ignored and all 6 retries ran with exponential
backoff (~31s). With the correct key the test finishes in ~1s.

Done: task/fix-notification-retry-key
2026-03-31 14:08:49 +00:00
Kevin Kelleher
d825c4dab3 Metrics home page and viewer cleanup (#71514) 2026-03-31 13:41:56 +00:00
lbrdnk
bee5aa840a Native query context formatting (#71566)
* Extend the schema for context received in requests

* Fix native question context formatting

* Adjust context schema

* Tests

* test

* Tests

* extract dialect

* remarks
2026-03-31 15:12:05 +02:00
Kevin Kelleher
214f776ad4 Metrics explorer dimension picker and tab creation fixes (#71536) 2026-03-31 07:13:57 -04:00
Timofey Kachalov
a4c66d0626 Update permissions in embedding SDK tests to avoid permission errors in some cases (#71705) 2026-03-31 13:56:11 +04:00
Nemanja Glumac
cc7506cc6f Allow Metabase forks (including private ones) to sync automatically (#71663)
Add a new sync-fork workflow that automatically syncs metabase internal forks
with upstream metabase/metabase (master + 5 most recent release branches).
Force-syncs from upstream as source of truth, skips push when no diff to
avoid unnecessary CI runs. Only runs on forks, never on the public repo.

Guard all scheduled workflows with repository check so they only run on
schedule for metabase/metabase, not on forks (manual dispatch
still works on both).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 06:05:11 +00:00
Alexander Polyankin
8f62ee5f3a Serdes skip default values (#71544) 2026-03-31 03:42:08 +00:00
Fraser
f922a58520 Clean dead code (#71463) 2026-03-31 16:02:41 +13:00
metabase-bot[bot]
1179147e3d [chore] Update E2E test timings with recent data (#71621) 2026-03-31 14:03:04 +13:00
Fraser
b2a016926e Push shared redux actions down (#71130) 2026-03-31 13:53:24 +13:00
Maz Ameli
a9f7e0504a Change label for stacked series setting (#71541)
change label for stacked series setting
2026-03-30 23:01:28 +00:00
Edward Paget
4b032951ad Include lock name in cluster lock log and error messages (#71677) 2026-03-30 21:09:03 +00:00
Braden Shepherdson
43ab4ba9d3 [MBQL lib] Add API docs for returned-columns and other metadata (#71443)
Part of QUE2-366.
2026-03-30 21:03:49 +00:00
Nemanja Glumac
610ff28b80 Fix a collection picker flake in cross-version tests (#71599) 2026-03-30 21:26:39 +02:00
Alexander Polyankin
4bbdb37c66 Serdes file and folder human readable names (#71583) 2026-03-30 18:52:39 +00:00
Nick Fitzpatrick
b56c33b6be adjust styling on parameter placeholders (#71577) 2026-03-30 23:40:08 +05:30
Nick Fitzpatrick
a14c0b00b9 don't recompute sensible chart groupings while sidebar is mounted (#71563) 2026-03-30 23:39:46 +05:30
Eric Normand
94cdeffdab Fix field filter dropdowns for users without create-queries permission GHY-1605 (#71046)
## Summary

Field filter dropdowns on native query cards show as text inputs ("Enter some text") for users who have view-data permission but lack create-queries permission on the underlying table.

**Root cause:** Two issues combined to break this:

1. **Frontend couldn't determine dropdown type** — The frontend needs `has_field_values` from field metadata to decide whether to show a dropdown vs text input. This metadata came from `GET /api/database/:id`, which returns 403 for users without `create-queries` permission. Without it, the widget falls back to a text input.

2. **Backend param values endpoint was too restrictive** — Even if the frontend showed a dropdown, the card param values endpoint (`GET /api/card/:id/params/:key/values`) would fail because `search-values-from-field-id` does `api/read-check` on the Field (which requires `create-queries`), and the QP permission check also required `create-queries`.

**Fix:**

- **Hydrate `param_fields` on Card API response** (matching existing Dashboard pattern) — includes `has_field_values` so the frontend can show the correct widget type without needing `GET /api/database/:id`
- **Populate metadata store with `param_fields`** on card load in the query builder
- **Bind `*param-values-query*`** in card param values endpoints (like dashboards already do) so the QP only checks `view-data` permission
- **Skip `api/read-check` on Field** in `search-values-from-field-id` when `*param-values-query*` is true
2026-03-30 12:55:14 -05:00
da
480ef19d1f Document reverse tunneling for SSH connections (#71628)
Added section on reverse tunneling for SSH connections.
2026-03-30 15:54:09 +00:00
Sébastien
65d66d2887 No request to pulse if user canot manage subscriptions (#71575) 2026-03-30 15:36:05 +02:00
Mahatthana (Kelvin) Nomsawadi
a1c3983037 Fix flaky test (#71623)
Attempt to fix flake
2026-03-30 14:26:33 +01:00
Jeff Bruemmer
424541b425 docs - mcp and AI updates (#71601) 2026-03-30 09:04:43 -04:00
Fraser
cc486a476e Lift dashboard only visualizations (#71271)
* Extract Dashboard-only visualizations

  Moved 4 dashboard-only visualization types from visualizations/visualizations/ to dashboard/visualizations/:
  - Heading/ — imports dashboard context, selectors, parameter components
  - IFrameViz/ — imports dashboard selectors
  - LinkViz/ — imports dashboard selectors
  - DashCardPlaceholder.tsx — imports dashboard actions

  Created dashboard/visualizations/register.js — registers these 4 viz types separately from the main visualizations/register.js

  Updated app.js — calls both registerVisualizations() and registerDashboardVisualizations()

  Updated 3 external imports to use the new paths:
  - dashboard/actions/utils.ts — IFrameViz utils
  - parameters/utils/mapping-options.ts — IFrameViz utils
  - PublicOrEmbeddedDashboardView-card-filters.stories.tsx — Heading

  Fixed 1 relative import in DashCardPlaceholder.tsx to use absolute path for VisualizationProps type.

  Violation progression: 157 → 111 → 86 → 78

* List DashboardChartSettings up

* Fix test setup

* Register dashboared viz in embedding
2026-03-30 22:01:47 +13:00
5211 changed files with 660074 additions and 518335 deletions

View File

@@ -15,7 +15,7 @@ Analytics events in Metabase use Snowplow with typed event schemas. All events m
**Key Files:**
- `frontend/src/metabase-types/analytics/event.ts` - Event type definitions
- `frontend/src/metabase-types/analytics/schema.ts` - Schema registry
- `frontend/src/metabase/lib/analytics.ts` - Core tracking functions
- `frontend/src/metabase/utils/analytics.ts` - Core tracking functions
- Feature-specific `analytics.ts` files - Tracking function wrappers
## Quick Checklist
@@ -91,7 +91,7 @@ export type DataStudioEvent =
In your feature's `analytics.ts` file (e.g., `enterprise/frontend/src/metabase-enterprise/data-studio/analytics.ts`):
```typescript
import { trackSimpleEvent } from "metabase/lib/analytics";
import { trackSimpleEvent } from "metabase/utils/analytics";
export const trackDataStudioTablePickerFiltersApplied = () => {
trackSimpleEvent({
@@ -471,7 +471,7 @@ Event type definitions (all in one place):
frontend/src/metabase-types/analytics/event.ts
Core tracking utilities:
frontend/src/metabase/lib/analytics.ts
frontend/src/metabase/utils/analytics.ts
```
## Real-World Examples

View File

@@ -0,0 +1,76 @@
---
name: serdes-workflow
description: Export content from a running Metabase instance, validate with checkers, edit YAML, and import back. Use when the user wants to export, import, or run the full serdes round-trip workflow.
---
# Serdes Workflow Skill
End-to-end workflow for exporting Metabase content as YAML, validating it, editing it, and importing it back.
## Prerequisites
All commands require the `:ee` alias since export, import, and checkers live in enterprise code.
You need `MB_DB_CONNECTION_URI` set to point at the target Metabase Postgres database when importing. The running dev instance's connection string is in the worktree environment (check the nREPL or startup output).
## Export
```bash
clojure -M:run:ee export /path/to/export-dir
```
Flags:
- `-c 123,456` - export only specific collection IDs
- `-f` - include field values
- `-e` - continue on error
This writes YAML files in the serdes directory layout:
```
databases/<db-name>/<db-name>.yaml
databases/<db-name>/schemas/<schema>/tables/<table>/<table>.yaml
databases/<db-name>/schemas/<schema>/tables/<table>/fields/<field>.yaml
collections/<eid>_<slug>/cards/<eid>_<slug>.yaml
```
## Validate
Always run both checkers after any modification. They skip full Metabase startup (~8s each).
### Structural checker (fast, schema-based)
```bash
clojure -M:run:ee --mode checker --checker structural --export /path/to/export-dir
```
Validates YAML shapes against Malli schemas. Catches typos, missing required fields, wrong types.
### Cards checker (query validation)
```bash
clojure -M:run:ee --mode checker --checker cards --export /path/to/export-dir
```
Validates that card queries resolve correctly against exported metadata (tables, fields, FKs). Uses MLv2 under the hood.
Additional cards checker flags:
- `--lenient` - fabricate metadata on demand (when no database schema files exist)
- `--manifest /path/to/manifest.yaml` - write manifest of all referenced entities (lenient mode only)
- `--output /path/to/results.edn` - write detailed results to file
## Edit YAML
See the **serdes-yaml-edit** skill for guidance on editing YAML files. The critical rule: **run both checkers after every edit before proceeding**.
## Import
```bash
MB_DB_CONNECTION_URI="postgres://user:pass@localhost:5432/metabase_dbname" \
clojure -M:run:ee import /path/to/export-dir -e
```
The `-e` flag continues on error. The `MB_DB_CONNECTION_URI` must point at the same Postgres database the running Metabase instance uses - without it, import defaults to H2 which is wrong.
## Full Demo Sequence
1. Export: `clojure -M:run:ee export /tmp/metabase-export`
2. Validate baseline: run both checkers
3. Edit YAML files (see serdes-yaml-edit skill)
4. Re-validate: run both checkers again
5. Import: `MB_DB_CONNECTION_URI=... clojure -M:run:ee import /tmp/metabase-export -e`
6. Verify in browser

View File

@@ -0,0 +1,243 @@
---
name: serdes-yaml-edit
description: Edit Metabase serdes YAML files (cards, dashboards, databases) with correct portable references and structural conventions. Use when modifying exported YAML content.
---
# Serdes YAML Edit Skill
## Golden Rule
**Run both checkers after every edit.** No exceptions. Do not batch multiple edits before validating.
```bash
clojure -M:run:ee --mode checker --checker structural --export /path/to/export-dir
clojure -M:run:ee --mode checker --checker cards --export /path/to/export-dir
```
If either checker fails, fix the issue before making further edits.
## Portable References
Serdes YAML uses portable references instead of integer IDs. This is the most important concept for editing.
### Database references
String name: `"Sample Database"`
### Table references
Array of `[database, schema, table]`:
```yaml
table_id:
- Sample Database
- PUBLIC
- ACCOUNTS
```
### Field references
Array of `[database, schema, table, field]`:
```yaml
id:
- Sample Database
- PUBLIC
- ACCOUNTS
- EMAIL
```
### Field refs in queries
```yaml
field_ref:
- field
- - Sample Database
- PUBLIC
- ACCOUNTS
- EMAIL
- null
```
The outer array is `[field, <field-path>, <options>]`. The options are usually `null`.
### Card references
Entity ID string (21 characters): `"Qk5TgsNx4ubXIUtsQmT8G"`
### Source table in queries
```yaml
dataset_query:
database: Sample Database
query:
source-table:
- Sample Database
- PUBLIC
- ACCOUNTS
type: query
```
## Safe Edits (low risk)
These rarely break validation:
- `name` - card/dashboard display name
- `description` - card/dashboard description
- `display` - visualization type (table, bar, line, pie, etc.)
- `visualization_settings` - chart configuration
- `archived` - true/false
- `collection_id` - move to a different collection (use entity_id of target collection, or null for root)
## Structural Edits (must match schema)
These must use valid portable refs and will be caught by the cards checker if wrong:
- `dataset_query` - the query definition
- `result_metadata` - column metadata (must match the query's output columns)
- `table_id` - must reference a table that exists in the export
- `database_id` - must reference a database that exists in the export
## Editing result_metadata
Each entry in `result_metadata` describes an output column. When changing a query's source table or fields, you must update result_metadata to match. Each field entry needs at minimum:
- `name` - column name (e.g., `EMAIL`)
- `base_type` - Metabase type (e.g., `type/Text`, `type/Integer`, `type/DateTime`)
- `display_name` - human-readable name
- `field_ref` - portable field reference
- `id` - portable field path
- `table_id` - portable table path
- `source` - usually `fields`
## Common Operations
### Rename a card
Change `name:` at the top level. Safe, no ref changes needed.
### Change a card's source table
Update all of these consistently:
1. `table_id` - top-level
2. `dataset_query.query.source-table` - in the query
3. `result_metadata` - every field entry's `id`, `field_ref`, and `table_id`
### Add a filter to a structured query
```yaml
dataset_query:
database: Sample Database
query:
source-table:
- Sample Database
- PUBLIC
- ORDERS
filter:
- ">"
- - field
- - Sample Database
- PUBLIC
- ORDERS
- TOTAL
- null
- 100
type: query
```
### Change visualization type
```yaml
display: bar # was: table
```
Valid types: `table`, `bar`, `line`, `pie`, `scalar`, `row`, `area`, `combo`, `scatter`, `funnel`, `map`, `pivot`, `progress`, `gauge`, `waterfall`
## Looking Up Valid References
The export directory IS the reference catalog. When you need to find the correct name for a database, table, or field, look it up directly from the export.
### List valid databases
```bash
ls databases/
```
Each entry is a database name (directory for serdes format, .yaml for concise format).
### List valid tables for a database
```bash
ls databases/<db-name>/schemas/<schema>/tables/
```
Example: `ls databases/Sample\ Database/schemas/PUBLIC/tables/` shows `ACCOUNTS`, `ORDERS`, `PRODUCTS`, etc.
### List valid fields for a table
```bash
ls databases/<db-name>/schemas/<schema>/tables/<table>/fields/
```
Example: `ls databases/Sample\ Database/schemas/PUBLIC/tables/PRODUCTS/fields/` shows `CATEGORY.yaml`, `TITLE.yaml`, `PRICE.yaml`, etc. The filename (minus `.yaml`) is the field name.
### Find card entity IDs
Card filenames encode the entity ID: `<entity-id>_<slug>.yaml`. The entity ID is the part before the first underscore (21 characters).
## Self-Healing Loop
When a checker reports errors, follow this loop:
1. **Read the error message** - it tells you what's wrong and often suggests the fix
2. **Look up the correct value** from the export directory (see "Looking Up Valid References" above)
3. **Fix the YAML**
4. **Re-run both checkers**
5. **Repeat until clean**
Do not guess at fixes. Always look up the correct value from the export.
## Understanding Checker Errors
### Structural checker errors
The structural checker validates YAML shape against Malli schemas. Common errors:
**Missing required key with typo suggestion:**
```
Missing required key 'name' - found 'nameee' which may be a typo
```
Fix: rename the typo'd key back to the correct name. The checker tells you what it expected and what it found.
**Wrong type:**
```
'archived' should be a boolean, got: "yes"
```
Fix: use `true`/`false`, not strings.
**Unknown key:**
```
Unknown key 'foobar' in card
```
Fix: remove the key, or check if it's a typo of a known key.
### Cards checker errors
The cards checker validates that queries resolve against exported metadata. Error types:
**UNRESOLVED REFERENCES:**
```
UNRESOLVED REFERENCES:
- field: Sample Database.PUBLIC.PRODUCTS.CATEGORYYY
```
The dotted path tells you exactly which reference failed. Look up the correct value:
- Last segment is the field name - check `databases/.../tables/<table>/fields/`
- Third segment is the table name - check `databases/.../schemas/<schema>/tables/`
- First segment is the database name - check `databases/`
**ERROR (query construction failed):**
```
ERROR: Error creating query from legacy query: Invalid output: ...
```
Usually means the query structure is malformed. This often follows unresolved references - fix the refs first, then re-check.
**ERROR (nil name):**
```
ERROR: Invalid output: {:name ["should be a string, got: nil"]}
```
The card is missing its `name` field (likely a structural issue that also affects the cards checker).
### Exit codes
- Exit 0: all checks passed
- Exit 1: one or more failures
## File Naming Convention
Card files: `collections/<collection-path>/cards/<entity-id>_<slug>.yaml`
The filename slug should match the card name (lowercase, underscored). If you rename a card, consider renaming the file to match, though import works on entity_id not filename.
## What NOT to Edit
- `entity_id` - this is the identity of the object, changing it creates a new object on import
- `serdes/meta` - internal serdes metadata, leave it alone
- `created_at` - timestamp, no reason to change
- `creator_id` - email reference, leave it
- `metabase_version` - informational, leave it

View File

@@ -68,6 +68,7 @@
:metabase/modules {:level :warning} ; more config in `.clj-kondo/config/modules/config.edn`
:metabase/namespace-name {:level :warning}
:metabase/no-jsqlparser-imports {:level :warning}
:metabase/toucan-model-ns {:level :warning}
:metabase/require-shape-checker {:level :warning}
:metabase/tests-must-live-in-known-modules {:level :warning}
:metabase/validate-defendpoint-route-uses-kebab-case {:level :warning}
@@ -185,6 +186,7 @@
;; Workspace isolation functions are safe in parallel tests because they operate on
;; isolated, test-specific workspaces and databases that don't affect other tests
metabase-enterprise.workspaces.isolation/destroy-workspace-isolation!
metabase.lib-metric.ast.plan/validate-arithmetic-ast!
metabase-enterprise.workspaces.isolation/ensure-database-isolation!
metabase-enterprise.workspaces.test-util/create-ready-ws!
;; Safe in tests, where we typically use a private, temporary index per thread.
@@ -471,14 +473,15 @@
metabase.premium-features.settings/define-premium-feature clojure.core/def
metabase.pulse.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let
metabase.pulse.send-test/with-pulse-for-card clojure.core/let
metabase.query-processor.pipeline-queries-test/pmbql-query clojure.core/->
metabase.query-processor.pipeline-queries-test/run-pmbql-query clojure.core/->
metabase.query-processor.pipeline-queries-test/mbql5-query clojure.core/->
metabase.query-processor.pipeline-queries-test/run-mbql5-query clojure.core/->
metabase.query-processor.error-type/deferror clojure.core/def
metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let
metabase.query-processor.middleware.process-userland-query-test/with-query-execution! clojure.core/let
metabase.query-processor.setup/with-qp-setup clojure.core/let
metabase.search.api-test/do-test-users clojure.core/let
metabase.settings.core/define-multi-setting clojure.core/def
metabase.api-scope.core/defscope clj-kondo.lint-as/def-catch-all
metabase.settings.core/defsetting clj-kondo.lint-as/def-catch-all
metabase.settings.models.setting.multi-setting/define-multi-setting clojure.core/def
metabase.settings.models.setting/defsetting clj-kondo.lint-as/def-catch-all
@@ -489,6 +492,7 @@
metabase.task.impl/defjob clojure.core/defn
metabase.test.data.interface/defdataset clojure.core/def
metabase.test.data.interface/defdataset-edn clojure.core/def
metabase.test.embedded-postgres.core/with-system clojure.core/let
metabase.test.gentest/defgentest clojure.test/deftest
metabase.test/defdataset clojure.core/def
metabase.test/let-url clojure.core/let
@@ -589,6 +593,8 @@
metabase.lib.schema.mbql-clause/define-catn-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
metabase.lib.schema.mbql-clause/define-tuple-mbql-clause hooks.metabase.lib.schema.mbql-clause/define-mbql-clause
methodical.core/defmethod hooks.metabase.toucan.table-name/lint-defmethod
methodical.macros/defmethod hooks.metabase.toucan.table-name/lint-defmethod
metabase.lib.test-util.macros/$ids hooks.metabase.test.data/$ids
metabase.lib.test-util.macros/mbql-query hooks.metabase.test.data/mbql-query
metabase.lib.test-util.macros/mbql-5-query hooks.metabase.test.data/mbql-query

View File

@@ -155,18 +155,27 @@
agent-api
{:team "Metabot"
:api #{metabase.agent-api.api}
:friends #{enterprise/agent-api}
:uses #{api
:api #{metabase.agent-api.api
metabase.agent-api.init}
:uses #{agent-lib
api
auth-identity
lib
llm
metabot
query-processor
request
server
settings
util}
:model-imports #{:model/User}}
agent-lib
{:team "Metabot"
:api #{metabase.agent-lib.core}
:uses #{lib
util}}
analytics
{:team "UX West"
:api #{metabase.analytics.api
@@ -216,8 +225,7 @@
analyze
{:team "Graphy"
:api #{metabase.analyze.core
metabase.analyze.query-results}
:api #{metabase.analyze.core}
:uses #{config
driver
lib
@@ -242,7 +250,8 @@
metabase.api.settings
metabase.api.util
metabase.api.util.handlers}
:uses #{config
:uses #{api-scope
config
events
models
settings
@@ -290,6 +299,11 @@
metabase.api-routes.core}
:uses :any}
api-scope
{:team "Metabot"
:api #{metabase.api-scope.core
metabase.api-scope.init}}
app-db
{:team "UX West"
:api #{metabase.app-db.cluster-lock ; TODO FIXME
@@ -947,6 +961,7 @@
:api #{metabase.frontend-errors.api}
:uses #{analytics
api
request
util}}
geojson
@@ -999,11 +1014,11 @@
internal-stats
{:team "UX West"
:api #{metabase.internal-stats.core
metabase.internal-stats.metabot}
:api #{metabase.internal-stats.core}
:uses #{app-db
models}
:model-imports #{:model/Card
:model-imports #{:model/AiUsageLog
:model/Card
:model/Dashboard
:model/MetabotMessage
:model/QueryExecution
@@ -1055,7 +1070,8 @@
legacy-mbql
types
util}
:friends #{legacy-mbql ;; Reuses internal schemas and a few helper functions. Deeply coupled to MBQL.
:friends #{agent-lib ;; Structured MBQL builder; needs direct access to a few lib internals.
legacy-mbql ;; Reuses internal schemas and a few helper functions. Deeply coupled to MBQL.
lib-be ;; BE-specific extension of lib; tight coupling is acceptable.
query-processor ;; Necessarily manipulates MBQL; better to pull most logic into a new module.
source-swap ;; Manipulates MBQL queries; tight coupling with lib is expected.
@@ -1095,16 +1111,18 @@
llm
{:team "Metabot"
:api #{metabase.llm.anthropic
metabase.llm.api
:api #{metabase.llm.api
metabase.llm.init
metabase.llm.settings}
metabase.llm.settings
metabase.llm.startup}
:uses #{analytics
api
driver
lib
lib-be
metabot
models
premium-features
request
settings
sql-tools
@@ -1140,13 +1158,18 @@
mcp
{:team "Metabot"
:api #{metabase.mcp.api}
:api #{metabase.mcp.api
metabase.mcp.core
metabase.mcp.init}
:uses #{agent-api
api
api-scope
config
llm
oauth-server
request
server
settings
system
util}}
@@ -1170,14 +1193,18 @@
{:team "Metabot"
:api #{metabase.metabot.api
metabase.metabot.api.entity-analysis
metabase.metabot.init}
metabase.metabot.core
metabase.metabot.init
metabase.metabot.self
metabase.metabot.settings}
:friends #{agent-api
enterprise/metabot
slackbot}
:uses #{activity-feed
agent-lib
analytics
analyze
api
api-scope
app-db
audit-app
channel
@@ -1185,7 +1212,7 @@
config
documents
driver
internal-stats
legacy-mbql
lib
lib-be
llm
@@ -1211,7 +1238,7 @@
transforms-base
util
warehouses}
:model-exports #{:model/MetabotMessage}
:model-exports #{:model/AiUsageLog :model/MetabotMessage}
:model-imports #{:model/Card
:model/Collection
:model/Dashboard
@@ -1235,11 +1262,16 @@
lib-metric
models
parameters
permissions
premium-features
query-processor
request
server
util}
:model-imports #{:model/Card :model/Field :model/Measure}}
:model-imports #{:model/Card
:model/Field
:model/Measure
:model/Table}}
model-persistence
{:team "Graphy"
@@ -1273,33 +1305,16 @@
metabase.models.interface
metabase.models.resolution
metabase.models.serialization
metabase.models.transforms.transform
metabase.models.serialization.resolve
metabase.models.util.spec-update
metabase.models.visualization-settings}
:uses #{api
app-db
classloader
collections
events
legacy-mbql
lib
lib-be
permissions
query-processor
remote-sync
search
settings
transforms
transforms-base
util
warehouse-schema}
:friends #{transforms}
:model-exports #{:model/Transform
:model/TransformJob
:model/TransformJobTransformTag
:model/TransformRun
:model/TransformTag
:model/TransformTransformTag}
util}
:model-imports #{:model/Card
:model/Collection
:model/Dashboard
@@ -1376,8 +1391,11 @@
:uses #{api
appearance
models
request
settings
system
task
tracing
util}}
;; TODO -- too many API namespaces
@@ -1444,7 +1462,6 @@
{:team "UX West"
:api #{metabase.permissions-rest.api}
:uses #{api
app-db
audit-app
events
permissions
@@ -1486,6 +1503,7 @@
config
events
internal-stats
llm
settings
startup
task
@@ -1538,7 +1556,10 @@
request
tiles
util}
:model-imports #{:model/Card :model/Dashboard :model/Document}}
:model-imports #{:model/Card
:model/Dashboard
:model/DashboardCard
:model/Document}}
pulse
{:team "Gadget"
@@ -1629,6 +1650,7 @@
:model/Field
:model/ModerationReview
:model/NativeQuerySnippet
:model/Notification
:model/Revision
:model/Table
:model/User}}
@@ -1766,6 +1788,7 @@
config
embedding
permissions ; FIXME
premium-features
session
settings
users
@@ -1887,6 +1910,7 @@
embedding
initialization-status
lib-be
mcp
oauth-server
premium-features
query-processor
@@ -1898,7 +1922,8 @@
tracing
users
util
enterprise/sso}}
enterprise/sso}
:model-imports #{:model/Session}}
session
{:team "UX West"
@@ -2000,7 +2025,8 @@
source-swap
{:team "Graphy"
:api #{metabase.source-swap.core
metabase.source-swap.schema}
metabase.source-swap.schema
metabase.source-swap.util}
:uses #{driver
lib
sql-tools
@@ -2037,6 +2063,7 @@
permissions
premium-features
request
server
settings
system
util}
@@ -2141,7 +2168,10 @@
premium-features
search
util}
:model-imports #{:model/Card :model/Dashboard}}
:model-imports #{:model/AiUsageLog
:model/Card
:model/Dashboard
:model/SecurityAdvisory}}
tiles
{:team "UX West"
@@ -2187,32 +2217,41 @@
metabase.transforms.util}
:uses #{analytics
api
app-db
channel
collections
database-routing
driver
events
lib
lib-be
models
permissions
premium-features
queries
query-processor
remote-sync
revisions
search
settings
task
task-history
tracing
transforms-base
util}
util
warehouse-schema}
:friends #{models
enterprise/transforms-python
enterprise/workspaces}
:model-imports #{:model/Database
:model/Field
:model/Table
:model/Transform
:model-exports #{:model/Transform
:model/TransformJob
:model/TransformJobTransformTag
:model/TransformTransformTag
:model/TransformRun
:model/TransformTag}
:model-imports #{:model/Collection
:model/Database
:model/Field
:model/Table
:model/User}}
transforms-base
@@ -2220,15 +2259,15 @@
:api #{metabase.transforms-base.init
metabase.transforms-base.interface
metabase.transforms-base.util}
;; TODO regressions — transforms-base should not depend on :model/Transform or
;; :model/TransformRun directly. Remove once the module is agnostic to global
;; versus workspace transforms. See https://linear.app/metabase/issue/BOT-1143
:model-imports #{:model/Database
:model/DatabaseRouter
:model/Table
;; TODO regressions — transforms-base should not depend on
;; these directly. Remove once the module is agnostic to
;; global versus workspace transforms.
:model/Transform
:model/TransformRun}
:uses #{driver
:uses #{database-routing
driver
events
lib
lib-be
@@ -2508,6 +2547,7 @@
request
sample-data
secrets
server
settings
sync
upload
@@ -2621,13 +2661,6 @@
:model/Sandbox
:model/Table}}
enterprise/agent-api
{:team "Metabot"
:api #{metabase-enterprise.agent-api.workspace}
:uses #{api
util
enterprise/workspaces}}
enterprise/analytics
{:team "UX West"
:api #{}
@@ -2722,10 +2755,25 @@
:model-imports #{:model/CacheConfig
:model/Card
:model/Dashboard
:model/DatabaseRouter
:model/Query
:model/QueryCache
:model/QueryExecution}}
enterprise/checker
{:team "Graphy"
;; todo: add ability to prevent usage (transitive even) of app db
:api #{}
:uses #{classloader
lib
models
plugins
query-processor
sql-tools
util
enterprise/dependencies}
:model-imports #{:model/Database}}
enterprise/cloud-add-ons
{:team "Cloud"
:api #{metabase-enterprise.cloud-add-ons.api}
@@ -2817,11 +2865,12 @@
warehouse-schema
warehouses}
:model-exports #{:model/DatabaseRouter}
:model-imports #{:model/Database}}
:model-imports #{:model/Database :model/Transform}}
enterprise/dependencies
{:team "Graphy"
:api #{metabase-enterprise.dependencies.api
:api #{metabase-enterprise.dependencies.analysis
metabase-enterprise.dependencies.api
metabase-enterprise.dependencies.core
metabase-enterprise.dependencies.init
metabase-enterprise.dependencies.models.dependency}
@@ -2829,7 +2878,6 @@
api
app-db
collections
config
documents
driver
events
@@ -2854,8 +2902,6 @@
:model-imports #{:model/Card
:model/Collection
:model/Dashboard
:model/DashboardCard
:model/DashboardCardSeries
:model/Document
:model/Field
:model/Measure
@@ -2944,24 +2990,30 @@
premium-features}
:model-imports #{:model/Card :model/Collection :model/Table}}
enterprise/metabot
{:team "Metabot"
:api #{}
:friends #{enterprise/agent-api
slackbot
:api #{metabase-enterprise.metabot.api
metabase-enterprise.metabot.api.routes
metabase-enterprise.metabot.init}
:friends #{slackbot
mcp
metabot}
:uses #{api
lib-be
metabot
models
permissions
premium-features
settings
task
util
enterprise/dependencies
enterprise/transforms-python}
:model-imports #{:model/Card :model/Transform}}
:model-imports #{:model/AiUsageLog
:model/Card
:model/PermissionsGroup
:model/PermissionsGroupMembership
:model/Transform}}
enterprise/permission-debug
{:team "UX West"
@@ -3029,6 +3081,7 @@
task
transforms
util
warehouse-schema
enterprise/dependencies}
:model-imports #{:model/Card
:model/Dashboard
@@ -3036,6 +3089,7 @@
:model/Field
:model/Measure
:model/PersistedInfo
:model/Sandbox
:model/Segment
:model/Table
:model/Transform}}
@@ -3098,6 +3152,28 @@
:uses #{premium-features
search}}
enterprise/security-center
{:team "Gadget"
:api #{metabase-enterprise.security-center.api
metabase-enterprise.security-center.init}
:uses #{analytics
api
app-db
channel
config
driver
events
models
notification
permissions
premium-features
settings
system
task
util}
:model-exports #{:model/SecurityAdvisory}
:model-imports #{:model/User}}
enterprise/semantic-search
{:team "Metabot"
:api #{metabase-enterprise.semantic-search.api
@@ -3133,6 +3209,7 @@
collections
config
events
lib
logger
models
plugins
@@ -3286,8 +3363,8 @@
:api #{metabase-enterprise.workspaces.api
metabase-enterprise.workspaces.init}
:model-exports #{:model/Workspace :model/WorkspaceMergeTransform}
:friends #{enterprise/agent-api}
:uses #{api
api-scope
app-db
config
database-routing
@@ -3303,7 +3380,6 @@
request
settings
sql-tools
system
transforms
transforms-base
util

View File

@@ -0,0 +1,43 @@
(ns hooks.metabase.toucan.table-name
"Lint that toucan2 model namespaces (those defining `t2/table-name`) live
under `metabase[-enterprise]/<module>/models[/…]`.
This wraps the methodical `defmethod` hook: we delegate to
`hooks.methodical.macros/defmethod` for the actual analysis (so arg bindings
etc. still work), and additionally check the file location when the
multimethod being defined is `t2/table-name`."
(:require
[clj-kondo.hooks-api :as hooks]))
(defn- models-file? [filename]
(boolean
(and filename
(re-find #"/(?:metabase|metabase_enterprise)/[^/]+/models(?:/|\.clj[cs]?$)"
filename))))
(defn- table-name-dispatch? [sym]
(and (symbol? sym)
(= (name sym) "table-name")
(#{"t2" "toucan2.core"} (namespace sym))))
(defn lint-defmethod
"Hook registered at the project level on `methodical.core/defmethod` (and
`methodical.macros/defmethod`). Performs the table-name check, then
delegates to the vendored methodical hook so kondo's normal analysis of the
defmethod body still runs."
[{:keys [filename node] :as input}]
(let [[_defmethod multimethod & _args] (:children node)
dispatch (when multimethod
(try (hooks/sexpr multimethod) (catch Exception _ nil)))]
(when (and (table-name-dispatch? dispatch)
(not (models-file? filename)))
(hooks/reg-finding!
(assoc (meta multimethod)
:message (format "Toucan2 `table-name` defmethods must live under `<module>/models[/…]` (got %s)"
filename)
:type :metabase/toucan-model-ns))))
;; Delegate to the vendored methodical defmethod hook so kondo's normal
;; analysis of the defmethod body still runs. Resolved lazily because
;; `hooks.methodical.macros` isn't on the classpath during `.clj-kondo/test`
;; unit-test runs.
((requiring-resolve 'hooks.methodical.macros/defmethod) input))

View File

@@ -0,0 +1,46 @@
(ns hooks.metabase.toucan.table-name-test
(:require
[clj-kondo.hooks-api :as hooks]
[clj-kondo.impl.utils]
[clojure.test :refer :all]
[hooks.metabase.toucan.table-name :as toucan.table-name]))
;; `hooks.methodical.macros` isn't on the classpath under `.clj-kondo/test`,
;; so stub it out — `lint-defmethod` delegates to it via `requiring-resolve`.
;; If it ever becomes available, warn so we remember to delete this stub.
(if (find-ns 'hooks.methodical.macros)
(println (str "WARNING: hooks.methodical.macros is now on the test classpath; "
"the stub in " *ns* " is no longer needed and can be removed."))
(do (create-ns 'hooks.methodical.macros)
(intern 'hooks.methodical.macros 'defmethod (fn [input] input))))
(defn- lint-defmethod [form filename]
(binding [clj-kondo.impl.utils/*ctx* {:config {:linters {:metabase/toucan-model-ns {:level :warning}}}
:ignores (atom nil)
:findings (atom [])
:namespaces (atom {})}]
(toucan.table-name/lint-defmethod {:node (hooks/parse-string (pr-str form))
:filename filename})
@(:findings clj-kondo.impl.utils/*ctx*)))
(deftest ^:parallel table-name-must-live-in-models-namespace-test
(testing "defmethod t2/table-name outside a models namespace is flagged"
(is (=? [{:type :metabase/toucan-model-ns}]
(lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/core.clj"))))
(testing "defmethod t2/table-name inside <module>/models.clj is allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/models.clj"))))
(testing "defmethod t2/table-name inside <module>/models/<model>.clj is allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/models/bar.clj"))))
(testing "enterprise modules are also allowed"
(is (empty? (lint-defmethod '(defmethod t2/table-name :model/Foo [_] :foo)
"/repo/enterprise/backend/src/metabase_enterprise/foo/models.clj"))))
(testing "toucan2.core/table-name is also recognized"
(is (=? [{:type :metabase/toucan-model-ns}]
(lint-defmethod '(defmethod toucan2.core/table-name :model/Foo [_] :foo)
"/repo/src/metabase/foo/core.clj"))))
(testing "unrelated defmethods are ignored"
(is (empty? (lint-defmethod '(defmethod t2/primary-keys :model/Foo [_] [:id])
"/repo/src/metabase/foo/core.clj")))))

View File

@@ -188,8 +188,8 @@
metabase.query-processor.error-type/deferror [[:block 1]]
metabase.query-processor.middleware.cache-backend.interface/with-cached-results [[:block 4]]
metabase.query-processor.middleware.process-userland-query-test/with-query-execution [[:block 1]]
metabase.query-processor.pipeline-queries-test/pmbql-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/run-pmbql-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/mbql5-query [[:block 1]]
metabase.query-processor.pipeline-queries-test/run-mbql5-query [[:block 1]]
metabase.query-processor.store/cached [[:block 1]]
metabase.query-processor.store/with-metadata-provider [[:inner 0]]
metabase.query-processor.streaming/streaming-response [[:block 1]]

10
.config/wt.toml Normal file
View File

@@ -0,0 +1,10 @@
# worktrunk project config — https://worktrunk.dev
# Hooks run when switching, creating, merging, or removing worktrees.
# Setup tasks for newly-created worktrees. Run once, blocking, in the
# new worktree's cwd before post-start background tasks kick off.
[pre-start]
# Trust mise config so it loads the project's tool versions without prompting.
trust-mise = "mise trust"
# Install JS deps so husky git hooks get wired up in the new worktree.
bun-install = "bun install"

View File

@@ -31,24 +31,25 @@ runs:
- name: Prepare frontend
uses: ./.github/actions/prepare-frontend
- name: Download current uberjar
uses: actions/download-artifact@v4
uses: ./.github/actions/fetch-artifact
with:
name: metabase-${{ inputs.edition }}-${{ github.event.pull_request.head.sha || github.sha }}-uberjar
path: temp/current-uberjar
commit: ${{ github.event.pull_request.head.sha || github.sha }}
edition: ${{ inputs.edition }}
extract-path: temp/current-uberjar
- name: Extract embedding-sdk bundles from current uberjar
shell: bash
run: |
# Try new layout first (app/embedding-sdk/legacy/), fall back to old layout (app/embedding-sdk.js)
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
unzip -j temp/current-uberjar/metabase.jar \
"frontend_client/app/embedding-sdk/legacy/embedding-sdk.js" \
-d temp/current-bundle 2>/dev/null || \
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
unzip -j temp/current-uberjar/metabase.jar \
"frontend_client/app/embedding-sdk.js" \
-d temp/current-bundle
# Extract chunks directory if it exists (new layout only)
mkdir -p temp/current-chunks
unzip -j temp/current-uberjar/target/uberjar/metabase.jar \
unzip -j temp/current-uberjar/metabase.jar \
"frontend_client/app/embedding-sdk/chunks/*" \
-d temp/current-chunks 2>/dev/null || true
- name: Checkout base ref

View File

@@ -7,9 +7,7 @@ inputs:
output-name:
required: true
default: test
aws-access-key-id:
required: true
aws-secret-access-key:
role-to-assume:
required: true
aws-region:
required: true
@@ -30,8 +28,7 @@ runs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
role-to-assume: ${{ inputs.role-to-assume }}
aws-region: ${{ inputs.aws-region }}
- name: zip test results
env:
@@ -50,13 +47,18 @@ runs:
aws s3 cp ${OUTPUT_FILE}.zip s3://$BUCKET/$DATE/$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/
- name: Upload results to Trunk
if: always() # Important! This step must always run because it controls job's exit code.
# Important!
# 1. This step must always run because it controls job's exit code.
# 2. There's no point in running this outside the organization
if: always() && github.repository_owner == 'metabase'
uses: trunk-io/analytics-uploader@main
env:
TRUNK_REPO_URL: git@github.com:metabase/metabase.git
with:
junit-paths: ${{ inputs.input-path }}
variant: ${{ inputs.variant || inputs.output-name }}
org-slug: metabase
dry-run: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true }} # quarantine, but do not upload results
dry-run: ${{ (github.event_name == 'pull_request' && github.event.pull_request.draft == true) || github.repository != 'metabase/metabase' }} # quarantine, but do not upload results
token: ${{ inputs.trunk-api-token }}
use-cache: true
previous-step-outcome: ${{ inputs.previous-step-outcome }}

View File

@@ -200,6 +200,10 @@ cljs:
- ".github/workflows/cljs.yml"
- "shadow-cljs.edn"
serialization_baseline:
- ".github/workflows/serialization.yml"
- "test_resources/serialization_baseline/**"
openapi:
- ".github/workflows/openapi.yml"
- "src/metabase/api/**"

9
.github/team.json vendored
View File

@@ -7,7 +7,8 @@
"bshepherdson",
"metamben",
"rileythomp",
"camsaul"
"camsaul",
"technomancy"
]
},
{
@@ -91,7 +92,8 @@
"deniskaber",
"alxnddr",
"andsalves",
"kevinkelleher12"
"kevinkelleher12",
"AndreyChernykh"
]
},
{
@@ -106,7 +108,8 @@
"kamilmielnik",
"stasgavrylov",
"uladzimirdev",
"egorgrushin"
"egorgrushin",
"metatyb"
]
},
{

21
.github/workflows/ai-benchmark.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: AI Benchmark
# Runs the ai-benchmark E2E test suite against a Metabase instance.
#
# NOTE: This is a stub. The real workflow — which builds the benchmark Docker
# image, boots Metabase + Postgres with a preloaded data dump, runs the
# benchmark suite, and uploads results as artifacts — is being developed on a
# feature branch. This stub exists on master so that the "Run workflow" button
# is available in the GitHub Actions UI, which lets us dispatch the real
# workflow against feature branches during development.
on:
workflow_dispatch:
jobs:
placeholder:
name: Placeholder
runs-on: ubuntu-latest
steps:
- name: Stub
run: echo "ai-benchmark workflow stub — real implementation pending"

View File

@@ -120,8 +120,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -244,8 +243,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -355,8 +353,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}

View File

@@ -7,6 +7,15 @@ on:
description: 'Athena JDBC driver version to publish'
required: true
type: string
force:
description: 'Re-deploy even if version already exists'
required: false
default: false
type: boolean
permissions:
id-token: write
contents: read
jobs:
deploy:
@@ -18,21 +27,29 @@ jobs:
uses: actions/checkout@v4
- name: Set up Babashka
uses: turtlequeue/setup-babashka@v1.7.0
uses: turtlequeue/setup-babashka@2d4df498c7a578b4f3e906283f9af913590bdec7 # v1.7.0
with:
babashka-version: 1.12.197
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_MAVEN_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_MAVEN_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_MAVEN_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Deploy Athena JDBC driver
run: ./bin/athena.bb ${{ github.event.inputs.version }} --deploy
run: ./bin/athena.bb "${VERSION}" --deploy ${{ github.event.inputs.force == 'true' && '--force' || '' }}
env:
VERSION: ${{ github.event.inputs.version }}
- name: Verify deployment
env:
VERSION: ${{ github.event.inputs.version }}
run: |
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc/${{ github.event.inputs.version }}/
echo "Athena JDBC driver version ${{ github.event.inputs.version }} has been deployed"
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc/"${VERSION}"/
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc-thin/"${VERSION}"/
echo "Verifying thin jar pom exists..."
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-jdbc-thin/"${VERSION}"/athena-jdbc-thin-"${VERSION}".pom
echo "Verifying athena-streaming..."
aws s3 ls s3://metabase-maven-downloads/com/metabase/athena-streaming/2.0/
echo "Athena JDBC driver version ${VERSION} has been deployed (uber-jar, thin jar, and athena-streaming)"

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
steps:
- uses: tibdex/github-app-token@v2.1.0
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0
id: generate-token
with:
app_id: ${{ secrets.METABASE_BOT_APP_ID }}

View File

@@ -57,7 +57,7 @@ jobs:
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json
@@ -104,7 +104,7 @@ jobs:
- name: Upload coverage to codecov.io
if: always()
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./target/coverage/codecov.json

View File

@@ -138,14 +138,13 @@ jobs:
input-path: ./target/junit/
output-name: be-tests-java-${{ matrix.java-version }}-${{ matrix.job.edition }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-java-tests.outcome }}
- name: Publish Test Report (JUnit)
uses: dorny/test-reporter@v1
uses: dorny/test-reporter@3eeb9fc888e82e8be2fb356bbeec2750231672bc # v1
if: failure()
with:
path: "target/junit/**/*_test.xml"

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
- uses: jesusvasquez333/verify-pr-label-action@657d111bbbe13e22bbd55870f1813c699bde1401 # v1.4.0
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
valid-labels: 'no-backport, backport, double-backport, triple-backport'

View File

@@ -39,6 +39,7 @@ concurrency:
jobs:
get-cache-keys:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
steps:
- uses: actions/checkout@v4
@@ -107,11 +108,6 @@ jobs:
bun run cypress verify
shell: bash
# Run Prettier to populate its cache (stored in node_modules/.cache/prettier)
# so it gets saved as part of the node_modules cache below
- name: Run Prettier to populate cache
run: bun run lint-prettier-pure
- name: Update node_modules cache
uses: ./.github/actions/update-cache
with:

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -43,7 +43,7 @@ jobs:
- name: Compile CLJS
run: NODE_ENV=development bun run build-pure:cljs
- name: Publish to Chromatic
uses: chromaui/action@v10.2.0
uses: chromaui/action@c03d144dc72e609acbb960489fdd1e1a4a23b34f # v10.2.0
env:
PUBLISH_CHROMATIC: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
STORYBOOK_BUILD_TIMEOUT: 900000

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -8,6 +8,7 @@ on:
jobs:
codespell:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
permissions:
@@ -67,7 +68,7 @@ jobs:
- name: Fix spelling errors with Claude Code
if: steps.codespell_check.outcome == 'failure'
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@3ac52d0da9f8ec9ca7b4dc23bb477e36ef9c77a9 # v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ steps.claude-app-token.outputs.token }}
@@ -82,7 +83,7 @@ jobs:
- name: Create Pull Request
if: steps.codespell_check.outcome == 'failure'
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: claude-fix/codespell-${{ steps.date.outputs.date }}

View File

@@ -55,9 +55,11 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 1
# we want the dockerfile from the commit we're building
- run: git checkout ${{ inputs.commit }} -- bin/docker
- run: |
git fetch --depth=1 origin ${{ inputs.commit }}
git checkout ${{ inputs.commit }} -- bin/docker
- name: Retrieve uberjar artifact
uses: ./.github/actions/fetch-artifact
with:
@@ -84,19 +86,19 @@ jobs:
- name: Move the Uberjar to the context dir
run: mv ./metabase.jar bin/docker/.
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build multi-arch container
if: contains(steps.platforms.outputs.result, 'arm')
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: bin/docker/.
platforms: ${{ steps.platforms.outputs.result }}
@@ -122,7 +124,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}
@@ -142,7 +144,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_USERNAME || secrets.DOCKERHUB_USERNAME }}
password: ${{ inputs.release == 'true' && secrets.DOCKERHUB_RELEASE_TOKEN || secrets.DOCKERHUB_TOKEN }}

View File

@@ -106,7 +106,7 @@ jobs:
edition: ee
- name: Post PR comment
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
with:
header: docker-build-status
message: |

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -7,6 +7,7 @@ on:
jobs:
fe-test-coverage:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 60
steps:
@@ -22,7 +23,7 @@ jobs:
--coverage \
--testTimeout=60000
- name: Upload coverage to codecov.io
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
with:
files: ./coverage/lcov.info
flags: front-end

View File

@@ -25,7 +25,7 @@ on:
jobs:
# Generate matrix for scheduled/PR runs
generate-matrix:
if: github.event_name != 'workflow_dispatch'
if: github.event_name != 'workflow_dispatch' && github.repository == 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
@@ -47,7 +47,7 @@ jobs:
# Matrix-based tests for scheduled/PR runs
test-matrix:
if: github.event_name != 'workflow_dispatch'
if: github.event_name != 'workflow_dispatch' && github.repository == 'metabase/metabase'
needs: generate-matrix
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
@@ -102,7 +102,11 @@ jobs:
# Notify Slack on nightly failures
notify-on-failure:
if: always() && github.event_name == 'schedule' && needs.test-matrix.result == 'failure'
if: >-
always()
&& github.event_name == 'schedule'
&& github.repository == 'metabase/metabase'
&& needs.test-matrix.result == 'failure'
needs: [test-matrix]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5

View File

@@ -7,6 +7,7 @@ on:
jobs:
notify-disabled-drivers:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:

View File

@@ -27,7 +27,7 @@ jobs:
- name: Run link checker
id: link-check
uses: lycheeverse/lychee-action@v2.3.0
uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # v2.3.0
with:
fail: true
args: docs --config ./.lychee/config.toml --offline

View File

@@ -7,6 +7,7 @@ on:
jobs:
update-master-docs:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Call documentation update for master
uses: ./.github/workflows/docs-generate-base.yml
with:

View File

@@ -13,6 +13,7 @@ on:
jobs:
update-release-docs:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Call documentation update for release branch
uses: ./.github/workflows/docs-generate-base.yml
with:

View File

@@ -707,7 +707,7 @@ jobs:
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC

View File

@@ -72,8 +72,8 @@ jobs:
# 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 || github.ref_name }}"
--is-master-or-release="${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}"
--git-ref="${BASE_REF}"
--is-master-or-release="${IS_MASTER_OR_RELEASE}"
--pr-labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
--skip="${{ inputs.skip }}"
)
@@ -87,6 +87,9 @@ jobs:
echo "Run './bin/mage -driver-decisions -h' locally for the full list of drivers and labels."
# Second call: output only key=value lines for GITHUB_OUTPUT
./bin/mage -driver-decisions "${MAGE_ARGS[@]}" --github-output-only >> $GITHUB_OUTPUT
env:
BASE_REF: ${{ github.event.pull_request.base.ref || github.ref_name }}
IS_MASTER_OR_RELEASE: ${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') }}
shell: bash
be-tests-athena-ee:
@@ -134,8 +137,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -200,8 +202,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -258,7 +259,7 @@ jobs:
run: |
sudo echo "127.0.0.1 server.clickhouseconnect.test" | sudo tee -a /etc/hosts
- name: Start ClickHouse in Docker
uses: hoverkraft-tech/compose-action@v2.0.0
uses: hoverkraft-tech/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
with:
compose-file: "modules/drivers/clickhouse/docker-compose.yml"
down-flags: "--volumes"
@@ -283,8 +284,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -338,8 +338,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -387,8 +386,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -436,8 +434,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -486,8 +483,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -517,8 +513,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -549,8 +544,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -680,8 +674,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -761,8 +754,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -844,8 +836,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -890,8 +881,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -976,8 +966,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1081,8 +1070,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1169,8 +1157,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1218,7 +1205,7 @@ jobs:
MB_REDSHIFT_TEST_USER: metabase_ci
MB_REDSHIFT_TEST_DB: testdb
MB_REDSHIFT_TEST_DB_ROUTING: dev
MB_REDSHIFT_TEST_HOST: ${{ secrets.MB_REDSHIFT_TEST_HOST }}
MB_REDSHIFT_TEST_HOSTS: ${{ secrets.MB_REDSHIFT_TEST_HOSTS }}
MB_REDSHIFT_TEST_PASSWORD: ${{ secrets.MB_REDSHIFT_TEST_PASSWORD }}
name: ${{ matrix.job.name }}
steps:
@@ -1254,8 +1241,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1335,8 +1321,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1388,8 +1373,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1430,8 +1414,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1507,8 +1490,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -1553,8 +1535,7 @@ jobs:
# input-path: ./target/junit/
# output-name: ${{ github.job }}
# bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
# aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
# aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
# role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
# aws-region: ${{ vars.AWS_REGION }}
# trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
@@ -1568,7 +1549,7 @@ jobs:
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.CI_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -1634,8 +1615,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}

View File

@@ -151,10 +151,12 @@ jobs:
GREP: ${{ github.event.inputs.grep }}
CYPRESS_BROWSER: ${{ steps.cypress-prep.outputs.chrome-path }}
FAIL_FAST: ${{ inputs.fail_fast }}
SPEC: ${{ github.event.inputs.spec }}
BURN_IN: ${{ github.event.inputs.burn_in }}
run: |
node e2e/runner/run_cypress_ci.js ${{ needs.determine-test-type.outputs.type }} \
--spec '${{ github.event.inputs.spec }}' \
--env burn=${{ github.event.inputs.burn_in }} \
--spec "${SPEC}" \
--env burn="${BURN_IN}" \
--config-file e2e/support/cypress-stress-test.config.js
- name: Upload Cypress Artifacts upon failure

View File

@@ -166,8 +166,7 @@ jobs:
# Related to our custom upload to S3
output-name: e2e-${{ inputs.name }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
# Related to Trunk uploader
variant: e2e-tests

View File

@@ -94,8 +94,7 @@ jobs:
input-path: ./target/junit
output-name: e2e-embedding-sdk-react-${{ matrix.react-version }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}
@@ -248,8 +247,7 @@ jobs:
input-path: ./target/junit
output-name: e2e-embedding-sdk
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}

View File

@@ -106,6 +106,8 @@ jobs:
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -114,7 +116,7 @@ jobs:
# 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
uses: docker/setup-compose-action@2fe291b7677a45ee1269ec56a42604c143505e7e # v1
with:
version: latest
@@ -213,6 +215,8 @@ jobs:
SAMPLE_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -277,6 +281,7 @@ jobs:
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4
@@ -362,6 +367,8 @@ jobs:
HOST_APP_ENVIRONMENT: ${{ matrix.environment }}
permissions:
id-token: write
contents: read
actions: read
steps:
- uses: actions/checkout@v4

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -33,7 +33,7 @@ jobs:
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: jesusvasquez333/verify-pr-label-action@v1.4.0
- uses: jesusvasquez333/verify-pr-label-action@657d111bbbe13e22bbd55870f1813c699bde1401 # v1.4.0
id: verify-release-notes-labels
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -18,7 +18,7 @@ jobs:
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Send escalated issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@v1.23.0
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 # v1.23.0
with:
payload: |
{

View File

@@ -47,11 +47,16 @@ jobs:
run: bun run build:cljs
- name: Stress-test ${{ github.event.inputs.spec }} ${{ github.event.inputs.burn_in }} times
run: |
for i in {1..${{ github.event.inputs.burn_in }}}; do
for ((i=1; i<=BURN_IN; i++)); do
bun run jest --runInBand --ci --silent --no-cache \
--testPathPatterns '${{ github.event.inputs.spec }}' \
--testNamePattern '${{ github.event.inputs.grep }}'
--testPathPatterns "${SPEC}" \
--testNamePattern "${GREP}"
if [[ "$?" != 0 ]]; then
echo "Failed after $i attempts" && break;
fi
done
env:
BURN_IN: ${{ github.event.inputs.burn_in }}
SPEC: ${{ github.event.inputs.spec }}
GREP: ${{ github.event.inputs.grep }}

View File

@@ -16,7 +16,7 @@ on:
type: boolean
default: false
skip-lint:
description: "Skip linting jobs (prettier, eslint, css variables check)"
description: "Skip linting jobs (oxfmt, eslint, css variables check)"
type: boolean
default: false
@@ -49,8 +49,8 @@ jobs:
run: bun run build-pure:cljs
- name: Find CSS variables that are used but never defined
run: bun run find-never-defined-css-variables
- name: Run Prettier formatting linter
run: bun run lint-prettier-pure
- name: Run oxfmt formatting linter
run: bun run lint-format-pure
- name: Run ESLint linter
run: bun run lint-eslint-pure
- name: Run CSS linter
@@ -103,8 +103,7 @@ jobs:
input-path: ./target/junit
output-name: frontend-unit
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.fe-tests.outcome }}

View File

@@ -47,7 +47,7 @@ jobs:
run: clojure -X:dev:ci:test:ee:ee-dev :only '${{ inputs.test }}'
- name: Publish Test Report (JUnit)
uses: dorny/test-reporter@v1
uses: dorny/test-reporter@3eeb9fc888e82e8be2fb356bbeec2750231672bc # v1
if: failure()
with:
path: "target/junit/**/*_test.xml"

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -7,6 +7,7 @@ on:
jobs:
js-stats:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -19,7 +19,7 @@ jobs:
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Send the issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@v1.26.0
uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0
with:
payload: |
{

View File

@@ -8,7 +8,7 @@ on:
- unlabeled
- synchronize
paths:
- src/metabase/lib/js.cljs
- src/metabase/utils/js.cljs
jobs:
mbql-library-changelog-check:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}

View File

@@ -6,10 +6,12 @@ on:
- 'master'
- 'release-**'
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
lint-migrations:
if: github.event.action != 'labeled' && github.event.action != 'unlabeled'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 15
steps:
@@ -18,3 +20,19 @@ jobs:
uses: ./.github/actions/prepare-backend
- name: Verify Liquibase Migrations
run: ./bin/mage lint-migrations
check-migration-versions:
if: github.event_name == 'pull_request'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0
- name: Check migration versions
env:
BASE_REF: ${{ github.base_ref }}
CURRENT_VERSION: ${{ vars.CURRENT_VERSION }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
run: bin/check-migration-versions

View File

@@ -8,6 +8,12 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
permissions:
id-token: write
contents: read
checks: write
actions: read
jobs:
# Only run if the PR has the minimal-tests label
check-label:
@@ -70,8 +76,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -118,8 +123,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -151,8 +155,7 @@ jobs:
input-path: ./target/junit
output-name: frontend-unit
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.run-tests.outcome }}

View File

@@ -7,11 +7,12 @@ on:
jobs:
module-boundaries-stats:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: get module boundary stats
@@ -23,6 +24,12 @@ jobs:
console.log(named.length);
")
ENFORCED_MODULES=$(node --input-type=module -e "
import { elements } from './frontend/lint/module-boundaries.mjs';
const enforced = new Set(elements.filter(e => e.enforceOutgoing).map(e => e.type));
console.log(enforced.size);
")
NAMED_DIRS=$(node --input-type=module -e "
import { elements } from './frontend/lint/module-boundaries.mjs';
const dirs = new Set();
@@ -38,15 +45,17 @@ jobs:
VIOLATIONS=$(bun run module-boundaries 2>&1 | grep -o '✖ [0-9]* problems' | grep -o '[0-9]*' || echo "0")
echo "$NAMED_MODULES modules, $UNMODULED unmoduled folders, $VIOLATIONS violations"
echo "$NAMED_MODULES modules, $ENFORCED_MODULES enforced, $UNMODULED unmoduled folders, $VIOLATIONS violations"
echo "NAMED_MODULES=$NAMED_MODULES" >> $GITHUB_OUTPUT
echo "ENFORCED_MODULES=$ENFORCED_MODULES" >> $GITHUB_OUTPUT
echo "UNMODULED=$UNMODULED" >> $GITHUB_OUTPUT
echo "VIOLATIONS=$VIOLATIONS" >> $GITHUB_OUTPUT
- name: Upload stats
uses: actions/github-script@v7
env:
NAMED_MODULES: ${{ steps.get-stats.outputs.NAMED_MODULES }}
ENFORCED_MODULES: ${{ steps.get-stats.outputs.ENFORCED_MODULES }}
UNMODULED: ${{ steps.get-stats.outputs.UNMODULED }}
VIOLATIONS: ${{ steps.get-stats.outputs.VIOLATIONS }}
API_KEY: ${{ secrets.STATS_API_KEY }}
@@ -55,6 +64,7 @@ jobs:
const { uploadCsvToMb } = require('${{ github.workspace }}/.github/scripts/csv-to-mb.js');
console.log("NAMED_MODULES", Number(process.env.NAMED_MODULES));
console.log("ENFORCED_MODULES", Number(process.env.ENFORCED_MODULES));
console.log("UNMODULED", Number(process.env.UNMODULED));
console.log("VIOLATIONS", Number(process.env.VIOLATIONS));
@@ -62,6 +72,7 @@ jobs:
{
'Date': new Date().toISOString().slice(0, 10), // YYYY-MM-DD
'Modules': Number(process.env.NAMED_MODULES),
'Enforced modules': Number(process.env.ENFORCED_MODULES),
'Unmoduled folders': Number(process.env.UNMODULED),
'Violations': Number(process.env.VIOLATIONS),
}

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -17,13 +17,13 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: us-east-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2
with:
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
- name: Retrieve uberjar artifact for ee
@@ -38,7 +38,7 @@ jobs:
jar xf target/uberjar/metabase.jar
mv target/uberjar/metabase.jar bin/docker/metabase.jar
- name: Build container
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: bin/docker/
platforms: linux/amd64
@@ -56,7 +56,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@v2
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -67,12 +67,14 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@v2
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
with:
method: kubeconfig
kubeconfig: |
@@ -95,7 +97,7 @@ jobs:
namespace: default
user: oidc-token
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -108,7 +110,7 @@ jobs:
id: split
run: echo "::set-output name=fragment::${SHA:5}"
- name: Render Deployment YAML
uses: nowactions/envsubst@v1
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
with:
input: ./perf-test-pr.yml.tmpl
output: ./perf-test-pr.yml

View File

@@ -28,7 +28,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@v2
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -39,12 +39,14 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@v2
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
with:
method: kubeconfig
kubeconfig: |
@@ -73,7 +75,7 @@ jobs:
kubectl delete ns hosting-pr${{ env.PR_NUMBER }}
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@v4
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
with:
version: 'latest'
- name: Destroy PR Review ENV (self-hosting)
@@ -87,9 +89,15 @@ jobs:
sudo apt-get install -y postgresql-client
- name: Drop app database if exists
run: |
PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}' \
PGPASSWORD="${PR_ENV_DB_PASSWORD}" \
psql \
-h ${{ secrets.PR_ENV_DB_HOST }} \
-U ${{ secrets.PR_ENV_DB_USER }} \
-d ${{ secrets.PR_ENV_DB_NAME }} \
-h "${PR_ENV_DB_HOST}" \
-U "${PR_ENV_DB_USER}" \
-d "${PR_ENV_DB_NAME}" \
-c "DROP DATABASE IF EXISTS hosting_pr${{ env.PR_NUMBER }};"
env:
PR_ENV_DB_PASSWORD: ${{ secrets.PR_ENV_DB_PASSWORD }}
PR_ENV_DB_HOST: ${{ secrets.PR_ENV_DB_HOST }}
PR_ENV_DB_USER: ${{ secrets.PR_ENV_DB_USER }}
PR_ENV_DB_NAME: ${{ secrets.PR_ENV_DB_NAME }}

View File

@@ -28,13 +28,13 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: us-east-1
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
uses: aws-actions/amazon-ecr-login@183a1442edf41672e66566b7fc560e297a290896 # v2
with:
registries: "${{ secrets.PR_ENV_AWS_ACCOUNT_ID }}"
- name: Retrieve uberjar artifact for ee
@@ -49,7 +49,7 @@ jobs:
jar xf target/uberjar/metabase.jar
mv target/uberjar/metabase.jar bin/docker/metabase.jar
- name: Build container
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: bin/docker/
platforms: linux/amd64
@@ -67,7 +67,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Tailscale
uses: tailscale/github-action@v2
uses: tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2
with:
oauth-client-id: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.PR_ENV_TAILSCALE_OAUTH_SECRET }}
@@ -78,12 +78,14 @@ jobs:
id: create-oidc-token
shell: bash
run: |
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${{ secrets.PR_ENV_K8S_AUDIENCE }}"
export OIDC_URL_WITH_AUDIENCE="$ACTIONS_ID_TOKEN_REQUEST_URL&audience=${PR_ENV_K8S_AUDIENCE}"
IDTOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" -H "Accept: application/json; api-version=2.0" "$OIDC_URL_WITH_AUDIENCE" | jq -r .value)
echo "::add-mask::${IDTOKEN}"
echo "idToken=${IDTOKEN}" >>$GITHUB_OUTPUT
env:
PR_ENV_K8S_AUDIENCE: ${{ secrets.PR_ENV_K8S_AUDIENCE }}
- name: Setup Kube Context
uses: azure/k8s-set-context@v2
uses: azure/k8s-set-context@0b53301dcc7ddd3f6d35f97fb67228ac4e5ab2fd # v2
with:
method: kubeconfig
kubeconfig: |
@@ -106,7 +108,7 @@ jobs:
namespace: default
user: oidc-token
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: ${{ secrets.PR_ENV_IAM_ROLE }}
role-session-name: GitHub_to_AWS_via_FederatedOIDC
@@ -117,19 +119,24 @@ jobs:
sudo apt-get install -y postgresql-client
- name: Create app database if not exists
run: |
export PGPASSWORD='${{ secrets.PR_ENV_DB_PASSWORD }}'
export PGUSER=${{ secrets.PR_ENV_DB_USER }}
export PGHOST=${{ secrets.PR_ENV_DB_HOST }}
export PGDATABASE=${{ secrets.PR_ENV_DB_NAME }}
export PGPASSWORD="${PR_ENV_DB_PASSWORD}"
export PGUSER="${PR_ENV_DB_USER}"
export PGHOST="${PR_ENV_DB_HOST}"
export PGDATABASE="${PR_ENV_DB_NAME}"
psql -tc "SELECT 1 FROM pg_database WHERE datname = 'hosting_pr${{ github.event.number }}'" \
| grep -q 1 \
|| psql -c "CREATE DATABASE hosting_pr${{ github.event.number }}"
env:
PR_ENV_DB_PASSWORD: ${{ secrets.PR_ENV_DB_PASSWORD }}
PR_ENV_DB_USER: ${{ secrets.PR_ENV_DB_USER }}
PR_ENV_DB_HOST: ${{ secrets.PR_ENV_DB_HOST }}
PR_ENV_DB_NAME: ${{ secrets.PR_ENV_DB_NAME }}
- name: Download Deployment YAML template
if: ${{ !inputs.self_hosting }}
run: aws s3 cp s3://metabase-pr-env/metabase.yml.tmpl ./metabase.yml.tmpl
- name: Render Deployment YAML
if: ${{ !inputs.self_hosting }}
uses: nowactions/envsubst@v1
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
with:
input: ./metabase.yml.tmpl
output: ./metabase.yml
@@ -148,7 +155,7 @@ jobs:
run: aws s3 cp s3://metabase-pr-env/values.yaml.tmpl ./values.yaml.tmpl
- name: Render Helm values (self-hosting)
if: ${{ inputs.self_hosting }}
uses: nowactions/envsubst@v1
uses: nowactions/envsubst@c4b8e3977354d294659a7ec164279ce8fba5c2e1 # v1
with:
input: ./values.yaml.tmpl
output: ./values.yaml
@@ -168,15 +175,18 @@ jobs:
- name: Setup Helm
if: ${{ inputs.self_hosting }}
uses: azure/setup-helm@v4
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
with:
version: 'latest'
- name: Login to Quay.io
if: ${{ inputs.self_hosting }}
run: |
helm registry login quay.io \
--username ${{ secrets.QUAY_USERNAME }} \
--password ${{ secrets.QUAY_PASSWORD }}
--username "${QUAY_USERNAME}" \
--password "${QUAY_PASSWORD}"
env:
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
- name: Deploy PR Review ENV (self-hosting)
if: ${{ inputs.self_hosting }}
run: |
@@ -191,7 +201,7 @@ jobs:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
needs: [deploy_pr]
steps:
- uses: marocchino/sticky-pull-request-comment@v2
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
with:
recreate: true
message: |

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -383,12 +383,14 @@ jobs:
- name: Publish to NPM
working-directory: sdk
run: |
echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_RELEASE_TOKEN }} > .npmrc
echo //registry.npmjs.org/:_authToken="${NPM_RELEASE_TOKEN}" > .npmrc
TAG="${{ steps.compute-tag.outputs.masterTag }}"
echo "Resolved npm tag: $TAG"
npm publish --tag "$TAG"
env:
NPM_RELEASE_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
# ⚠️ This step should only be executed on the latest stable (gold) release branch.
# Only uncomment this when the new release branch becomes stable (gold).
# - name: Add `latest` tag to the latest release branch (`release-x.55.x`) deployment

View File

@@ -21,6 +21,10 @@ on:
type: number # needs to be a number to pass variables
required: true
permissions:
id-token: write
contents: read
jobs:
update-release-log:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
@@ -29,10 +33,9 @@ jobs:
if: ${{ github.event_name != 'push' || github.repository == 'metabase/metabase' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:

View File

@@ -14,7 +14,6 @@ on:
jobs:
check-milestone:
permissions: write-all
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
steps:
@@ -22,7 +21,7 @@ jobs:
with:
sparse-checkout: release
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -27,7 +27,7 @@ jobs:
with:
sparse-checkout: release
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -53,6 +53,11 @@ on:
type: boolean
default: false
permissions:
id-token: write
contents: read
actions: read
jobs:
green-check-stub:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
@@ -146,10 +151,9 @@ jobs:
edition: ${{ fromJson(needs.check-version.outputs.edition_matrix) }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Determine the source path
uses: actions/github-script@v7
@@ -243,7 +247,7 @@ jobs:
fi
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ secrets.DOCKERHUB_RELEASE_USERNAME }}
password: ${{ secrets.DOCKERHUB_RELEASE_TOKEN }}
@@ -255,7 +259,8 @@ jobs:
$DOCKERHUB_REPO\:$DOCKERHUB_VERSION
push-git-tags:
permissions: write-all
permissions:
contents: write
needs: check-version
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
@@ -299,10 +304,9 @@ jobs:
AWS_REGION: ${{ vars.AWS_REGION }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}
@@ -37,7 +37,7 @@ jobs:
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -56,7 +56,7 @@ jobs:
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -75,7 +75,7 @@ jobs:
with:
sparse-checkout: "release"
- name: Install bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -46,6 +46,13 @@ on:
type: boolean
default: false
permissions:
id-token: write
contents: write
checks: write
issues: write
actions: read
jobs:
check-version:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
@@ -186,10 +193,9 @@ jobs:
edition: [oss, ee]
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -285,7 +291,6 @@ jobs:
release: "true"
push-tags:
permissions: write-all
needs: [verify-s3-download, containerize-oss, containerize-ee, check-version]
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
@@ -448,7 +453,6 @@ jobs:
needs: add-extra-tags
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 15
permissions: write-all
env:
AWS_S3_DOWNLOADS_BUCKET: ${{ vars.AWS_S3_DOWNLOADS_BUCKET }}
DOCKERHUB_OWNER: ${{ vars.DOCKERHUB_OWNER }}
@@ -530,10 +534,9 @@ jobs:
echo "skipping version info update for nightly patch"
exit 0
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -622,7 +625,6 @@ jobs:
manage-milestones:
if: ${{ !inputs.auto }}
permissions: write-all
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
needs: [draft-release-notes, publish-version-info] # this will ensure that the milestone stays open until the release notes are published
timeout-minutes: 15

View File

@@ -33,13 +33,15 @@ jobs:
id: issue
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
ISSUE_NUMBER="${{ github.event.inputs.issue_number }}"
ISSUE_NUMBER="${DISPATCH_ISSUE_NUMBER}"
else
ISSUE_NUMBER="${{ github.event.issue.number }}"
fi
echo "number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT
echo "Issue: https://github.com/${{ github.repository }}/issues/$ISSUE_NUMBER"
env:
DISPATCH_ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}
- uses: actions/checkout@v4
- uses: ./.github/actions/prepare-backend
- uses: ./.github/actions/prepare-frontend
@@ -128,7 +130,7 @@ jobs:
- name: Run repro-bot with Claude Code
id: claude
continue-on-error: true
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@3ac52d0da9f8ec9ca7b4dc23bb477e36ef9c77a9 # v1
env:
CLAUDE_CODE_DEBUG_LOGS_DIR: /tmp/claude-debug-logs
CLAUDE_WORKING_DIR: ${{ github.workspace }}/repro-bot

View File

@@ -0,0 +1,321 @@
name: Auto-resolve backport conflicts
run-name: Auto-resolve conflicts on PR #${{ github.event.pull_request.number || github.event.inputs.pr_number }}
on:
pull_request:
types: [labeled]
workflow_dispatch:
inputs:
pr_number:
description: "PR number of the backport PR with conflicts"
required: true
type: string
max_turns:
description: "Maximum number of Claude turns"
required: false
type: number
default: 75
jobs:
resolve-conflicts:
if: |
(github.event_name == 'pull_request' && github.event.label.name == 'auto-resolve-conflicts') ||
(github.event_name == 'workflow_dispatch')
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
id-token: write
env:
GIT_EDITOR: "true"
steps:
- name: Get PR data
id: pr
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
let pr;
if (context.eventName === 'workflow_dispatch') {
const { data } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: Number('${{ github.event.inputs.pr_number }}'),
});
pr = data;
} else {
pr = context.payload.pull_request;
}
core.setOutput('number', pr.number);
core.setOutput('head_ref', pr.head.ref);
core.setOutput('base_ref', pr.base.ref);
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ steps.pr.outputs.head_ref }}
fetch-depth: 0
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Detect package manager
id: pkg
run: |
if [ -f "bun.lock" ] || [ -f "bun.lockb" ]; then
echo "mgr=bun" >> $GITHUB_OUTPUT
else
echo "mgr=yarn" >> $GITHUB_OUTPUT
fi
- name: Validate backport PR
id: validate
run: |
if [ ! -f "./backport.sh" ]; then
echo "::error::No backport.sh found on this branch. This workflow only works on backport PRs with conflicts."
exit 1
fi
echo "backport.sh found, proceeding with conflict resolution"
cat ./backport.sh
- name: Set up git configuration
run: |
git config --global user.name "Metabase bot"
git config --global user.email "metabase-bot@metabase.com"
git config --global core.editor "true"
- name: Run backport script to create cherry-pick conflicts
id: backport
run: |
bash ./backport.sh 2>&1 || true
# Check if we're in a cherry-pick state with conflicts
CONFLICTED=$(git diff --name-only --diff-filter=U || true)
if [ -n "$CONFLICTED" ]; then
echo "has_conflicts=true" >> $GITHUB_OUTPUT
echo "conflicted_files<<EOF" >> $GITHUB_OUTPUT
echo "$CONFLICTED" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "::notice::Found conflicts in: $CONFLICTED"
else
echo "has_conflicts=false" >> $GITHUB_OUTPUT
echo "::notice::Cherry-pick applied cleanly, no conflicts to resolve"
fi
- name: Push clean cherry-pick (no conflicts)
if: steps.backport.outputs.has_conflicts == 'false'
run: git push --force
- name: Prepare back-end environment
if: steps.backport.outputs.has_conflicts == 'true'
uses: ./.github/actions/prepare-backend
- name: Prepare front-end environment
if: steps.backport.outputs.has_conflicts == 'true'
uses: ./.github/actions/prepare-frontend
- name: Build CLJS for type checking
if: steps.backport.outputs.has_conflicts == 'true'
run: ${{ steps.pkg.outputs.mgr }} run build-pure:cljs
- name: Generate GitHub App Token for Claude
if: steps.backport.outputs.has_conflicts == 'true'
uses: actions/create-github-app-token@v1
id: claude-app-token
with:
app-id: ${{ secrets.METABASE_BOT_APP_ID }}
private-key: ${{ secrets.METABASE_BOT_APP_PRIVATE_KEY }}
- name: Resolve conflicts with Claude
if: steps.backport.outputs.has_conflicts == 'true'
id: claude
uses: anthropics/claude-code-base-action@beta
env:
NODE_VERSION: "22"
with:
# TODO: get a dedicated API key, sharing repro bot's key for now
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_BACKPORT_FIX }}
model: "claude-sonnet-4-20250514"
max_turns: "${{ github.event.inputs.max_turns || '75' }}"
allowed_tools: "Read,Edit,Write,Bash(git:*),Bash(grep:*),Bash(find:*),Bash(ls:*),Bash(cat:*),Bash(clojure:*),Bash(${{ steps.pkg.outputs.mgr }} run type-check-pure:*),Bash(${{ steps.pkg.outputs.mgr }} run lint-eslint:*),Bash(./bin/mage cljfmt:*),Glob,Grep"
prompt: |
You are resolving git cherry-pick merge conflicts for a Metabase backport PR.
## Context
A commit from the `master` branch is being cherry-picked onto the `${{ steps.pr.outputs.base_ref }}` release branch.
The cherry-pick has failed with conflicts in these files:
${{ steps.backport.outputs.conflicted_files }}
## How to read conflict markers
```
<<<<<<< HEAD
(code from the release branch - the current state)
=======
(code from master - the change being backported)
>>>>>>> <commit-sha>
```
## Common conflict patterns in Metabase backports
- **Module/namespace renames**: A Clojure namespace was renamed or moved between versions. Check what the correct namespace is on the release branch.
- **Test file divergence**: Tests may have been added or reordered differently. Keep the backported test changes while preserving existing release-branch tests.
- **Import path changes**: Frontend imports may reference different paths. Check actual file locations on this branch.
- **Translation files (.po)**: For translation conflicts, prefer the release branch version of existing translations and add new translations from the backported change.
- **Minor context differences**: Surrounding code changed slightly. Adapt the backported change to fit the release branch context.
## Instructions
1. Read each conflicted file to understand the conflict
2. For context, you may search the codebase to understand the current state of the release branch (e.g., check if a module exists at a certain path)
3. Resolve each conflict by editing the file to remove ALL conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and leave only the correct resolved code
4. The goal is to apply the INTENT of the backported change while adapting it to the release branch's code structure
5. Verify no conflict markers remain by searching for them
6. Auto-format resolved files:
- **Clojure/ClojureScript** (.clj, .cljc, .cljs): run cljfmt to fix formatting:
```
./bin/mage cljfmt-files <file1> <file2> ...
```
- **TypeScript/JavaScript** (.ts, .tsx, .js, .jsx): run eslint with auto-fix:
```
${{ steps.pkg.outputs.mgr }} run lint-eslint --fix
```
7. Validate resolved files before committing:
- **Clojure/ClojureScript** (.clj, .cljc, .cljs): run clj-kondo to verify syntax:
```
clojure -M:kondo --lint <file1> <file2> ...
```
- **TypeScript/JavaScript** (.ts, .tsx, .js, .jsx): run the type checker:
```
${{ steps.pkg.outputs.mgr }} run type-check-pure
```
If either tool reports errors or warnings, fix them before proceeding. CI treats kondo warnings as errors.
Repeat steps 6-7 until all errors are resolved.
7. Once all files are clean, stage and complete the cherry-pick:
```
git add -A
git cherry-pick --continue
```
8. Push the result:
```
git push --force
```
IMPORTANT: Do not skip any conflicted files. Every conflict marker must be resolved.
- name: Check for max turns exceeded
if: always() && steps.claude.outcome != 'skipped'
id: check-failure
run: |
if [ -f "${{ steps.claude.outputs.execution_file }}" ]; then
SUBTYPE=$(jq -r '.[] | select(.type == "result") | .subtype' "${{ steps.claude.outputs.execution_file }}" | tail -1)
if [ "$SUBTYPE" = "error_max_turns" ]; then
echo "max_turns_exceeded=true" >> $GITHUB_OUTPUT
else
echo "max_turns_exceeded=false" >> $GITHUB_OUTPUT
fi
else
echo "max_turns_exceeded=false" >> $GITHUB_OUTPUT
fi
- name: Get commit details
if: always() && steps.claude.outcome == 'success' && steps.check-failure.outputs.max_turns_exceeded != 'true'
id: commit-info
run: |
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
FILES=$(git diff --name-only HEAD~1)
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment on PR (success)
if: always() && steps.claude.outcome == 'success' && steps.check-failure.outputs.max_turns_exceeded != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
const sha = '${{ steps.commit-info.outputs.sha }}';
const changedFiles = `${{ steps.commit-info.outputs.changed_files }}`.trim().split('\n').map(f => `- \`${f}\``).join('\n');
await github.rest.issues.createComment({
issue_number: ${{ steps.pr.outputs.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: [
'🤖 Backport conflicts have been automatically resolved and force-pushed.',
'',
`**Commit:** [\`${sha.substring(0, 10)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha})`,
'',
'<details>',
'<summary>Changed files</summary>',
'',
changedFiles,
'',
'</details>',
'',
'Please review the changes to make sure the resolution is correct before merging.',
].join('\n')
});
- name: Comment on PR (max turns exceeded)
if: always() && steps.check-failure.outputs.max_turns_exceeded == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: ${{ steps.pr.outputs.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: [
'🤖 Auto-resolution of backport conflicts stopped because Claude reached the maximum number of turns (${{ github.event.inputs.max_turns || '75' }}).',
'',
'The conflicts may be too complex for automatic resolution, or Claude needed more turns to finish.',
'',
'You can try again with more turns by manually running the [resolve-backport-conflicts workflow](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions/workflows/resolve-backport-conflicts.yml) with a higher `max_turns` value, or resolve the conflicts manually by checking out the branch and running `./backport.sh`.',
].join('\n')
});
- name: Comment on PR (failure)
if: always() && steps.claude.outcome == 'failure' && steps.check-failure.outputs.max_turns_exceeded != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: ${{ steps.pr.outputs.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: [
'🤖 Auto-resolution of backport conflicts failed.',
'',
'Manual resolution is still required. Check out the branch and run `./backport.sh` to resolve conflicts manually.',
].join('\n')
});
- name: Comment on PR (clean cherry-pick)
if: always() && steps.backport.outputs.has_conflicts == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: ${{ steps.pr.outputs.number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: '🤖 Cherry-pick applied cleanly with no conflicts. Force-pushed the resolved branch.'
});
- name: Remove label
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
script: |
try {
await github.rest.issues.removeLabel({
issue_number: ${{ steps.pr.outputs.number }},
owner: context.repo.owner,
repo: context.repo.repo,
name: 'auto-resolve-conflicts'
});
} catch (e) {
console.log('Label already removed or not found:', e.message);
}

View File

@@ -10,6 +10,14 @@ on:
merge_group:
workflow_dispatch:
permissions:
id-token: write
contents: read
actions: read
checks: write
pull-requests: write
security-events: write
jobs:
files-changed:
if: ${{ !contains(github.event.pull_request.labels.*.name,'ci skip') && !contains(github.event.pull_request.labels.*.name,'minimal-tests') }}
@@ -32,7 +40,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -19,7 +19,7 @@ jobs:
core.setOutput('issue_title', ${{ toJson(github.event.issue.title) }}.replaceAll(/"/g, '\\"'));
- name: Send the issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@v1.26.0
uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0
with:
payload: |
{

View File

@@ -61,8 +61,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -135,8 +134,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -209,8 +207,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}
@@ -284,8 +281,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
previous-step-outcome: ${{ steps.test-driver.outputs.test-outcome }}

39
.github/workflows/serialization.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Serialization
on:
push:
branches:
- 'master'
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
files-changed:
name: Check which files changed
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 3
outputs:
serialization_baseline: ${{ steps.changes.outputs.serialization_baseline }}
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
id: changes
with:
token: ${{ github.token }}
filters: .github/file-paths.yaml
validate-serialization-baseline:
needs: files-changed
if: needs.files-changed.outputs.serialization_baseline == 'true'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Validate serialization baseline schema
run: bunx @metabase/representations validate-schema --folder test_resources/serialization_baseline

View File

@@ -9,6 +9,10 @@ on:
type: string
required: true
permissions:
id-token: write
contents: read
jobs:
set-announce-url:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
@@ -21,10 +25,9 @@ jobs:
exit 1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- name: Update announcement url
uses: actions/github-script@v7

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

View File

@@ -51,7 +51,7 @@ jobs:
category: release-helper
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.7"
token: ${{ secrets.GITHUB_TOKEN }}
@@ -71,7 +71,7 @@ jobs:
bun install --yarn
sed -i -E 's/< ([0-9]+)\.0\.0"/< \1"/g; s/< ([0-9]+)\.0"/< \1"/g' yarn.lock || true
working-directory: release
- uses: snyk/actions/setup@0.4.0
- uses: snyk/actions/setup@b98d498629f1c368650224d6d212bf7dfa89e4bf # 0.4.0
- name: Run snyk test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
@@ -148,7 +148,7 @@ jobs:
category: driver-vertica
steps:
- uses: actions/checkout@v4
- uses: snyk/actions/setup@0.4.0
- uses: snyk/actions/setup@b98d498629f1c368650224d6d212bf7dfa89e4bf # 0.4.0
- name: Download pom files
uses: actions/download-artifact@v4
with:

196
.github/workflows/sync-fork.yml vendored Normal file
View File

@@ -0,0 +1,196 @@
name: Sync Fork with Upstream
on:
schedule:
- cron: "0 2 * * *" # Every day at 2 AM UTC
workflow_dispatch:
inputs:
force:
description: "⚠️ DESTRUCTIVE: Force-push from upstream, overwriting any fork-only commits"
required: false
default: false
type: boolean
release_branch_count:
description: "Number of most recent release branches to sync (0 to skip)"
required: false
default: "5"
type: number
jobs:
workflow-summary:
# ONLY run on internal metabase org forks — NEVER on the public repo or external forks
if: github.repository_owner == 'metabase' && github.repository != 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 5
steps:
- name: Generate workflow summary
run: |
echo '**Inputs:**' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '- `release_branch_count`: ${{ inputs.release_branch_count || 5 }}' >> $GITHUB_STEP_SUMMARY
echo '- `force`: ${{ inputs.force || false }}' >> $GITHUB_STEP_SUMMARY
echo '- `trigger`: ${{ github.event_name }}' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo 'triggered by: @${{ github.event.sender.login }}' >> $GITHUB_STEP_SUMMARY
else
echo 'triggered by: schedule' >> $GITHUB_STEP_SUMMARY
fi
sync-master:
# ONLY run on internal metabase org forks — NEVER on the public repo or external forks
if: github.repository_owner == 'metabase' && github.repository != 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: master
fetch-depth: 0
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Add upstream remote
run: git remote add metabase https://github.com/metabase/metabase.git
- name: Fetch upstream master
run: git fetch metabase master
- name: Sync master from upstream
id: sync
env:
FORCE: ${{ inputs.force || 'false' }}
run: |
if git diff --quiet master metabase/master; then
echo "master is already up to date — skipping push"
echo "detail=up to date" >> "$GITHUB_OUTPUT"
exit 0
fi
git checkout master
if [ "$FORCE" = "true" ]; then
echo "⚠️ Force-syncing master (overwriting fork-only commits)"
git reset --hard metabase/master
git push origin master --force
echo "detail=force-synced" >> "$GITHUB_OUTPUT"
else
if git pull metabase master; then
git push origin master
echo "detail=synced" >> "$GITHUB_OUTPUT"
else
echo "::warning::master has diverged from upstream — skipping (use force to override)"
echo "detail=diverged (skipped)" >> "$GITHUB_OUTPUT"
fi
fi
- name: Summary
if: always()
env:
REPO: ${{ github.repository }}
run: |
if [ "${{ steps.sync.outcome }}" = "success" ]; then
echo "| [\`master\`](https://github.com/${REPO}/tree/master) | ✅ ${{ steps.sync.outputs.detail }} |" >> $GITHUB_STEP_SUMMARY
else
echo "| [\`master\`](https://github.com/${REPO}/tree/master) | ❌ failed |" >> $GITHUB_STEP_SUMMARY
fi
list-release-branches:
# ONLY run on internal metabase org forks — NEVER on the public repo or external forks
if: github.repository_owner == 'metabase' && github.repository != 'metabase/metabase'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
outputs:
branches: ${{ steps.discover.outputs.branches }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Add upstream remote
run: git remote add metabase https://github.com/metabase/metabase.git
- name: Fetch upstream release branches
run: git fetch metabase 'refs/heads/release-*:refs/remotes/metabase/release-*'
- name: Discover release branches to sync
id: discover
env:
BRANCH_COUNT: ${{ inputs.release_branch_count || '5' }}
run: |
# Get only canonical release branches (release-x.NN.x), skip ad-hoc branches like backports
branches=$(git branch -r --list 'metabase/release-*' | sed 's|^ *||' | grep -E '^metabase/release-x\.[0-9]+\.x$' | sort -t. -k2,2rn | head -n "$BRANCH_COUNT")
json="[]"
for branch in $branches; do
branch_name="${branch#metabase/}"
json=$(echo "$json" | jq -c --arg b "$branch_name" '. + [$b]')
done
echo "branches=$json" >> "$GITHUB_OUTPUT"
echo "Release branches to sync: $json"
sync-release-branch:
needs: list-release-branches
if: needs.list-release-branches.outputs.branches != '[]'
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
strategy:
fail-fast: false
matrix:
branch: ${{ fromJson(needs.list-release-branches.outputs.branches) }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
- name: Add upstream remote
run: git remote add metabase https://github.com/metabase/metabase.git
- name: Fetch upstream branch
run: git fetch metabase "${{ matrix.branch }}"
- name: Sync ${{ matrix.branch }} from upstream
id: sync
env:
FORCE: ${{ inputs.force || 'false' }}
run: |
branch_name="${{ matrix.branch }}"
# Check if branch exists on origin
if git rev-parse --verify "origin/$branch_name" &>/dev/null; then
if git diff --quiet "origin/$branch_name" "metabase/$branch_name"; then
echo "$branch_name is already up to date — skipping push"
echo "detail=up to date" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
if [ "$FORCE" = "true" ]; then
echo "⚠️ Force-syncing $branch_name (overwriting fork-only commits)"
git checkout -B "$branch_name" "metabase/$branch_name"
git push origin "$branch_name" --force
echo "detail=force-synced" >> "$GITHUB_OUTPUT"
else
git checkout -B "$branch_name" "origin/$branch_name" 2>/dev/null || git checkout -B "$branch_name" "metabase/$branch_name"
if git pull metabase "$branch_name"; then
git push origin "$branch_name"
echo "detail=synced" >> "$GITHUB_OUTPUT"
else
echo "::warning::$branch_name has diverged from upstream — skipping (use force to override)"
echo "detail=diverged (skipped)" >> "$GITHUB_OUTPUT"
fi
fi
- name: Summary
if: always()
env:
REPO: ${{ github.repository }}
run: |
branch="${{ matrix.branch }}"
if [ "${{ steps.sync.outcome }}" = "success" ]; then
echo "| [\`${branch}\`](https://github.com/${REPO}/tree/${branch}) | ✅ ${{ steps.sync.outputs.detail }} |" >> $GITHUB_STEP_SUMMARY
else
echo "| [\`${branch}\`](https://github.com/${REPO}/tree/${branch}) | ❌ failed |" >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -16,6 +16,13 @@ on:
type: boolean
default: false
permissions:
id-token: write
contents: write
checks: write
issues: write
actions: read
jobs:
start-message:
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
@@ -198,10 +205,9 @@ jobs:
timeout-minutes: 15
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_RELEASE_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
- uses: actions/checkout@v4
with:
@@ -226,8 +232,10 @@ jobs:
return version_path;
- name: Upload to S3
run: |
aws s3 cp ./metabase.jar s3://${{ secrets.AWS_S3_CLOUD_DEPLOY_BUCKET }}/${{ steps.version_path.outputs.result }}/metabase.jar
aws s3 cp ./metabase.jar s3://"${AWS_S3_CLOUD_DEPLOY_BUCKET}"/${{ steps.version_path.outputs.result }}/metabase.jar
env:
AWS_S3_CLOUD_DEPLOY_BUCKET: ${{ secrets.AWS_S3_CLOUD_DEPLOY_BUCKET }}
run-pre-release-tests:
name: Run pre-release tests for ${{ inputs.version }}
needs: tag-uberjar-for-release

View File

@@ -30,7 +30,7 @@ jobs:
echo "team_name=${team_name^^}_SLACK_ISSUES_WEBHOOK_URL" >> "$GITHUB_OUTPUT"
- name: Send the issue ${{ github.event.issue.number }} to Slack
id: slack
uses: slackapi/slack-github-action@v1.23.0
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 # v1.23.0
with:
payload: |
{

View File

@@ -24,6 +24,12 @@ on:
- snowflake
- sqlserver
permissions:
id-token: write
contents: read
checks: write
actions: read
jobs:
be-tests-bigquery-transforms:
@@ -64,8 +70,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -99,7 +104,7 @@ jobs:
run: |
sudo echo "127.0.0.1 server.clickhouseconnect.test" | sudo tee -a /etc/hosts
- name: Start ClickHouse in Docker
uses: hoverkraft-tech/compose-action@v2.0.0
uses: hoverkraft-tech/compose-action@802a148945af6399a338c7906c267331b39a71af # v2.0.0
with:
compose-file: "modules/drivers/clickhouse/docker-compose.yml"
down-flags: "--volumes"
@@ -124,8 +129,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -175,8 +179,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -230,8 +233,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -286,8 +288,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -332,8 +333,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -387,8 +387,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner
@@ -440,8 +439,7 @@ jobs:
input-path: ./target/junit/
output-name: ${{ github.job }}
bucket: ${{ vars.AWS_S3_TEST_RESULTS_BUCKET }}
aws-access-key-id: ${{ secrets.AWS_TEST_RESULTS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_TEST_RESULTS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.CI_TEST_RESULTS_IAM_ROLE }}
aws-region: ${{ vars.AWS_REGION }}
trunk-api-token: ${{ secrets.TRUNK_API_TOKEN }}
- name: Cleanup python-runner

View File

@@ -7,6 +7,7 @@ on:
jobs:
harvest:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
permissions:
contents: read
@@ -35,7 +36,7 @@ jobs:
# Hence, the three minute timeout and continue-on-error.
- name: Trigger pre-translations
timeout-minutes: 3
uses: crowdin/github-action@v2
uses: crowdin/github-action@ce33ce793a5cbc401d9cd748716e03fc90c001f1 # v2
# https://crowdin.github.io/crowdin-cli/commands/crowdin-pre-translate
with:
command: "pre-translate"

View File

@@ -10,6 +10,7 @@ permissions:
jobs:
synchronize-translations:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Synchronize Translations
env:
BRANCH_NAME: update-translations-${{ github.run_id }}
@@ -33,7 +34,7 @@ jobs:
run: sudo apt-get install gettext
- name: Crowdin download
uses: crowdin/github-action@v2
uses: crowdin/github-action@ce33ce793a5cbc401d9cd748716e03fc90c001f1 # v2
id: crowdin-download
with:
config: locales/crowdin.yml
@@ -74,7 +75,7 @@ jobs:
git push -u origin $BRANCH_NAME
- name: Crowdin upload
uses: crowdin/github-action@v2
uses: crowdin/github-action@ce33ce793a5cbc401d9cd748716e03fc90c001f1 # v2
id: crowdin-upload
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -105,13 +106,13 @@ jobs:
echo "::set-output name=pr_number::$PR_NUMBER"
- name: Auto approve PR
uses: juliangruber/approve-pull-request-action@v1
uses: juliangruber/approve-pull-request-action@f83b836abaed1b3ab639ae61f9117c52ae405f65 # v1
with:
github-token: ${{ github.token }}
number: ${{ steps.create_pr.outputs.pr_number }}
- name: Enable Pull Request Automerge
uses: peter-evans/enable-pull-request-automerge@v2
uses: peter-evans/enable-pull-request-automerge@684fed02ccc9b5eefcf7d40b65b3cd44255bd5bc # v2
with:
token: ${{ secrets.METABASE_AUTOMATION_USER_TOKEN }}
pull-request-number: ${{ steps.create_pr.outputs.pr_number }}

View File

@@ -13,6 +13,7 @@ on:
- master
- internal-tools
- ai-service-port
- metabot-mbql-improvements
paths-ignore:
# config files
- ".**"

View File

@@ -18,6 +18,7 @@ on:
jobs:
collect-and-average-timings:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
env:
@@ -64,7 +65,7 @@ jobs:
echo "Branch name will be: $BRANCH_NAME"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
with:
token: ${{ steps.app-token.outputs.token }}
branch: ${{ env.BRANCH_NAME }}

View File

@@ -17,6 +17,7 @@ permissions:
jobs:
bump-deps:
if: ${{ github.event_name != 'schedule' || github.repository == 'metabase/metabase' }}
name: Open PRs to Bump Backend Deps
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 20

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Test which files changed
uses: dorny/paths-filter@v3.0.0
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: changes
with:
token: ${{ github.token }}

3
.gitignore vendored
View File

@@ -145,8 +145,6 @@ modules/drivers/*/.lsp/*
.github/static-viz-sources.yaml
# airgap
/resources/airgap/
# swc
.swc/
@@ -177,3 +175,4 @@ CLAUDE.local.md
/resources/python-sources/.lock
__pycache__/
.mcp.json
.claude/worktrees

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

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