@metabase/cli 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/{add-collection-H4LcP-9B.mjs → add-collection-CRZAFCZy.mjs} +5 -5
- package/dist/add-collection-DbalcC3_.mjs +11 -0
- package/dist/{archive-D1mfiftv.mjs → archive-3mEhK9_H.mjs} +8 -7
- package/dist/{archive-C6MDtV1F.mjs → archive-C3RWdM-2.mjs} +8 -6
- package/dist/{archive-CQQWkC5S.mjs → archive-CFHYRF65.mjs} +7 -6
- package/dist/{archive-hN8PfvhX.mjs → archive-De4yVEV8.mjs} +7 -6
- package/dist/{archive-BCXoM1nX.mjs → archive-Z8ZM-ilB.mjs} +9 -7
- package/dist/{archive-CdUS-OG9.mjs → archive-d74nDcp4.mjs} +7 -6
- package/dist/{archive-B3cjzOIK.mjs → archive-errg-EKw.mjs} +8 -7
- package/dist/auth--PWX0oj5.mjs +22 -0
- package/dist/{body-DB2upz6a.mjs → body-BS2s2zDl.mjs} +3 -3
- package/dist/{branches-B6R60Vr1.mjs → branches-mcyT7wI-.mjs} +7 -6
- package/dist/{cancel-oPsWomYc.mjs → cancel-B2l-i2NS.mjs} +6 -5
- package/dist/{cancel-task-CleJVDNI.mjs → cancel-task-CugcIeIi.mjs} +7 -6
- package/dist/{capabilities-BX1rnVuH.mjs → capabilities-N0jo5U7S.mjs} +1 -1
- package/dist/card-D07WYCQY.mjs +26 -0
- package/dist/{card-wCPcuKSi.mjs → card-DEmcRlNO.mjs} +6 -5
- package/dist/{cards-Bw37jizL.mjs → cards-BGkz5BeZ.mjs} +8 -6
- package/dist/cli.mjs +54 -27
- package/dist/collection-Dss2qSEF.mjs +23 -0
- package/dist/{collection-namespace-CUDPh2rF.mjs → collection-namespace-BP_LJrAD.mjs} +2 -2
- package/dist/command-augment-DdZIfx1V.mjs +11 -0
- package/dist/{create-Bj6PuaAW.mjs → create-B4HAEEE0.mjs} +9 -8
- package/dist/{create-BEkuqChu.mjs → create-BOmmMSjF.mjs} +16 -11
- package/dist/{create-DSWjS09p.mjs → create-CD9Rp8qs.mjs} +21 -12
- package/dist/{create-DwoqFYb9.mjs → create-CkRFKtFG.mjs} +9 -8
- package/dist/{create-CZ0_s5ap.mjs → create-Cztp5kES.mjs} +7 -6
- package/dist/{create-CF2Zn4pT.mjs → create-D8KA4zJA.mjs} +10 -9
- package/dist/{create-B-mvVFIl.mjs → create-DhrftYre.mjs} +28 -12
- package/dist/{create-D-qE3Oq8.mjs → create-DzyMvAVy.mjs} +9 -8
- package/dist/{create-Cy2TNnJB.mjs → create-YfGzANNB.mjs} +9 -8
- package/dist/{create-branch-BaZ00MIc.mjs → create-branch-DsoMfx_2.mjs} +7 -6
- package/dist/{create-CfLaBO0V.mjs → create-kWzqvTGR.mjs} +16 -11
- package/dist/{create-B87ZQjNM.mjs → create-wpvDXSSY.mjs} +17 -12
- package/dist/{current-task-Dr5dOD4V.mjs → current-task-WuKPZpZ6.mjs} +7 -6
- package/dist/dashboard-Crh4v6Fq.mjs +35 -0
- package/dist/{dashboard-B4bn3z6t.mjs → dashboard-DOplbKyQ.mjs} +7 -5
- package/dist/{database-BJxGUXhA.mjs → database-D9fftP-i.mjs} +1 -1
- package/dist/db-BCAURQei.mjs +28 -0
- package/dist/{delete-DHrFA1SZ.mjs → delete-BCitmugk.mjs} +8 -7
- package/dist/{delete-DJti0TOA.mjs → delete-Buai0_e_.mjs} +8 -7
- package/dist/{delete-evwMw6Hk.mjs → delete-CmGh0jo1.mjs} +8 -7
- package/dist/{delete-runtime-CZMw_AGX.mjs → delete-runtime-B0ha5QR4.mjs} +3 -3
- package/dist/{delete-table-DvuuwVqu.mjs → delete-table-BOkYxdjA.mjs} +8 -7
- package/dist/{dependencies-DrV31Rj5.mjs → dependencies-XZrEvHUA.mjs} +7 -6
- package/dist/{dirty-C-rkrnVM.mjs → dirty-B-5NDbG9.mjs} +7 -6
- package/dist/document-upxL2nKH.mjs +22 -0
- package/dist/{eid-BeXI-eII.mjs → eid-BCuJRv7a.mjs} +13 -7
- package/dist/{error-CIObEXLY.mjs → error-BaBm-UrT.mjs} +2 -2
- package/dist/{export-CGa-SgEV.mjs → export-Dod2gdyE.mjs} +9 -8
- package/dist/field-CloFa1oe.mjs +21 -0
- package/dist/{fields-Recymu7n.mjs → fields-B0yttrzR.mjs} +8 -7
- package/dist/{get-C6g2C4dM.mjs → get-9p0Gt1c7.mjs} +7 -6
- package/dist/{get-CYh2DBsq.mjs → get-B0OsW6jk.mjs} +7 -6
- package/dist/{get-CHi8tU_1.mjs → get-B8tugTku.mjs} +9 -8
- package/dist/{get-BD_P_Ejc.mjs → get-BkGh9BPd.mjs} +7 -6
- package/dist/{get-BM-d3-zk.mjs → get-BpZ81hX8.mjs} +7 -6
- package/dist/{get-CARdLkmp.mjs → get-CbpHt4Hs.mjs} +7 -6
- package/dist/{get-b4xbfFbA.mjs → get-CfeYa0v5.mjs} +7 -6
- package/dist/{get-DaixPDU9.mjs → get-D-pNrQGA.mjs} +7 -6
- package/dist/{get-rFcAVIch.mjs → get-D7QMqPOK.mjs} +7 -6
- package/dist/{get-BAH_M5vj.mjs → get-DBY5esTW.mjs} +7 -6
- package/dist/{get-Q2WZ79q_.mjs → get-DISgP66L.mjs} +8 -7
- package/dist/{get-Nc5GOs6-.mjs → get-DJ8huA8y.mjs} +8 -6
- package/dist/{get-CXMv-r1p.mjs → get-Dz7fcqoG.mjs} +9 -7
- package/dist/{get-BQxLtFE-.mjs → get-k6M5nGMC.mjs} +7 -6
- package/dist/{get-run-CfQR6ZNa.mjs → get-run-DQJVpDw1.mjs} +7 -6
- package/dist/{get-7fWSU6ow.mjs → get-u5Uq9vms.mjs} +6 -5
- package/dist/git-sync-C9m-OHac.mjs +31 -0
- package/dist/group-BNE_RiH5.mjs +28 -0
- package/dist/{has-remote-changes-Bvyv4FLP.mjs → has-remote-changes-DvQBXudi.mjs} +7 -6
- package/dist/{import-c3o3OAx0.mjs → import-PGS8-DwE.mjs} +9 -8
- package/dist/{input-BXWgdKiS.mjs → input-7Sj85_K7.mjs} +1 -1
- package/dist/is-dirty-DpyKeGAJ.mjs +10 -0
- package/dist/{is-dirty-BE53XwOC.mjs → is-dirty-DyEVFQVJ.mjs} +4 -4
- package/dist/{items-1KkBMiO4.mjs → items-CkFEy2Du.mjs} +9 -8
- package/dist/{key-DwiMOWRQ.mjs → key-bltP32Pm.mjs} +1 -1
- package/dist/library-1AAVbk-K.mjs +24 -0
- package/dist/{list-C4KnM3Rq.mjs → list-82NkvRpI.mjs} +8 -6
- package/dist/{list-Drr4JiWg.mjs → list-B9wXg3qi.mjs} +6 -5
- package/dist/{list-BwdO1_gX.mjs → list-BBxRjuMn.mjs} +6 -5
- package/dist/{list-CSjFJDls.mjs → list-BCYTTCFD.mjs} +6 -5
- package/dist/{list-BqgbrpQQ.mjs → list-BVzu2RIZ.mjs} +6 -5
- package/dist/{list-DUEYX3bX.mjs → list-BWAYDSbQ.mjs} +6 -5
- package/dist/{list-BC2B02IR.mjs → list-BY4S32Lg.mjs} +6 -5
- package/dist/{list-7rwzxX6t.mjs → list-Bb8YRON_.mjs} +6 -5
- package/dist/{list-DIvOPW1g.mjs → list-CJx5q-Yn.mjs} +8 -7
- package/dist/{list-F0vkE22V.mjs → list-CK0p7vvK.mjs} +7 -6
- package/dist/{list-CO5J3SZU.mjs → list-CKzpoTgP.mjs} +8 -7
- package/dist/{list-FR8Q1SzV.mjs → list-CjF12k1G.mjs} +7 -6
- package/dist/{list-D52_BozQ.mjs → list-D-rgDFa5.mjs} +9 -7
- package/dist/{list-DPcPTqFU.mjs → list-Sgo3RfDY.mjs} +6 -5
- package/dist/{list-DHb4vQUM.mjs → list-pQ22nXhQ.mjs} +6 -5
- package/dist/{login-C0Rf2hg0.mjs → login-C6ZAnGHz.mjs} +10 -9
- package/dist/{logout-C7_UON-s.mjs → logout-CWjyY3Y8.mjs} +6 -5
- package/dist/{manifest-B2F8iL7X.mjs → manifest-BVf8P4bl.mjs} +9 -2
- package/dist/measure-Cbly1r0E.mjs +25 -0
- package/dist/{metadata-Db3Kpo-z.mjs → metadata-CW5Lfw5d.mjs} +9 -8
- package/dist/{metadata-FltZq5Ek.mjs → metadata-aQAqseCm.mjs} +8 -7
- package/dist/{command-augment-CAur0XOQ.mjs → notice-DyVl5aYB.mjs} +1 -11
- package/dist/parameter-CiJ4CwWE.mjs +118 -0
- package/dist/parameter-values-DmDOuE-j.mjs +56 -0
- package/dist/{parse-enum-BatHQ-Gs.mjs → parse-enum-BL9i_brN.mjs} +1 -1
- package/dist/{parse-id-B5adfBlS.mjs → parse-id-DlXnOcmP.mjs} +1 -1
- package/dist/{parse-ref-CZr1bYIl.mjs → parse-ref-CB_KvF9h.mjs} +1 -1
- package/dist/{path-BojuJkE4.mjs → path-D8IJ4YrW.mjs} +6 -5
- package/dist/{poll-AduuU55-.mjs → poll-hgnrHBoh.mjs} +2 -2
- package/dist/{poll-task-B00Qwd87.mjs → poll-task-NQNLT_aA.mjs} +2 -2
- package/dist/{preflight-QVPvG_Xg.mjs → preflight-DvaPQHHf.mjs} +4 -4
- package/dist/{process-DsGf7Mg5.mjs → process-j8UHMHc2.mjs} +1 -1
- package/dist/{prompt-Bc_bHSD0.mjs → prompt-C85xd9HR.mjs} +1 -1
- package/dist/{publish-h5RJh6im.mjs → publish-BntmFR6g.mjs} +9 -8
- package/dist/{query-BUkuB4bZ.mjs → query-BQfgKV68.mjs} +10 -8
- package/dist/{query-Dvi-Rksy.mjs → query-DSKQZu91.mjs} +19 -13
- package/dist/{query-result-L5_NrwQR.mjs → query-result-D6mfoVfQ.mjs} +1 -1
- package/dist/{remove-collection-D8ZfB2RN.mjs → remove-collection-BguaI3-6.mjs} +9 -8
- package/dist/{rescan-values-DOsDLrRG.mjs → rescan-values-DjD6IW3J.mjs} +9 -8
- package/dist/{resolve-BQ9vjlNJ.mjs → resolve-Dj2MTBkn.mjs} +1 -1
- package/dist/{run-CeG0KH5W.mjs → run-Bp1yxkBN.mjs} +6 -5
- package/dist/{run-CjhD-Zbr.mjs → run-RQfQj7Rk.mjs} +9 -8
- package/dist/{runs-6k8C6kXF.mjs → runs-CSsatfWb.mjs} +8 -7
- package/dist/{runtime-CmAIahm5.mjs → runtime-oxjmrYoP.mjs} +5 -3
- package/dist/{schema-tables-C45QegaY.mjs → schema-tables-5I5pCxHl.mjs} +8 -7
- package/dist/{schemas-EVwEFuTj.mjs → schemas-CfzFCfBt.mjs} +6 -5
- package/dist/{search-C_uw_D1U.mjs → search-Vo-BqliA.mjs} +11 -5
- package/dist/segment-BuN_IoM8.mjs +25 -0
- package/dist/{selectors-AktxTEMK.mjs → selectors-DBnJsKlW.mjs} +3 -3
- package/dist/{set-C3EAuyb8.mjs → set-1Vh0AF-T.mjs} +9 -8
- package/dist/{set-active-BW6LN6y0.mjs → set-active-C0mUZlNN.mjs} +6 -5
- package/dist/setting-BbdKR-lO.mjs +20 -0
- package/dist/{setup-CYrbNrlG.mjs → setup-CzYFvFK5.mjs} +8 -7
- package/dist/{skills-DJsuBguh.mjs → skills-B6gfH0iR.mjs} +1 -1
- package/dist/{skills-D6xQkmhu.mjs → skills-CR2xyV33.mjs} +3 -3
- package/dist/snippet-Bzo2U9Fv.mjs +22 -0
- package/dist/{stash-C1V2FvJR.mjs → stash-CQHXwBu_.mjs} +9 -8
- package/dist/{status-DY92F9mn.mjs → status-C_7aqTvB.mjs} +6 -5
- package/dist/{status-CxYw6zQM.mjs → status-DXQkM18v.mjs} +8 -7
- package/dist/{summary-DOxgqJoA.mjs → summary-1aNpQc8j.mjs} +7 -6
- package/dist/{sync-schema-CQPfffjU.mjs → sync-schema-BqLp5uTS.mjs} +11 -10
- package/dist/table-BwGOz97O.mjs +22 -0
- package/dist/{table-CDMG0Zi5.mjs → table-DE3i82T_.mjs} +1 -1
- package/dist/transform-B65ZD9-e.mjs +31 -0
- package/dist/transform-job-BWVKXSV6.mjs +25 -0
- package/dist/transform-tag-C4qvmicL.mjs +21 -0
- package/dist/{transforms-DzBJDydn.mjs → transforms-BelyllUL.mjs} +7 -6
- package/dist/{tree-B3f5F_dP.mjs → tree-YvmwGql7.mjs} +6 -5
- package/dist/{unpublish-B5RDeN-V.mjs → unpublish-CHjGLMu9.mjs} +7 -6
- package/dist/{update-CcvDVqNd.mjs → update-BACl_bJS.mjs} +10 -9
- package/dist/{update-PZPNx0Xd.mjs → update-BGMJTpA6.mjs} +17 -12
- package/dist/{update-DOfL_KPx.mjs → update-BKhekuqr.mjs} +22 -13
- package/dist/{update-DSueNZRw.mjs → update-BWlN3QDo.mjs} +10 -9
- package/dist/{update-C0pFSc1B.mjs → update-BnHYSu96.mjs} +18 -13
- package/dist/{update-D698CaeV.mjs → update-C9fndCqb.mjs} +10 -9
- package/dist/{update-I3TA2Tem.mjs → update-CrRz47aj.mjs} +22 -13
- package/dist/{update-BIZ9XhjS.mjs → update-D7vc8GBF.mjs} +17 -12
- package/dist/{update-DudyZ-FP.mjs → update-K-jjQ0Aw.mjs} +11 -10
- package/dist/{update-CiWPEqQ-.mjs → update-Zz01Woxj.mjs} +10 -9
- package/dist/{update-dashcard-BXZ4vS15.mjs → update-dashcard-Dvth-yLC.mjs} +11 -9
- package/dist/{update-D5gioyBa.mjs → update-txUfAJxy.mjs} +10 -9
- package/dist/{upgrade-4cWfLu90.mjs → upgrade-DsXlfPef.mjs} +7 -6
- package/dist/{uuid---pAboNQ.mjs → uuid-ByJmWV7b.mjs} +10 -5
- package/dist/{validate-VawhJ5Sc.mjs → validate-BqNW4Sk1.mjs} +2 -2
- package/dist/{validate-query-CSV-TTnd.mjs → validate-query-CcZVKYPV.mjs} +3 -3
- package/dist/{values-I503dI7K.mjs → values-CIjJI4sz.mjs} +7 -6
- package/dist/{verify-BMhTWW9s.mjs → verify-Jsuc2dat.mjs} +2 -2
- package/dist/{wait-oMSs_IdS.mjs → wait-5ltTFvSa.mjs} +8 -7
- package/dist/{wait-flags-HtCL2l1r.mjs → wait-flags-D6kd_G7c.mjs} +2 -2
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +47 -56
- package/skill-data/dashboard/SKILL.md +107 -0
- package/skill-data/data-workflow/SKILL.md +116 -0
- package/skill-data/{data-analysis/SKILL.md → data-workflow/references/answering-questions.md} +7 -13
- package/skill-data/{data-transformation/SKILL.md → data-workflow/references/building-clean-tables.md} +23 -32
- package/skill-data/{semantic-layer/SKILL.md → data-workflow/references/reusable-definitions.md} +26 -56
- package/skill-data/document/SKILL.md +11 -21
- package/skill-data/git-sync/SKILL.md +39 -39
- package/skill-data/mbql/SKILL.md +19 -31
- package/skill-data/mbql/references/operators.md +13 -3
- package/skill-data/metadata/SKILL.md +78 -0
- package/skill-data/metadata/references/semantic-types.md +83 -0
- package/skill-data/native-sql/SKILL.md +118 -0
- package/skill-data/native-sql/references/template-tags.md +178 -0
- package/skill-data/transform/SKILL.md +30 -46
- package/skill-data/visualization/SKILL.md +8 -8
- package/skill-data/visualization/references/settings.md +14 -0
- package/skills/metabase-cli/SKILL.md +2 -2
- package/dist/add-collection-BqfYL4FU.mjs +0 -10
- package/dist/auth-BaCMFLTA.mjs +0 -19
- package/dist/card-DzH3aK0a.mjs +0 -20
- package/dist/collection-Wagz-ira.mjs +0 -20
- package/dist/dashboard-OUgS1Gi-.mjs +0 -21
- package/dist/db-Btfl5JMZ.mjs +0 -22
- package/dist/document-CU28GfFw.mjs +0 -19
- package/dist/field-BTbzlcyC.mjs +0 -18
- package/dist/git-sync-CBxS2urR.mjs +0 -28
- package/dist/is-dirty-Z-pqyVyB.mjs +0 -9
- package/dist/library-BlbH0xyK.mjs +0 -18
- package/dist/measure-BUedPu4K.mjs +0 -19
- package/dist/segment-CkZUZcWz.mjs +0 -19
- package/dist/setting-C50HEiGG.mjs +0 -17
- package/dist/snippet-BjaWAxCu.mjs +0 -19
- package/dist/table-B35ovbcd.mjs +0 -19
- package/dist/transform-DF79sJ0_.mjs +0 -25
- package/dist/transform-job-PmA_D8gz.mjs +0 -22
- package/dist/transform-tag-CE3cuO1K.mjs +0 -18
- package/skill-data/robot-data-engineer/SKILL.md +0 -142
- /package/dist/{body-flags-D7q87Btw.mjs → body-flags-DWTTxJpP.mjs} +0 -0
- /package/dist/{collection-Deiziuu2.mjs → collection-DrLpA1SO.mjs} +0 -0
- /package/dist/{document-qfwR0r63.mjs → document-1W7NRaO_.mjs} +0 -0
- /package/dist/{field-E0IBy4Uw.mjs → field-CMY_LWUe.mjs} +0 -0
- /package/dist/{measure-BCv5wDDN.mjs → measure-DoJvtCaA.mjs} +0 -0
- /package/dist/{paginate-BexjkjbY.mjs → paginate-FVZUxL4J.mjs} +0 -0
- /package/dist/{render-CkuFkWlQ.mjs → render-BTKnWL0d.mjs} +0 -0
- /package/dist/{revision-message-flag-CP5NFrWQ.mjs → revision-message-flag-CHrJgFFx.mjs} +0 -0
- /package/dist/{segment-BAUuELKs.mjs → segment-TXktTCfU.mjs} +0 -0
- /package/dist/{setting-DhMk0TNo.mjs → setting-m46MUtW5.mjs} +0 -0
- /package/dist/{snippet-D4SyVLKB.mjs → snippet-CtA2Pkoa.mjs} +0 -0
- /package/dist/{transform-MmqHKGU-.mjs → transform-DEF38FWe.mjs} +0 -0
- /package/dist/{transform-job-CtVziW85.mjs → transform-job-CtixL4An.mjs} +0 -0
- /package/dist/{transform-tag-wFiWmiyO.mjs → transform-tag-rsIrckCM.mjs} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Template tags — full reference
|
|
2
|
+
|
|
3
|
+
Every template-tag body, the widget-type vocabulary, and the parameter-object shapes. The main skill covers the two you author most (field filter, raw variable); this is the rest plus the exhaustive field lists.
|
|
4
|
+
|
|
5
|
+
## Template-tag bodies by `type`
|
|
6
|
+
|
|
7
|
+
The `template-tags` value is a map keyed by tag name; each entry's `name` must equal its key and the `{{name}}` in the SQL. `id` is a UUID — mint with `mb uuid`.
|
|
8
|
+
|
|
9
|
+
### Raw variable — `text` / `number` / `date` / `boolean`
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
"min_total": {
|
|
13
|
+
"id": "<uuid>",
|
|
14
|
+
"name": "min_total",
|
|
15
|
+
"display-name": "Minimum total",
|
|
16
|
+
"type": "number",
|
|
17
|
+
"required": false,
|
|
18
|
+
"default": "50"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Field | Req | Notes |
|
|
23
|
+
| -------------- | --- | -------------------------------------------- |
|
|
24
|
+
| `name` | ✓ | equals map key and `{{name}}` |
|
|
25
|
+
| `display-name` | ✓ | label shown in the widget |
|
|
26
|
+
| `type` | ✓ | `text` \| `number` \| `date` \| `boolean` |
|
|
27
|
+
| `id` | — | UUID; supply one |
|
|
28
|
+
| `required` | — | `true` blocks the run until a value is given |
|
|
29
|
+
| `default` | — | value used when none passed (string form) |
|
|
30
|
+
|
|
31
|
+
SQL: `{{min_total}}`, spliced literally — you write the operator (`total > {{min_total}}`).
|
|
32
|
+
|
|
33
|
+
### Field filter — `dimension`
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
"status": {
|
|
37
|
+
"id": "<uuid>",
|
|
38
|
+
"name": "status",
|
|
39
|
+
"display-name": "Status",
|
|
40
|
+
"type": "dimension",
|
|
41
|
+
"dimension": ["field", {}, 141],
|
|
42
|
+
"widget-type": "string/=",
|
|
43
|
+
"default": null,
|
|
44
|
+
"options": null,
|
|
45
|
+
"alias": null
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Req | Notes |
|
|
50
|
+
| ------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
51
|
+
| `type` | ✓ | `"dimension"` |
|
|
52
|
+
| `dimension` | ✓ | field ref `["field", {}, <id>]` — options object second, id third (the `mbql` rule); the legacy `["field", <id>, null]` form is rejected by pre-flight |
|
|
53
|
+
| `widget-type` | ✓ | the widget/operator; must suit the column type (table below) |
|
|
54
|
+
| `default` | — | e.g. a value, or a `["2024-01-01","2024-12-31"]` range |
|
|
55
|
+
| `options` | — | filter options map (e.g. case sensitivity), usually `null` |
|
|
56
|
+
| `alias` | — | set when the column comes from an aliased table in the SQL |
|
|
57
|
+
|
|
58
|
+
SQL: bare — `WHERE {{status}}`. Never `WHERE status = {{status}}`. On write, send `{}` for the ref's options; the server fills a `lib/uuid` and the card reads back `["field", {"lib/uuid": "…"}, <id>]`.
|
|
59
|
+
|
|
60
|
+
### Snippet — `snippet`
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
"snippet: Active Rows": {
|
|
64
|
+
"id": "<uuid>",
|
|
65
|
+
"name": "snippet: Active Rows",
|
|
66
|
+
"display-name": "Snippet: Active Rows",
|
|
67
|
+
"type": "snippet",
|
|
68
|
+
"snippet-name": "Active Rows",
|
|
69
|
+
"snippet-id": 5
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
SQL: `{{snippet: Active Rows}}`. Create/manage the fragment with `mb snippet` (`content` is bare SQL). No user value.
|
|
74
|
+
|
|
75
|
+
### Card reference — `card`
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"#42": {
|
|
79
|
+
"id": "<uuid>",
|
|
80
|
+
"name": "#42",
|
|
81
|
+
"display-name": "#42",
|
|
82
|
+
"type": "card",
|
|
83
|
+
"card-id": 42
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
SQL: `{{#42}}` or `{{#42-slug}}`, used where a table/subquery goes (`FROM {{#42}}`, `WITH x AS {{#42}}`). Runs with the referenced card's own defaults; no user value.
|
|
88
|
+
|
|
89
|
+
### Source table — `table` (v59+)
|
|
90
|
+
|
|
91
|
+
A niche v59+ type that references a warehouse table by id (`{type: "table", table-id: <id>}`, optional `source-filters`) where a table/subquery goes — analogous to a card reference but pointing at a raw table. Absent on v0.58. Reach for a card reference (`card`) unless you specifically need a bare-table source tag.
|
|
92
|
+
|
|
93
|
+
### Temporal unit — `temporal-unit`
|
|
94
|
+
|
|
95
|
+
A widget that lets the viewer pick the time bucket (day/week/month/…) for a datetime column. Body mirrors a field filter (`dimension` legacy ref, optional `alias`) with `type: "temporal-unit"`.
|
|
96
|
+
|
|
97
|
+
## `widget-type` by column type
|
|
98
|
+
|
|
99
|
+
Closed enum — same vocabulary as a dashboard parameter `type`. Pick one whose family matches the bound column.
|
|
100
|
+
|
|
101
|
+
| Column type | Common widget-types |
|
|
102
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| Text / string | `string/=` `string/!=` `string/contains` `string/does-not-contain` `string/starts-with` `string/ends-with` `category` |
|
|
104
|
+
| Number | `number/=` `number/!=` `number/between` `number/>=` `number/<=` |
|
|
105
|
+
| Date / datetime | `date/all-options` `date/single` `date/range` `date/relative` `date/month-year` `date/quarter-year` |
|
|
106
|
+
| Boolean | `boolean/=` |
|
|
107
|
+
| ID / FK | `id` |
|
|
108
|
+
| Location (with matching semantic type) | `location/city` `location/state` `location/zip_code` `location/country` |
|
|
109
|
+
|
|
110
|
+
`date/all-options` gives the fullest date picker (single, range, relative). `category` yields a value-list dropdown for a low-cardinality text column.
|
|
111
|
+
|
|
112
|
+
## Parameter object — declared vs. runtime
|
|
113
|
+
|
|
114
|
+
Same object, two contexts. `target` links the parameter to a template tag: `["dimension", ["template-tag", "<name>"]]` for a field filter, `["variable", ["template-tag", "<name>"]]` for a raw variable.
|
|
115
|
+
|
|
116
|
+
**Declared** — in the card's `parameters` array, to set a default or a dropdown source:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"id": "<uuid>",
|
|
121
|
+
"name": "status",
|
|
122
|
+
"slug": "status",
|
|
123
|
+
"type": "string/=",
|
|
124
|
+
"target": ["dimension", ["template-tag", "status"]],
|
|
125
|
+
"default": "active",
|
|
126
|
+
"values_source_type": "static-list",
|
|
127
|
+
"values_source_config": { "values": ["active", "churned", "trial"] }
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`values_source_type`: omit to pull live distinct values from the bound field; `"static-list"` + `values_source_config.values` for a fixed list; `"card"` + `{card_id, value_field, label_field}` to source from a query.
|
|
132
|
+
|
|
133
|
+
**Runtime** — passed to `card query --parameters`; carries a `value`, no source config:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{ "type": "string/=", "target": ["dimension", ["template-tag", "status"]], "value": "active" }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The runtime `type` is the value's type, not the tag's. Date ranges pass as `"value": ["2024-01-01", "2024-12-31"]`. Omit a parameter entirely to leave an optional (`[[ ]]`) clause out.
|
|
140
|
+
|
|
141
|
+
## Full native card body
|
|
142
|
+
|
|
143
|
+
What `mb card create --file` consumes:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"name": "Active orders by status",
|
|
148
|
+
"display": "table",
|
|
149
|
+
"visualization_settings": {},
|
|
150
|
+
"dataset_query": {
|
|
151
|
+
"lib/type": "mbql/query",
|
|
152
|
+
"database": 1,
|
|
153
|
+
"stages": [
|
|
154
|
+
{
|
|
155
|
+
"lib/type": "mbql.stage/native",
|
|
156
|
+
"native": "SELECT status, count(*) FROM orders WHERE total > {{min_total}} [[AND {{status}}]] GROUP BY status",
|
|
157
|
+
"template-tags": {
|
|
158
|
+
"min_total": {
|
|
159
|
+
"id": "<uuid>",
|
|
160
|
+
"name": "min_total",
|
|
161
|
+
"display-name": "Minimum total",
|
|
162
|
+
"type": "number",
|
|
163
|
+
"default": "0"
|
|
164
|
+
},
|
|
165
|
+
"status": {
|
|
166
|
+
"id": "<uuid>",
|
|
167
|
+
"name": "status",
|
|
168
|
+
"display-name": "Status",
|
|
169
|
+
"type": "dimension",
|
|
170
|
+
"dimension": ["field", {}, 141],
|
|
171
|
+
"widget-type": "string/="
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: transform
|
|
3
|
-
description: Author and run Metabase transforms via `mb` — body shape (native SQL
|
|
3
|
+
description: Author and run Metabase transforms via `mb` — body shape (native SQL or structured MBQL), create + run-with-wait, run inspection, dependencies, cancel, the `update`-vs-recreate iteration rule, the writable-keys-only PATCH contract, plus transform tags and tag-driven transform-job schedules. Load when the user touches transforms — "create a transform", "run a transform", "fix a failing transform", "list transform runs", "cancel a running transform", "manage transform tags", "run a transform job", or anything `mb transform …` / `mb transform-job …` / `mb transform-tag …`.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
|
8
8
|
|
|
9
9
|
A **transform** persists the result of a query (native SQL or MBQL) to a warehouse table the user can read from cards, dashboards, and other transforms. It runs on a schedule (via `transform-job`) or on-demand (`transform run`).
|
|
10
10
|
|
|
11
|
-
Flag conventions, body-input precedence, and
|
|
11
|
+
Flag conventions, body-input precedence, and the `./.scratch` convention live in `core` (`mb skills get core`). Deciding _which_ transforms to build — modeling a whole raw database into clean, analysis-ready tables — is the `data-workflow` skill's build-clean-tables stage (`mb skills get data-workflow`).
|
|
12
12
|
|
|
13
13
|
## Body shape
|
|
14
14
|
|
|
@@ -17,14 +17,13 @@ A transform has two halves:
|
|
|
17
17
|
- `source` — the query to run (`type: "query"`, with `query.type` of `native` or `mbql`).
|
|
18
18
|
- `target` — the warehouse destination (`type: "table"`, with `database`, `schema`, `name`).
|
|
19
19
|
|
|
20
|
-
Native SQL is the simplest source
|
|
21
|
-
|
|
22
|
-
For an **MBQL 5** `source.query` (`lib/type: "mbql/query"`), the body shape, the "options object is always second" clause rule, UUID minting, aggregation/order-by refs, naming aggregation output columns, and the `--print-schema` → `--dry-run` validation loop are all in the `mbql` skill — **`mb skills get mbql`**. The MBQL-5 pre-flight on `transform create`/`update` is documented there too (legacy MBQL 4 and native sources skip it). For a transform target, naming your aggregation output columns matters more than usual — a bare `count` / `avg_2` becomes the warehouse column name; see the `mbql` skill's "Naming aggregation output columns".
|
|
20
|
+
Native SQL is the simplest source — author it as an `mbql.stage/native` stage (the SQL string sits at `source.query.stages[0].native`), the form below. For a **structured** `source.query` (an `mbql.stage/mbql` stage) — the options-object-is-always-second clause rule, UUID minting, aggregation/order-by refs, naming aggregation output columns, and the `--print-schema` → `--dry-run` validation loop — see `mbql` (**`mb skills get mbql`**). Both stage types are the `mbql/query` shape, so `transform create`/`update` pre-flight them (only the legacy flat forms skip it). Pull a sample body with `mb transform get <id> --full --json`. For a transform target, naming aggregation output columns matters more than usual: a bare `count` / `avg_2` becomes the warehouse column name.
|
|
23
21
|
|
|
24
22
|
## Create + run (native SQL)
|
|
25
23
|
|
|
24
|
+
**Keep the SQL formatted.** Author it multi-line in `./.scratch/<name>.sql` and embed with `jq --rawfile` (jq ≥1.6, which JSON-encodes the file so newlines become `\n`). The stored SQL (`source.query.stages[0].native`) is what `mb transform get` and the Metabase editor render — a single-line blob is valid JSON but unreadable when anyone opens the transform. Single-quote the heredoc delimiter (`<<'SQL'`) so the shell leaves `$vars` in the query alone (e.g. Postgres `$1`, `$$`).
|
|
25
|
+
|
|
26
26
|
```bash
|
|
27
|
-
# Author the SQL formatted — it's what `mb transform get` and the Metabase editor show.
|
|
28
27
|
cat > ./.scratch/user_counts_by_signup_year.sql <<'SQL'
|
|
29
28
|
SELECT
|
|
30
29
|
date_trunc('year', created_at)::date AS signup_year,
|
|
@@ -34,11 +33,10 @@ GROUP BY 1
|
|
|
34
33
|
ORDER BY 1
|
|
35
34
|
SQL
|
|
36
35
|
|
|
37
|
-
# Embed it with jq --rawfile so the newlines survive as \n in valid JSON (don't hand-write the SQL as one line).
|
|
38
36
|
jq -n --rawfile q ./.scratch/user_counts_by_signup_year.sql \
|
|
39
37
|
'{ name: "user_counts_by_signup_year",
|
|
40
38
|
description: "Sample transform: counts users by year of signup",
|
|
41
|
-
source: { type: "query", query: { type: "
|
|
39
|
+
source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } },
|
|
42
40
|
target: { type: "table", database: <db-id>, schema: "public", name: "user_counts_by_signup_year" } }' \
|
|
43
41
|
> ./.scratch/transform.json
|
|
44
42
|
|
|
@@ -46,17 +44,13 @@ TRANSFORM_ID=$(mb transform create --file ./.scratch/transform.json --profile <n
|
|
|
46
44
|
mb transform run "$TRANSFORM_ID" --wait --profile <name> --json
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- **Keep the SQL formatted.** Author it multi-line in `./.scratch/<name>.sql` and embed with `jq --rawfile` (jq ≥1.6, which JSON-encodes the file so newlines become `\n`). The stored `native.query` is what `mb transform get` and the Metabase editor render — a single-line blob is valid JSON but unreadable when anyone opens the transform. Single-quote the heredoc delimiter (`<<'SQL'`) so the shell leaves `$vars` in the query alone (e.g. Postgres `$1`, `$$`).
|
|
57
|
-
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off the create output — no follow-up `transform get` needed to verify where the transform will write.
|
|
58
|
-
- If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept" workflows, prefer `transform update <id>` — see "Iterating on a failing transform" below.
|
|
59
|
-
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — passing a normal analytics collection id (the kind a dashboard lives in) fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or create one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` and pass the returned `id`. Cards and dashboards you build **on top of** the transform's output table go in ordinary collections as usual — so "put the transform and its dashboard in collection X" generally means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
47
|
+
- `<db-id>` comes from `mb database list --profile <name> --json`; ids are per-instance. Target `schema` is the schema the result table is written into (e.g. `public`).
|
|
48
|
+
- `--wait` polls until status is `succeeded` or `failed`. Without it you get only `{message: "Transform run started", run_id, final: null}` and must poll yourself — don't put bare `transform run` in a tight loop; let `--wait` do the polling.
|
|
49
|
+
- `--sync` implies `--wait`, then waits until the run registers its output table (the run registers it itself — no `db sync-schema` needed), adding `target_table_id` to the envelope. Use it when you'll build MBQL on the output (see "Inspect").
|
|
50
|
+
- The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
|
|
51
|
+
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off it — no follow-up `transform get`.
|
|
52
|
+
- If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept", prefer `transform update <id>` — see "Iterating on a failing transform".
|
|
53
|
+
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — a normal analytics collection id fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or provision one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` (see `core`) and pass the returned `id`. Cards and dashboards you build **on top of** the output table go in ordinary collections — so "put the transform and its dashboard in collection X" means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
60
54
|
|
|
61
55
|
## Inspect
|
|
62
56
|
|
|
@@ -66,7 +60,7 @@ mb transform get <id> --profile <name> --full --json # full transform i
|
|
|
66
60
|
mb transform dependencies <id> --profile <name> --json # upstream transforms this one must run after
|
|
67
61
|
```
|
|
68
62
|
|
|
69
|
-
After a run the table physically exists in the warehouse, but Metabase addresses tables/columns by numeric id, so **MBQL and the UI can't reference a brand-new table until the instance syncs** (native SQL — a native `card` or `mb query` against `<schema>.<name>` — reads it immediately). Run and register in one step with `--sync
|
|
63
|
+
After a run the table physically exists in the warehouse, but Metabase addresses tables/columns by numeric id, so **MBQL and the UI can't reference a brand-new table until the instance syncs** (native SQL — a native `card` or `mb query` against `<schema>.<name>` — reads it immediately). Run and register in one step with `--sync`:
|
|
70
64
|
|
|
71
65
|
```bash
|
|
72
66
|
TABLE_ID=$(mb transform run <id> --sync --profile <name> --json | jq -r '.target_table_id')
|
|
@@ -91,12 +85,10 @@ mb transform get-run <run-id> --profile <name> --json
|
|
|
91
85
|
mb transform cancel <id> --profile <name> --json
|
|
92
86
|
```
|
|
93
87
|
|
|
94
|
-
Notes:
|
|
95
|
-
|
|
96
88
|
- `transform runs` and `transform get-run` parse against the same `TransformRun` schema, so `get-run` returns the same per-run shape as one entry of `runs`. The compact projection is `{id, transform_id, status, run_method, start_time, end_time, message}`. Pass `--full` on `get-run` for the hydrated row including `is_active`, `user_id`, `transform_name`, `transform_entity_id`, `checkpoint_*` fields, and a nested `transform: {id, name, …}` block.
|
|
97
|
-
- `transform cancel` takes the **transform** id and 404s with `Endpoint not found — is this a Metabase instance?` if there is no active run.
|
|
98
|
-
- For native
|
|
99
|
-
- The `--transform-id` filter on `runs` accepts a single integer
|
|
89
|
+
- `transform cancel` takes the **transform** id and returns `{canceled: true, id: <transform-id>}`. It 404s with `Endpoint not found — is this a Metabase instance?` if there is no active run.
|
|
90
|
+
- **Cancel semantics differ by source.** For native SQL, cancel marks the run `canceling` but does **not** kill the warehouse query mid-flight — the query runs to completion, then the run lands as `canceled` (or stays `succeeded` if the cancel arrived after the writer committed). For Python transforms the worker is interrupted directly. Don't expect cancel to free warehouse resources instantly on long native queries; expect it to flip state and prevent downstream consumers from treating the result as good.
|
|
91
|
+
- The `--transform-id` filter on `runs` accepts a single integer (translated to the server's `transform-ids` vector). To cross-filter multiple transforms, run `transform runs --json` and `jq` post-hoc.
|
|
100
92
|
|
|
101
93
|
## Update body: send only writable keys, never round-trip the GET body
|
|
102
94
|
|
|
@@ -107,7 +99,7 @@ name, description, source, target, run_trigger,
|
|
|
107
99
|
tag_ids, collection_id, owner_user_id, owner_email
|
|
108
100
|
```
|
|
109
101
|
|
|
110
|
-
**
|
|
102
|
+
**Never paste the output of `transform get` into a `transform update` body.** The GET response carries server-side fields (`id`, `entity_id`, `created_at`, `updated_at`, `creator_id`, `last_run`, `target_db_id`, `target_table_id`, `source_type`, `source_database_id`, `source_readable`, `creator`, `owner`, `table`, …) that the PUT endpoint isn't built to handle. Unknown top-level keys flow into `t2/update!` and leak a raw H2 SQL error like:
|
|
111
103
|
|
|
112
104
|
```
|
|
113
105
|
Column "TAGS" not found; SQL statement:
|
|
@@ -116,11 +108,11 @@ UPDATE "TRANSFORM" SET "TAGS" = (), "UPDATED_AT" = NOW() WHERE "ID" = ? [42122-2
|
|
|
116
108
|
|
|
117
109
|
Three specific footguns:
|
|
118
110
|
|
|
119
|
-
- **`tags` is not a
|
|
111
|
+
- **`tags` is not a REST key.** The serdes/YAML representation uses `tags`; the REST contract uses `tag_ids` (an array of integer ids). If you pulled a YAML representation and want to PUT it, translate `tags: [...]` → `tag_ids: [...]` first (or omit it if you're not changing tag membership).
|
|
120
112
|
- **`source_type`, `target_db_id`, `target_table_id`, `entity_id`** are derived/computed by the server. They appear in GET responses for the agent's benefit; the server doesn't accept them on update.
|
|
121
|
-
- **`collection_id` must be a `:transforms`-namespace collection** — a regular card/dashboard collection id is rejected with `A Transform can only go in Collections in the :transforms namespace.`
|
|
113
|
+
- **`collection_id` must be a `:transforms`-namespace collection** — a regular card/dashboard collection id is rejected with `A Transform can only go in Collections in the :transforms namespace.` Round-tripping the existing value is safe; setting it to an ordinary collection is what fails.
|
|
122
114
|
|
|
123
|
-
|
|
115
|
+
Patch only what changes:
|
|
124
116
|
|
|
125
117
|
```bash
|
|
126
118
|
# Rename only:
|
|
@@ -132,7 +124,7 @@ SELECT …
|
|
|
132
124
|
FROM public.orders
|
|
133
125
|
SQL
|
|
134
126
|
jq -n --rawfile q ./.scratch/orders.sql \
|
|
135
|
-
'{ source: { type: "query", query: { type: "
|
|
127
|
+
'{ source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } } }' \
|
|
136
128
|
> ./.scratch/patch.json
|
|
137
129
|
mb transform update <id> --file ./.scratch/patch.json --profile <name> --json
|
|
138
130
|
|
|
@@ -140,7 +132,7 @@ mb transform update <id> --file ./.scratch/patch.json --profile <name> --json
|
|
|
140
132
|
mb transform update <id> --body '{"tag_ids":[1,3]}' --profile <name> --json
|
|
141
133
|
```
|
|
142
134
|
|
|
143
|
-
`tag_ids` are the integer ids of **transform tags** —
|
|
135
|
+
`tag_ids` are the integer ids of **transform tags** — manage them with the `transform-tag` group: `mb transform-tag list --json` (find ids; the four built-ins `hourly`/`daily`/`weekly`/`monthly` are seeded), `mb transform-tag create --body '{"name":"nightly"}' --json`, `mb transform-tag update <id>`, `mb transform-tag delete <id>`. Tags are also how a `transform-job` selects what to run — a job executes every transform carrying one of the job's tags (see "Transform jobs").
|
|
144
136
|
|
|
145
137
|
If you really must round-trip, project to the writable subset:
|
|
146
138
|
|
|
@@ -153,13 +145,11 @@ mb transform get <id> --full --profile <name> --json \
|
|
|
153
145
|
|
|
154
146
|
## Iterating on a failing transform
|
|
155
147
|
|
|
156
|
-
When `transform run` fails and you want to retry with a fixed body, **prefer `transform update <id> --file body.json` over `transform delete <id>` + `transform create`.** Update keeps the same row,
|
|
148
|
+
When `transform run` fails and you want to retry with a fixed body, **prefer `transform update <id> --file body.json` over `transform delete <id>` + `transform create`.** Update keeps the same row, `entity_id`, materialized table, and on-disk YAML filename:
|
|
157
149
|
|
|
158
150
|
- `git-sync export` produces **one** clean commit containing only the fix, instead of "broken transform" + "remove broken transform" landing as two commits in `git log`.
|
|
159
|
-
- You don't
|
|
160
|
-
- The materialized output table either updates in place or, if the SELECT shape changed incompatibly, errors loudly on the next run rather than landing in a parallel `..._2` table
|
|
161
|
-
|
|
162
|
-
Recipe:
|
|
151
|
+
- You don't chase `_2` suffixes minted when two YAMLs share a `name` on disk.
|
|
152
|
+
- The materialized output table either updates in place or, if the SELECT shape changed incompatibly, errors loudly on the next run rather than landing in a parallel `..._2` table you have to clean up. (`transform delete-table <id>` resets the column shape for a clean slate.)
|
|
163
153
|
|
|
164
154
|
```bash
|
|
165
155
|
# 1. Try once
|
|
@@ -172,7 +162,7 @@ cat > ./.scratch/source.sql <<'SQL'
|
|
|
172
162
|
<fixed SQL, formatted>
|
|
173
163
|
SQL
|
|
174
164
|
jq -n --rawfile q ./.scratch/source.sql \
|
|
175
|
-
'{ source: { type: "query", query: { type: "
|
|
165
|
+
'{ source: { type: "query", query: { "lib/type": "mbql/query", database: <db-id>, stages: [{ "lib/type": "mbql.stage/native", native: $q }] } } }' \
|
|
176
166
|
> ./.scratch/source-patch.json
|
|
177
167
|
mb transform update "$ID" --file ./.scratch/source-patch.json --profile <n> --json
|
|
178
168
|
|
|
@@ -180,7 +170,7 @@ mb transform update "$ID" --file ./.scratch/source-patch.json --profile <n> --js
|
|
|
180
170
|
mb transform run "$ID" --wait --profile <n> --json # → succeeded
|
|
181
171
|
```
|
|
182
172
|
|
|
183
|
-
If you really must `create + delete` instead, do the `delete` **before** the first `git-sync export` so the failed entity never lands in git history
|
|
173
|
+
If you really must `create + delete` instead, do the `delete` **before** the first `git-sync export` so the failed entity never lands in git history — an export of a soft-failed state is noise that needs a follow-up cleanup commit. See `git-sync`, "Read state before mutating", for the ordering rule.
|
|
184
174
|
|
|
185
175
|
## Drop the materialized table (keep the transform)
|
|
186
176
|
|
|
@@ -188,7 +178,7 @@ If you really must `create + delete` instead, do the `delete` **before** the fir
|
|
|
188
178
|
mb transform delete-table <id> --yes --profile <name>
|
|
189
179
|
```
|
|
190
180
|
|
|
191
|
-
Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the next run. **`--yes` is required**
|
|
181
|
+
Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the next run. **`--yes` is required** non-interactively; without it the command exits with `refusing to delete <id> without confirmation — pass --yes to proceed non-interactively`.
|
|
192
182
|
|
|
193
183
|
## Delete the transform
|
|
194
184
|
|
|
@@ -196,7 +186,7 @@ Useful when you've changed the SELECT and want a fresh `CREATE TABLE` on the nex
|
|
|
196
186
|
mb transform delete <id> --yes --profile <name>
|
|
197
187
|
```
|
|
198
188
|
|
|
199
|
-
Removes the definition. Whether the materialized table is dropped depends on the server — check with `mb table list --db-id <db-id> --profile <name> --json` if it matters. Same `--yes` rule as `delete-table`.
|
|
189
|
+
Removes the definition. Whether the materialized table is dropped depends on the server — check with `mb table list --db-id <db-id> --profile <name> --json` if it matters. Same `--yes` rule and message as `delete-table`.
|
|
200
190
|
|
|
201
191
|
## Transform jobs (schedules)
|
|
202
192
|
|
|
@@ -212,9 +202,3 @@ mb transform-job set-active false --profile <name> --json # disable every job a
|
|
|
212
202
|
```
|
|
213
203
|
|
|
214
204
|
`transform-job run` is fire-and-forget — the server returns `{message, job_run_id}` immediately, with no per-job-run polling (no `--wait`). Most ad-hoc agent work is one-off `transform run`, not job authoring.
|
|
215
|
-
|
|
216
|
-
## Don't (transform-specific)
|
|
217
|
-
|
|
218
|
-
- Don't put `transform run` calls in tight polling loops — pass `--wait` and let the CLI handle the polling. Manual loops without `--wait` will hammer the server.
|
|
219
|
-
- Don't author MBQL 4 (the legacy nested `{ type: "query", query: {...} }` shape) by hand — pull a sample with `mb transform get <id> --full --json`. MBQL 5 (`lib/type: "mbql/query"`) **is** authorable by hand thanks to the `mb query --print-schema` + `--dry-run` feedback loop; for non-trivial pipelines you may still prefer building in the UI and exporting.
|
|
220
|
-
- Don't paste a `transform get` body into `transform update` — the PUT endpoint only accepts writable keys, and unknown keys (notably `tags`, `source_type`, `entity_id`, `created_at`, `last_run`) leak as raw SQL errors. See "Update body: send only writable keys" above. Use `tag_ids` (not `tags`) on the REST contract.
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: visualization
|
|
3
|
-
description:
|
|
3
|
+
description: Choose a card's `display` (chart type) and author its `visualization_settings` for the `mb` CLI — which chart fits which data shape, the required keys per chart, the rule that settings name OUTPUT columns, and the `column_settings` JSON-string-key footgun; the full per-chart key catalog is in references. Use when deciding or fixing how a card renders — "what chart should I use", "make this a bar/line/pie chart", "map this by state", "format this column as currency", "add conditional formatting", "the card renders as a table instead of a chart", or any `display` / `visualization_settings` work.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Visualization: pick the chart, then set it
|
|
8
8
|
|
|
9
|
-
> **
|
|
9
|
+
> **Building charts as part of a guided data project?** Follow the `data-workflow` **Shared Contract** — answer-first with detail on demand, ask before showing PII, honor the autonomy mode, name what the CLI can't do instead of erroring into raw SQL: `mb skills get data-workflow`.
|
|
10
10
|
|
|
11
11
|
A card has two presentation fields alongside its `dataset_query`:
|
|
12
12
|
|
|
13
|
-
- **`display`** — the chart type (`bar`, `line`, `pie`, `scalar`, `map`, `table`, …)
|
|
13
|
+
- **`display`** — the chart type (`bar`, `line`, `pie`, `scalar`, `map`, `table`, …); pick from the valid values below.
|
|
14
14
|
- **`visualization_settings`** — a map whose keys are **namespaced by `display`** (`graph.*` for bar/line/area/combo, `pie.*` for pie, `table.*` for table, …). The server stores almost anything and **silently ignores keys that don't apply** to the chosen `display`.
|
|
15
15
|
|
|
16
16
|
Nothing validates `visualization_settings` — there is no pre-flight to fail past. A `display` typo or a misnamed key is accepted by the API; the card just renders as a default table or drops the setting. So **the feedback loop is read-back, not pre-flight**: after `card create`/`update`, confirm with `mb card get <id> --full --json` (or open the card) that it rendered as intended.
|
|
17
17
|
|
|
18
|
-
Flag conventions and body-input precedence live in
|
|
18
|
+
Flag conventions and body-input precedence live in `core` (`mb skills get core`); the `dataset_query` itself is the `mbql` skill's job (`mb skills get mbql`). This skill is only about how the result is displayed.
|
|
19
19
|
|
|
20
20
|
Two steps: **(1) pick the `display` that fits the data**, then **(2) bind the data columns and set options**.
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ Decide which relationship in the data matters most, then pick the chart. The sha
|
|
|
27
27
|
- **Compare a measure across categories** → `bar` (vertical). Use `row` (horizontal bar) when labels are long or there are many categories. Sort by value unless the dimension has a natural order.
|
|
28
28
|
- **Change over time / trend** → `line` for a continuous series; `bar`/`area` for a few discrete periods. Two measures on unlike scales → `combo` (line + bar, dual-axis) — only when the metrics are genuinely related.
|
|
29
29
|
- **Part-to-whole, one snapshot** → `pie`, but only for a meaningful whole with **≤5 slices**; beyond that use a sorted `bar`/`row`. Composition over time → stacked `area`/`bar`.
|
|
30
|
-
- **Distribution / spread / outliers** → `
|
|
30
|
+
- **Distribution / spread / outliers** → a `bar` histogram (bin the measure — see `mbql` binning); on **v59+** servers `boxplot` compares several groups' spread directly.
|
|
31
31
|
- **Correlation between two measures** → `scatter` (a third measure → bubble size).
|
|
32
32
|
- **Sequential additive contributions** (start → +/− steps → total) → `waterfall`.
|
|
33
33
|
- **Stage drop-off in an ordered, cumulative funnel** → `funnel`.
|
|
@@ -35,7 +35,7 @@ Decide which relationship in the data matters most, then pick the chart. The sha
|
|
|
35
35
|
- **Geographic** → `map`: region/choropleth (a region dimension + a measure), pin (lat + long), or grid/heat (coordinates + measure).
|
|
36
36
|
- **Precise values, many columns, mixed types, or no chart fits** → `table`; `pivot` for a cross-tab of two dimensions; `object` for a single record's detail.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Valid `display` values — the registered visualizations: `table`, `bar`, `line`, `area`, `row`, `pie`, `scalar`, `smartscalar`, `combo`, `pivot`, `funnel`, `map`, `scatter`, `waterfall`, `progress`, `gauge`, `object`, `sankey`. The API types `display` as a plain string and accepts any value — it renders an unknown one as nothing. (`boxplot` is registered only on **v59+** servers — older ones render it blank; use a `bar` histogram for distributions instead. `scalar` **is** the "Number" viz — `display: number` is a legacy serialization alias, not a registered visualization; use `scalar`. `list` exists but is hidden — don't pick it. `heading`/`text`/`link`/`iframe`/`action` are dashcard virtuals, not standalone cards — see references.) A typo like `bargraph`/`linechart` is accepted and renders blank — the most common "why is my chart blank" cause.
|
|
39
39
|
|
|
40
40
|
## Step 2 — bind data columns and set options
|
|
41
41
|
|
|
@@ -53,7 +53,7 @@ Closed `display` enum (card-level, non-hidden): `table`, `bar`, `line`, `area`,
|
|
|
53
53
|
| `row` | as bar; prefer for long/many category labels | `graph.dimensions`, `graph.metrics` |
|
|
54
54
|
| `scatter` | two numeric measures (correlation) | `graph.dimensions`, `graph.metrics` (`scatter.bubble` opt) |
|
|
55
55
|
| `waterfall` | exactly 1 dimension + ≥1 measure; sequential | `graph.dimensions` (1), `graph.metrics` (1) |
|
|
56
|
-
| `boxplot`
|
|
56
|
+
| `boxplot` _(v59+)_ | ≥3 cols, ≥2 dimensions, ≥1 measure | `graph.dimensions`, `graph.metrics` |
|
|
57
57
|
| `pie` | ≥2 rows, ≥2 cols, ≥1 dimension + ≥1 measure; ≤~5 slices | `pie.dimension`, `pie.metric` |
|
|
58
58
|
| `funnel` | 2 columns (stage + value); ordered stages | `funnel.dimension`, `funnel.metric` |
|
|
59
59
|
| `map` (region) | a string/region dimension + a measure | `map.region`, `map.dimension`, `map.metric` |
|
|
@@ -151,7 +151,7 @@ mb skills path visualization # → the skill dir; then Read references
|
|
|
151
151
|
|
|
152
152
|
## Don't
|
|
153
153
|
|
|
154
|
-
- Don't invent `display` values (`bargraph`, `linechart`, `histogram`) or use `number`/`list` — use
|
|
154
|
+
- Don't invent `display` values (`bargraph`, `linechart`, `histogram`) or use `number`/`list` — use a registered value; the API accepts a typo and renders nothing.
|
|
155
155
|
- Don't put numeric field ids in `graph.dimensions`/`pie.metric`/`scalar.field`/`map.latitude_column` etc. — they take **output column-name strings**.
|
|
156
156
|
- Don't reach for a `pie` with >5 slices, a `combo` of unrelated metrics, or a `pie`/`scalar` to show a trend — see Step 1.
|
|
157
157
|
- Don't write a `column_settings` key as an object — it's a JSON **string** (`"[\"name\",\"COL\"]"`), inner quotes escaped.
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# visualization_settings — per-chart key reference
|
|
2
2
|
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Cartesian — `bar`, `line`, `area`, `combo`, `scatter`, `waterfall`, `row`, `boxplot`](#cartesian--bar-line-area-combo-scatter-waterfall-row-boxplot) — shared keys (binding, stacking, goal/trend, data labels, axes, tooltip) plus `scatter`, `waterfall`, `row`, `boxplot` extras
|
|
6
|
+
- [Part-to-whole & single value — `pie`, `funnel`, `gauge`, `progress`, `scalar`, `smartscalar`](#part-to-whole--single-value--pie-funnel-gauge-progress-scalar-smartscalar)
|
|
7
|
+
- [Tabular, geographic & flow — `table`, `pivot`, `object`, `map`, `sankey`](#tabular-geographic--flow--table-pivot-object-map-sankey) — includes `table.column_formatting` conditional formatting
|
|
8
|
+
- [`column_settings` — per-column formatting](#column_settings--per-column-formatting) — number/date/currency, `view_as`, alignment, mini bars
|
|
9
|
+
- [`series_settings` — per-series styling (cartesian)](#series_settings--per-series-styling-cartesian)
|
|
10
|
+
- [Virtual cards (dashcards only, `card_id: null`)](#virtual-cards-dashcards-only-card_id-null) — heading/text/link/iframe
|
|
11
|
+
- [Click behavior (dashcards only)](#click-behavior-dashcards-only)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
3
15
|
Authorable keys per `display`, plus the data shape each chart suits and the minimum needed to render. Set keys only to override defaults — an empty `{}` works for a simple aggregate.
|
|
4
16
|
|
|
5
17
|
All column-naming keys (`graph.dimensions`, `pie.dimension`, `table.columns[].name`, `map.latitude_column`, …) take **output column-name strings** — the names the query produces. Every key and value below is identical in the API form (`mb card create`) and the portable git-sync form, with two exceptions: `column_settings` `["ref", …]` keys and click-behavior dimension targets carry a numeric field id in the API form and a name-path in the portable form. In a JSON body, `column_settings` keys are escaped strings: `"[\"name\",\"TOTAL\"]"`.
|
|
@@ -92,6 +104,8 @@ Horizontal bars — use when category labels are long or numerous. Here `graph.d
|
|
|
92
104
|
|
|
93
105
|
## boxplot
|
|
94
106
|
|
|
107
|
+
_Registered only on **v59+** servers — older ones render `display: boxplot` blank; use a `bar` histogram there._
|
|
108
|
+
|
|
95
109
|
Use for distribution/spread/outliers, especially across several groups. Needs **unaggregated** rows. **Use for:** ≥3 columns, ≥2 dimensions, ≥1 measure. **Required:** `graph.dimensions`, `graph.metrics`. X-scale is `"ordinal"`.
|
|
96
110
|
|
|
97
111
|
| Key | Type | Values | Default |
|
|
@@ -20,8 +20,8 @@ mb skills get core # auth, flag conventions, every command group
|
|
|
20
20
|
mb skills list # everything available on the installed version
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**Doing a whole job, not one command?** If the user wants an outcome — "make sense of my data", "build a data model", "go from raw data to a dashboard", "answer questions about my data", "be my data analyst", "set up analytics for X" — load the
|
|
23
|
+
**Doing a whole job, not one command?** If the user wants an outcome — "make sense of my data", "build a data model", "go from raw data to a dashboard", "answer questions about my data", "be my data analyst", "set up analytics for X" — load the guided end-to-end skill instead and let it drive:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
mb skills get
|
|
26
|
+
mb skills get data-workflow
|
|
27
27
|
```
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import "./command-augment-CAur0XOQ.mjs";
|
|
2
|
-
import "./error-CIObEXLY.mjs";
|
|
3
|
-
import "./runtime-CmAIahm5.mjs";
|
|
4
|
-
import "./capabilities-BX1rnVuH.mjs";
|
|
5
|
-
import "./parse-id-B5adfBlS.mjs";
|
|
6
|
-
import "./poll-AduuU55-.mjs";
|
|
7
|
-
import "./poll-task-B00Qwd87.mjs";
|
|
8
|
-
import { SyncSettingsUpdateResult, add_collection_default, setCollectionRemoteSynced, syncSettingsUpdateView } from "./add-collection-H4LcP-9B.mjs";
|
|
9
|
-
|
|
10
|
-
export { add_collection_default as default };
|
package/dist/auth-BaCMFLTA.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/auth/index.ts
|
|
4
|
-
var auth_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "auth",
|
|
7
|
-
description: "Authenticate against a Metabase instance"
|
|
8
|
-
},
|
|
9
|
-
default: "login",
|
|
10
|
-
subCommands: {
|
|
11
|
-
login: () => import("./login-C0Rf2hg0.mjs").then((m) => m.default),
|
|
12
|
-
status: () => import("./status-DY92F9mn.mjs").then((m) => m.default),
|
|
13
|
-
list: () => import("./list-FR8Q1SzV.mjs").then((m) => m.default),
|
|
14
|
-
logout: () => import("./logout-C7_UON-s.mjs").then((m) => m.default)
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
//#endregion
|
|
19
|
-
export { auth_default as default };
|
package/dist/card-DzH3aK0a.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/card/index.ts
|
|
4
|
-
var card_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "card",
|
|
7
|
-
description: "Manage Metabase cards (questions, models, metrics)"
|
|
8
|
-
},
|
|
9
|
-
subCommands: {
|
|
10
|
-
list: () => import("./list-D52_BozQ.mjs").then((mod) => mod.default),
|
|
11
|
-
get: () => import("./get-CXMv-r1p.mjs").then((mod) => mod.default),
|
|
12
|
-
query: () => import("./query-BUkuB4bZ.mjs").then((mod) => mod.default),
|
|
13
|
-
create: () => import("./create-B-mvVFIl.mjs").then((mod) => mod.default),
|
|
14
|
-
update: () => import("./update-I3TA2Tem.mjs").then((mod) => mod.default),
|
|
15
|
-
archive: () => import("./archive-BCXoM1nX.mjs").then((mod) => mod.default)
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { card_default as default };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/collection/index.ts
|
|
4
|
-
var collection_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "collection",
|
|
7
|
-
description: "Manage Metabase collections"
|
|
8
|
-
},
|
|
9
|
-
subCommands: {
|
|
10
|
-
list: () => import("./list-F0vkE22V.mjs").then((mod) => mod.default),
|
|
11
|
-
get: () => import("./get-rFcAVIch.mjs").then((mod) => mod.default),
|
|
12
|
-
items: () => import("./items-1KkBMiO4.mjs").then((mod) => mod.default),
|
|
13
|
-
tree: () => import("./tree-B3f5F_dP.mjs").then((mod) => mod.default),
|
|
14
|
-
create: () => import("./create-CF2Zn4pT.mjs").then((mod) => mod.default),
|
|
15
|
-
archive: () => import("./archive-hN8PfvhX.mjs").then((mod) => mod.default)
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
|
-
export { collection_default as default };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
//#region src/commands/dashboard/index.ts
|
|
4
|
-
var dashboard_default = defineCommand({
|
|
5
|
-
meta: {
|
|
6
|
-
name: "dashboard",
|
|
7
|
-
description: "Manage Metabase dashboards"
|
|
8
|
-
},
|
|
9
|
-
subCommands: {
|
|
10
|
-
list: () => import("./list-C4KnM3Rq.mjs").then((mod) => mod.default),
|
|
11
|
-
get: () => import("./get-Nc5GOs6-.mjs").then((mod) => mod.default),
|
|
12
|
-
cards: () => import("./cards-Bw37jizL.mjs").then((mod) => mod.default),
|
|
13
|
-
create: () => import("./create-DSWjS09p.mjs").then((mod) => mod.default),
|
|
14
|
-
update: () => import("./update-DOfL_KPx.mjs").then((mod) => mod.default),
|
|
15
|
-
"update-dashcard": () => import("./update-dashcard-BXZ4vS15.mjs").then((mod) => mod.default),
|
|
16
|
-
archive: () => import("./archive-C6MDtV1F.mjs").then((mod) => mod.default)
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
//#endregion
|
|
21
|
-
export { dashboard_default as default };
|