@metabase/cli 0.5.2 → 0.5.3
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 +1 -1
- package/dist/{add-collection-C0BQHjJd.mjs → add-collection-c1dfnavz.mjs} +1 -1
- package/dist/{alert-mXHOt00T.mjs → alert-BkpPdBQ_.mjs} +6 -6
- package/dist/{alerts-CyB1Z7AU.mjs → alerts-TDYiIxjs.mjs} +2 -2
- package/dist/api-key-9jVV67hF.mjs +38 -0
- package/dist/api-key-DbsqHlN6.mjs +31 -0
- package/dist/api-key-wGtJ8kYy.mjs +13 -0
- package/dist/{append-DeHhUAeY.mjs → append-CoeZRD6u.mjs} +2 -2
- package/dist/{archive-CjVd1fJ5.mjs → archive-3fdIvMsf.mjs} +1 -1
- package/dist/{archive-Bi-Sfq5T.mjs → archive-9uzfWGuq.mjs} +1 -1
- package/dist/{archive-CpvpGZlz.mjs → archive-CAJHrKwd.mjs} +1 -1
- package/dist/{archive-DRMXvkiI.mjs → archive-CH1h8jz5.mjs} +1 -1
- package/dist/{archive-DfDqPgtk.mjs → archive-D-BJwRLc.mjs} +1 -1
- package/dist/{archive-CfNLlebd.mjs → archive-DQRehboT.mjs} +1 -1
- package/dist/{archive-CCKxZEuq.mjs → archive-DY76wU2k.mjs} +1 -1
- package/dist/{archive-B1MA5MZp.mjs → archive-DZDkfhdH.mjs} +1 -1
- package/dist/{archive-HCVTRXI2.mjs → archive-DxUnaV4x.mjs} +1 -1
- package/dist/{archive-DpgtAdEa.mjs → archive-N3lp1o-k.mjs} +1 -1
- package/dist/{archive-qy0CFo1s2.mjs → archive-NfFWXRx5.mjs} +1 -1
- package/dist/{archive-CDSAXKKB.mjs → archive-f7wKQ7Uy.mjs} +1 -1
- package/dist/{auth-_cdypXeX.mjs → auth-hgkvHPpH.mjs} +4 -4
- package/dist/{body-C0VnEiRV.mjs → body-M5qsJoub.mjs} +1 -1
- package/dist/{branches-C7mNYAky.mjs → branches-B3gHAanU.mjs} +1 -1
- package/dist/{breaking-BindR3ZW.mjs → breaking-DuWIof4T.mjs} +1 -1
- package/dist/{broken-SoXfkx7d.mjs → broken-D2ST8sTx.mjs} +1 -1
- package/dist/{bulk-discard-values-D_d5_1w5.mjs → bulk-discard-values-77khEfpj.mjs} +2 -2
- package/dist/{bulk-edit-Cce05vb_.mjs → bulk-edit-girwcjY6.mjs} +2 -2
- package/dist/{bulk-rescan-values-DIQN4VT_.mjs → bulk-rescan-values-Df8KK8um.mjs} +1 -1
- package/dist/{bulk-sync-schema-DLWf5dCl.mjs → bulk-sync-schema-DEPUZ6MN.mjs} +1 -1
- package/dist/{cancel-rnd6UI-6.mjs → cancel-Oek5PJnu.mjs} +1 -1
- package/dist/{cancel-task-acq1GT6Z.mjs → cancel-task-rkqY0qLU.mjs} +1 -1
- package/dist/{card-BnL92zoT.mjs → card-BWTjCE93.mjs} +8 -8
- package/dist/{cards-DmzPtx81.mjs → cards-DWJOtZoN.mjs} +1 -1
- package/dist/cli.mjs +51 -35
- package/dist/{client-D-Aw6JLk.mjs → client-BFP2Ssqu.mjs} +110 -3
- package/dist/{collection-DRHGwFSL.mjs → collection-9_mj8rVX.mjs} +7 -7
- package/dist/{content-translation-DxT1OMnP.mjs → content-translation-B3oARbgD.mjs} +2 -2
- package/dist/{copy-BTdTiDQo.mjs → copy-BzBp5FaY.mjs} +1 -1
- package/dist/{copy-CyFf3e2Y.mjs → copy-C7Rc4qM7.mjs} +1 -1
- package/dist/{create-DeCPAYJi.mjs → create-71_DODbc.mjs} +2 -2
- package/dist/{create-roJbQOmm.mjs → create-BKRD11i-.mjs} +1 -1
- package/dist/{create-BH6zpwKD.mjs → create-Bc4ADRmU.mjs} +2 -2
- package/dist/{create-DeiBnRRz.mjs → create-Bh3q3eHg.mjs} +2 -2
- package/dist/{create-D6NLpIzV.mjs → create-ByeGK_BE.mjs} +2 -2
- package/dist/{create-GU8_GNbN.mjs → create-C-2ivjed.mjs} +2 -2
- package/dist/{create-qtTB8rIE.mjs → create-C0gmhNnk.mjs} +2 -2
- package/dist/create-C3H9ljex.mjs +34 -0
- package/dist/{create-BwIOlpLc.mjs → create-CGE1D0Ay.mjs} +2 -2
- package/dist/{create-DiG165B2.mjs → create-CGxxztoE.mjs} +2 -2
- package/dist/{create-Ce5YVusp.mjs → create-CfXKNnkK.mjs} +2 -2
- package/dist/{create-B3QZpifA.mjs → create-CjGEkSFz.mjs} +2 -2
- package/dist/{create-Dn8V5siS.mjs → create-DO4ic4Kx.mjs} +2 -2
- package/dist/{create-C3rabvE_.mjs → create-DQJ9h86G.mjs} +2 -2
- package/dist/{create-CuJJd7Rh.mjs → create-YBDXZgyP.mjs} +2 -2
- package/dist/{create-Bg3WTMMU.mjs → create-YIC3Q0a-.mjs} +2 -2
- package/dist/create-_rOCx5b8.mjs +38 -0
- package/dist/{create-branch-C4Ql9G2g.mjs → create-branch-DBCBMDWh.mjs} +1 -1
- package/dist/{create-Ch-NbeMr.mjs → create-f0bBEfDI.mjs} +2 -2
- package/dist/{create-DsCydgm22.mjs → create-myJ8HR4B.mjs} +2 -2
- package/dist/{create-B6dABrs7.mjs → create-vgP9K2eH2.mjs} +2 -2
- package/dist/{csv-DmuyyLdz.mjs → csv-Dtgy7sTg.mjs} +2 -2
- package/dist/{current-task-C2PPBVVv.mjs → current-task-AwQisIT9.mjs} +1 -1
- package/dist/{dashboard-Bq1pae4N.mjs → dashboard-BZV-0Qjp.mjs} +11 -11
- package/dist/{data-action-BZ2PvlaP.mjs → data-action-B7kRpvCH.mjs} +7 -7
- package/dist/data-app-BIAvvV2P.mjs +41 -0
- package/dist/data-app-BUW-HlUo.mjs +52 -0
- package/dist/data-app-Blz61mG5.mjs +29 -0
- package/dist/data-app-project-BL4xpu_m.mjs +57 -0
- package/dist/{db-BgKKN1-4.mjs → db-wsSYKqCB.mjs} +7 -7
- package/dist/delete-4v4_lDwD.mjs +44 -0
- package/dist/{delete-DtIbkF42.mjs → delete-BHX_F8Qu.mjs} +2 -2
- package/dist/{delete-DiZg2wit.mjs → delete-BP9-YS-f.mjs} +2 -2
- package/dist/delete-BSgxmR3y.mjs +40 -0
- package/dist/{delete-_XC6R-Jn.mjs → delete-BegkpIGi.mjs} +2 -2
- package/dist/{delete-DeqGTuwO.mjs → delete-D9FB7qjX.mjs} +2 -2
- package/dist/{delete-CH32Ew4m.mjs → delete-DmM5L0wO.mjs} +2 -2
- package/dist/{delete-CS6hsBnd.mjs → delete-E-g5b-HU.mjs} +2 -2
- package/dist/{delete-B3aylPHD.mjs → delete-WrV9I9uw.mjs} +2 -2
- package/dist/delete-runtime-CJ7gMYA1.mjs +72 -0
- package/dist/{delete-table-BEuMlqw1.mjs → delete-table-CJq8JM5J.mjs} +2 -2
- package/dist/{delete-5ZOvFr-7.mjs → delete-wnHUWWjX.mjs} +2 -2
- package/dist/{dependencies-DD48u9i3.mjs → dependencies-CnW3RSoZ.mjs} +1 -1
- package/dist/{dependency-6kfpR8Ga.mjs → dependency-DDRGBlFL.mjs} +5 -5
- package/dist/{dependents-ByfHs8pO.mjs → dependents-DHXqJ3lo.mjs} +1 -1
- package/dist/{dirty-DsrGfLit.mjs → dirty-BRPWPUYH.mjs} +1 -1
- package/dist/{discard-values-BfpwwC62.mjs → discard-values-DBHfVN8o.mjs} +2 -2
- package/dist/{document-C0mZEg5w.mjs → document-BeLf7NqP.mjs} +6 -6
- package/dist/{download-kpgGtfpw.mjs → download-BFu9xYHX.mjs} +1 -1
- package/dist/{eid-Tp-ACZ4O.mjs → eid-eKmbXRds.mjs} +2 -2
- package/dist/{entry-flags-nhBrUS6p.mjs → entry-flags-Cxn09krz.mjs} +2 -2
- package/dist/{events-Dm9-ofXu.mjs → events-CZL9MU64.mjs} +1 -1
- package/dist/{execute-BL0dAlrp.mjs → execute-DBJGtHHG.mjs} +2 -2
- package/dist/{export-preflight-BMlz3Hp9.mjs → export-preflight-SM9zJkBp.mjs} +1 -1
- package/dist/{export-CIW-dHft.mjs → export-xn_-NaBd.mjs} +1 -1
- package/dist/{field-BUkyWiWk.mjs → field-CQDz-6DK.mjs} +7 -7
- package/dist/{fields-CcerFaiu.mjs → fields-CB06GFBW.mjs} +1 -1
- package/dist/{fks-CYfBesHV.mjs → fks-CSoS7xZ6.mjs} +1 -1
- package/dist/{get-CZgAr8rM.mjs → get--1qTpTIQ.mjs} +1 -1
- package/dist/{get-Cj3-w_Nu.mjs → get-B8xQXMU6.mjs} +1 -1
- package/dist/{get-CEJE9Rmp.mjs → get-B8xrmipw.mjs} +1 -1
- package/dist/{get-LRTRu85E.mjs → get-BIf6eFWZ.mjs} +1 -1
- package/dist/{get--E7UvR-v.mjs → get-BQ7SAJEz.mjs} +1 -1
- package/dist/{get-DHSuVxrH.mjs → get-BSPxmP_W.mjs} +1 -1
- package/dist/{get-BvEHWLGL.mjs → get-BW3jSR3y.mjs} +1 -1
- package/dist/{get-DKbGKa6F.mjs → get-BYAzIaGG.mjs} +1 -1
- package/dist/{get-p2sZvp7v.mjs → get-BZDUOZxI.mjs} +1 -1
- package/dist/{get-CmxYFUuR.mjs → get-BZFyqZXa.mjs} +1 -1
- package/dist/{get-B0RQPTV-2.mjs → get-BfFZCiZR.mjs} +1 -1
- package/dist/get-Bq_9VSTa.mjs +28 -0
- package/dist/{get-BFloqjrN.mjs → get-Bt4C-oxh.mjs} +1 -1
- package/dist/{get-eNRULmE5.mjs → get-C1rCVUYw.mjs} +2 -2
- package/dist/{get-RCDF-hEq.mjs → get-Cc4-e-Q_.mjs} +1 -1
- package/dist/{get-BaiyMem3.mjs → get-Cl-O6dFs.mjs} +1 -1
- package/dist/{get-Be7yjY9v.mjs → get-CtDZOoWy.mjs} +1 -1
- package/dist/{get-BLnQ2aut.mjs → get-CuSwArMS.mjs} +1 -1
- package/dist/{get-Dp45rXRS.mjs → get-DIfjxTP8.mjs} +1 -1
- package/dist/{get-DRhcS76J.mjs → get-DePbMmjc.mjs} +1 -1
- package/dist/{get-CRfrkxqc.mjs → get-eZEHipwA.mjs} +1 -1
- package/dist/{get-HzDQViDP2.mjs → get-jbOgdCQg.mjs} +1 -1
- package/dist/{get-run-C3AHKn3-.mjs → get-run-C-CNDbv3.mjs} +1 -1
- package/dist/git-sync-D_T9oNC6.mjs +29 -0
- package/dist/{glossary-B9Eo4hXc.mjs → glossary-BVK5DaLV.mjs} +4 -4
- package/dist/{graph-BGOYocJc.mjs → graph-CqBe2J4N.mjs} +1 -1
- package/dist/{has-remote-changes-DxsRvZsw.mjs → has-remote-changes-CDe1teSb.mjs} +1 -1
- package/dist/{import-izn2_OsW.mjs → import-B64gjryF.mjs} +1 -1
- package/dist/{is-dirty-DHYYRl6Y.mjs → is-dirty-DhR3Xqjk.mjs} +1 -1
- package/dist/{items-mIoZBqUc.mjs → items-CLl2QgS8.mjs} +1 -1
- package/dist/{library-5I0TlXmf.mjs → library-C3eO2J07.mjs} +4 -4
- package/dist/{list-jT5ndEGr.mjs → list-B7Yvz5vA.mjs} +1 -1
- package/dist/{list-BKhJfFyB.mjs → list-BIqqh3A-.mjs} +1 -1
- package/dist/{list-NeImhwLD.mjs → list-BiSwl56L.mjs} +1 -1
- package/dist/{list-BxIulkp3.mjs → list-BmvitCWZ.mjs} +1 -1
- package/dist/{list-OWPg67JC.mjs → list-C3GBocvB.mjs} +3 -3
- package/dist/{list-CuEPFHrk.mjs → list-C7TFreS8.mjs} +1 -1
- package/dist/{list-C1dEh2yq.mjs → list-CA939qkn.mjs} +1 -1
- package/dist/{list-CpxEOoqw.mjs → list-CFaJA317.mjs} +1 -1
- package/dist/{list-B8FPpgct.mjs → list-CdcFNphE.mjs} +1 -1
- package/dist/{list-D8jiz8YP.mjs → list-CjpZCiks.mjs} +1 -1
- package/dist/{list-B6TYzXyW.mjs → list-Cu6ET_yD.mjs} +1 -1
- package/dist/{list-C-f-G-cC.mjs → list-D4HNG282.mjs} +1 -1
- package/dist/{list-C6roDyBd.mjs → list-DDPvxfhe.mjs} +1 -1
- package/dist/{list-DV4wojtv.mjs → list-DLLzMaS3.mjs} +1 -1
- package/dist/{list-BEBAq1xc.mjs → list-DLwby7YA.mjs} +1 -1
- package/dist/{list-C3CXHKTW.mjs → list-D_IT8l9B.mjs} +1 -1
- package/dist/{list-CmOTwt_z.mjs → list-DpfOh8-5.mjs} +1 -1
- package/dist/{list-CTlIVHeG.mjs → list-DrIcKxXX.mjs} +2 -2
- package/dist/{list-DVnPzc5m.mjs → list-DyFu5Wx1.mjs} +1 -1
- package/dist/{list-O_nmgwSj.mjs → list-DyQNRVAf.mjs} +1 -1
- package/dist/list-YFwmHXGG.mjs +33 -0
- package/dist/{list-CT7AOXTS.mjs → list-eGlqXJno.mjs} +1 -1
- package/dist/list-s9_LlXCV.mjs +29 -0
- package/dist/{login-DOW1mr40.mjs → login-Bg7Z3jDy.mjs} +3 -3
- package/dist/{logout-BQ8wywf7.mjs → logout-CAmp7DcS.mjs} +1 -1
- package/dist/{measure-Bz-bGn-Z.mjs → measure-C31HLp1C.mjs} +5 -5
- package/dist/{parameter-values-B7rr6fcy.mjs → parameter-values-3eHpqznI.mjs} +1 -1
- package/dist/{path-CHQguAPa.mjs → path-XwdHlC23.mjs} +2 -2
- package/dist/{publish-CaoFOucS.mjs → publish-C2-pK0cf.mjs} +1 -1
- package/dist/{query-0u1WR1bV.mjs → query-DkaUzmMa.mjs} +2 -2
- package/dist/{query-CzDBwAZD.mjs → query-pmw23KkL.mjs} +1 -1
- package/dist/{remapping-CodmT28F.mjs → remapping-C3htTzJn.mjs} +1 -1
- package/dist/{remove-collection-DfUsf6Dd.mjs → remove-collection-p_t9rUUX.mjs} +1 -1
- package/dist/{render-DP6UoZNT.mjs → render-C50k8-jc.mjs} +1 -1
- package/dist/{replace-qWiUOgmn.mjs → replace-BQBuFJ1B.mjs} +2 -2
- package/dist/{requirement-check-sfmyH2PH.mjs → requirement-check-CpiVuL0H.mjs} +1 -1
- package/dist/{rescan-values-BcrhdSj_.mjs → rescan-values-Ctq8wpkI.mjs} +1 -1
- package/dist/{rescan-values-BuDmeda3.mjs → rescan-values-DOqXJ3qH.mjs} +1 -1
- package/dist/{run-BBRnFayr.mjs → run-2xgPRuKR.mjs} +1 -1
- package/dist/{run-DXXD-CnS.mjs → run-CtMbObQI.mjs} +2 -2
- package/dist/{run-BG_5lR0x.mjs → run-Dn3e8G4x.mjs} +1 -1
- package/dist/{runs-CovgMSwy.mjs → runs-CgNoQW6M.mjs} +1 -1
- package/dist/{runtime-Cmes70jO.mjs → runtime-Y4VFWmmr.mjs} +1 -1
- package/dist/scaffold-BuL0_ptz.mjs +50 -0
- package/dist/{schema-tables-CjKl8jr5.mjs → schema-tables-BdXuo_lG.mjs} +1 -1
- package/dist/{schemas-CAGvBAD_.mjs → schemas-Dap3KFwY.mjs} +1 -1
- package/dist/{search-DFU6FHRd.mjs → search-DUmFK5Sy.mjs} +1 -1
- package/dist/{search-OpYS2Ub4.mjs → search-Dxcejw_v.mjs} +1 -1
- package/dist/{segment-D-SKpFzF.mjs → segment-CebcXn2E.mjs} +5 -5
- package/dist/{send-DNu83_3E.mjs → send-DYdwcPTb.mjs} +1 -1
- package/dist/{set-active-32IaLx1O.mjs → set-active-CVI_DDQi.mjs} +1 -1
- package/dist/set-bundle-DX5HqDji.mjs +40 -0
- package/dist/{set-data-actions-DRhgAfhE.mjs → set-data-actions-DAI1XCQ2.mjs} +1 -1
- package/dist/set-enabled-0QI-CHAT.mjs +41 -0
- package/dist/{set-sensitivity-D_i3XEe8.mjs → set-sensitivity-DqGkiLSr.mjs} +1 -1
- package/dist/{set-CGYZcLVo.mjs → set-xs8Vyvy2.mjs} +2 -2
- package/dist/{setting-BBn9Lv9J.mjs → setting-Bmry-y_l.mjs} +3 -3
- package/dist/{setup-8d7Uplhy.mjs → setup-UM8N9Vh3.mjs} +2 -2
- package/dist/{skills-BeIDC-_H.mjs → skills-BiSfuDqA.mjs} +3 -3
- package/dist/{skills-BZGd_9d3.mjs → skills-CcLTm3_2.mjs} +1 -1
- package/dist/slug-qeARO25o.mjs +15 -0
- package/dist/{snippet-CpZ5cJ_y.mjs → snippet-DKARah-L.mjs} +5 -5
- package/dist/{stash-Dyc1xZ8u.mjs → stash-e5cSvnj3.mjs} +1 -1
- package/dist/{status-lCN8unWe.mjs → status-BHiO3YBe.mjs} +1 -1
- package/dist/{status-CQZwl1PX.mjs → status-CH7BAWGe.mjs} +2 -2
- package/dist/{subscription-CEFxmtGH.mjs → subscription-DWleULhN.mjs} +5 -5
- package/dist/{subscriptions-AU14uznL.mjs → subscriptions-DcCOA2p1.mjs} +2 -2
- package/dist/{summary-Do9IBOQM.mjs → summary-CipFA6M6.mjs} +1 -1
- package/dist/{sync-schema-_gw9RQyF.mjs → sync-schema-C45WO4db.mjs} +1 -1
- package/dist/{sync-schema-B3nyoKZl.mjs → sync-schema-D7p-P8F6.mjs} +1 -1
- package/dist/table-BPYHnzyV.mjs +26 -0
- package/dist/{timeline-x0s_3sfc.mjs → timeline-C2m_QHxn.mjs} +7 -7
- package/dist/{timeline-event-B-nGnoVL.mjs → timeline-event-BF1dqSnD.mjs} +5 -5
- package/dist/transform-N_elgRuh.mjs +28 -0
- package/dist/transform-job-CTV6KRP2.mjs +22 -0
- package/dist/{transform-tag-dKCqQPTd.mjs → transform-tag-BYoguZfW.mjs} +4 -4
- package/dist/{transform-test-jFuzVhlT.mjs → transform-test-jaBpHOM5.mjs} +6 -6
- package/dist/{transforms-DRAws1f4.mjs → transforms-C6MFnT4C.mjs} +1 -1
- package/dist/{transport-Ck4anhoc.mjs → transport-k_fabWdF.mjs} +2 -2
- package/dist/{tree-Ddsj298M.mjs → tree-C4Igf6xs.mjs} +1 -1
- package/dist/typed-schema-Bo0Q6C0S.mjs +45 -0
- package/dist/{unpublish-BMfjQ8UV.mjs → unpublish-EStytsEm.mjs} +1 -1
- package/dist/{unreferenced-DRQrvH5r.mjs → unreferenced-DtUuyBrr.mjs} +1 -1
- package/dist/{update-pXVhyiFo.mjs → update-B0Na11v-.mjs} +2 -2
- package/dist/{update-CGpiLu7N.mjs → update-B1UXRPN-.mjs} +2 -2
- package/dist/{update-CnxzmPRG.mjs → update-BOg1jZ_s.mjs} +2 -2
- package/dist/{update-C1VwtJWu.mjs → update-BXLY92om.mjs} +2 -2
- package/dist/{update-6sfqiInZ.mjs → update-Bba_zPSE.mjs} +2 -2
- package/dist/{update-DlFHoUxe.mjs → update-BogvN2Mb.mjs} +2 -2
- package/dist/{update-D1aKeWfo.mjs → update-CCLpNTPa.mjs} +2 -2
- package/dist/{update-_hHnM1Hx.mjs → update-CciLWH_s.mjs} +2 -2
- package/dist/{update-BQLTaw3z.mjs → update-DAG8m5f4.mjs} +2 -2
- package/dist/{update-BtfUX9CN.mjs → update-DECiEVlk.mjs} +2 -2
- package/dist/update-DIBaY-fy.mjs +37 -0
- package/dist/{update-0AoF6QJ_.mjs → update-DT2ViMFr.mjs} +2 -2
- package/dist/{update-BCC1Igr2.mjs → update-DT8U29Kk.mjs} +2 -2
- package/dist/{update-8eXJso_l.mjs → update-DhgfReDD.mjs} +2 -2
- package/dist/{update-Bpsdi6kM.mjs → update-DkvIK-eL.mjs} +2 -2
- package/dist/{update-C1x90FSH.mjs → update-FfeHReec.mjs} +2 -2
- package/dist/{update-C19B1Sry.mjs → update-HbggUPuy.mjs} +2 -2
- package/dist/{update-DrT10fxQ.mjs → update-VGgvQu7W.mjs} +2 -2
- package/dist/{update-dashcard-CDI0Tnxy.mjs → update-dashcard-m-hkIGUu.mjs} +2 -2
- package/dist/{update-BIL0JPqm.mjs → update-pk0b6vXU.mjs} +2 -2
- package/dist/{update-DpP1EEC0.mjs → update-z8ZGCg1r.mjs} +2 -2
- package/dist/{upgrade-Cfouv1Ii.mjs → upgrade-iETJ8fH2.mjs} +1 -1
- package/dist/{upload-DnLXgUA-.mjs → upload-BF5pdKRN.mjs} +1 -1
- package/dist/{upload-DIEcI5iM.mjs → upload-CoK5LnyA.mjs} +2 -2
- package/dist/{upload-Cqv-In6j.mjs → upload-CqSGEZQW.mjs} +3 -3
- package/dist/{uuid-D0sH5txV.mjs → uuid-8Ha-8bDX.mjs} +1 -1
- package/dist/{values-BFPO6y5o.mjs → values-BsgA7-6h.mjs} +1 -1
- package/dist/{verify-C-StvB4f.mjs → verify-CzBZbP4W.mjs} +1 -1
- package/dist/{verify-CuGs0X3C.mjs → verify-cjUPMF_B.mjs} +1 -1
- package/dist/{verify-7fW020r_.mjs → verify-crIrbGZV.mjs} +2 -2
- package/dist/{wait-Cvt8xO0h.mjs → wait-mFUfpSaj.mjs} +1 -1
- package/package.json +2 -2
- package/skill-data/core/SKILL.md +1 -1
- package/skill-data/data-action/SKILL.md +1 -1
- package/skill-data/data-app/SKILL.md +138 -0
- package/skill-data/data-app/template/.env.local.example +2 -0
- package/skill-data/data-app/template/LICENSE +21 -0
- package/skill-data/data-app/template/README.md +29 -0
- package/skill-data/data-app/template/gitignore +5 -0
- package/skill-data/data-app/template/package.json +25 -0
- package/skill-data/data-app/template/src/App.tsx +9 -0
- package/skill-data/data-app/template/src/index.tsx +14 -0
- package/skill-data/data-app/template/src/theme.ts +25 -0
- package/skill-data/data-app/template/tsconfig.json +25 -0
- package/skill-data/data-app/template/vite.config.ts +3 -0
- package/skill-data/data-app-actions/SKILL.md +96 -0
- package/skill-data/data-app-data/SKILL.md +158 -0
- package/skill-data/data-app-filters/SKILL.md +87 -0
- package/skill-data/data-app-ui/SKILL.md +120 -0
- package/dist/delete-runtime-DRGveT8H.mjs +0 -55
- package/dist/git-sync-CNTB5jvI.mjs +0 -29
- package/dist/table-Cb_Uvw8m.mjs +0 -26
- package/dist/transform-job-Dlx2c6II.mjs +0 -22
- package/dist/transform-kP11Mv9_.mjs +0 -28
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as createClient } from "./client-
|
|
1
|
+
import { n as createClient } from "./client-BFP2Ssqu.mjs";
|
|
2
2
|
import { Et as NetworkError, Mt as errorMessage, Tt as MetabaseError, W as HttpError, kt as TimeoutError } from "./cli.mjs";
|
|
3
|
-
import { B as probeServer, T as USER_AGENT } from "./runtime-
|
|
3
|
+
import { B as probeServer, T as USER_AGENT } from "./runtime-Y4VFWmmr.mjs";
|
|
4
4
|
//#region src/core/auth/verify.ts
|
|
5
5
|
const VERIFY_TIMEOUT_MS = 15e3;
|
|
6
6
|
async function verifyAndProbe(url, credential, options = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as renderSummary, ct as outputFlags, lt as profileFlag, rt as connectionFlags } from "./cli.mjs";
|
|
2
|
-
import { t as defineMetabaseCommand } from "./runtime-
|
|
2
|
+
import { t as defineMetabaseCommand } from "./runtime-Y4VFWmmr.mjs";
|
|
3
3
|
import { n as DEFAULT_TIMEOUT_MS, t as DEFAULT_INTERVAL_MS } from "./poll-CgtcJopL.mjs";
|
|
4
4
|
import { r as parseWaitSchedule } from "./wait-flags-BnZ-dfE2.mjs";
|
|
5
5
|
import { u as syncTaskView } from "./git-sync-D1aKczo6.mjs";
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metabase/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Metabase CLI",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/metabase/
|
|
8
|
+
"url": "git+https://github.com/metabase/metabase-cli.git",
|
|
9
9
|
"directory": "packages/cli"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
package/skill-data/core/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ Top-level command groups (run `mb <group> --help` to discover verbs):
|
|
|
13
13
|
```
|
|
14
14
|
auth | db | table | field | upload | content-translation | query | card | dashboard | snippet | segment | measure | collection | library
|
|
15
15
|
document | glossary | timeline | timeline-event | transform | transform-job | transform-tag | transform-test | alert | subscription | setting
|
|
16
|
-
search | dependency | git-sync | data-action | setup | eid | uuid | upgrade | skills
|
|
16
|
+
search | dependency | git-sync | data-action | data-app | setup | eid | uuid | upgrade | skills
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
The conventions below — auth, flags, output, body input — hold across **every** group. Per-command flags and examples live in each command's `--help`; add `--json` for the machine-readable form with the output JSON Schema. A few flows have their own skills (see "Specialized skills"). When a card needs a query, prefer MBQL over native SQL (portable, pre-flight-validated — load `mbql`); fall back to native SQL when MBQL can't express it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-action
|
|
3
|
-
description: Author and run Metabase data actions with the `mb` CLI — saved, parameterized native SQL writes (INSERT, UPDATE, DELETE) filed in a collection and run with values. Covers enabling them on a database, the collection they go in, the body, mapping template tags to parameters, running, and the lifecycle. Triggers — "add a data action", "let users update a row", "a button that inserts a record", "run a write query", "a form that saves to the database", "delete rows from Metabase".
|
|
3
|
+
description: Author and run Metabase data actions with the `mb` CLI — saved, parameterized native SQL writes (INSERT, UPDATE, DELETE) filed in a collection and run with values. Covers enabling them on a database, the collection they go in, the body, mapping template tags to parameters, running, and the lifecycle. Triggers — "add a data action", "let users update a row", "a button that inserts a record", "run a write query", "a form that saves to the database", "delete rows from Metabase". Inside a data app, start with `data-app-actions`.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
5
|
requires: [dataActionsWithoutModel]
|
|
6
6
|
---
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-app
|
|
3
|
+
description: Create, preview, publish and manage a Metabase data app — a React app Metabase serves at /apps/<slug> — create it, scaffold its project, run the dev preview and read its diagnostics feed, build and publish the bundle, turn it on or off, move it to another instance, delete it. Triggers — "build a data app", "make an internal tool in Metabase", "scaffold a data app", "publish my data app", "update the app", "the app is blank". Start here; the UI is in `data-app-ui`, questions and charts in `data-app-data`, filter controls in `data-app-filters`, writes in `data-app-actions`.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
requires: [dataApps]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Data apps
|
|
9
|
+
|
|
10
|
+
A **data app** is a React app built into one JavaScript bundle, which Metabase runs in a sandbox at `/apps/<slug>`. Each app has its own collection — **the app's collection** — and the questions and actions in it are everything the app can run. Flag conventions are in `core`.
|
|
11
|
+
|
|
12
|
+
The work, in order:
|
|
13
|
+
|
|
14
|
+
1. Create the app on the instance.
|
|
15
|
+
2. Scaffold the project and install the SDK.
|
|
16
|
+
3. Give it data: questions over the Library's published tables and metrics in the app's collection, then `src/metabase.ts` (`data-app-data`); writes are `data-app-actions`.
|
|
17
|
+
4. Start the dev preview (_Dev preview_), then write the UI under `src/` (`data-app-ui`; filter controls in `data-app-filters`).
|
|
18
|
+
5. Build and publish (_Build and publish_).
|
|
19
|
+
|
|
20
|
+
For every later change: regenerate `src/metabase.ts` if the app's collection changed, then build and set the bundle. Metabase receives only the built bundle; the source stays in the project directory, under git if the user keeps one.
|
|
21
|
+
|
|
22
|
+
## Before you start
|
|
23
|
+
|
|
24
|
+
- Use an **admin** profile — only admins create apps and write the app's collection. `mb auth status --json | jq .user.isAdmin` prints `true` for one; otherwise ask the user for an admin profile (`mb auth list`).
|
|
25
|
+
- Run `mb data-app list`. A failure means the instance has no data apps: stop and tell the user.
|
|
26
|
+
- Settle the **slug**, the `/apps/<slug>` URL: lowercase letters, digits and single dashes (`sales-overview`), at most 100 characters. If the app's purpose is unclear, ask "what's this app for?" and propose one.
|
|
27
|
+
|
|
28
|
+
## Create the app
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mb data-app create --body '{"name":"sales-overview","display_name":"Sales overview"}' --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`name` is the slug; `display_name` and `description` are optional. The app starts disabled, with an empty bundle. The answer's `resource_collection_id` is the id of the app's collection — note it. A `409` means the slug is taken: `mb data-app list` shows that app; reuse it or pick another slug.
|
|
35
|
+
|
|
36
|
+
## Scaffold and install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
mb data-app scaffold sales-overview && cd sales-overview
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`scaffold [dir]` takes a **directory**, not the slug: it copies the Vite + React + TypeScript template into a new or empty directory (the current one when omitted) and needs no server. Then install the SDK, then the rest:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @metabase/embedding-sdk-react@<major>-beta # <major>: 66 for a version tag v1.66.x (`mb auth status --json | jq -r .version.tag`)
|
|
46
|
+
npm install && npm run typecheck && npm run build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Skip the SDK line when `package.json` already points it at a local build (`file:` or `link:`), and never install `latest`. A tag without a version number, such as `vLOCAL_DEV`, is a development build: ask the user which SDK build to install. The starter renders "Hello, data app".
|
|
50
|
+
|
|
51
|
+
The template is the bundle contract. Don't edit `package.json` by hand, and never rewrite `vite.config.ts`, `tsconfig.json` or `src/index.tsx`: `vite.config.ts` stays `export default dataAppConfig()` (its only option is `port`), and `src/index.tsx` default-exports the `DataAppFactory` — change it only to add a `providerProps` key. Metabase and the dev preview each wrap the app in a `<MetabaseProvider>`, so the app never renders one.
|
|
52
|
+
|
|
53
|
+
**An existing project.** Before changing an app you didn't scaffold in this session, check both: `vite.config.ts` is `export default dataAppConfig()` from `@metabase/embedding-sdk-react/data-app-dev/config`, with no local `config/` directory; and `src/index.tsx` default-exports a `DataAppFactory` that takes no arguments and returns `{ component, providerProps? }`. If either fails, stop: tell the user the project differs from the template, and ask whether to scaffold a fresh project and port the code over, or go on at their own risk.
|
|
54
|
+
|
|
55
|
+
## Dev preview
|
|
56
|
+
|
|
57
|
+
`npm run dev` serves the app at `http://localhost:5174` — or the next free port, in Vite's `Local:` line — through the same sandbox Metabase uses. An edit under `src/` rebuilds the bundle and remounts the app, resetting component state. The preview reads `DATA_APP_MB_URL` and an admin `DATA_APP_MB_API_KEY` from `.env.local` beside `package.json`. Write a new key straight into that file, never into the chat or the terminal:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
( umask 077
|
|
61
|
+
URL=$(mb auth status --json | jq -r .url)
|
|
62
|
+
KEY=$(mb api-key create --body '{"name":"data-app <slug> dev","group_id":2}' --json | jq -r .unmasked_key)
|
|
63
|
+
[ -n "$KEY" ] && [ "$KEY" != null ] && printf 'DATA_APP_MB_URL=%s\nDATA_APP_MB_API_KEY=%s\n' "$URL" "$KEY" > .env.local && echo written )
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Group `2` is Administrators. Never print `.env.local`. Delete the key at handoff (`mb api-key list`, then `mb api-key delete <id> --yes`); the next session writes a new one. A CORS error means the instance doesn't allow the preview's origin: ask the user to add `http://localhost:5174` under Admin → Embedding → Embedded analytics SDK → CORS.
|
|
67
|
+
|
|
68
|
+
### The diagnostics feed
|
|
69
|
+
|
|
70
|
+
Sandbox blocks, failed queries and uncaught errors never reach the terminal. With a preview tab open, read them from the feed:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl -s "http://localhost:5174/__data-app/diagnostics?startEventId=0"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
77
|
+
{
|
|
78
|
+
"entries": [
|
|
79
|
+
{
|
|
80
|
+
"eventId": 31,
|
|
81
|
+
"kind": "blocked-network",
|
|
82
|
+
"alert": true,
|
|
83
|
+
"summary": "Blocked fetch to api.example.com (not in allowed_hosts)",
|
|
84
|
+
"detail": null,
|
|
85
|
+
"hint": "…",
|
|
86
|
+
"buildId": 7,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
"clients": 1, // open preview tabs; 0 means nothing ran
|
|
90
|
+
"buildId": 7, // the bundle running now
|
|
91
|
+
"staleEntries": 164, // withheld: reported by a build already replaced
|
|
92
|
+
"nextEventId": 32, // pass back as ?startEventId=
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `clients: 0` means no tab is open, so empty `entries` prove nothing.
|
|
97
|
+
- Before an edit, note `nextEventId`; after it, read again with that value as `startEventId`.
|
|
98
|
+
- The feed answers for the running bundle: entries from replaced builds are counted in `staleEntries`, and anything still broken reports again under the new `buildId` (`?includeStale=true` shows the withheld ones). A failed build doesn't advance `buildId` and doesn't reach the feed — read the `npm run dev` terminal.
|
|
99
|
+
- Quote `summary` as it is when you report it, and follow `hint` when there is one — except that you never add the Metabase URL to `allowed_hosts`. The feed keeps the last 200 entries; `curl -X DELETE …/__data-app/diagnostics` clears it.
|
|
100
|
+
|
|
101
|
+
| `kind` | Fix |
|
|
102
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `blocked-network`, `csp-violation` | An external API: `data-app-ui`, _External APIs_. A `script-src` or `style-src` violation: bundle the asset instead. |
|
|
104
|
+
| `blocked-api` | Blocked in Metabase too: use the replacement in `data-app-ui`. |
|
|
105
|
+
| `sdk-call` with `alert: true` | A Metabase request failed; `summary` names it. |
|
|
106
|
+
| `error` | A bug; `detail` has the stack. |
|
|
107
|
+
|
|
108
|
+
## Build and publish
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm run typecheck && npm run build
|
|
112
|
+
mb data-app set-bundle sales-overview
|
|
113
|
+
mb data-app set-enabled sales-overview true # the first time
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`set-bundle` uploads `dist/index.js` (`--bundle` for another path) and replaces the app's bundle, live at once. A new app is disabled until `set-enabled` turns it on. Give the user `<metabase-url>/apps/<slug>`.
|
|
117
|
+
|
|
118
|
+
Admins open every app, enabled or not. Anyone else needs the app enabled (otherwise "not found") and one of their groups assigned to it (otherwise "You don't have access"), which only the user can do (Admin → Settings → Data apps → the app → Groups). A group reaches the app's collection and nothing more, so a viewer also needs data access to the tables its questions read.
|
|
119
|
+
|
|
120
|
+
## Pitfalls
|
|
121
|
+
|
|
122
|
+
| Symptom | Fix |
|
|
123
|
+
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `set-bundle`: bundle not found | Run `npm run build` in the project directory first. |
|
|
125
|
+
| Blank app, `Bundle did not assign a function to __dataAppFactory__` | `src/index.tsx` must `export default` the factory and nothing else; otherwise the diagnostics feed has the real error. |
|
|
126
|
+
| "Failed to fetch the user, the session might be invalid." | A bad key in `.env.local`, or CORS (_Dev preview_). |
|
|
127
|
+
| A bundle of several MB | `vite.config.ts` no longer uses `dataAppConfig()`, or the SDK isn't the `<major>-beta` tag. |
|
|
128
|
+
| "Invalid hook call" | A second React got bundled: nothing may add another `react` or `react-dom`. |
|
|
129
|
+
| Drill-through popups don't open | The app renders its own `<MetabaseProvider>`: remove it. |
|
|
130
|
+
| A rebuilt or reinstalled SDK still behaves like the old one | Vite caches it: stop `npm run dev`, delete `node_modules/.vite`, start again. |
|
|
131
|
+
| A viewer's query or action fails with 403 | It reads something outside the app's collection, or the viewer lacks data access to its tables. |
|
|
132
|
+
|
|
133
|
+
## Change, move, delete
|
|
134
|
+
|
|
135
|
+
- **Turn off:** `mb data-app set-enabled <slug> false`. **Rename:** `mb data-app update <slug> --body '{"display_name":"…"}'` (also `description`, `allowed_hosts`).
|
|
136
|
+
- **Outdated:** an app Metabase lists as _Outdated_ targets an older data-app contract: it won't open for anyone, and only admins see it listed, until it's migrated. Tell the user; don't change its code ad hoc.
|
|
137
|
+
- **Move to another instance:** the app, its collection and what the collection holds are git-synced content. `mb git-sync export` (`git-sync`) writes `data_apps/<slug>/` with the bundle, and the collection under `collections/data_apps/`; importing the repository on the other instance recreates them with the same entity IDs, so the same `src/metabase.ts` and bundle work there. On a read-only instance the app's collection can't be edited: change it where it was built and export again.
|
|
138
|
+
- **Delete:** confirm with the user, then `mb data-app delete <slug> --yes` — it removes the app, its bundle, its collection and the questions and actions in it. The project directory stays.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Metabase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Data app
|
|
2
|
+
|
|
3
|
+
A Metabase data app: a single-bundle React app, built with the Embedding SDK,
|
|
4
|
+
that Metabase serves at `/apps/<slug>`. Build and change it with a coding agent
|
|
5
|
+
and the `mb` data-app skills.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install the SDK tag matching the Metabase instance first, then the rest.
|
|
10
|
+
`<major>` is 66 for a version tag v1.66.x (`mb auth status --json | jq -r .version.tag`);
|
|
11
|
+
`package.json` asks for any version, which would install `latest`.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @metabase/embedding-sdk-react@<major>-beta
|
|
15
|
+
npm install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run dev # preview at http://localhost:5174, in the sandbox Metabase runs apps in
|
|
22
|
+
npm run typecheck # type-checks src/ and vite.config.ts
|
|
23
|
+
npm run build # builds dist/index.js
|
|
24
|
+
mb data-app typed-schema <slug> --out src/metabase.ts # the app's queries and actions
|
|
25
|
+
mb data-app set-bundle <slug> # makes dist/index.js the app's bundle
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`npm run dev` reads `DATA_APP_MB_URL` and `DATA_APP_MB_API_KEY` from
|
|
29
|
+
`.env.local`; `.env.local.example` lists them.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-app-template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@metabase/embedding-sdk-react": "*",
|
|
13
|
+
"react": "^18.3.1",
|
|
14
|
+
"react-dom": "^18.3.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/react": "^18.3.12",
|
|
18
|
+
"@types/react-dom": "^18.3.1",
|
|
19
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
20
|
+
"typescript": "^6.0.3",
|
|
21
|
+
"vite": "^8.0.16",
|
|
22
|
+
"vite-plugin-css-injected-by-js": "^3.5.2",
|
|
23
|
+
"vite-plugin-svgr": "^5.2.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DataAppFactory } from "@metabase/embedding-sdk-react/data-app";
|
|
2
|
+
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { sdkTheme } from "./theme";
|
|
5
|
+
|
|
6
|
+
// The data app's entry point. Return the root component plus any `providerProps`
|
|
7
|
+
// the host should apply to `MetabaseProvider` (e.g. `theme`,
|
|
8
|
+
// `allowedCustomVisualizations`).
|
|
9
|
+
const factory: DataAppFactory = () => ({
|
|
10
|
+
component: App,
|
|
11
|
+
providerProps: { theme: sdkTheme },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default factory;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MetabaseTheme } from "@metabase/embedding-sdk-react";
|
|
2
|
+
|
|
3
|
+
export const sdkTheme: MetabaseTheme = {
|
|
4
|
+
colors: {
|
|
5
|
+
brand: "#4D96FF",
|
|
6
|
+
"brand-hover": "#EAF4FF",
|
|
7
|
+
positive: "#4D96FF",
|
|
8
|
+
charts: [
|
|
9
|
+
"#4D96FF",
|
|
10
|
+
"#E8743B",
|
|
11
|
+
"#2BA672",
|
|
12
|
+
"#9461D9",
|
|
13
|
+
"#E0475B",
|
|
14
|
+
"#1C9FB8",
|
|
15
|
+
"#C99A1E",
|
|
16
|
+
"#5468D4",
|
|
17
|
+
],
|
|
18
|
+
background: "white",
|
|
19
|
+
"background-secondary": "white",
|
|
20
|
+
"text-primary": "#1f2937",
|
|
21
|
+
"text-secondary": "#4b5563",
|
|
22
|
+
"text-tertiary": "#6b7280",
|
|
23
|
+
},
|
|
24
|
+
fontFamily: "system-ui, -apple-system, Segoe UI, sans-serif",
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"allowJs": false,
|
|
15
|
+
"checkJs": false,
|
|
16
|
+
"noImplicitAny": true,
|
|
17
|
+
"strictNullChecks": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": false,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"types": ["vite/client"]
|
|
23
|
+
},
|
|
24
|
+
"include": ["src", "vite.config.ts"]
|
|
25
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-app-actions
|
|
3
|
+
description: Make a Metabase data app write data — an action in the app's collection, its typed definition in `src/metabase.ts`, and the `useAction` hook from `@metabase/embedding-sdk-react/data-app` behind a form or a button, with inputs typed from each parameter, the server's error shown as it comes, and every affected query refreshed. Triggers — "a form that saves a row", "a button that updates a record", "delete an entry from the app", "submit doesn't refresh the list", "the action fails in the app". Creating the action itself is `data-action`; reads are `data-app-data`.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
requires: [dataApps]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Writes in a data app
|
|
9
|
+
|
|
10
|
+
An **action** is a saved, parameterized SQL write — `INSERT`, `UPDATE` or `DELETE`. A data app runs only the actions in **the app's collection** (`data-app`, _Create the app_).
|
|
11
|
+
|
|
12
|
+
1. Draft the action as `data-action` describes — the database must allow actions — with `collection_id` the app's collection. Show the user its SQL and parameters, and create it once they agree.
|
|
13
|
+
2. Regenerate `src/metabase.ts` (`data-app-data`).
|
|
14
|
+
3. Pass `actions.<name>` from it to `useAction`.
|
|
15
|
+
|
|
16
|
+
Each action there lists its parameters: the parameter slug (the key `execute` takes), `displayName` (a label, never a key), `jsType` (`string`, `number`, `Date`, `boolean` or `unknown`) and `required`. Never fake a call to an action `src/metabase.ts` doesn't list.
|
|
17
|
+
|
|
18
|
+
## The hook
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { useAction } from "@metabase/embedding-sdk-react/data-app";
|
|
22
|
+
import { actions } from "../metabase";
|
|
23
|
+
|
|
24
|
+
const { execute, isExecuting, result, error, reset } = useAction(actions.setOrderNote);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **Import it from `/data-app`.** The main `@metabase/embedding-sdk-react` entry exports the general SDK `useAction`, which takes raw ids and isn't typed from the definition. Write no generics: the definition types `execute`'s keys and values.
|
|
28
|
+
- `execute(params)` resolves to the response body; on failure it both throws and sets `error`. It resolves to `null` without sending anything while the SDK is still starting, so guard a call that can happen at mount. It never runs on its own and has no `enabled`: return early in the handler to skip it.
|
|
29
|
+
- `isExecuting` is true until it settles: disable the trigger and show progress, so a double click can't send twice.
|
|
30
|
+
- `result` is the last response, `null` before the first call; `result?.["rows-affected"]` is a quick confirmation. `reset()` clears `result` and `error`, for example when the user dismisses a message.
|
|
31
|
+
|
|
32
|
+
## A form
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
export function NoteForm({ orderId, onSaved }: { orderId: number; onSaved: () => Promise<void> }) {
|
|
36
|
+
const { execute, isExecuting, error } = useAction(actions.setOrderNote);
|
|
37
|
+
const [note, setNote] = useState("");
|
|
38
|
+
const [isValid, setIsValid] = useState(false);
|
|
39
|
+
|
|
40
|
+
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
try {
|
|
43
|
+
await execute({ order_id: orderId, note }); // keys are parameter slugs
|
|
44
|
+
await onSaved(); // the parent refetches what the write changed
|
|
45
|
+
setNote("");
|
|
46
|
+
} catch {
|
|
47
|
+
// shown from `error`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<form onSubmit={onSubmit} onChange={(event) => setIsValid(event.currentTarget.checkValidity())}>
|
|
53
|
+
<input type="text" required value={note} onChange={(event) => setNote(event.target.value)} />
|
|
54
|
+
<button type="submit" disabled={isExecuting || !isValid}>
|
|
55
|
+
{isExecuting ? "Saving…" : "Save"}
|
|
56
|
+
</button>
|
|
57
|
+
{error ? (
|
|
58
|
+
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
|
59
|
+
{error.data.message ?? "Action failed."}
|
|
60
|
+
</pre>
|
|
61
|
+
) : null}
|
|
62
|
+
</form>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Inputs.** Pick each input's type from `jsType`, never from the parameter slug — one called `email` with `jsType: "string"` is still `type="text"`.
|
|
68
|
+
|
|
69
|
+
| `jsType` | Input |
|
|
70
|
+
| --------- | ---------------------------------------------------------- |
|
|
71
|
+
| `string` | `type="text"` |
|
|
72
|
+
| `number` | `type="number"`, read with `valueAsNumber` |
|
|
73
|
+
| `boolean` | a checkbox or switch |
|
|
74
|
+
| `Date` | `type="date"`, or `datetime-local` when a time is expected |
|
|
75
|
+
| `unknown` | `type="text"`, converted at the call |
|
|
76
|
+
|
|
77
|
+
**Validation** is `required` on each required parameter and nothing else — no length, range or format checks of your own. Disable submit from the browser's verdict, `form.checkValidity()`, never from hand-rolled checks. Anything stricter comes back from the server.
|
|
78
|
+
|
|
79
|
+
**Errors.** `error` is `{ status?, data: { message?, errors? }, isCancelled }` — read it without a cast. A failing SQL action comes back as a 500 whose `data.message` wraps the driver's text (`Error executing Action: …`): show it as it comes, in a `white-space: pre-wrap` element, since driver messages span lines — never "Something went wrong", `String(error)` or a friendlier paraphrase. `error.data.errors?.[parameterSlug]`, when present, goes beside its field (`aria-invalid` on the input).
|
|
80
|
+
|
|
81
|
+
## Refresh
|
|
82
|
+
|
|
83
|
+
After a write succeeds, refetch every query on screen it could have changed: `await Promise.all([orders.refetch(), totals.refetch()])`, the `refetch` that `useMetabaseQuery` returns.
|
|
84
|
+
|
|
85
|
+
- Those queries live in a component above the trigger, which passes the refresh down; a query in the trigger's own component or below it can't be wired to it.
|
|
86
|
+
- Await the refresh before clearing the form or closing the modal, so stale rows never flash.
|
|
87
|
+
- Never prepend `result` to a local list instead: ids, timestamps and computed columns come from the server.
|
|
88
|
+
|
|
89
|
+
## When it doesn't work
|
|
90
|
+
|
|
91
|
+
1. Log `result`, `error` and the object passed to `execute`: every key is a parameter slug, every value matches its `jsType`.
|
|
92
|
+
2. `execute` resolved to `null` and nothing was sent: it ran before the SDK started.
|
|
93
|
+
3. The screen doesn't update: see _Refresh_.
|
|
94
|
+
4. The action is missing from `src/metabase.ts`: it isn't in the app's collection, or the file is stale — regenerate it.
|
|
95
|
+
|
|
96
|
+
Then build and publish (`data-app`, _Build and publish_).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-app-data
|
|
3
|
+
description: Give a Metabase data app its data — questions in the app's collection over the Library's published tables and metrics, the typed `src/metabase.ts` that `mb data-app typed-schema` generates, and the `useMetabaseQuery` and `useMetabaseQueryObject` hooks that run them, with runtime filter clauses, sorting and aggregation. Triggers — "show revenue by month in my data app", "list customers in the app", "add a KPI", "a chart of orders by category", "regenerate metabase.ts". Creating and publishing the app is `data-app`; filter controls are `data-app-filters`; writes are `data-app-actions`; chart choice and sizing are `data-app-ui`.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
requires: [dataApps]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Data in a data app
|
|
9
|
+
|
|
10
|
+
A data app runs only the questions and actions in **the app's collection** (`data-app`, _Create the app_); its viewers can read nothing else. So every piece of data starts there:
|
|
11
|
+
|
|
12
|
+
1. Create a question (below) or an action (`data-app-actions`) in the app's collection.
|
|
13
|
+
2. Regenerate `src/metabase.ts`, the typed list of everything in the collection.
|
|
14
|
+
3. Pass `queries.<name>` or `actions.<name>` from it to a hook.
|
|
15
|
+
|
|
16
|
+
## Sources: the semantic layer only
|
|
17
|
+
|
|
18
|
+
An app reads only what the user curated in the Library. These are hard rules:
|
|
19
|
+
|
|
20
|
+
- **Tables published to the Library**, and nothing else: `mb table list --published-only --json`. That covers the question's source table, every join, and every table reached through a foreign key column.
|
|
21
|
+
- **Metrics in the Library's Metrics collection** (its id is in `mb library get`; list it with `mb collection items <id> --json`), and the segments and measures of published tables (`mb segment list`, `mb measure list`). Prefer one that matches the concept over rebuilding it with filters and thresholds of your own.
|
|
22
|
+
- **Other questions in the app's collection**, as a source. Never a question, model or metric from anywhere else.
|
|
23
|
+
- **MBQL only**, never native SQL.
|
|
24
|
+
|
|
25
|
+
When the app needs a table or metric the Library lacks, stop: name what's missing and ask the user to publish the table or move the metric into the Library. Don't publish it yourself unless the user says to, and never work around the gap with an unpublished table, a copy of the data, or SQL.
|
|
26
|
+
|
|
27
|
+
## Questions
|
|
28
|
+
|
|
29
|
+
Write each question's query in MBQL (the `mbql` skill), finding ids with `mb table list --published-only --term <name>` and `mb table get <id> --include fields --json`. Write the `dataset_query` object to `query.json`, dry-run it, then create the question in the app's collection:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
mb query --dry-run --file query.json
|
|
33
|
+
mb card create --json --body '{
|
|
34
|
+
"name": "Revenue by month",
|
|
35
|
+
"type": "question",
|
|
36
|
+
"display": "line",
|
|
37
|
+
"collection_id": 42,
|
|
38
|
+
"visualization_settings": {},
|
|
39
|
+
"dataset_query": { … }
|
|
40
|
+
}'
|
|
41
|
+
mb card query <id> --json --max-bytes 0 | jq '{row_count, first: .data.rows[0]}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Plan the questions from what the app shows and picks from — each chart, KPI, table and filter option list needs one, and several panels may share one. Each takes one of two shapes:
|
|
45
|
+
|
|
46
|
+
- **Aggregated**, when the breakdown is fixed: revenue by month, top products. Runtime clauses can then name only the columns it returns, its breakouts and aggregations.
|
|
47
|
+
- **Rows**, when a runtime filter targets a column an aggregation would drop — revenue by month, filtered by product category. The question returns the raw columns (`CREATED_AT`, `TOTAL`, `CATEGORY`), and the hook filters and aggregates them at runtime (_Runtime clauses_).
|
|
48
|
+
|
|
49
|
+
Also:
|
|
50
|
+
|
|
51
|
+
- A question filtered at runtime has no `limit` of its own: it would apply before the filters. Put the limit in the hook. A question nothing filters, such as "the 20 latest reviews", keeps its sort and limit.
|
|
52
|
+
- The name becomes the key in `queries`, camelCased: `Revenue by month` → `queries.revenueByMonth`.
|
|
53
|
+
- `display` and `visualization_settings` show only in Metabase; the app picks its own chart type.
|
|
54
|
+
- Nothing in the app's collection is archived, public or embedded. To drop a question, move it out (`mb card update <id> --body '{"collection_id":<other>}'`), then archive it.
|
|
55
|
+
|
|
56
|
+
## `src/metabase.ts`
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
mb data-app typed-schema <slug> --out src/metabase.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run it after **every** change to the app's collection, then `npm run typecheck`, which catches code still using a renamed or removed column. Never edit the file. It names every question and action by entity ID, so it works unchanged on every instance the app is synced to.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
export const queries = {
|
|
66
|
+
revenueByMonth: defineQuery({
|
|
67
|
+
source: {
|
|
68
|
+
type: "card",
|
|
69
|
+
entityId: "…",
|
|
70
|
+
name: "Revenue by month",
|
|
71
|
+
columns: [
|
|
72
|
+
{ type: "column", name: "CREATED_AT", displayName: "Created At: Month", jsType: "Date", semanticType: "type/CreationTimestamp" },
|
|
73
|
+
{ type: "column", name: "sum", displayName: "Sum of Total", jsType: "number", semanticType: "type/Currency" },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
export const actions = { setOrderNote: defineAction({ action: { id: "…", parameters: [ … ] } }) };
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
A column's `name` keys the rows and names the column in runtime clauses. Take it from this file, never from SQL:
|
|
82
|
+
|
|
83
|
+
- A column reached through a foreign key keeps its own name (`CATEGORY`, not `PRODUCTS__via__PRODUCT_ID__CATEGORY`), and a named aggregation keeps its name.
|
|
84
|
+
- A foreign key that Metabase displays as a name returns that name as an extra column: `USER_ID` comes with a `NAME` column whose `displayName` is "User ID". Show `NAME`; filter by `USER_ID`.
|
|
85
|
+
- A `Date` column's values arrive as ISO strings.
|
|
86
|
+
|
|
87
|
+
## Rows: `useMetabaseQuery`
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { useMetabaseQuery } from "@metabase/embedding-sdk-react/data-app";
|
|
91
|
+
import { queries } from "../metabase";
|
|
92
|
+
|
|
93
|
+
const { data, error, refetch } = useMetabaseQuery(queries.revenueByMonth);
|
|
94
|
+
const rows = data?.rows ?? []; // row.CREATED_AT, row.sum, typed from jsType
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- Rows are objects keyed by column `name`. Never index them by position or cast them, and guard `null` values.
|
|
98
|
+
- `data` is `null` until the first answer, and stays `null` while the query is held back with `enabled: false` — show the before-a-choice state then. Otherwise `data === null && !error` is loading, and `data.rows.length === 0` is empty.
|
|
99
|
+
- `error` is `unknown`: render `error instanceof Error ? error.message : String(error)` in the panel.
|
|
100
|
+
- Call each hook once, in the highest component that needs the data, and pass the result down. Never call a hook in a loop or a condition.
|
|
101
|
+
|
|
102
|
+
## Charts: `useMetabaseQueryObject`
|
|
103
|
+
|
|
104
|
+
A chart, KPI or table Metabase can draw renders as an SDK question:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { StaticQuestion, type MetabaseCard } from "@metabase/embedding-sdk-react";
|
|
108
|
+
import { useMetabaseQueryObject } from "@metabase/embedding-sdk-react/data-app";
|
|
109
|
+
import { useMemo } from "react";
|
|
110
|
+
|
|
111
|
+
const { query, isLoading, error } = useMetabaseQueryObject(queries.revenueByMonth);
|
|
112
|
+
const card = useMemo(() => ({ query, visualization: "line" }) satisfies MetabaseCard, [query]);
|
|
113
|
+
|
|
114
|
+
// PanelError and Spinner are the app's own components
|
|
115
|
+
if (error) return <PanelError error={error} />;
|
|
116
|
+
if (isLoading || !query) return <Spinner />;
|
|
117
|
+
return <StaticQuestion card={card} height={360} />;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- Always pass `visualization`: the question's own `display` doesn't reach the app, and without one Metabase picks a default, often a table. Which chart, its settings and its size: `data-app-ui`.
|
|
121
|
+
- Build `card` with `useMemo` keyed on `query`: a new object re-runs the question.
|
|
122
|
+
- Render `error` wherever this hook is used: reading only `query` makes a failed question look like endless loading.
|
|
123
|
+
|
|
124
|
+
## Runtime clauses
|
|
125
|
+
|
|
126
|
+
Both hooks take a second argument: one object with the clauses the UI changes, and `enabled`. They run on top of the question and name its columns:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { aggregations, filter, orderBy } from "@metabase/embedding-sdk-react/data-app";
|
|
130
|
+
|
|
131
|
+
const [start, end] = range; // from DateRangePopover (data-app-filters)
|
|
132
|
+
const { query } = useMetabaseQueryObject(queries.orderRows, {
|
|
133
|
+
filters:
|
|
134
|
+
start && end ? [filter({ type: "column", name: "CREATED_AT" }, "between", [start, end])] : [],
|
|
135
|
+
aggregations: [aggregations.sum({ type: "column", name: "TOTAL" }, { name: "revenue" })],
|
|
136
|
+
breakouts: [{ type: "column", name: "CREATED_AT", unit: "month" }],
|
|
137
|
+
orderBys: [orderBy({ type: "column", name: "CREATED_AT" }, "asc")],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- A column is `{ type: "column", name }` with a `name` from `src/metabase.ts`; a wrong name, or an operator its type doesn't take, fails to compile. A date breakout takes a `unit` (`day`, `week`, `month`, `quarter`, `year`).
|
|
142
|
+
|
|
143
|
+
| Column type | Operators |
|
|
144
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| string | `=`, `!=`, `contains`, `does-not-contain`, `starts-with`, `ends-with`, `is-empty`, `not-empty`, `is-null`, `not-null` |
|
|
146
|
+
| number | `=`, `!=`, `>`, `>=`, `<`, `<=`, `between`, `is-null`, `not-null` |
|
|
147
|
+
| date | `=`, `!=`, `>`, `>=`, `<`, `<=`, `between`, `time-interval`, `is-null`, `not-null` |
|
|
148
|
+
| boolean | `=`, `is-null`, `not-null` |
|
|
149
|
+
|
|
150
|
+
- `filter(...)` takes one value: `filter(col, "=", [a, b])` compiles but is not an IN filter, and several filters AND together. So a filter picks one value or All; for a multi-select, ask the user whether a single choice will do.
|
|
151
|
+
- Text operators are case-sensitive, with no option to change it: say so in the search box's placeholder.
|
|
152
|
+
- `time-interval` (`[-30, "day"]`, `[0, "year"]` for this year) is relative to when the query runs: a fixed window belongs in the question, not the hook.
|
|
153
|
+
- On a rows question, aggregate with any of `aggregations.count`, `sum`, `avg`, `median`, `min`, `max`, `distinct`, naming each result (`{ name: "revenue" }`) to order or read it by — two unnamed results of one operator collide, and generated names such as `sum_2` aren't stable. On an aggregated question, re-aggregate only sums and counts, which add up.
|
|
154
|
+
- A sort the user picks: map each choice to a column explicitly (`{ revenue: "revenue", orders: "order_count" }`) rather than passing the user's value through.
|
|
155
|
+
- `enabled: false` holds the query back, for example until the user picks an item.
|
|
156
|
+
- Write the arrays inline (spreads are fine): the hooks compare by content, so a new array per render doesn't refetch. A clauses object built outside the call needs `as const`, or `aggregations` loses the tuple type `breakouts` need; a shared `ReturnType<typeof filter>[]` built with `push` breaks the operator types the same way.
|
|
157
|
+
|
|
158
|
+
The controls that feed these clauses are `data-app-filters`. When the data is in place, build and publish (`data-app`, _Build and publish_).
|