@metabase/cli 0.1.17 → 0.2.0
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 +61 -35
- package/dist/{add-collection-C-t9SQBk.mjs → add-collection-BTSGp4f9.mjs} +4 -5
- package/dist/add-collection-DlAwt2Z3.mjs +9 -0
- package/dist/append-DY1-O789.mjs +42 -0
- package/dist/{archive-wNXwIiF0.mjs → archive-BOEEMrYL.mjs} +6 -8
- package/dist/{archive-CaoUUTIb.mjs → archive-CJ9rElKn.mjs} +6 -8
- package/dist/{archive-DS-KEB4a.mjs → archive-CWtUzlHO.mjs} +5 -7
- package/dist/{archive-LG8u7ec5.mjs → archive-DCPw3df6.mjs} +5 -7
- package/dist/{archive-D1FX-sbU.mjs → archive-DOa1u1aJ.mjs} +5 -7
- package/dist/{archive-C7dnyzVY.mjs → archive-pHsa4dL0.mjs} +8 -9
- package/dist/{archive-qSOcACQo.mjs → archive-w6TX-zEk.mjs} +7 -8
- package/dist/auth-BrQtY0pO.mjs +22 -0
- package/dist/{body-IcJ5kFtk.mjs → body-C1_AaTS4.mjs} +3 -3
- package/dist/{branches-BlNCTmFB.mjs → branches-BsBzaGIf.mjs} +5 -7
- package/dist/{cancel-BnveTPNw.mjs → cancel-BNM0yyx-.mjs} +4 -6
- package/dist/{cancel-task-CSVI8Zgl.mjs → cancel-task-Bggzn4JL.mjs} +5 -7
- package/dist/capabilities-DrDJo_ng.mjs +684 -0
- package/dist/{card-DEmcRlNO.mjs → card-B8G5jQQT.mjs} +24 -18
- package/dist/card-DHCcmpgr.mjs +26 -0
- package/dist/{cards-CMgGA8OZ.mjs → cards-D0hvA13L.mjs} +7 -8
- package/dist/cli.mjs +175 -29
- package/dist/{collection-DrLpA1SO.mjs → collection-CFoLPrY5.mjs} +1 -1
- package/dist/collection-D0O9gp3S.mjs +23 -0
- package/dist/{collection-namespace-CsaxEqOb.mjs → collection-namespace-DqPNrCdQ.mjs} +2 -2
- package/dist/{create-I5Cv-MBa.mjs → create-05rX4oL-.mjs} +11 -12
- package/dist/{create-B6LQutd0.mjs → create-BR7x66KW.mjs} +12 -13
- package/dist/{create-B79M8YpX.mjs → create-C17Fh1_X.mjs} +9 -10
- package/dist/{create-FZOrCw5k.mjs → create-C8ndg3g4.mjs} +28 -21
- package/dist/{create-C7u3umLj.mjs → create-CZ9KFnbi.mjs} +8 -9
- package/dist/{create-B1xfaZJB.mjs → create-Cii_utzV.mjs} +5 -7
- package/dist/{create-SHKlj0z3.mjs → create-Cyv7vHV_.mjs} +8 -9
- package/dist/{create-Bg_uMu0p.mjs → create-DKuixGJ2.mjs} +8 -9
- package/dist/{create-AeWNv0v-.mjs → create-E6L3WMqV.mjs} +8 -9
- package/dist/{create-LHqwcVbS.mjs → create-a3s1S2Dw.mjs} +11 -12
- package/dist/{create-branch-CGyT99Ny.mjs → create-branch-Dxn6UnV_.mjs} +5 -7
- package/dist/{create-BpkWlgoR.mjs → create-oLb0E-ZP.mjs} +17 -14
- package/dist/csv-T6_vlLJS.mjs +58 -0
- package/dist/{current-task-C-0hw2Ae.mjs → current-task-DxWbw-bU.mjs} +5 -7
- package/dist/{dashboard-DOplbKyQ.mjs → dashboard-B-MTpfeS.mjs} +35 -15
- package/dist/dashboard-BpSPoeAn.mjs +35 -0
- package/dist/{database-D9fftP-i.mjs → database-CIbVSfua.mjs} +2 -2
- package/dist/db-DBRijnh4.mjs +27 -0
- package/dist/{delete-tCVrYjKD.mjs → delete--P1xFX37.mjs} +6 -8
- package/dist/{delete-Do3nn9sl.mjs → delete-BBOIUl0D.mjs} +6 -8
- package/dist/{delete--NYYN6wv.mjs → delete-Ce2EBQYA.mjs} +6 -8
- package/dist/{delete-runtime-B0ha5QR4.mjs → delete-runtime-B-8I9V1g.mjs} +2 -3
- package/dist/{delete-table-CWSGPw0i.mjs → delete-table-Bj32NsXn.mjs} +6 -8
- package/dist/{dependencies-CyFocD8I.mjs → dependencies-ixzjVjUz.mjs} +5 -7
- package/dist/{dirty-BXM0YJ_a.mjs → dirty-CH70h5m5.mjs} +5 -7
- package/dist/{document-1W7NRaO_.mjs → document-CL4ML5FN.mjs} +20 -4
- package/dist/document-dZpuFu3I.mjs +22 -0
- package/dist/{eid-p-zn_1RJ.mjs → eid-XjkBbyb_.mjs} +10 -11
- package/dist/embedding-CKdw8q7p.mjs +11 -0
- package/dist/{export-B_8wghyo.mjs → export-CChMg-mL.mjs} +7 -9
- package/dist/field-B1heFeCz.mjs +21 -0
- package/dist/{field-CMY_LWUe.mjs → field-DsXTeqBy.mjs} +6 -4
- package/dist/{fields-BsufL9kv.mjs → fields-C7h2QwUp.mjs} +6 -8
- package/dist/{get-Cl5Ak73C.mjs → get-AvGH_oph.mjs} +8 -9
- package/dist/{get-DurjkUKJ.mjs → get-B6ZJCJ8H.mjs} +5 -7
- package/dist/{get-68P-L4ja.mjs → get-BWHJDa0B.mjs} +5 -7
- package/dist/{get-B67pe00Z.mjs → get-BsyccuCH.mjs} +5 -7
- package/dist/{get-C7mq17-2.mjs → get-C98IFgFi.mjs} +5 -7
- package/dist/{get-BTKeO4vE.mjs → get-Cj90oyxG.mjs} +5 -7
- package/dist/{get-BJoZkATD.mjs → get-D9r2znrn.mjs} +5 -7
- package/dist/{get-D3k2JUsa.mjs → get-DRZENkCj.mjs} +5 -7
- package/dist/{get-4lufgahL.mjs → get-DWWmpQ87.mjs} +5 -7
- package/dist/{get-CPw0ROO4.mjs → get-Dg1J0wxb.mjs} +5 -7
- package/dist/{get-B45uYMAs.mjs → get-DrWYvefc.mjs} +7 -8
- package/dist/{get-DTqBuIf3.mjs → get-KVN9ANVh.mjs} +4 -6
- package/dist/get-RlP_xiUd.mjs +67 -0
- package/dist/{get-Df_3LCVC.mjs → get-cyR67Lmq.mjs} +5 -7
- package/dist/{get-DeLnNVQE.mjs → get-kqY2lwhO.mjs} +16 -9
- package/dist/{get-run-Dq4qfGfD.mjs → get-run-C4C_QQqO.mjs} +5 -7
- package/dist/git-sync-JHa7bIyY.mjs +31 -0
- package/dist/{group-BNE_RiH5.mjs → group-CILtQqCL.mjs} +2 -1
- package/dist/{has-remote-changes-E-4N_O_t.mjs → has-remote-changes-dLUGr0MD.mjs} +5 -7
- package/dist/{import-Dg0j3HCP.mjs → import-Byull6jF.mjs} +7 -9
- package/dist/{input-7Sj85_K7.mjs → input-m9ctfLxZ.mjs} +6 -3
- package/dist/is-dirty-Bb2dfEYs.mjs +8 -0
- package/dist/{is-dirty-DWw1yBeR.mjs → is-dirty-CU3ovgdG.mjs} +3 -4
- package/dist/{items-CQtt9X4M.mjs → items-CEGDYWuF.mjs} +7 -9
- package/dist/{key-bltP32Pm.mjs → key-Ge4VwWvS.mjs} +1 -1
- package/dist/{library-B5AvpACG.mjs → library-DHWWa4Tt.mjs} +6 -6
- package/dist/{list-BFVuPodI.mjs → list-3z1ZChTG.mjs} +4 -6
- package/dist/{list-D3TSAqwl.mjs → list-B9TyELob.mjs} +4 -6
- package/dist/{list-BAOTQHst.mjs → list-BFm4iEBt.mjs} +4 -6
- package/dist/{list-BKUvhAs4.mjs → list-BliCJAjt.mjs} +4 -6
- package/dist/{list-BgUWqZa2.mjs → list-Bpv06V24.mjs} +6 -8
- package/dist/{list-DG0FIhTK.mjs → list-Br48kL09.mjs} +4 -6
- package/dist/{list-BHAWYZ1z.mjs → list-BtcpNl4-.mjs} +4 -6
- package/dist/{list-B1uVWy6A.mjs → list-BuyRG3GB.mjs} +7 -8
- package/dist/{list-CvhVYOVl.mjs → list-CMb03cv3.mjs} +5 -7
- package/dist/{list-CfqpDTna.mjs → list-CmCJwaGT.mjs} +4 -6
- package/dist/{list-C4bALfCs.mjs → list-Cur_72xZ.mjs} +5 -7
- package/dist/{list-BF-W4jOZ.mjs → list-D9FY30xH.mjs} +8 -9
- package/dist/{list-D5Gdz8hi.mjs → list-DEkPxVZu.mjs} +4 -6
- package/dist/{list-C9O2RY5u.mjs → list-O2w5xd1q.mjs} +4 -6
- package/dist/{list-KxQNqp4T.mjs → list-th_Ym6Cv.mjs} +6 -8
- package/dist/{login-Bt6j6yBM.mjs → login-hKlYsAa1.mjs} +8 -10
- package/dist/{logout-ANkL02p0.mjs → logout-DO8GVC6b.mjs} +4 -6
- package/dist/measure-R3T20wnB.mjs +25 -0
- package/dist/{measure-DoJvtCaA.mjs → measure-S9WszHre.mjs} +5 -4
- package/dist/{parameter-values-D4J8Ctu0.mjs → parameter-values-BJb2GuLz.mjs} +7 -9
- package/dist/{parse-enum-BL9i_brN.mjs → parse-enum-Cfne2suT.mjs} +1 -1
- package/dist/{parse-id-D4LeTUsP.mjs → parse-id-DfY0Lugw.mjs} +1 -1
- package/dist/{parse-ref-CB_KvF9h.mjs → parse-ref-C_MsXYI6.mjs} +1 -1
- package/dist/{path-5nQgdvrs.mjs → path-DzhQVCH8.mjs} +4 -6
- package/dist/{poll-BpAJpvb-.mjs → poll-D1Yg78c3.mjs} +2 -2
- package/dist/{poll-task-TilgciQn.mjs → poll-task-DLUZaglY.mjs} +2 -2
- package/dist/{preflight-OfHU3Toi.mjs → preflight-Cy2jUSca.mjs} +4 -5
- package/dist/{process-j8UHMHc2.mjs → process-D5HN1wpT.mjs} +1 -1
- package/dist/{prompt-C85xd9HR.mjs → prompt-ZG9Drin_.mjs} +1 -1
- package/dist/{publish-DKiqyDwo.mjs → publish-3hmHoaX-.mjs} +7 -9
- package/dist/{query-CQ3xXa9P.mjs → query-DTASj9Zu.mjs} +9 -10
- package/dist/{query-BWJ5h1g3.mjs → query-DkZViD_3.mjs} +15 -15
- package/dist/{query-result-D6mfoVfQ.mjs → query-result-CbTFdlSf.mjs} +1 -1
- package/dist/{remove-collection-CO-aMzTO.mjs → remove-collection-D3wH9H6R.mjs} +7 -9
- package/dist/replace-CFzqkZXs.mjs +42 -0
- package/dist/requests-KBiY9UYy.mjs +96 -0
- package/dist/{rescan-values-DpL6LGAK.mjs → rescan-values-B1WycObS.mjs} +7 -9
- package/dist/{resolve-Dj2MTBkn.mjs → resolve-Ct084wx3.mjs} +1 -1
- package/dist/{run-DUcdaZs3.mjs → run-B8qEEdC8.mjs} +4 -6
- package/dist/{run-QYJ-mAaG.mjs → run-cw0qTEQ7.mjs} +7 -9
- package/dist/{runs-DgasTnGd.mjs → runs-BOA4kK_X.mjs} +6 -8
- package/dist/{runtime-BJtuxWM8.mjs → runtime-B-c-Fc2i.mjs} +3 -4
- package/dist/{schema-tables-ooYjimrV.mjs → schema-tables-DRYzSVcW.mjs} +6 -8
- package/dist/{schemas-HjFPzsd-.mjs → schemas-BiMjD25L.mjs} +4 -6
- package/dist/{search-vaT5EwhG.mjs → search-DjKxRXO5.mjs} +4 -6
- package/dist/{segment-TXktTCfU.mjs → segment-BPb1H6DW.mjs} +6 -5
- package/dist/segment-CZkk0g79.mjs +25 -0
- package/dist/{selectors-DlmZpo2L.mjs → selectors-B7MJUn3I.mjs} +3 -3
- package/dist/{set-active-oZOUe9V7.mjs → set-active-CF868qdW.mjs} +4 -6
- package/dist/{set-CLHJauzG.mjs → set-kyoORFG3.mjs} +10 -10
- package/dist/setting-BZgjURQ_.mjs +20 -0
- package/dist/{setup-D1de5Sbc.mjs → setup-D9joJYPb.mjs} +11 -12
- package/dist/{skills-B6gfH0iR.mjs → skills-BqI05u72.mjs} +1 -1
- package/dist/{skills-NhgVypJ7.mjs → skills-D8NrJxjp.mjs} +3 -3
- package/dist/{snippet-CtA2Pkoa.mjs → snippet-DWiXNy4T.mjs} +7 -4
- package/dist/snippet-y_FzKxaQ.mjs +22 -0
- package/dist/{stash-DoRwelwY.mjs → stash-BVMerTaF.mjs} +7 -9
- package/dist/{status-CmRgHTrV.mjs → status-C-0jFjFn.mjs} +4 -6
- package/dist/{status-BpMOlfsA.mjs → status-x8bDOa1i.mjs} +6 -8
- package/dist/{summary-BEu7pmpq.mjs → summary-B-R9SdMF.mjs} +5 -7
- package/dist/{sync-schema-DTUXubMg.mjs → sync-schema-wne2KXIW.mjs} +9 -11
- package/dist/{table-DE3i82T_.mjs → table-C3RIKiYM.mjs} +16 -4
- package/dist/table-CS2-2INi.mjs +21 -0
- package/dist/transform-6hqM1DXA.mjs +31 -0
- package/dist/{transform-DEF38FWe.mjs → transform-JQel7e5H.mjs} +31 -5
- package/dist/transform-job-DuERhLaj.mjs +25 -0
- package/dist/transform-tag-DaNKreDj.mjs +21 -0
- package/dist/{transforms-CWvMpLc9.mjs → transforms-DB2WNP2R.mjs} +5 -7
- package/dist/{tree-D18vYSe4.mjs → tree-BIOphGVs.mjs} +4 -6
- package/dist/{unpublish-CJbL4ZsC.mjs → unpublish-BWM36ZTg.mjs} +5 -7
- package/dist/{update-mr9todHq.mjs → update-6umfSz5t.mjs} +9 -10
- package/dist/{update-BjjZIe2W.mjs → update-7Ts7KrB8.mjs} +9 -10
- package/dist/{update-Doia9MP_.mjs → update-BCbv880u.mjs} +12 -13
- package/dist/{update-CSxwZ2us.mjs → update-BchtHlbH.mjs} +10 -11
- package/dist/{update-BvsvyBw9.mjs → update-BiUeEGU-.mjs} +12 -13
- package/dist/{update-DhOfrW1j.mjs → update-BlzcZwRQ.mjs} +18 -15
- package/dist/{update-Bos8nnv0.mjs → update-CTZg2Lia.mjs} +13 -14
- package/dist/{update-DV7IY7IQ.mjs → update-DJDuIhYG.mjs} +9 -10
- package/dist/{update-Dxa_6H2A.mjs → update-HxYcvpSq.mjs} +15 -15
- package/dist/{update-Ck0Kxv8u.mjs → update-Sz0QRGWB.mjs} +9 -10
- package/dist/{update-dashcard-CZUWZhal.mjs → update-dashcard-CfDpy7Lz.mjs} +11 -11
- package/dist/{update-7DacwIvi.mjs → update-zfLbmC-H.mjs} +9 -10
- package/dist/{upgrade-CBW8V9qZ.mjs → upgrade-BWl0VjMK.mjs} +5 -7
- package/dist/upload-CPzOhGeX.mjs +16 -0
- package/dist/{uuid-D6JVJ-R1.mjs → uuid-D_YeqDui.mjs} +3 -5
- package/dist/{validate-BqNW4Sk1.mjs → validate-clAWO3X-.mjs} +1 -2
- package/dist/{validate-query-CcZVKYPV.mjs → validate-query-CsK2_2_u.mjs} +2 -3
- package/dist/{values-DBvuGnnA.mjs → values-DSk8ZJ3B.mjs} +5 -7
- package/dist/{verify-LIShMNZ2.mjs → verify-DkQdLjf3.mjs} +2 -2
- package/dist/{wait-DUHze3_B.mjs → wait-DvSsMjea.mjs} +6 -8
- package/dist/{wait-flags-Ybpt98PK.mjs → wait-flags-dXGThljd.mjs} +2 -2
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +31 -22
- package/skill-data/dashboard/SKILL.md +130 -0
- package/skill-data/data-workflow/SKILL.md +17 -15
- package/skill-data/data-workflow/references/building-clean-tables.md +1 -1
- package/skill-data/document/SKILL.md +3 -3
- package/skill-data/git-sync/SKILL.md +5 -5
- package/skill-data/mbql/SKILL.md +17 -17
- package/skill-data/mbql/references/operators.md +4 -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 +6 -6
- package/skill-data/visualization/SKILL.md +3 -3
- package/skill-data/visualization/references/settings.md +2 -0
- package/dist/add-collection-Dek6kiRI.mjs +0 -11
- package/dist/auth-Kv2MRkRk.mjs +0 -22
- package/dist/capabilities-N0jo5U7S.mjs +0 -239
- package/dist/card-Bp58WEUF.mjs +0 -26
- package/dist/collection-D8TI20Ir.mjs +0 -23
- package/dist/dashboard-C9pQCnX6.mjs +0 -28
- package/dist/db-DWykmBOh.mjs +0 -28
- package/dist/document-DNDm8_Py.mjs +0 -22
- package/dist/error-5H_tcfL8.mjs +0 -227
- package/dist/field-CFt1-KDR.mjs +0 -21
- package/dist/get-CDLa6LiC.mjs +0 -50
- package/dist/git-sync-ByvjqSiy.mjs +0 -31
- package/dist/is-dirty-D7UMN0mt.mjs +0 -10
- package/dist/manifest-BVf8P4bl.mjs +0 -126
- package/dist/measure-CgFwtL1r.mjs +0 -25
- package/dist/metadata-ClehtgZj.mjs +0 -39
- package/dist/metadata-DplshwI3.mjs +0 -38
- package/dist/notice-DyVl5aYB.mjs +0 -206
- package/dist/segment-BPC725mo.mjs +0 -25
- package/dist/setting-DHYO-W5g.mjs +0 -20
- package/dist/snippet-DtLmHr2J.mjs +0 -22
- package/dist/table-B1iBqmNu.mjs +0 -22
- package/dist/transform-BuIooRQh.mjs +0 -31
- package/dist/transform-job-C3yz0krA.mjs +0 -25
- package/dist/transform-tag-CJD6mJUe.mjs +0 -21
- /package/dist/{body-flags-DWTTxJpP.mjs → body-flags-CgHgJBRC.mjs} +0 -0
- /package/dist/{command-augment-DdZIfx1V.mjs → command-augment-D9pI9Vbh.mjs} +0 -0
- /package/dist/{paginate-FVZUxL4J.mjs → paginate-CMqbqhMC.mjs} +0 -0
- /package/dist/{parameter-CiJ4CwWE.mjs → parameter-BZ6y-Gfs.mjs} +0 -0
- /package/dist/{render-BTKnWL0d.mjs → render-CkuFkWlQ.mjs} +0 -0
- /package/dist/{revision-message-flag-CHrJgFFx.mjs → revision-message-flag-CHzX0cqV.mjs} +0 -0
- /package/dist/{setting-m46MUtW5.mjs → setting-Cko6bE_w.mjs} +0 -0
- /package/dist/{transform-job-CtixL4An.mjs → transform-job-CtVziW85.mjs} +0 -0
- /package/dist/{transform-tag-rsIrckCM.mjs → transform-tag-wFiWmiyO.mjs} +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dashboard
|
|
3
|
+
description: Build Metabase dashboards via the `mb` CLI — lay out dashcards on the 24-column grid (`{col,row,size_x,size_y}` math, per-chart default sizes) and turn cards into a filterable, cross-linked app. Covers wiring filters to cards (parameters + parameter_mappings), the field-filter vs. raw-variable target grammar, linked/cascading filters and their foreign-key requirement, cross-filtering, click-through, multi-series overlays, and tabs. The parameter type enum and whole-array replace semantics live in `core`. Triggers — "build a dashboard from these cards", "my dashboard is squished into half the width", "wire a filter to these cards", "make a filter cascade", "click a bar to filter the other charts", "add a dashboard tab", "add a second series", "why isn't my filter showing".
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Dashboard
|
|
8
|
+
|
|
9
|
+
A dashboard starts as cards on a grid; it becomes an **app** when filters drive the cards, charts cross-filter each other, and clicks navigate. This skill owns both: the grid layout and the interactive layer.
|
|
10
|
+
|
|
11
|
+
**`core` owns the transport mechanics** — the whole-array replace semantics (editing `dashcards` or `parameters` replaces the entire set; omitted entries are deleted; new cards use negative ids), `update-dashcard` for a single safe patch vs. `update --body` for a full replace, the closed `parameter.type` enum, and the `parameter-values` verb. Read it first (`mb skills get core`). **`visualization`** owns each card's chart and the full `click_behavior` key catalog.
|
|
12
|
+
|
|
13
|
+
Inspect before you wire: `mb dashboard get <id> --json` hydrates `parameters`, `dashcards`, and `tabs`; `mb dashboard cards <id>` lists just the dashcards.
|
|
14
|
+
|
|
15
|
+
## Layout: the grid is 24 columns — not 12
|
|
16
|
+
|
|
17
|
+
Every dashcard carries `{col, row, size_x, size_y}` in grid units: `col` is 0-indexed from the left edge, `row` grows downward, and `col + size_x ≤ 24`. **Full-width is `size_x: 24` — Metabase's per-chart _default_ width of 12 is half a row.** A layout authored on the usual 12-column web-grid assumption crams the whole dashboard into the left half of the viewport. The server stores whatever geometry you send — overlaps and gaps included, no auto-fix.
|
|
18
|
+
|
|
19
|
+
Default sizes (w×h): `scalar`/`smartscalar` 6×3, `pie` 12×8, `table`/`pivot`/`object` 12×9, `waterfall` 14×6, `sankey` 16×10, `heading` 24×1, `text` 12×3, every other chart 12×6.
|
|
20
|
+
|
|
21
|
+
The standard shape — a KPI row of scalars across the full 24, charts in halves or thirds below, wide tables full-width:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
"dashcards": [
|
|
25
|
+
{ "id": -1, "card_id": 101, "col": 0, "row": 0, "size_x": 6, "size_y": 3 }, // 4 KPIs × 6 = 24
|
|
26
|
+
{ "id": -2, "card_id": 102, "col": 6, "row": 0, "size_x": 6, "size_y": 3 },
|
|
27
|
+
{ "id": -3, "card_id": 103, "col": 12, "row": 0, "size_x": 6, "size_y": 3 },
|
|
28
|
+
{ "id": -4, "card_id": 104, "col": 18, "row": 0, "size_x": 6, "size_y": 3 },
|
|
29
|
+
{ "id": -5, "card_id": 105, "col": 0, "row": 3, "size_x": 12, "size_y": 6 }, // two halves
|
|
30
|
+
{ "id": -6, "card_id": 106, "col": 12, "row": 3, "size_x": 12, "size_y": 6 },
|
|
31
|
+
{ "id": -7, "card_id": 107, "col": 0, "row": 9, "size_x": 24, "size_y": 9 } // full-width table
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Sanity-check before sending:** rows should fill to 24 and at least one card must end at `col + size_x = 24`. If nothing in the array crosses column 12, you've authored a 12-column layout — double every width.
|
|
36
|
+
|
|
37
|
+
## The wiring loop: a filter is a parameter + a mapping per card
|
|
38
|
+
|
|
39
|
+
A dashboard filter is one entry in the dashboard's `parameters` array **plus** a `parameter_mappings` entry on every dashcard it should control. A parameter with no mapping is an inert widget — the most common "my filter does nothing" cause.
|
|
40
|
+
|
|
41
|
+
```jsonc
|
|
42
|
+
// dashboard.parameters — the widget
|
|
43
|
+
{ "id": "status", "name": "Status", "slug": "status", "type": "string/=" }
|
|
44
|
+
|
|
45
|
+
// on each target dashcard — bind the widget to a column of that card
|
|
46
|
+
{ "parameter_id": "status", "card_id": 42,
|
|
47
|
+
"target": ["dimension", ["field", 1779, null]] }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `target` grammar depends on what the card is:
|
|
51
|
+
|
|
52
|
+
| Card's query | `target` |
|
|
53
|
+
| --------------------------- | -------------------------------------------- |
|
|
54
|
+
| MBQL column | `["dimension", ["field", <field-id>, null]]` |
|
|
55
|
+
| Native **field filter** tag | `["dimension", ["template-tag", "<tag>"]]` |
|
|
56
|
+
| Native **raw variable** tag | `["variable", ["template-tag", "<tag>"]]` |
|
|
57
|
+
|
|
58
|
+
(Field ids from `table get <id> --include fields`; native tags from `native-sql`.) Because editing replaces the whole set, adding a filter is read-modify-write: `dashboard get <id> --json`, append to `parameters` and to each dashcard's `parameter_mappings`, send the full arrays back. To wire one card without touching the rest, `update-dashcard <dash-id> <dashcard-id> --body '{"parameter_mappings":[…]}'`.
|
|
59
|
+
|
|
60
|
+
## Choose the interaction
|
|
61
|
+
|
|
62
|
+
Four distinct mechanisms — pick by what the user wants clicking or filtering to _do_:
|
|
63
|
+
|
|
64
|
+
| Want | Mechanism |
|
|
65
|
+
| -------------------------------------------------- | ----------------------------------------------------- |
|
|
66
|
+
| One widget filters several cards | a **dashboard parameter** mapped to each card (above) |
|
|
67
|
+
| One filter's choices narrow another's | a **linked filter** (`filteringParameters`) |
|
|
68
|
+
| Clicking a chart filters the other charts | **cross-filter** click behavior |
|
|
69
|
+
| Clicking navigates to a question / dashboard / URL | **link** click behavior |
|
|
70
|
+
|
|
71
|
+
### Linked (cascading) filters
|
|
72
|
+
|
|
73
|
+
Make a child filter (City) show only values consistent with a parent (State) by listing the parent's id in the child parameter's `filteringParameters`:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{ "id": "city", "type": "category", "filteringParameters": ["state"] }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Two hard constraints, both from the same root: **linked filters read table-metadata foreign keys only.** They ignore joins defined inside a saved question or model. So the parent and child columns must be connected by a FK set in metadata — if the cascade shows values it shouldn't, the FK is missing (fix it via `metadata`, then retry). And `filteringParameters` is **incompatible with a `values_source_type` of `static-list` or `card`** — a custom value source overrides the cascade, so Metabase clears the link. Leave the child's value source live (omit it) for linked filtering to work.
|
|
80
|
+
|
|
81
|
+
### Cross-filtering (click a chart to filter the rest)
|
|
82
|
+
|
|
83
|
+
Set the **driver** chart's whole-card click behavior to `crossfilter`, mapping the clicked value into a dashboard parameter; map that same parameter onto the **follower** cards normally. The driver stays unmapped to it (it emits the value; it doesn't consume it). `click_behavior` lives in the dashcard's `visualization_settings` (whole card) or `column_settings[<col>].click_behavior` (per column on tables), with **camelCase** keys:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"click_behavior": {
|
|
88
|
+
"type": "crossfilter",
|
|
89
|
+
"parameterMapping": {
|
|
90
|
+
"status": {
|
|
91
|
+
"id": "status",
|
|
92
|
+
"source": { "type": "column", "id": "STATUS", "name": "Status" },
|
|
93
|
+
"target": { "type": "parameter", "id": "status" }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Click-through (navigate)
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{ "click_behavior": { "type": "link", "linkType": "dashboard",
|
|
104
|
+
"targetId": 7, "parameterMapping": { … } } }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`linkType` is `question` / `dashboard` (carry a `parameterMapping` to pass the clicked context) or `url` (a `linkTemplate` like `"https://app/orders/{{ORDER_ID}}"`, `{{Column}}` interpolated). The full `click_behavior`/`parameterMapping` key catalog is in `visualization`'s settings reference — don't hand-author a complex one; build it once in the UI and copy it (`mb dashboard get <id> --full --json`). Note: a **native-SQL card can't drill through** — only cross-filter and link click behaviors work on it.
|
|
108
|
+
|
|
109
|
+
## Series, tabs, value sources
|
|
110
|
+
|
|
111
|
+
**Multi-series overlay** — put several cards on one chart (**line / area / bar only**): the dashcard's `series` is an array of card ids in draw order (`"series": [43, 51]`). Sending it replaces the set; an empty array clears it. Patch with `update-dashcard`.
|
|
112
|
+
|
|
113
|
+
**Tabs** — `tabs` is `[{ "name": "Overview", "position": 0 }, …]`; a dashcard joins a tab via `dashboard_tab_id`. Creating tabs and cards together uses **negative ids** (per `core`): give a new tab `id: -1`, point new dashcards at `dashboard_tab_id: -1`, and the create/update response returns the real ids. A filter widget only appears on a tab if it's mapped to at least one card on **that** tab.
|
|
114
|
+
|
|
115
|
+
**Filter value source** (the dropdown behind a parameter) — omit `values_source_type` to pull live distinct values from the mapped column; `"static-list"` + `values_source_config.values` for a fixed list; `"card"` + `{card_id, value_field, label_field}` to source from a query. `mb dashboard parameter-values <id> <param-id> [--query <substr>]` previews what a widget will offer.
|
|
116
|
+
|
|
117
|
+
## Gotchas
|
|
118
|
+
|
|
119
|
+
- **Auto-connect / the missing map:** a filter that "does nothing" or "won't show" is almost always unmapped, or mapped only to cards on another tab.
|
|
120
|
+
- **Time-grouping parameters** (`temporal-unit`) bind only to a datetime column in the query's **last** stage — add one after a time-bucketed summary and it can't attach.
|
|
121
|
+
- **Required + default:** a `required: true` parameter with no `default` blocks its cards until a value is chosen; give it a `default` for expensive queries you don't want running unfiltered.
|
|
122
|
+
- **Whole-array replace:** never send a partial `parameters`/`dashcards` array to `update` — you'll delete what you omit. Use `update-dashcard` for a single-card change.
|
|
123
|
+
|
|
124
|
+
## Don't
|
|
125
|
+
|
|
126
|
+
- Don't lay out on a 12-column assumption — full-width is `size_x: 24`; a layout where no card crosses column 12 renders in the left half of the viewport.
|
|
127
|
+
- Don't declare a parameter and forget the per-card `parameter_mappings` — the widget won't filter anything.
|
|
128
|
+
- Don't expect a linked filter to work off a model/question join, a custom column, or with a static/card value source — it needs a metadata FK and a live source.
|
|
129
|
+
- Don't hand-author a complex `click_behavior` — copy a UI-built one.
|
|
130
|
+
- Don't add a second `series` to a pie/scalar/table — it's line/area/bar only.
|
|
@@ -10,14 +10,14 @@ The front door for turning a raw database into clean tables, reusable definition
|
|
|
10
10
|
|
|
11
11
|
A data project moves through stages. A user can start at any of them — detect where their data already is, don't assume.
|
|
12
12
|
|
|
13
|
-
| Stage | What it does | Where the method lives
|
|
14
|
-
| --------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
15
|
-
| **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md`
|
|
16
|
-
| **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md`
|
|
17
|
-
| **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md`
|
|
18
|
-
| **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (`
|
|
13
|
+
| Stage | What it does | Where the method lives |
|
|
14
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
15
|
+
| **Build clean tables** | Raw, normalized source DB → a small set of wide, clean, analysis-ready tables (built as transforms) | `references/building-clean-tables.md` |
|
|
16
|
+
| **Define reusable metrics** | Clean tables → shared segments (saved filters), measures (saved calculations), metrics (official numbers) | `references/reusable-definitions.md` |
|
|
17
|
+
| **Answer questions** | Clean tables → a trustworthy plain-language written answer | `references/answering-questions.md` |
|
|
18
|
+
| **Build dashboards** | Clean tables / definitions → charts and dashboards people look at | the `visualization` skill (charts) + `dashboard` skill (grid layout, filters, interactivity) |
|
|
19
19
|
|
|
20
|
-
The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/<file>.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards"
|
|
20
|
+
The first three methods are references in this skill — read **only the one the current stage needs**: run `mb skills path data-workflow` and Read `references/<file>.md`. (`mb skills get data-workflow --full` appends all three at once — heavier; prefer the single Read.) "Build dashboards" lives in two standalone skills — `visualization` for authoring each chart, `dashboard` for laying cards out on the grid and wiring filters and interactivity — both CLI capabilities in their own right. Load `dashboard` before composing any dashboard, even a plain no-filter layout: the grid geometry lives there.
|
|
21
21
|
|
|
22
22
|
CLI mechanics come from the reference skills, not from here: `mb skills get core` (auth, inspection, the `field`/`table`/`library`/`segment`/`measure` verbs), `mbql` (query and definition bodies), `transform` (creating/running transforms). This skill owns the _judgment_ — which tables, which definitions, what to keep — and the conventions below.
|
|
23
23
|
|
|
@@ -88,19 +88,21 @@ The rules every stage follows. The reference files assume this contract rather t
|
|
|
88
88
|
|
|
89
89
|
Don't make the user name a _stage_ — but do find out _where their data lives_ before going looking.
|
|
90
90
|
|
|
91
|
+
**Data not in a database yet?** A local CSV gets in via `mb upload csv --file <path>` (creates a table + model); treat the result as a starting table and clean it with a transform if needed. Uploads must be admin-enabled — check `mb setting get uploads-settings --json` (`db_id: null` ⇒ not configured). See `core`'s **upload** quirk for the rest.
|
|
92
|
+
|
|
91
93
|
**Ask before you crawl.** If you don't already know which database/schema/table the user means, ask — one plain question short-circuits a dozen tool calls. The asymmetry: if they name a **database**, ask which **schema**; if they name a **table**, ask which **database**. "If you don't know, no problem — I'll look" is the fallback, not the opener.
|
|
92
94
|
|
|
93
|
-
**When you do crawl,** use `core`'s cheap, narrowest-first ladder (never whole-warehouse rollups): `mb db list` → `db schemas <id>` → `db schema-tables <id> <schema>` → `table list [--db-id]` → `table fields <id>` (or `table
|
|
95
|
+
**When you do crawl,** use `core`'s cheap, narrowest-first ladder (never whole-warehouse rollups): `mb db list` → `db schemas <id>` → `db schema-tables <id> <schema>` → `table list [--db-id]` → `table fields <id>` (or `table get <id> --include fields --full` for FK targets and dimensions). Have a _name_ rather than a tree to walk? `mb search <query> [--models] [--db-id]`. Need to know what's in a column? `mb field summary <id>` (counts) and `field values <id>` (sample values). If a database looks freshly connected or an expected table is missing, offer `mb db sync-schema <id> --wait` before concluding it doesn't exist.
|
|
94
96
|
|
|
95
97
|
**Read the shape to pick a stage.** Raw, normalized, SaaS-synced tables (many tables, coded columns, `*_field`/`*_choice` lookups)? → **build clean tables** first. Already wide, clean, human-readable ones? Then it depends on the goal:
|
|
96
98
|
|
|
97
|
-
| What the user wants / what's there | Stage
|
|
98
|
-
| -------------------------------------------------------------------------------------------------------------- |
|
|
99
|
-
| "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md`
|
|
100
|
-
| Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md`
|
|
101
|
-
| Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md`
|
|
102
|
-
| Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `
|
|
103
|
-
| "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages
|
|
99
|
+
| What the user wants / what's there | Stage |
|
|
100
|
+
| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
101
|
+
| "Clean up / flatten / make sense of" raw, normalized data; no clean tables yet | Build clean tables → `references/building-clean-tables.md` |
|
|
102
|
+
| Clean tables exist; "make this reusable", "define active customers / revenue / MRR officially" | Define reusable metrics → `references/reusable-definitions.md` |
|
|
103
|
+
| Clean tables exist; "answer this question", "who registered", "analyze / report on X" (wants a written answer) | Answer questions → `references/answering-questions.md` |
|
|
104
|
+
| Clean tables (and maybe definitions) exist; "chart this", "build a dashboard", "show me X over time" | Build dashboards → `visualization` (charts) + `dashboard` (layout, interactivity) |
|
|
105
|
+
| "Do the whole thing" / "set up analytics for X" from raw data | Start at build-clean-tables, then continue down the stages |
|
|
104
106
|
|
|
105
107
|
**Answer and dashboard are alternative endpoints, not a sequence.** Once tables are clean (and maybe defined), answering in prose and building a dashboard are two different things you can do with the data — route to whichever the goal calls for; neither has to precede the other.
|
|
106
108
|
|
|
@@ -131,7 +131,7 @@ Then make the links real, not just implied:
|
|
|
131
131
|
- **Wire foreign keys between your tables.** Mark each linking id as a foreign key pointing at the id it references — set the column's type to foreign-key and its target so Metabase itself knows the tables connect and can traverse them.
|
|
132
132
|
- **Graft onto existing clean data** the user approved (step 3 / Phase 1): point the linking id at the existing table's id the same way. Link, don't duplicate.
|
|
133
133
|
|
|
134
|
-
**Set the metadata — a transform's output starts blank, and these tables are Library-bound.** A fresh transform table has no descriptions, raw column names, and untyped columns. You worked it out while investigating; don't leave that knowledge stranded in this chat. Set it on the table so the data explains itself inside Metabase (search, the Question editor, Metabot) and is fit to publish. The mechanics — `mb field update` for semantic types / FK targets / display names, `mb table update` for table descriptions
|
|
134
|
+
**Set the metadata — a transform's output starts blank, and these tables are Library-bound.** A fresh transform table has no descriptions, raw column names, and untyped columns. You worked it out while investigating; don't leave that knowledge stranded in this chat. Set it on the table so the data explains itself inside Metabase (search, the Question editor, Metabot) and is fit to publish. The mechanics — `mb field update` for semantic types / FK targets / display names, `mb table update` for table descriptions, and the feature each edit unlocks — are in the `metadata` skill; the calls about _what_ to set:
|
|
135
135
|
|
|
136
136
|
- **Semantic types — the highest-value piece.** A column's semantic type is what makes Metabase treat it right: `type/Email`, `type/Currency`/`type/Price`, `type/Category` (turns into a filter dropdown), `type/City`/`type/State`/`type/Country`, `type/CreationTimestamp`, `type/Description`. Set it on every column whose meaning you decoded. A typed column shows money as money, offers a filter dropdown, and lands on the right chart axis for everyone downstream; an untyped one is a guess.
|
|
137
137
|
- **Descriptions.** A one-line description on each table and every non-obvious column.
|
|
@@ -22,11 +22,11 @@ mb document archive <id> --profile <name> --json # soft-delete (PUT arch
|
|
|
22
22
|
|
|
23
23
|
- `list` returns the standard envelope (`{data, returned, total}`). The compact item is `{id, name, collection_id, archived, creator_id, can_write}` and omits the (potentially huge) `document` body — pull the body with `get --full`.
|
|
24
24
|
- `archive` is the only delete, mirroring `card` / `dashboard`. **Unarchive** with `mb document update <id> --body '{"archived":false}'`.
|
|
25
|
-
- `update` is PATCH — send only the keys you want to change
|
|
25
|
+
- `update` is PATCH — send only the keys you want to change: `name`, `document`, `collection_id`, `collection_position`, `archived`, and `cards` (inline card creation works on update too, not just create — see below). Replacing `document` replaces the **whole** body; there is no partial-node patch.
|
|
26
26
|
|
|
27
27
|
## Node ids (`_id`)
|
|
28
28
|
|
|
29
|
-
The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** and
|
|
29
|
+
The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** — the CLI validates before sending and rejects a body missing any (`every … node needs a non-empty string _id (mint with mb uuid)`). Other node types (`doc`, `text`, `listItem`, `resizeNode`, `flexContainer`, …) take no `_id` and are left alone. Without them the editor backfills ids when the document opens, which makes a freshly-saved document show a spurious "unsaved changes" prompt.
|
|
30
30
|
|
|
31
31
|
Mint the ids with `mb uuid --count <n> --json` (→ `["…", …]`), one per id-bearing node, and set each as that node's `attrs._id`.
|
|
32
32
|
|
|
@@ -81,7 +81,7 @@ Every node is `{ "type": string, "attrs"?: object, "content"?: [nodes], "text"?:
|
|
|
81
81
|
- **`resizeNode`** — wraps a single `cardEmbed` or `flexContainer` to make it resizable (no `_id`). `attrs: { "height": <px>, "minHeight": <px> }`, `content` is exactly one `cardEmbed`/`flexContainer`.
|
|
82
82
|
- **`flexContainer`** — a horizontal row of 1–3 `cardEmbed` / `supportingText` cells side by side (no `_id`). `attrs.columnWidths` is an array of width percentages.
|
|
83
83
|
- **`supportingText`** — a text column that sits next to a card inside a `flexContainer` (id-bearing); `content` is the usual block nodes (`paragraph`, `heading`, lists, …).
|
|
84
|
-
- **`smartLink`** — an inline reference to a Metabase entity (renders as a live chip). Inline, atomic, no `_id`. `attrs: { "entityId": <id>, "model": <model>, "label": <string|null>, "href": <relative-path> }`. `model` ∈ `card`, `dataset`, `metric`, `dashboard`, `collection`, `table`, `database`, `document`, `transform`, `segment`, `
|
|
84
|
+
- **`smartLink`** — an inline reference to a Metabase entity (renders as a live chip). Inline, atomic, no `_id`. `attrs: { "entityId": <id>, "model": <model>, "label": <string|null>, "href": <relative-path> }`. `model` ∈ `card`, `dataset`, `metric`, `dashboard`, `collection`, `table`, `database`, `document`, `transform`, `segment`, `user`, `action`, `indexed-entity` (plus `measure` on v60+).
|
|
85
85
|
- **`metabot`** — an inline Metabot prompt block.
|
|
86
86
|
|
|
87
87
|
## Embedding an existing card
|
|
@@ -16,8 +16,8 @@ Always run `status` (or `is-dirty` + `has-remote-changes`) before `import` or `e
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
mb git-sync status --profile <n> --json # → branch, dirty, current task
|
|
19
|
-
mb git-sync is-dirty --profile <n> --json # → {
|
|
20
|
-
mb git-sync has-remote-changes --profile <n> --json # → {
|
|
19
|
+
mb git-sync is-dirty --profile <n> --json # → {is_dirty: bool}; instance has unexported changes
|
|
20
|
+
mb git-sync has-remote-changes --profile <n> --json # → {has_changes: bool, remote_version, local_version, cached}; remote has unimported commits
|
|
21
21
|
mb git-sync dirty --profile <n> --json # → list the dirty objects
|
|
22
22
|
mb git-sync current-task --profile <n> --json # → in-flight task (or idle)
|
|
23
23
|
```
|
|
@@ -43,8 +43,8 @@ Pulls the configured branch and applies it to the instance. Polls until the task
|
|
|
43
43
|
|
|
44
44
|
Workflow:
|
|
45
45
|
|
|
46
|
-
1. Read state (above) — confirm `
|
|
47
|
-
2. Confirm `has-remote-changes` reports there's actually something to import.
|
|
46
|
+
1. Read state (above) — confirm `is_dirty: false` (or `--force` is intended).
|
|
47
|
+
2. Confirm `has-remote-changes` reports `has_changes: true` — there's actually something to import.
|
|
48
48
|
3. `git-sync import --branch <branch>` — runs to terminal status by default.
|
|
49
49
|
|
|
50
50
|
## Export (instance → remote)
|
|
@@ -67,7 +67,7 @@ Workflow:
|
|
|
67
67
|
1. **Branch guard** (below) — confirm the instance isn't tracking `main`/`master`, or that the user has explicitly accepted exporting to it.
|
|
68
68
|
2. Read state (above) — confirm `is-dirty` reports there's something to export.
|
|
69
69
|
3. `git-sync export -m "..."` — pushes and polls.
|
|
70
|
-
4. (Optional) `git-sync status` — verify `
|
|
70
|
+
4. (Optional) `git-sync status` — verify `is_dirty: false` after.
|
|
71
71
|
|
|
72
72
|
### Branch guard: don't export to main/master without confirmation
|
|
73
73
|
|
package/skill-data/mbql/SKILL.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mbql
|
|
3
|
-
description: Author and debug MBQL
|
|
3
|
+
description: Author and debug MBQL query bodies for the `mb` CLI — the only hand-authorable query format. Covers the JSON shape (flat numeric-id stages, options-object-second clauses, optional lib/uuid), joins and FK traversal, multi-stage pipelines, aggregation naming, the flat-vs-legacy-envelope footgun, and the print-schema → dry-run → run validation loop. Use when writing or fixing any query body — `mb query`, a card's `dataset_query`, a transform's `source.query`, or a segment/measure `definition` — or when `--dry-run`/run reports validation errors. Triggers — "write an MBQL query", "the dataset_query is wrong", "aggregate and group by", "join two tables", "month-over-month".
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# MBQL
|
|
7
|
+
# MBQL
|
|
8
8
|
|
|
9
|
-
MBQL
|
|
9
|
+
MBQL is the query format you author by hand — it has a bundled JSON Schema, so the CLI pre-flight-validates it before sending. A native SQL query is **also** MBQL: its single stage is `mbql.stage/native` (raw SQL) instead of `mbql.stage/mbql` (structured), so it's pre-flight-validated the same way (its SQL string aside) — see `native-sql`. Only the legacy flat forms below skip validation.
|
|
10
10
|
|
|
11
|
-
Prefer
|
|
11
|
+
Prefer a **structured** stage over a native SQL stage: portable across warehouse engines. Try it first; fall back to a native stage when structured MBQL can't express what you need, or when a structured body keeps failing server-side and you can't resolve it. For native SQL with parameters (template tags, field filters, snippets), load `native-sql`.
|
|
12
12
|
|
|
13
13
|
General flag conventions, body-input precedence, output flags, `./.scratch`, and `mb uuid` mechanics live in `core` (`mb skills get core`).
|
|
14
14
|
|
|
@@ -47,7 +47,7 @@ Every clause is `[op, {options}, ...args]`. The options object is element **1**,
|
|
|
47
47
|
["asc", {}, ["field", {}, 42]]
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
The legacy
|
|
50
|
+
The legacy field shape `["field", id, opts]` (id second) is **rejected** here. A slot-1 violation surfaces from `--dry-run` as `must be the field options object` / `must be the clause options object` at `/stages/0/<verb>/<n>/1`.
|
|
51
51
|
|
|
52
52
|
The same `[op, {options}, …]` rule holds for `aggregation`, `breakout` (a list of field refs), `filters` (implicitly ANDed; nest an explicit `["or", {}, …]` for OR), `order-by`, `expressions`, and join `conditions`.
|
|
53
53
|
|
|
@@ -89,15 +89,15 @@ mb query --file q.json --profile <n> --json # 3. validate +
|
|
|
89
89
|
- `Invalid :expression reference: no expression named "X"` (or an invalid `:aggregation` reference) → a **ref** points to an expression name / aggregation `lib/uuid` that isn't defined in the query; fix the target string.
|
|
90
90
|
- `Duplicate :lib/uuid` → you reused a `lib/uuid`. Omit them (the server mints unique ones) or give each clause a distinct value.
|
|
91
91
|
|
|
92
|
-
A successful run emits the compact envelope by default: `data.rows` + slim `data.cols` (`name`, `display_name`, `base_type`, `semantic_type`). Pass `--full` for the raw `/api/dataset` envelope (`results_metadata`, `native_form`, per-column fingerprints/`field_ref`) only when you need that metadata; `--fields data.rows` narrows to rows alone. `mb query` also runs a
|
|
92
|
+
A successful run emits the compact envelope by default: `data.rows` + slim `data.cols` (`name`, `display_name`, `base_type`, `semantic_type`). Pass `--full` for the raw `/api/dataset` envelope (`results_metadata`, `native_form`, per-column fingerprints/`field_ref`) only when you need that metadata; `--fields data.rows` narrows to rows alone. `mb query` also runs a native query — author it as an `mbql.stage/native` stage (pre-flight-validated like any MBQL body; see `native-sql`).
|
|
93
93
|
|
|
94
94
|
`--skip-validate` bypasses pre-flight and sends as-is — use only when the bundled schema disagrees with what the server actually accepts (drift / false negative). Mutually exclusive with `--dry-run`. Same flag exists on `card create/update` and `transform create/update`.
|
|
95
95
|
|
|
96
|
-
## Where
|
|
96
|
+
## Where the query is consumed
|
|
97
97
|
|
|
98
|
-
The same body and pre-flight apply everywhere a query is embedded. Each pre-flights only when the value is
|
|
98
|
+
The same body and pre-flight apply everywhere a query is embedded. Each pre-flights only when the value is the `mbql/query` shape (`lib/type: "mbql/query"`); legacy shapes skip it; `--skip-validate` bypasses.
|
|
99
99
|
|
|
100
|
-
| Command |
|
|
100
|
+
| Command | The query lives at | Notes |
|
|
101
101
|
| --------------------------------------- | ---------------------------------------------- | ------------------------------------------- |
|
|
102
102
|
| `mb query` | the whole body | ad-hoc run against `/api/dataset` |
|
|
103
103
|
| `card create` / `card update` | `dataset_query` | a **flat** `mbql/query` — see footgun below |
|
|
@@ -107,7 +107,7 @@ The same body and pre-flight apply everywhere a query is embedded. Each pre-flig
|
|
|
107
107
|
|
|
108
108
|
## Footgun: `dataset_query` is the flat mbql/query, not a legacy envelope
|
|
109
109
|
|
|
110
|
-
The most common mistake. The legacy
|
|
110
|
+
The most common mistake. The legacy shape `{ "type": "query", "database": N, "query": {…} }` looks similar but is wrong. `dataset_query` (and `source.query`, and `definition`) **is the `mbql/query` value itself**:
|
|
111
111
|
|
|
112
112
|
```json
|
|
113
113
|
"dataset_query": {
|
|
@@ -118,16 +118,16 @@ The most common mistake. The legacy MBQL 4 shape `{ "type": "query", "database":
|
|
|
118
118
|
}
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
No `type:"query"` wrapper, no `query:` nesting. If you wrap
|
|
121
|
+
No `type:"query"` wrapper, no `query:` nesting. If you wrap the query inside a legacy envelope the CLI rejects it pre-send with a `ConfigError` (no `--skip-validate` gets it past). If it reached the server it would store silently and fail at run time with `Initial MBQL stage must have either :source-table or :source-card`.
|
|
122
122
|
|
|
123
|
-
##
|
|
123
|
+
## Legacy formats you may encounter
|
|
124
124
|
|
|
125
|
-
Anything not `lib/type: "mbql/query"` is sent as-is and normalized server-side:
|
|
125
|
+
Older Metabase servers used a different query envelope (sometimes called MBQL 4 / "legacy MBQL"); the `mbql/query` shape above is what recent servers store and return. You won't author the legacy shapes, but you may see them in queries created long ago. Anything not `lib/type: "mbql/query"` is sent as-is and normalized server-side — you lose validation, so don't author these:
|
|
126
126
|
|
|
127
|
-
- **Legacy
|
|
128
|
-
- **
|
|
127
|
+
- **Legacy structured** — `{ "type": "query", "database": N, "query": { "source-table": T, … } }`
|
|
128
|
+
- **Flat native** — `{ "type": "native", "database": N, "native": { "query": "SELECT …" } }` — the server accepts it, but author the native stage instead (`native-sql`).
|
|
129
129
|
|
|
130
|
-
`mb query --file probe.json` runs these directly; `--dry-run` on them returns `{ ok: true, errors: [] }`. Don't author
|
|
130
|
+
`mb query --file probe.json` runs these directly; `--dry-run` on them returns `{ ok: true, errors: [] }`. Don't author them by hand — build a legacy or complex query in the Metabase UI and pull the body with `mb card get <id> --full --json` / `mb transform get <id> --full --json` (which returns the `mbql/query` shape).
|
|
131
131
|
|
|
132
132
|
## Joins and FK traversal
|
|
133
133
|
|
|
@@ -193,7 +193,7 @@ Later stages address the first stage's aggregation by the `name` you gave it (`"
|
|
|
193
193
|
|
|
194
194
|
## Naming aggregation output columns
|
|
195
195
|
|
|
196
|
-
Default
|
|
196
|
+
Default aggregations materialize as `count`, `count_where`, `avg`, `avg_2`, `sum`, … — fine for an ad-hoc run, ugly for a transform target table or card column. Set `name` (the warehouse column name) and `display-name` (the UI header) in the aggregation's options:
|
|
197
197
|
|
|
198
198
|
```json
|
|
199
199
|
["count", { "name": "shipments_shipped", "display-name": "Shipments shipped" }]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MBQL
|
|
1
|
+
# MBQL operator reference
|
|
2
2
|
|
|
3
3
|
The complete clause vocabulary the bundled schema accepts, in the CLI's API/numeric
|
|
4
4
|
form. The clause _structure_ and the slot-1-options rule are in the SKILL.md body —
|
|
@@ -240,8 +240,9 @@ positional arg is the default.)
|
|
|
240
240
|
- Truncation: `default`, `millisecond`, `second`, `minute`, `hour`, `day`, `week`,
|
|
241
241
|
`month`, `quarter`, `year`.
|
|
242
242
|
- Extraction (returns an integer): `minute-of-hour`, `hour-of-day`, `day-of-week`,
|
|
243
|
-
`day-of-
|
|
244
|
-
`
|
|
243
|
+
`day-of-month`, `day-of-year`, `week-of-year`, `month-of-year`, `quarter-of-year`,
|
|
244
|
+
`year-of-era`, `second-of-minute`. (The `*-iso`/`*-us` variants are `temporal-extract`
|
|
245
|
+
operator modes, not field-option bucketing units.)
|
|
245
246
|
|
|
246
247
|
```json
|
|
247
248
|
["field", { "temporal-unit": "month" }, 22]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metadata
|
|
3
|
+
description: Set Metabase field and table metadata via the `mb` CLI — semantic types, foreign-key targets, dropdown/scan behavior, column visibility, and display names. The point is the causal chain — one metadata edit unlocks a downstream feature (a FK target enables joins and linked filters; `has_field_values` picks the filter widget; `visibility_type` can block queries). Covers `field update` / `table update`, the writable-vs-read-only split, the semantic-type catalog, why semantic types are labels not casts (and how to actually cast), and sync-vs-scan-vs-fingerprint. Triggers — "set this column as currency / email / a category", "mark this as a foreign key", "make this column a dropdown", "why doesn't the query builder suggest a join", "hide this column", "set the entity key".
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Metadata
|
|
8
|
+
|
|
9
|
+
Metabase reads the raw column types from your warehouse; **metadata** is the layer you edit on top to make columns behave well — the right filter widget, joins, formatting, maps. You set it per-column with `mb field update <id>` and per-table with `mb table update <id>`. Both are **PATCH** — send only the keys you're changing.
|
|
10
|
+
|
|
11
|
+
Metadata is a small set of fields with large, indirect effects. Get the field ids from `mb table get <id> --include fields` (or `mb table fields <id>`); inspect a column's shape with `mb field get <id>`, its live cardinality with `mb field summary <id>`, its cached distinct set with `mb field values <id>`. General flag/output/body mechanics live in `core`.
|
|
12
|
+
|
|
13
|
+
## The causal chain — set X, unlock Y
|
|
14
|
+
|
|
15
|
+
This is the whole point of the skill. Each edit below is a key in the `field update` (or `table update`) body; the value change is what turns a feature on.
|
|
16
|
+
|
|
17
|
+
| Set (via `field update`) | Unlocks / does |
|
|
18
|
+
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `semantic_type: "type/PK"` | marks the row's identity key — enables record/detail view, and lets other tables' FKs point here |
|
|
20
|
+
| `semantic_type: "type/FK"` **+** `fk_target_field_id: <pk field id>` | the join relationship — implicit FK joins in queries (`mbql` `source-field`), query-builder join suggestions, and dashboard **linked filters** |
|
|
21
|
+
| `semantic_type: "type/Currency"` / `type/Email` / `type/City` / … | correct display formatting and the matching filter widget (and region/pin maps for location types) |
|
|
22
|
+
| `has_field_values: "list"` (or `"auto-list"`) | a **dropdown** filter widget, backed by a scanned distinct-value set |
|
|
23
|
+
| `has_field_values: "search"` | a **search box** (no value set stored) — for high-cardinality columns |
|
|
24
|
+
| `has_field_values: "none"` | a plain input box, no dropdown |
|
|
25
|
+
| `visibility_type: "sensitive"` or `"retired"` | **blocks queries** that touch the field — not a UI hint, an error |
|
|
26
|
+
| `visibility_type: "hidden"` | removes the column from the query builder and data reference (SQL can still read it — **not access control**) |
|
|
27
|
+
| `visibility_type: "details-only"` | hidden in table views, shown in the single-record detail view (for long blobs) |
|
|
28
|
+
| `coercion_strategy: <strategy>` | **actually casts** the column — the only entry here that changes the value's type (below) |
|
|
29
|
+
| `display_name` / `description` | the human label and help text shown everywhere |
|
|
30
|
+
|
|
31
|
+
`table update` carries the table-level equivalents: `display_name`, `description`, `visibility_type` (`hidden` / `technical` / `cruft` — hides the whole table from the builder), `field_order`, and `entity_type`.
|
|
32
|
+
|
|
33
|
+
## Foreign keys are the highest-leverage edit
|
|
34
|
+
|
|
35
|
+
A FK relationship is what makes a warehouse browsable. Set it in **two keys on the FK column**, in one PATCH:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
mb field update 1711 --body '{"semantic_type":"type/FK","fk_target_field_id":1684}' --profile <n> --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`1711` is `orders.customer_id`; `1684` is `customers.id` (which should itself be `type/PK`). Once set:
|
|
42
|
+
|
|
43
|
+
- Queries can pull columns from the related table with no explicit join — `["field", {"source-field": 1711}, 1682]` in MBQL (see `mbql`).
|
|
44
|
+
- Dashboard **linked filters** become possible (a State filter narrowing a City filter). **Linked filters read only these table-metadata FKs** — never a join you wrote inside a saved question — which is why a linked filter that "shows values it shouldn't" almost always means the FK isn't set in metadata. (See `dashboard`.)
|
|
45
|
+
|
|
46
|
+
Removing the `type/FK` semantic type auto-clears `fk_target_field_id`. Point a FK only at a field in the **same database** — v60+ rejects a cross-database target; v58–v59 accept it silently and leave a broken relationship.
|
|
47
|
+
|
|
48
|
+
## Semantic types are labels, not casts
|
|
49
|
+
|
|
50
|
+
The commonest misconception. `semantic_type: "type/Quantity"` on a text column does **not** make it a number — it changes formatting and widget choice, nothing about the stored value. Sorting still sorts as text.
|
|
51
|
+
|
|
52
|
+
To genuinely change the type, use **`coercion_strategy`**, which casts `base_type` → an `effective_type` (e.g. a Unix-epoch integer read as a timestamp, or a numeric string read as a number):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mb field update 42 --body '{"coercion_strategy":"Coercion/UNIXSeconds->DateTime"}' --profile <n> --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`base_type`, `effective_type`, and the physical `name` are **read-only** — set by warehouse sync, never editable here. For a durable transformation (splitting, combining, recomputing columns), build a `transform` rather than leaning on coercion.
|
|
59
|
+
|
|
60
|
+
The full semantic-type catalog — every value grouped by the base type it attaches to, plus the `has_field_values` and `visibility_type` value tables and the exact writable-key lists — is in `references/semantic-types.md` (`mb skills get metadata --full`).
|
|
61
|
+
|
|
62
|
+
## Sync, scan, fingerprint — three different refreshes
|
|
63
|
+
|
|
64
|
+
When a column looks stale or missing, know which one you need (`db` verbs, mechanics in `core`):
|
|
65
|
+
|
|
66
|
+
- **Sync** (`mb db sync-schema <id> --wait`) — re-reads table/column **structure** (new tables, new columns, types). Run after a schema change.
|
|
67
|
+
- **Scan / rescan** (`mb db rescan-values <id>`) — refreshes the **distinct-value sets** behind dropdown filters. Run when a `list` column's values changed but its dropdown is stale.
|
|
68
|
+
- **Fingerprint** — value-distribution stats (min/max, null count) computed on a sample; drives smart defaults. Refreshed by sync; not a separate CLI verb.
|
|
69
|
+
|
|
70
|
+
A newly connected database or a missing expected column usually just needs a `sync-schema --wait` before you conclude anything.
|
|
71
|
+
|
|
72
|
+
## Don't
|
|
73
|
+
|
|
74
|
+
- Don't expect a `semantic_type` to cast — it's a label. Use `coercion_strategy`, or a `transform`.
|
|
75
|
+
- Don't edit `name`, `base_type`, or `effective_type` — they're read-only from sync.
|
|
76
|
+
- Don't treat `visibility_type: hidden` as security — it only hides from the builder; native SQL still reads the column. Real restriction is a permissions concern, outside the CLI.
|
|
77
|
+
- Don't set a `type/City` / `type/State` filter and expect a map or clean dropdown if the values are inconsistent (abbreviations mixed with full names) — fix the values first (a `transform`), then the metadata.
|
|
78
|
+
- Don't blame the data when a dashboard linked filter misbehaves — check the FK is set here first.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Metadata — full reference
|
|
2
|
+
|
|
3
|
+
The semantic-type catalog, the `has_field_values` / `visibility_type` value tables, and the exact writable-key lists for `field update` and `table update`. All values are strings in JSON (`"type/Currency"`). Unknown values are rejected — a new server type surfaces as a parse error, a deliberate signal.
|
|
4
|
+
|
|
5
|
+
## Semantic types by base type
|
|
6
|
+
|
|
7
|
+
Assign the one that matches the column's meaning. Grouped by the base type it belongs on; assigning a numeric semantic type to a text column is legal but only affects formatting, not behavior.
|
|
8
|
+
|
|
9
|
+
**Relations (any type)**
|
|
10
|
+
`type/PK` (entity key) · `type/FK` (needs `fk_target_field_id`)
|
|
11
|
+
|
|
12
|
+
**Text — categorical & descriptive**
|
|
13
|
+
`type/Category` · `type/Enum` · `type/Name` · `type/Title` · `type/Description` · `type/Comment` · `type/Source`
|
|
14
|
+
|
|
15
|
+
**Text — communication & links**
|
|
16
|
+
`type/Email` · `type/URL` · `type/ImageURL` · `type/AvatarURL` · `type/IPAddress`
|
|
17
|
+
|
|
18
|
+
**Text — business entities**
|
|
19
|
+
`type/User` · `type/Author` · `type/Owner` · `type/Product` · `type/Company` · `type/Subscription`
|
|
20
|
+
|
|
21
|
+
**Location** (text unless noted)
|
|
22
|
+
`type/City` · `type/State` · `type/Country` · `type/ZipCode` · `type/Latitude` (numeric) · `type/Longitude` (numeric) · `type/Coordinate` (numeric) · `type/Address`
|
|
23
|
+
|
|
24
|
+
**Numeric — quantities & ratios**
|
|
25
|
+
`type/Quantity` · `type/Score` · `type/Percentage` · `type/Share` · `type/Duration`
|
|
26
|
+
|
|
27
|
+
**Numeric — money**
|
|
28
|
+
`type/Currency` · `type/Price` · `type/Income` · `type/Cost` · `type/Discount` · `type/GrossMargin`
|
|
29
|
+
|
|
30
|
+
**Temporal** (each has `…Timestamp` / `…Time` / `…Date` variants)
|
|
31
|
+
`type/CreationTimestamp` · `type/UpdatedTimestamp` · `type/JoinTimestamp` · `type/CancelationTimestamp` · `type/DeletionTimestamp` · `type/Birthdate`
|
|
32
|
+
|
|
33
|
+
**Structured**
|
|
34
|
+
`type/Structured` · `type/SerializedJSON` · `type/XML`
|
|
35
|
+
|
|
36
|
+
Location maps need clean inputs: lat/long must be numeric; `City`/`State`/`Country` must hold consistent, correctly-spelled values (and usually a scanned value set) to render region maps.
|
|
37
|
+
|
|
38
|
+
## `has_field_values`
|
|
39
|
+
|
|
40
|
+
Controls the filter widget and whether Metabase stores a distinct-value set (scanned from the column).
|
|
41
|
+
|
|
42
|
+
| Value | Widget | Value set stored? | Use for |
|
|
43
|
+
| ----------- | ----------- | ------------------------------------------------- | ------------------------------------------------ |
|
|
44
|
+
| `list` | dropdown | yes (kept even if cardinality grows) | low-cardinality columns you want as a picker |
|
|
45
|
+
| `auto-list` | dropdown | yes (sync-assigned; reverts if too many distinct) | the default Metabase picks automatically |
|
|
46
|
+
| `search` | search box | no | high-cardinality text (names, emails) |
|
|
47
|
+
| `none` | plain input | no | free-form values |
|
|
48
|
+
| `null` | inferred | — | let sync decide (you rarely set this explicitly) |
|
|
49
|
+
|
|
50
|
+
A `list`/`auto-list` column's dropdown is refreshed by `mb db rescan-values <db-id>`.
|
|
51
|
+
|
|
52
|
+
## `visibility_type` (field)
|
|
53
|
+
|
|
54
|
+
| Value | Effect |
|
|
55
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `normal` | default — visible everywhere |
|
|
57
|
+
| `details-only` | hidden in table views; shown in the single-record detail view (long text / JSON blobs) |
|
|
58
|
+
| `hidden` | removed from the query builder and data reference — **UI only, not access control** (native SQL still reads it) |
|
|
59
|
+
| `sensitive` | **queries touching the field error** — stronger than hidden |
|
|
60
|
+
| `retired` | auto-set for dropped columns; **queries error** |
|
|
61
|
+
|
|
62
|
+
## `visibility_type` (table)
|
|
63
|
+
|
|
64
|
+
`hidden` · `technical` · `cruft` — all hide the table from the query builder and data reference (degrees of "don't show this"). `null` is normal.
|
|
65
|
+
|
|
66
|
+
## Writable keys
|
|
67
|
+
|
|
68
|
+
Everything else on a field/table (physical `name`, `base_type`, `effective_type`, `active`, ids, timestamps) is read-only, set by sync.
|
|
69
|
+
|
|
70
|
+
**`PUT /api/field/:id` (`mb field update`)**
|
|
71
|
+
`display_name` · `description` · `caveats` · `points_of_interest` · `semantic_type` · `coercion_strategy` · `fk_target_field_id` · `visibility_type` · `has_field_values` · `settings` · `nfc_path` · `json_unfolding`
|
|
72
|
+
|
|
73
|
+
**`PUT /api/table/:id` (`mb table update`)**
|
|
74
|
+
`display_name` · `description` · `visibility_type` · `field_order` (`database` / `alphabetical` / `custom` / `smart`) · `entity_type` · `caveats` · `points_of_interest` · `show_in_getting_started` · `owner_user_id` / `owner_email` (ownership) · `data_layer` / `data_authority` / `data_source` (data-governance tiers) · `collection_id` (v62+)
|
|
75
|
+
|
|
76
|
+
## Coercion strategies (common)
|
|
77
|
+
|
|
78
|
+
Cast a `base_type` to a more useful `effective_type`. The value must be compatible with the column's base type and is driver-dependent (an unsupported one 400s at update).
|
|
79
|
+
|
|
80
|
+
- Epoch numbers → datetime: `Coercion/UNIXSeconds->DateTime`, `Coercion/UNIXMilliSeconds->DateTime`, `Coercion/UNIXMicroSeconds->DateTime`, `Coercion/UNIXNanoSeconds->DateTime`
|
|
81
|
+
- ISO-8601 strings → temporal: `Coercion/ISO8601->DateTime`, `Coercion/ISO8601->Date`, `Coercion/ISO8601->Time`
|
|
82
|
+
- Numeric strings → number: `Coercion/String->Integer`, `Coercion/String->Float`
|
|
83
|
+
- Narrowing: `Coercion/Float->Integer`, `Coercion/DateTime->Date`
|