@metabase/cli 0.5.1 → 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-BnGPc0Vv.mjs → add-collection-c1dfnavz.mjs} +4 -4
- package/dist/{alert-DQkF8jYR.mjs → alert-BkpPdBQ_.mjs} +6 -6
- package/dist/{alerts-BncG7w1_.mjs → alerts-TDYiIxjs.mjs} +3 -3
- 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-D4ekYCuD.mjs → append-CoeZRD6u.mjs} +3 -3
- package/dist/{archive-ULXc3xzI.mjs → archive-3fdIvMsf.mjs} +2 -2
- package/dist/{archive-v08JESCM.mjs → archive-9uzfWGuq.mjs} +2 -2
- package/dist/{archive-BQ4OA-R4.mjs → archive-CAJHrKwd.mjs} +2 -2
- package/dist/{archive-DpCP4yR1.mjs → archive-CH1h8jz5.mjs} +2 -2
- package/dist/{archive-BifJXCHF.mjs → archive-D-BJwRLc.mjs} +2 -2
- package/dist/{archive-BXr9kFNz.mjs → archive-DQRehboT.mjs} +5 -5
- package/dist/{archive-DjHLWUfC.mjs → archive-DY76wU2k.mjs} +2 -2
- package/dist/{archive-EhW6blNc.mjs → archive-DZDkfhdH.mjs} +4 -4
- package/dist/{archive-D2WCz3Oe.mjs → archive-DxUnaV4x.mjs} +2 -2
- package/dist/{archive-DocyUIME.mjs → archive-N3lp1o-k.mjs} +4 -4
- package/dist/{archive-CxNUeWII2.mjs → archive-NfFWXRx5.mjs} +4 -4
- package/dist/{archive-DnCF-Ecm.mjs → archive-f7wKQ7Uy.mjs} +2 -2
- package/dist/{auth-BPbsm8yV.mjs → auth-hgkvHPpH.mjs} +4 -4
- package/dist/{body-BkkQkGJg.mjs → body-M5qsJoub.mjs} +2 -2
- package/dist/{branch-flag-CTDvbaBa.mjs → branch-flag-CPTQ4wXp.mjs} +1 -1
- package/dist/{branches-lPHtM_xR.mjs → branches-B3gHAanU.mjs} +2 -2
- package/dist/{breaking-DjMlCD96.mjs → breaking-DuWIof4T.mjs} +5 -5
- package/dist/{broken-DUX3GWS5.mjs → broken-D2ST8sTx.mjs} +7 -7
- package/dist/{bulk-discard-values-Dl8Lzypf.mjs → bulk-discard-values-77khEfpj.mjs} +4 -4
- package/dist/{bulk-edit-N2xLVtfG.mjs → bulk-edit-girwcjY6.mjs} +5 -5
- package/dist/{bulk-rescan-values-BK2rH2Ik.mjs → bulk-rescan-values-Df8KK8um.mjs} +4 -4
- package/dist/{bulk-sync-schema-BKJc9Rzq.mjs → bulk-sync-schema-DEPUZ6MN.mjs} +4 -4
- package/dist/{cancel-DzkRVGIk.mjs → cancel-Oek5PJnu.mjs} +2 -2
- package/dist/{cancel-task-BiTlDY7a.mjs → cancel-task-rkqY0qLU.mjs} +3 -3
- package/dist/{card-CQNUT9pm.mjs → card-3NmUiU5F.mjs} +1 -1
- package/dist/{card-BTBDBsu-.mjs → card-BWTjCE93.mjs} +8 -8
- package/dist/{card-P5mBknsl.mjs → card-QR3zEu6E.mjs} +39 -16
- package/dist/{cards-CSL8ukS1.mjs → cards-DWJOtZoN.mjs} +5 -5
- package/dist/cli.mjs +172 -37
- package/dist/{client-BBwYPXDW.mjs → client-BFP2Ssqu.mjs} +221 -39
- package/dist/{collection-B19phRNU.mjs → collection-9_mj8rVX.mjs} +7 -7
- package/dist/{collection-CTnYZ2Jj.mjs → collection-D5vt2IJ_.mjs} +1 -1
- package/dist/{collection-B2wRXyTF.mjs → collection-DNmFKALA.mjs} +1 -0
- package/dist/{collection-namespace-B1OkM7p1.mjs → collection-namespace-Dl8_7xII.mjs} +1 -1
- package/dist/{content-translation-DoGTQnvH.mjs → content-translation-B3oARbgD.mjs} +2 -2
- package/dist/{copy-BYukQEtf.mjs → copy-BzBp5FaY.mjs} +5 -5
- package/dist/{copy-XlCT3gvG.mjs → copy-C7Rc4qM7.mjs} +3 -3
- package/dist/{copy-flags-ByB2MicL.mjs → copy-flags-CTM91pxH.mjs} +2 -2
- package/dist/{create-T4PZPVE9.mjs → create-71_DODbc.mjs} +3 -3
- package/dist/{create-jr4xJgSI.mjs → create-BKRD11i-.mjs} +3 -3
- package/dist/{create-k1Bo7h7g.mjs → create-Bc4ADRmU.mjs} +2 -2
- package/dist/{create-Bnb1abv_.mjs → create-Bh3q3eHg.mjs} +5 -5
- package/dist/{create-3_oY9KHY.mjs → create-ByeGK_BE.mjs} +2 -2
- package/dist/{create-DZJQp3r7.mjs → create-C-2ivjed.mjs} +2 -2
- package/dist/{create-CLZ7w83R.mjs → create-C0gmhNnk.mjs} +3 -3
- package/dist/create-C3H9ljex.mjs +34 -0
- package/dist/{create-DxMmIHI4.mjs → create-CGE1D0Ay.mjs} +2 -2
- package/dist/{create-CbT4SPHV.mjs → create-CGxxztoE.mjs} +2 -2
- package/dist/{create-CYoarNrb.mjs → create-CfXKNnkK.mjs} +2 -2
- package/dist/{create-Bl9sS7v7.mjs → create-CjGEkSFz.mjs} +6 -6
- package/dist/{create-Cdt8u1wF.mjs → create-DO4ic4Kx.mjs} +5 -5
- package/dist/{create-BP39g5Jk.mjs → create-DQJ9h86G.mjs} +2 -2
- package/dist/{create-DBNxy5rN.mjs → create-YBDXZgyP.mjs} +2 -2
- package/dist/{create-D5_c5SHo.mjs → create-YIC3Q0a-.mjs} +2 -2
- package/dist/create-_rOCx5b8.mjs +38 -0
- package/dist/{create-branch-CUNFdKyB.mjs → create-branch-DBCBMDWh.mjs} +4 -4
- package/dist/{create-CqF7vqXC.mjs → create-f0bBEfDI.mjs} +4 -4
- package/dist/{create-B3fm5I-l2.mjs → create-myJ8HR4B.mjs} +4 -4
- package/dist/{create-CrtIqtXN.mjs → create-vgP9K2eH2.mjs} +4 -4
- package/dist/{csv-CcnhKpuj.mjs → csv-Dtgy7sTg.mjs} +3 -3
- package/dist/{current-task-nl2z8sHI.mjs → current-task-AwQisIT9.mjs} +3 -3
- package/dist/{dashboard-BP0p62fp.mjs → dashboard-BZV-0Qjp.mjs} +11 -11
- package/dist/{dashboard-Phz3XpRM.mjs → dashboard-Chjy5GKR.mjs} +52 -5
- package/dist/{dashboard-XSTr9QCr.mjs → dashboard-D5E3fMKh.mjs} +1 -1
- package/dist/{data-action-Cr17CfgE.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/{database-2HYCdHqK.mjs → database-CxykzTAA.mjs} +14 -3
- package/dist/{database-BewllGR7.mjs → database-D8XzgJEz.mjs} +19 -2
- package/dist/{dataset-D-x2Ho2i.mjs → dataset-DIQLAp2P.mjs} +5 -4
- package/dist/db-wsSYKqCB.mjs +25 -0
- package/dist/delete-4v4_lDwD.mjs +44 -0
- package/dist/{delete-BOma6qIq.mjs → delete-BHX_F8Qu.mjs} +3 -3
- package/dist/{delete-DulVnxD_.mjs → delete-BP9-YS-f.mjs} +3 -3
- package/dist/delete-BSgxmR3y.mjs +40 -0
- package/dist/{delete-BUCh72Bq.mjs → delete-BegkpIGi.mjs} +3 -3
- package/dist/{delete-xf2n7SNg.mjs → delete-D9FB7qjX.mjs} +3 -3
- package/dist/{delete-QHsiWYay.mjs → delete-DmM5L0wO.mjs} +3 -3
- package/dist/{delete-BrbmJ3sn.mjs → delete-E-g5b-HU.mjs} +3 -3
- package/dist/{delete-Oc2GE170.mjs → delete-WrV9I9uw.mjs} +3 -3
- package/dist/delete-runtime-CJ7gMYA1.mjs +72 -0
- package/dist/{delete-table-LgJ17t6j.mjs → delete-table-CJq8JM5J.mjs} +3 -3
- package/dist/{delete-BXMc-g3Q.mjs → delete-wnHUWWjX.mjs} +3 -3
- package/dist/{dependencies-BE5QW7Lb.mjs → dependencies-CnW3RSoZ.mjs} +3 -3
- package/dist/{dependency-qP1hx2YN.mjs → dependency-CaC6XuTY.mjs} +2 -2
- package/dist/{dependency-BlTYysYG.mjs → dependency-DDRGBlFL.mjs} +5 -5
- package/dist/{dependency-BACk_cVP.mjs → dependency-Dxvb2Znu.mjs} +1 -1
- package/dist/{dependents-mbgHQNiv.mjs → dependents-DHXqJ3lo.mjs} +8 -8
- package/dist/{dirty-fYlA1N1e.mjs → dirty-BRPWPUYH.mjs} +4 -4
- package/dist/{discard-values-cIRBP5nT.mjs → discard-values-DBHfVN8o.mjs} +4 -4
- package/dist/{document-BaRQ5fBd.mjs → document-BeLf7NqP.mjs} +6 -6
- package/dist/{download-CokVpeK6.mjs → download-BFu9xYHX.mjs} +1 -1
- package/dist/{eid-CaumGUFD.mjs → eid-eKmbXRds.mjs} +3 -4
- package/dist/{entity-BHYJtRxp.mjs → entity-DjDkaMkB.mjs} +2 -2
- package/dist/{entry-flags-DjICpQkc.mjs → entry-flags-Cxn09krz.mjs} +3 -3
- package/dist/{events-jEo9fytL.mjs → events-CZL9MU64.mjs} +3 -3
- package/dist/{execute-H09Qus4_.mjs → execute-DBJGtHHG.mjs} +3 -3
- package/dist/{export-flags-DzW0Ktkh.mjs → export-flags-CpukgETM.mjs} +13 -5
- package/dist/{export-preflight-Db_TChn5.mjs → export-preflight-SM9zJkBp.mjs} +4 -4
- package/dist/{export-YtdZo7LZ.mjs → export-xn_-NaBd.mjs} +24 -12
- package/dist/{field-wmN6raOG.mjs → field-BVz4AS3f.mjs} +1 -1
- package/dist/{field-DktidTPB.mjs → field-CQDz-6DK.mjs} +7 -7
- package/dist/{field-5nivEfd6.mjs → field-DoWQUl7V.mjs} +58 -45
- package/dist/{fields-DUXlosmx.mjs → fields-CB06GFBW.mjs} +5 -5
- package/dist/{filter-flags-DTDcU7OI.mjs → filter-flags-DhE4olyf.mjs} +4 -4
- package/dist/{fks-Cl2sq-c0.mjs → fks-CSoS7xZ6.mjs} +5 -5
- package/dist/{get-C_rjxpoe.mjs → get--1qTpTIQ.mjs} +4 -4
- package/dist/{get-BilMWiFq.mjs → get-B8xQXMU6.mjs} +2 -2
- package/dist/{get-okEsnkvC.mjs → get-B8xrmipw.mjs} +2 -2
- package/dist/{get-DPiNYmXo.mjs → get-BIf6eFWZ.mjs} +2 -2
- package/dist/{get-DtcChy9h.mjs → get-BQ7SAJEz.mjs} +2 -2
- package/dist/{get-XS-6R81E.mjs → get-BSPxmP_W.mjs} +4 -4
- package/dist/{get-zPMv3kZE.mjs → get-BW3jSR3y.mjs} +2 -2
- package/dist/{get-DyK_bvg6.mjs → get-BYAzIaGG.mjs} +2 -2
- package/dist/{get-Cg0up9gm.mjs → get-BZDUOZxI.mjs} +2 -2
- package/dist/{get-oQNL6pLY.mjs → get-BZFyqZXa.mjs} +2 -2
- package/dist/{get-CVMWhbA82.mjs → get-BfFZCiZR.mjs} +4 -4
- package/dist/get-Bq_9VSTa.mjs +28 -0
- package/dist/{get-FxCF0rxc.mjs → get-Bt4C-oxh.mjs} +2 -2
- package/dist/{get-GuWW4J24.mjs → get-C1rCVUYw.mjs} +3 -3
- package/dist/{get-CqlNUyFY.mjs → get-Cc4-e-Q_.mjs} +4 -4
- package/dist/{get-CTtzccwr.mjs → get-Cl-O6dFs.mjs} +3 -3
- package/dist/{get-Bt_zhviQ.mjs → get-CtDZOoWy.mjs} +5 -5
- package/dist/{get-CWyRAHpz.mjs → get-CuSwArMS.mjs} +5 -5
- package/dist/{get-CM596pmP.mjs → get-DIfjxTP8.mjs} +2 -2
- package/dist/{get-B-WT6ess.mjs → get-DePbMmjc.mjs} +5 -5
- package/dist/{get-DTDvlLT3.mjs → get-eZEHipwA.mjs} +2 -2
- package/dist/{get-DySyvotk2.mjs → get-jbOgdCQg.mjs} +2 -2
- package/dist/{get-run-CFGxSm_S.mjs → get-run-C-CNDbv3.mjs} +2 -2
- package/dist/{git-sync-Ch1V1fEK.mjs → git-sync-D1aKczo6.mjs} +15 -8
- package/dist/{git-sync-COokpYgc.mjs → git-sync-DP4wQFzm.mjs} +18 -1
- package/dist/git-sync-D_T9oNC6.mjs +29 -0
- package/dist/{glossary-BSgRQ2HO.mjs → glossary-BVK5DaLV.mjs} +4 -4
- package/dist/{graph-Wf4e0XIa.mjs → graph-CqBe2J4N.mjs} +5 -5
- package/dist/{has-remote-changes-BsS3XB0P.mjs → has-remote-changes-CDe1teSb.mjs} +3 -3
- package/dist/{import-CXd5HdB0.mjs → import-B64gjryF.mjs} +7 -7
- package/dist/{is-dirty-TDcfeOde.mjs → is-dirty-DhR3Xqjk.mjs} +1 -1
- package/dist/{items-BxvhoxYm.mjs → items-CLl2QgS8.mjs} +5 -5
- package/dist/{key-CUPbYxkk.mjs → key-BYEv4Wgq.mjs} +2 -2
- package/dist/{library-DzejNMqX.mjs → library-C3eO2J07.mjs} +4 -4
- package/dist/{library-BKSFtIV9.mjs → library-DKIqzyUk.mjs} +2 -2
- package/dist/{library-DuIrR8LS.mjs → library-wUiD2jbw.mjs} +1 -1
- package/dist/{list-DA1Fo6S6.mjs → list-B7Yvz5vA.mjs} +2 -2
- package/dist/{list-DlgjWr4r.mjs → list-BIqqh3A-.mjs} +2 -2
- package/dist/{list-CRXTfEFk.mjs → list-BiSwl56L.mjs} +2 -2
- package/dist/{list-DpK3ioES.mjs → list-BmvitCWZ.mjs} +5 -5
- package/dist/{list-D7yH-64Z.mjs → list-C3GBocvB.mjs} +4 -4
- package/dist/{list-B8V_OESj.mjs → list-C7TFreS8.mjs} +2 -2
- package/dist/{list-6nGX0Rqc.mjs → list-CA939qkn.mjs} +5 -5
- package/dist/list-CFaJA317.mjs +48 -0
- package/dist/{list-3Dh5yeq-.mjs → list-CdcFNphE.mjs} +2 -2
- package/dist/{list-DjlfBd7W.mjs → list-CjpZCiks.mjs} +2 -2
- package/dist/{list-B5kRBn7k.mjs → list-Cu6ET_yD.mjs} +4 -4
- package/dist/{list-BUjI5G0_.mjs → list-D4HNG282.mjs} +4 -4
- package/dist/{list-Ba7u5Ai9.mjs → list-DDPvxfhe.mjs} +5 -5
- package/dist/{list-Cr5Hoq-E.mjs → list-DLLzMaS3.mjs} +2 -2
- package/dist/{list-D_fsrh8w.mjs → list-DLwby7YA.mjs} +2 -2
- package/dist/{list-B3Pngm2j.mjs → list-D_IT8l9B.mjs} +2 -2
- package/dist/{list-CCCy6cBI.mjs → list-DpfOh8-5.mjs} +3 -3
- package/dist/{list-DsR58aMq.mjs → list-DrIcKxXX.mjs} +3 -3
- package/dist/{list-D1Q8H3jn.mjs → list-DyFu5Wx1.mjs} +3 -3
- package/dist/{list-Cf6A9Csi.mjs → list-DyQNRVAf.mjs} +2 -2
- package/dist/list-YFwmHXGG.mjs +33 -0
- package/dist/{list-Do-Xnz0I.mjs → list-eGlqXJno.mjs} +2 -2
- package/dist/list-s9_LlXCV.mjs +29 -0
- package/dist/{login-CXbCAcuB.mjs → login-Bg7Z3jDy.mjs} +15 -8
- package/dist/{logout-B6IGzkpG.mjs → logout-CAmp7DcS.mjs} +2 -2
- package/dist/{measure-CbMgNhlG.mjs → measure-C31HLp1C.mjs} +5 -5
- package/dist/{moderation-review-BdqEH6N1.mjs → moderation-review-Bf9bPNQI.mjs} +1 -1
- package/dist/{parameter-Celg0wZa.mjs → parameter-DvRQMKd2.mjs} +6 -6
- package/dist/{parameter-values-IWi_DNhp.mjs → parameter-values-3eHpqznI.mjs} +21 -7
- package/dist/{parse-enum-B2rGiC6p.mjs → parse-enum-DAcTzG1q.mjs} +1 -1
- package/dist/{parse-id-Cv5eM7sg.mjs → parse-id-5BZGQ9Vd.mjs} +1 -1
- package/dist/{parse-ref-DW7ik6ny.mjs → parse-ref-ByXc-pls.mjs} +1 -1
- package/dist/{parse-text-CUCSWOeR.mjs → parse-text-FgX60vIg.mjs} +1 -1
- package/dist/{path-C9r9urRK.mjs → path-XwdHlC23.mjs} +3 -3
- package/dist/{poll-CX3jPtXb.mjs → poll-CgtcJopL.mjs} +2 -2
- package/dist/{preflight-CiTgqNUy.mjs → preflight-ChTKhB8Y.mjs} +2 -2
- package/dist/{process-DImdroix.mjs → process-aqXm5QgC.mjs} +1 -1
- package/dist/{publish-xH1n74gd.mjs → publish-C2-pK0cf.mjs} +4 -4
- package/dist/{pulse-Dz0ocv0l.mjs → pulse-CC81N2_0.mjs} +1 -1
- package/dist/{pulse-CFj1WfIC.mjs → pulse-JPrq6NFP.mjs} +1 -1
- package/dist/{query-CN3BW5WB.mjs → query-DkaUzmMa.mjs} +12 -12
- package/dist/{query-BOCw0FBK.mjs → query-pmw23KkL.mjs} +17 -7
- package/dist/{remapping-Dggq2Fv0.mjs → remapping-C3htTzJn.mjs} +4 -4
- package/dist/{remove-collection-cxSSFxza.mjs → remove-collection-p_t9rUUX.mjs} +4 -4
- package/dist/{render-Cb8Hct67.mjs → render-C50k8-jc.mjs} +2 -2
- package/dist/{replace-wVi_XDut.mjs → replace-BQBuFJ1B.mjs} +3 -3
- package/dist/{requirement-check-BE3PuGsC.mjs → requirement-check-CpiVuL0H.mjs} +1 -1
- package/dist/{rescan-values-DAp_avxY.mjs → rescan-values-Ctq8wpkI.mjs} +4 -4
- package/dist/{rescan-values-C7b1Ghy-.mjs → rescan-values-DOqXJ3qH.mjs} +4 -4
- package/dist/{run-CUGs7mok.mjs → run-2xgPRuKR.mjs} +4 -4
- package/dist/{run-DywhwjNG.mjs → run-CtMbObQI.mjs} +4 -4
- package/dist/{run-CE6ei7s7.mjs → run-Dn3e8G4x.mjs} +2 -2
- package/dist/{runs--14jim_0.mjs → runs-CgNoQW6M.mjs} +3 -3
- package/dist/{runtime-Ckns6TKK.mjs → runtime-Y4VFWmmr.mjs} +63 -80
- package/dist/scaffold-BuL0_ptz.mjs +50 -0
- package/dist/{schema-tables-CeW0kpA5.mjs → schema-tables-BdXuo_lG.mjs} +5 -5
- package/dist/{schemas-CHMD0HrY.mjs → schemas-Dap3KFwY.mjs} +3 -3
- package/dist/{search-CXRavT7G.mjs → search-1et9tv_s.mjs} +21 -4
- package/dist/{search-LmBQu1Jg.mjs → search-DUmFK5Sy.mjs} +5 -5
- package/dist/{search-DyC54bGf.mjs → search-Dxcejw_v.mjs} +20 -5
- package/dist/{segment-DeT_Yic6.mjs → segment-CebcXn2E.mjs} +5 -5
- package/dist/{send-BWSJMrDL.mjs → send-DYdwcPTb.mjs} +2 -2
- package/dist/{set-active-C5cLG4_O.mjs → set-active-CVI_DDQi.mjs} +2 -2
- package/dist/set-bundle-DX5HqDji.mjs +40 -0
- package/dist/set-data-actions-DAI1XCQ2.mjs +46 -0
- package/dist/set-enabled-0QI-CHAT.mjs +41 -0
- package/dist/{set-sensitivity-BMLKazua.mjs → set-sensitivity-DqGkiLSr.mjs} +5 -5
- package/dist/{set-r2xiXrT_.mjs → set-xs8Vyvy2.mjs} +3 -3
- package/dist/{setting-CDRusiqP.mjs → setting-Bmry-y_l.mjs} +3 -3
- package/dist/{setup-BZaJVww3.mjs → setup-UM8N9Vh3.mjs} +2 -2
- package/dist/{signal-4tpCIkqR.mjs → signal-D8MH3LMN.mjs} +1 -1
- package/dist/{skills-9ihLaSmM.mjs → skills-BiSfuDqA.mjs} +3 -3
- package/dist/{skills-y2iVfW9k.mjs → skills-CcLTm3_2.mjs} +2 -2
- package/dist/slug-qeARO25o.mjs +15 -0
- package/dist/{snippet-Ce0o33Rj.mjs → snippet-DKARah-L.mjs} +5 -5
- package/dist/{stash-Dj-mnIa4.mjs → stash-e5cSvnj3.mjs} +6 -6
- package/dist/{status-BtWz7Pzn.mjs → status-BHiO3YBe.mjs} +36 -15
- package/dist/{status-B2Xj_q8f.mjs → status-CH7BAWGe.mjs} +2 -2
- package/dist/{subscription-Dh--agqE.mjs → subscription-DWleULhN.mjs} +5 -5
- package/dist/{subscriptions-CJ8Jdp9A.mjs → subscriptions-DcCOA2p1.mjs} +4 -4
- package/dist/{summary-ccbkNDbC.mjs → summary-CipFA6M6.mjs} +4 -4
- package/dist/{sync-schema-CewUOkmm.mjs → sync-schema-C45WO4db.mjs} +5 -5
- package/dist/{sync-schema-HwQectZd.mjs → sync-schema-D7p-P8F6.mjs} +4 -4
- package/dist/{sync-task-DEzhiaU9.mjs → sync-task-DNKG9Bk4.mjs} +2 -2
- package/dist/{table-CFZU-BRg.mjs → table-BDsqRU7a.mjs} +2 -2
- package/dist/table-BPYHnzyV.mjs +26 -0
- package/dist/{table-zAdW5_3H.mjs → table-CF7zW9cI.mjs} +33 -20
- package/dist/{table-selector-flags-p4jun7-v.mjs → table-selector-flags-CHfrVpYQ.mjs} +3 -3
- package/dist/{timeline-DLaBypse.mjs → timeline-C2m_QHxn.mjs} +7 -7
- package/dist/{timeline-event-xAAR6b-v.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-BXusFzMX.mjs → transform-tag-BYoguZfW.mjs} +4 -4
- package/dist/{transform-test-UsH1udc4.mjs → transform-test-jaBpHOM5.mjs} +6 -6
- package/dist/{transforms-CqV01bMM.mjs → transforms-C6MFnT4C.mjs} +3 -3
- package/dist/{transport-0qFZhEuP.mjs → transport-k_fabWdF.mjs} +4 -4
- package/dist/{tree-Td6rgyOa.mjs → tree-C4Igf6xs.mjs} +3 -3
- package/dist/typed-schema-Bo0Q6C0S.mjs +45 -0
- package/dist/{unpublish-D4XTZsFW.mjs → unpublish-EStytsEm.mjs} +2 -2
- package/dist/{unreferenced-cR-l4zXN.mjs → unreferenced-DtUuyBrr.mjs} +5 -5
- package/dist/{update-C8xdeYI2.mjs → update-B0Na11v-.mjs} +5 -5
- package/dist/{update-C7Wmom6x.mjs → update-B1UXRPN-.mjs} +7 -7
- package/dist/{update-dQeji6RA.mjs → update-BOg1jZ_s.mjs} +5 -5
- package/dist/{update-CpwWURTp.mjs → update-BXLY92om.mjs} +4 -4
- package/dist/{update-DrVc25R8.mjs → update-Bba_zPSE.mjs} +4 -4
- package/dist/{update-C4iu-vQm.mjs → update-BogvN2Mb.mjs} +3 -3
- package/dist/{update-nGURR-y6.mjs → update-CCLpNTPa.mjs} +3 -3
- package/dist/{update-CHB4kvz-.mjs → update-CciLWH_s.mjs} +7 -8
- package/dist/{update-BqkqMOVN.mjs → update-DAG8m5f4.mjs} +5 -5
- package/dist/{update-Dk0E66Va.mjs → update-DECiEVlk.mjs} +7 -7
- package/dist/update-DIBaY-fy.mjs +37 -0
- package/dist/{update-C7dZLbsf.mjs → update-DT2ViMFr.mjs} +3 -3
- package/dist/{update-CND9At61.mjs → update-DT8U29Kk.mjs} +3 -3
- package/dist/{update-Js0IiqCq.mjs → update-DhgfReDD.mjs} +5 -5
- package/dist/{update-DFK2iUSP.mjs → update-DkvIK-eL.mjs} +3 -3
- package/dist/{update-DZHgr6Gu.mjs → update-FfeHReec.mjs} +4 -4
- package/dist/{update-BJL5975i.mjs → update-HbggUPuy.mjs} +3 -3
- package/dist/{update-D-cTgmb0.mjs → update-VGgvQu7W.mjs} +3 -3
- package/dist/{update-dashcard-BmkfJ7t9.mjs → update-dashcard-m-hkIGUu.mjs} +6 -6
- package/dist/{update-DROjGe3Q.mjs → update-pk0b6vXU.mjs} +3 -3
- package/dist/{update-DqCZsbLh.mjs → update-z8ZGCg1r.mjs} +3 -3
- package/dist/{upgrade-B6V_1cUh.mjs → upgrade-iETJ8fH2.mjs} +4 -4
- package/dist/{upload-B6zBzMPW.mjs → upload-BF5pdKRN.mjs} +3 -3
- package/dist/{upload-CfYmBv51.mjs → upload-CoK5LnyA.mjs} +2 -2
- package/dist/{upload-Dvjfz16M.mjs → upload-CqSGEZQW.mjs} +3 -3
- package/dist/{uuid-B8ynbkqG.mjs → uuid-8Ha-8bDX.mjs} +2 -2
- package/dist/{validate-C7DGkwb-.mjs → validate-BQ7vI8_x.mjs} +1 -1
- package/dist/{validate-query-CYUvRvVM.mjs → validate-query-YtH3d5BP.mjs} +2 -2
- package/dist/{values-36bzf9cK.mjs → values-BsgA7-6h.mjs} +4 -4
- package/dist/{verify-C4Tml4fD.mjs → verify-CzBZbP4W.mjs} +4 -4
- package/dist/{verify-BdW-GExk.mjs → verify-cjUPMF_B.mjs} +4 -4
- package/dist/{verify-Cs9e28YA.mjs → verify-crIrbGZV.mjs} +3 -3
- package/dist/{verify-flags-CMpRkMPa.mjs → verify-flags-DEVMB9w0.mjs} +1 -1
- package/dist/{wait-flags-DjyK03cm.mjs → wait-flags-BnZ-dfE2.mjs} +2 -2
- package/dist/{wait-BV6JOdgi.mjs → wait-mFUfpSaj.mjs} +5 -5
- package/package.json +2 -2
- package/skill-data/core/SKILL.md +1 -1
- package/skill-data/data-action/SKILL.md +18 -7
- 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/skill-data/git-sync/SKILL.md +3 -3
- package/skill-data/transform/SKILL.md +1 -1
- package/dist/db-m5bQcXdn.mjs +0 -24
- package/dist/delete-runtime-CkNvZfFA.mjs +0 -55
- package/dist/embedding-i-KqpefC.mjs +0 -9
- package/dist/git-sync-BwkWeJAz.mjs +0 -29
- package/dist/input-AXP2LCOV.mjs +0 -61
- package/dist/list-CUj04tg3.mjs +0 -38
- package/dist/table-P9ovwcaB.mjs +0 -26
- package/dist/transform-10YE8j5K.mjs +0 -28
- package/dist/transform-job-D97AutnF.mjs +0 -22
|
@@ -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_).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-app-filters
|
|
3
|
+
description: Build the filter controls of a Metabase data app — the filter contract, which cards a filter reaches, All by default, options from their own question, a searchable combobox for entities, date presets, `DateRangePopover` and `DateRangeCalendar` with labels from `useDateFormatter`, and a single-date picker. Triggers — "add a filter to the app", "filter the chart by date", "a date range picker", "filter by customer", "a dropdown of categories", "the filter doesn't change the chart", "the date shows the previous day". The query side — runtime clauses and their limits — is in `data-app-data`.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
requires: [dataApps]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Filter controls in a data app
|
|
9
|
+
|
|
10
|
+
A filter is an ordinary React control whose state becomes `filter(...)` clauses in a hook's second argument, on a column the question returns (`data-app-data`, _Runtime clauses_). This skill covers the controls; "panel" below means a chart, KPI or table on the page.
|
|
11
|
+
|
|
12
|
+
## Write the contract first
|
|
13
|
+
|
|
14
|
+
Before any control, write one row per filter: the question that gives its options, the raw value `filter(...)` receives, the panels whose question returns that column, and the panels whose question doesn't.
|
|
15
|
+
|
|
16
|
+
| Filter | Options question | Raw value | Panels | Not these |
|
|
17
|
+
| --------- | -------------------------- | ------------ | ---------------------- | -------------- |
|
|
18
|
+
| Franchise | franchises, by id and name | franchise id | orders, revenue, stock | — |
|
|
19
|
+
| Plan | orders broken out by plan | plan text | orders | revenue, stock |
|
|
20
|
+
|
|
21
|
+
- A filter in the page's global bar changes every panel that can take it. One that can't reach a panel is scoped to its section, visibly, or left out. A KPI and its detail table about the same thing take the same filters, unless the UI says why not.
|
|
22
|
+
- One date control per scope. Never a global date range beside a page-specific date where one of the two does nothing; a page whose cards use another date column gets one control for that column.
|
|
23
|
+
- Build each panel's clauses from its own question's columns, inline at its hook call. Never reuse an array built for another question.
|
|
24
|
+
|
|
25
|
+
## Values and options
|
|
26
|
+
|
|
27
|
+
- Every filter starts at **All**. Keep it as `"all"` in state and send it as no clause (`[]`); treat `""`, `null` and `undefined` the same way. In a Yes/No/All filter, Yes and No each filter (`true`, `false`), and only All sends nothing.
|
|
28
|
+
- Options come at runtime from their own question in the app's collection, broken out on the filtered column — never hardcoded unless the user gave a closed list. Dedupe them from its rows, and keep the label apart from the value.
|
|
29
|
+
- Filter by a stable raw value (an id) and show its label; never filter by a display label when an id exists, and never feed free text into an exact-id filter.
|
|
30
|
+
- All stays selectable while options load, come back empty or fail; don't disable the control. When fresh options no longer hold the selected value, reset to All:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (selected !== "all" && !options.some((option) => option.value === selected)) {
|
|
35
|
+
setSelected("all");
|
|
36
|
+
}
|
|
37
|
+
}, [options, selected]);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Entities: a searchable combobox
|
|
41
|
+
|
|
42
|
+
Any list whose options come from a question — an id shown as a name, a category, anything that can grow — is one searchable combobox, even while it's short. A `<select>` is only for a short closed list the user gave. Prefer the app's component library or a small established combobox package; hand-roll only a small one. Either way:
|
|
43
|
+
|
|
44
|
+
- Click or focus opens the list at once, before any typing; search matches labels, not ids.
|
|
45
|
+
- All is always an option, and picking an option stores its raw value.
|
|
46
|
+
- The list is capped or scrolls, and looks like the app's other controls.
|
|
47
|
+
- Don't close the list from the trigger's `onBlur` when the popover holds an auto-focused input — moving focus into it closes the list at once. Close on selection, Escape, or focus leaving the whole wrapper.
|
|
48
|
+
|
|
49
|
+
## Date ranges
|
|
50
|
+
|
|
51
|
+
A preset bar lists the durations, then All time, then Custom last — leave Custom out only if the user asked for fixed presets. Every option ends in the same `[start, end]` range (All time is `[null, null]`), so one `between` filter serves them all. Custom is `DateRangePopover` around your own trigger, or `DateRangeCalendar` inline, both from `@metabase/embedding-sdk-react/data-app`. No date-picker library and no `<input type="date">` for a range.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { DateRangePopover, filter, useDateFormatter } from "@metabase/embedding-sdk-react/data-app";
|
|
55
|
+
|
|
56
|
+
const { formatDateRange } = useDateFormatter();
|
|
57
|
+
const [range, setRange] = useState<[string | null, string | null]>([null, null]);
|
|
58
|
+
const [start, end] = range;
|
|
59
|
+
// at the hook: filters: start && end ? [filter({ type: "column", name: "CREATED_AT" }, "between", [start, end])] : []
|
|
60
|
+
|
|
61
|
+
<DateRangePopover value={range} onChange={setRange}>
|
|
62
|
+
<button className="filter-button">{formatDateRange(range) || "Custom"}</button>
|
|
63
|
+
</DateRangePopover>;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Both ends are `YYYY-MM-DD` strings, the shape `filter(...)` takes. `onChange` fires on every click, so a half-picked range arrives as `[start, null]`: filter only once both ends are set, never with a sentinel date.
|
|
67
|
+
- The child is the trigger: one element or a `forwardRef` component, since the popover attaches a ref and a click handler.
|
|
68
|
+
- Label it with `formatDateRange(range)` (or `formatDate` for one end), which formats in the instance's locale; both take a dayjs `format`, and `formatDateRange` a `separator`. It returns `""` until the SDK loads. Never `new Date("YYYY-MM-DD")`: a date-only string parses as UTC and shows the previous day west of Greenwich.
|
|
69
|
+
- The popover closes once both ends are picked (`closeOnSelect`); `opened` and `onOpenedChange` control it yourself. Also `defaultValue`, `minDate` and `maxDate` (`YYYY-MM-DD`), `numberOfColumns` (default 2) and `position` (default `bottom-start`). `DateRangeCalendar` takes the same value props plus `className` and `style`.
|
|
70
|
+
|
|
71
|
+
A preset's range, from local dates — never `toISOString()`, which shifts the day:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const ymd = (d: Date) =>
|
|
75
|
+
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
76
|
+
const lastMonths = (n: number): [string, string] => {
|
|
77
|
+
const end = new Date();
|
|
78
|
+
const start = new Date(end.getFullYear(), end.getMonth() - n, end.getDate());
|
|
79
|
+
return [ymd(start), ymd(end)];
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**A single date or a date-time** is the one case for a library: `npm install react-datepicker`, import `react-datepicker/dist/react-datepicker.css`, type the callback (`(date: Date | null) => …`), and convert with `ymd` above. Pass `null`, never `new Date("")`, for an empty value.
|
|
84
|
+
|
|
85
|
+
## Check
|
|
86
|
+
|
|
87
|
+
For every visible filter: All runs the unfiltered question; its options come from a question; each option changes every panel the contract says it reaches, and no other; a preset bar ends with Custom; and no scope has two date controls. Search the filter controls for `<select` and `type="date"` — an action form keeps its date inputs. Then build and publish (`data-app`, _Build and publish_).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-app-ui
|
|
3
|
+
description: Write a Metabase data app's UI — what the sandbox blocks and what replaces it, external APIs through `allowed_hosts`, layout and sizing, the SDK theme, a custom error state, the SDK components on offer, a Metabase chart versus a hand-drawn one and its visualization settings, pages with `DataAppRouter`, and what the app may show. Triggers — "change the layout", "theme the app", "add a page", "link between pages", "call an external API from the app", "the chart overflows", "invisible chart labels", "make it look like our brand". Creating and publishing is `data-app`; its data is `data-app-data`.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
+
requires: [dataApps]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A data app's UI
|
|
9
|
+
|
|
10
|
+
Everything here runs inside the sandbox, the same in the dev preview as in Metabase; check each change in the preview and its diagnostics feed (`data-app`).
|
|
11
|
+
|
|
12
|
+
## Source
|
|
13
|
+
|
|
14
|
+
- Plain ESM and TSX. SDK components come from `@metabase/embedding-sdk-react`; the data hooks, routing, `copy`, `DateRangePopover`, `DateRangeCalendar` and `useDateFormatter` from `@metabase/embedding-sdk-react/data-app`. JSX needs no `import React`; import hooks and React types by name.
|
|
15
|
+
- React, React DOM and both SDK entries come from the host. Never read SDK values off `globalThis`: the sandbox globals there are private.
|
|
16
|
+
- The build is one self-contained file: CSS is inlined, `import logo from "./logo.png"` is a data URI, `import Icon from "./icon.svg?react"` a component (add `declare module "*.svg?react";` in a `.d.ts` under `src/` if the editor doesn't know it). Keep large binaries out. There's no `index.html` and no Vite plugin to add: solve it in `src/`.
|
|
17
|
+
- Once the app grows: `src/App.tsx` for routing and composition, `pages/` one file per screen, `components/`, `hooks/`, `lib/` for pure helpers.
|
|
18
|
+
|
|
19
|
+
## What the app shows
|
|
20
|
+
|
|
21
|
+
- Only what Metabase returned, or deterministic transforms of it: no sample data, invented KPIs, trends, statuses, or claims such as "real-time" or "at risk" the data doesn't support. A section no question supports is left out.
|
|
22
|
+
- Don't read a `rate`, `score`, `margin` or `percent` column as a percentage or threshold — no `%`, `* 100`, colors or stars — unless its `semanticType` in `src/metabase.ts`, or the user, says so.
|
|
23
|
+
- Every panel has a loading state, its error, and an empty state for no rows — never an empty slot or a stale value.
|
|
24
|
+
- Label with names, not ids; currency to two decimals, counts as integers; a `null` breakout is "Unknown". A trend needs several points: never a sparkline from one value. Cap long lists with a limit, pages or a scroll area.
|
|
25
|
+
|
|
26
|
+
## What the sandbox blocks
|
|
27
|
+
|
|
28
|
+
| Blocked | Use instead |
|
|
29
|
+
| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
30
|
+
| `fetch` to Metabase or to any origin outside the app's `allowed_hosts`; `XMLHttpRequest`; `WebSocket` | The data hooks for Metabase; _External APIs_ for anything else. |
|
|
31
|
+
| `alert`, `confirm`, `prompt` | A modal in your own tree. |
|
|
32
|
+
| `localStorage`, `sessionStorage`, `indexedDB`, `document.cookie` | Nothing persists across reloads; persist through an action (`data-app-actions`). |
|
|
33
|
+
| `window.open`, `history.pushState`, `history.replaceState` | `useDataAppLocation().navigate`; `<a target="_blank" rel="noopener">` for external links. |
|
|
34
|
+
| `navigator.clipboard`, `execCommand("copy")` | `await copy(text)` in a user event — write-only, there's no paste. |
|
|
35
|
+
| `navigator.geolocation` and other device APIs | Nothing. |
|
|
36
|
+
| Key, input, composition and clipboard listeners (`keydown`, `paste`, `input`, …) on `window` or `document` | A React handler (`onKeyDown`, `onPaste`, …) on your own element. |
|
|
37
|
+
|
|
38
|
+
**External APIs.** A raw `fetch` reaches only the origins in the app's `allowed_hosts`, exact or with a `*.` subdomain wildcard: `mb data-app update <slug> --body '{"allowed_hosts":["https://api.example.com"]}'`, plus an `allowed_hosts:` list in `data_app.yaml` beside `package.json` for the preview, then restart `npm run dev`. Never list the Metabase URL. In Metabase the same list governs `<iframe src>` and native `<form action>` submits, and the target must allow framing; the preview blocks both. Use `<form onSubmit>` with `preventDefault`: a native submit navigates the app away.
|
|
39
|
+
|
|
40
|
+
## Layout
|
|
41
|
+
|
|
42
|
+
- The root element gets `minHeight: "100vh"` (plus `boxSizing: "border-box"` with padding) and the page background; never `height`. Regions that reach the bottom, such as a sidebar, go in a flex-column root with `flex: 1`. Check the emptiest screens — loading, empty, a lone KPI — since they render shortest.
|
|
43
|
+
- SDK questions don't size to their parent: pass `height` and `width` to the component that draws (`StaticQuestion`, `InteractiveQuestion`, or `InteractiveQuestion.QuestionVisualization`) — the height actually left in its panel, or `height="100%"` in a fixed-height box. A KPI needs little: about `120` for a `scalar`, more for a `smartscalar`'s comparison or a `gauge`.
|
|
44
|
+
- Don't wrap an SDK question in `overflow: hidden`, hover transforms or hover-driven layout shifts: its menus and tooltips need stable geometry.
|
|
45
|
+
|
|
46
|
+
## Theme
|
|
47
|
+
|
|
48
|
+
`src/theme.ts`, passed as `providerProps.theme`, is the only way to style SDK components, one theme for the whole app; it never styles your own UI (inline styles or CSS modules).
|
|
49
|
+
|
|
50
|
+
| Key | Rule |
|
|
51
|
+
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `colors.brand` | The app's accent. |
|
|
53
|
+
| `colors["brand-hover"]`, `colors["brand-hover-light"]` | A light tint (`#EAF4FF` for a blue brand), never the saturated brand. |
|
|
54
|
+
| `colors.charts` | All 8 slots, starting with `brand`, vivid and distinct — never pale tints, which can color labels. An empty slot keeps Metabase's color. |
|
|
55
|
+
| `colors.background`, `colors["background-secondary"]` | The color of the SDK component's own container: `white` inside a white card. |
|
|
56
|
+
| `colors["text-primary"]`, `-secondary`, `-tertiary` | Contrast with `background`; set them whenever you set it. Missing contrast is the "invisible chart labels" bug. |
|
|
57
|
+
| `colors.positive`, `colors.negative`, `fontFamily` | Up and down in trends and changes; the SDK's typeface. A `text-hover` must contrast with both brand-hover colors in open menus. |
|
|
58
|
+
|
|
59
|
+
**Error state.** A question that can't load shows Metabase's neutral error. To match the app, add an `errorComponent` to `providerProps` — it replaces the error of every SDK component, so keep the wording generic, render its `message` (a `ReactNode`) as it is, and use no data hooks in it:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import type { SdkErrorComponentProps } from "@metabase/embedding-sdk-react";
|
|
63
|
+
|
|
64
|
+
export function AppError({ message }: SdkErrorComponentProps) {
|
|
65
|
+
return <div style={{ padding: 16, color: "#6b7280" }}>{message}</div>;
|
|
66
|
+
}
|
|
67
|
+
// src/index.tsx: providerProps: { theme, errorComponent: AppError }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## SDK components
|
|
71
|
+
|
|
72
|
+
| Import | For |
|
|
73
|
+
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
74
|
+
| `StaticQuestion` | A chart, KPI or table without drill-through — the default. |
|
|
75
|
+
| `InteractiveQuestion` | One meant to be explored: drill-through, chart switching, the editor. |
|
|
76
|
+
| `StaticDashboard`, `InteractiveDashboard`, `EditableDashboard` | A dashboard. |
|
|
77
|
+
| `CreateQuestion`, `MetabotQuestion`, `CollectionBrowser`, `CreateDashboardModal` | Authoring flows, rarely in an app. |
|
|
78
|
+
|
|
79
|
+
## A Metabase chart or your own
|
|
80
|
+
|
|
81
|
+
Ask "can Metabase draw this at all?", never "which looks closer to the design?" — and decide per element, not per page.
|
|
82
|
+
|
|
83
|
+
- **Yes** — bar, line, area, combo, row, pie, scalar, smartscalar, gauge, progress, funnel, scatter, waterfall, boxplot, sankey, pivot, map, tables: render it with `useMetabaseQueryObject` and `StaticQuestion` (`data-app-data`). It keeps the instance's formatting, tooltips, theming and drill-through. Styling is never a reason to draw it yourself.
|
|
84
|
+
- **No**, or the user asked for a custom one — scorecards, timelines, calendar grids, one visual from several queries: draw it from `useMetabaseQuery` rows, with SVG or a chart library the app already has.
|
|
85
|
+
- A KPI is `scalar`, `smartscalar`, `gauge` or `progress`; put what Metabase doesn't draw (a caption, stars) beside it.
|
|
86
|
+
- `InteractiveQuestion`: pass `isSaveEnabled={false}`, and `title={false}` in a panel with its own heading. Children replace its layout: `<InteractiveQuestion card={card}><InteractiveQuestion.QuestionVisualization height={360} /></InteractiveQuestion>` is the chart alone.
|
|
87
|
+
|
|
88
|
+
**Visualization settings.** Add `visualizationSettings` only for a change the user asked for. Their keys are a question's `visualization_settings` keys — the `visualization` skill lists them — and the installed types decide what's legal: `rg -n "<key>|declare type .*VisualizationSettings" node_modules/@metabase/embedding-sdk-react/dist/index.d.ts`, never the whole file, never from memory. Keep them few: keys such as `graph.dimensions`, `pie.metric` and `column_settings` name result columns and break when the question changes.
|
|
89
|
+
|
|
90
|
+
- A `table` with two breakouts and one aggregation renders as a pivot when a breakout has at most 16 values; `{ "table.pivot": false }` keeps one row per group.
|
|
91
|
+
- A scalar shows the first column: with a key column first, point it at the number with `{ "scalar.field": "avg_rating" }`.
|
|
92
|
+
- A header: `column_settings: { '["name","approvers"]': { column_title: "Approvers" } }`.
|
|
93
|
+
|
|
94
|
+
## Pages
|
|
95
|
+
|
|
96
|
+
No router library — it calls `history.pushState`, which the sandbox blocks. Wrap the app once in `<DataAppRouter>`, link with `<DataAppLink to="/customers/42">` (an `<a href>` that takes the usual anchor props), and read `{ pathname, navigate }` from `useDataAppLocation()`. There's no `useParams`: match `pathname` yourself.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
const TABS = [
|
|
100
|
+
{ path: "/", label: "Overview", render: () => <Overview /> },
|
|
101
|
+
{ path: "/customers", label: "Customers", render: () => <Customers /> },
|
|
102
|
+
];
|
|
103
|
+
// `/` and any unknown path open the first tab
|
|
104
|
+
const activeTab = (pathname: string) =>
|
|
105
|
+
TABS.find((tab) => tab.path !== "/" && pathname.startsWith(tab.path)) ?? TABS[0];
|
|
106
|
+
|
|
107
|
+
function Page() {
|
|
108
|
+
const { pathname } = useDataAppLocation();
|
|
109
|
+
const customerId = pathname.match(/^\/customers\/(\d+)$/)?.[1];
|
|
110
|
+
return customerId ? <CustomerDetail id={Number(customerId)} /> : activeTab(pathname).render();
|
|
111
|
+
}
|
|
112
|
+
// nav: <DataAppLink to={tab.path} aria-current={tab === activeTab(pathname) ? "page" : undefined}>
|
|
113
|
+
// App: <DataAppRouter><Nav /><Page /></DataAppRouter>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Metabase mirrors each route to `/apps/<slug>/<path>`, and a reload there opens that page. A middle-click opens a new tab only in the preview; in Metabase that tab shows "This data app can't be opened directly." Check in the preview: reload on a sub-path, then on `/` — the first tab shows, marked selected.
|
|
117
|
+
|
|
118
|
+
## Before handoff
|
|
119
|
+
|
|
120
|
+
`npm run dev` only transpiles: run `npm run typecheck` after every round of edits and fix what it reports, and report the result, errors grouped by cause. Search the changed files and fix what turns up: `card={{` (every `card` comes from `useMemo`), `row[0]` and `as unknown as` (rows are keyed by column name), `<select` for a list from a question (`data-app-filters`), `type="date"` and `new Date(` in a date filter, `* 100` and `%`. Read the diagnostics feed with every panel mounted, then build and publish (`data-app`, _Build and publish_).
|
|
@@ -24,7 +24,7 @@ Exporting after the remote moved on ends in a `conflict` task unless the export
|
|
|
24
24
|
<!-- /requires -->
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
mb git-sync status --profile <n> --json # → branch, dirty, current task
|
|
27
|
+
mb git-sync status --profile <n> --json # → remote configured, sync type, branch, dirty, current task
|
|
28
28
|
mb git-sync is-dirty --profile <n> --json # → {is_dirty: bool}; instance has unexported changes
|
|
29
29
|
mb git-sync dirty --profile <n> --json # → list the dirty objects
|
|
30
30
|
mb git-sync current-task --profile <n> --json # → in-flight task (or idle)
|
|
@@ -197,7 +197,7 @@ Both verbs return `{ success: true }`, plus a `task_id` only when the toggle sta
|
|
|
197
197
|
|
|
198
198
|
**Cascade.** A toggle on a parent cascades to every descendant by `location` prefix — `add-collection 4` flips `4` plus every collection nested under it. `remove-collection 4` is the symmetric inverse. There is no per-leaf-only mode.
|
|
199
199
|
|
|
200
|
-
**Mode prerequisite.** The server rejects toggles while `remote-sync-type` is `:read-only` (the install default). If `mb git-sync add-collection 12` returns `Metabase returned 400 … Cannot change synced collections when remote-sync-type is read-only.`, switch first with:
|
|
200
|
+
**Mode prerequisite.** The server rejects toggles while `remote-sync-type` is `:read-only` (the install default; `mb git-sync status --json` reports it as `sync_type`). If `mb git-sync add-collection 12` returns `Metabase returned 400 … Cannot change synced collections when remote-sync-type is read-only.`, switch first with:
|
|
201
201
|
|
|
202
202
|
```bash
|
|
203
203
|
mb setting set remote-sync-type '"read-write"' --profile <n>
|
|
@@ -256,7 +256,7 @@ Flagging the collection records it for the next export, which serializes its cur
|
|
|
256
256
|
- Don't turn instance-side changes into hand-written repo files: export them and PR the exported branch (see the top of this skill). Files pushed behind Metabase's back race its own sync tasks.
|
|
257
257
|
- Don't conclude from an empty `dirty` list that a change type isn't tracked. Dirty-tracking only records changes to _eligible_ objects; the usual cause is scope (the collection isn't flagged — see "Published table metadata"), not capability. Check `synced_collections` in `status` before concluding.
|
|
258
258
|
- Don't run `git-sync import --force` or `git-sync export --force` without explicit user confirmation. Both are lossy — `--force` import discards instance-side work, `--force` export overwrites the remote branch.
|
|
259
|
-
- Don't drive `git-sync` against a Metabase instance that doesn't have remote-sync configured — every verb returns an error pointing at the missing `remote-sync-*` settings. To check: `mb
|
|
259
|
+
- Don't drive `git-sync` against a Metabase instance that doesn't have remote-sync configured — every verb returns an error pointing at the missing `remote-sync-*` settings. To check: `mb git-sync status --profile <n> --json` reports `remote_configured: false` when no remote is configured.
|
|
260
260
|
- Don't author content directly via `card create` / `transform create` and then assume `git-sync export` will commit it cleanly — the instance and repo can drift if you mix direct API writes with sync-tracked changes. If you do, follow direct writes immediately with `git-sync export -m "..."` to keep them in step.
|
|
261
261
|
- Don't move a dashboard or question into a synced collection before what it depends on, and don't retry a `Uses content that is not remote synced.` refusal unchanged. Move the listed dependencies first, move the whole containing collection, or build the content in the synced collection to begin with (see "Moving content into or out of a synced collection").
|
|
262
262
|
- Don't omit `-m` on `export` if the user wants a meaningful commit message — the default server-generated message is generic.
|
|
@@ -51,7 +51,7 @@ mb transform run "$TRANSFORM_ID" --wait --profile <name> --json
|
|
|
51
51
|
- The `--json` envelope is shape-stable: `{message, run_id, final}` (plus `target_table_id` under `--sync` — a number, or `null` if the table didn't register before the timeout). `final` is `null` when `--wait` is omitted or the run never started, otherwise a full `TransformRun` with `status` and `message`. On a failed run (`final.status` ∈ {`failed`, `timeout`, `canceled`}) the CLI exits 1 and writes a one-line `transform run <id> failed` to stderr; the failure detail lives only in `final.message` on stdout, so `jq -r '.final.message'` is where to look.
|
|
52
52
|
- `transform create --json` returns the agent-facing compact projection: `{id, name, description, source_type, target: {type, database, schema, name}, target_db_id}`. Read `target.schema`/`target.name` directly off it — no follow-up `transform get`.
|
|
53
53
|
- If a transform with the same `name` already has a YAML representation on disk under the configured remote-sync repo, `create` mints a `_2` suffix on the exported filename (the new transform gets a fresh `entity_id`; the prior one isn't touched). For "iterate on the same concept", prefer `transform update <id>` — see "Iterating on a failing transform".
|
|
54
|
-
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — a normal analytics collection id fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or provision one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` (see `core`) and pass the returned `id`. Cards and dashboards you build **on top of** the output table go in ordinary collections — so "put the transform and its dashboard in collection X" means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
54
|
+
- **`collection_id` only accepts a collection in the `:transforms` namespace.** Transforms aren't filed next to cards and dashboards — a normal analytics collection id fails create/update with `collection_id: A Transform can only go in Collections in the :transforms namespace.` Omit `collection_id` to leave the transform uncollected (the common case), or reuse one from `mb collection list --namespace transforms --json`, or provision one with `mb collection create --body '{"name":"…"}' --namespace transforms --json` (see `core`) and pass the returned `id`. Cards and dashboards you build **on top of** the output table go in ordinary collections — so "put the transform and its dashboard in collection X" means _X holds the dashboard + cards; the transform stays in the transforms namespace._
|
|
55
55
|
|
|
56
56
|
## Inspect
|
|
57
57
|
|
package/dist/db-m5bQcXdn.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { t as defineCommandGroup } from "./group-81zoQcEP.mjs";
|
|
2
|
-
//#region src/commands/db/index.ts
|
|
3
|
-
var db_default = defineCommandGroup({
|
|
4
|
-
name: "db",
|
|
5
|
-
description: "Inspect and sync Metabase databases",
|
|
6
|
-
alias: "database",
|
|
7
|
-
skills: [{
|
|
8
|
-
skill: "core",
|
|
9
|
-
purpose: "the database traversal ladder and schema sync"
|
|
10
|
-
}, {
|
|
11
|
-
skill: "data-workflow",
|
|
12
|
-
purpose: "model a raw database into clean tables"
|
|
13
|
-
}],
|
|
14
|
-
subCommands: {
|
|
15
|
-
list: () => import("./list-B5kRBn7k.mjs").then((m) => m.default),
|
|
16
|
-
get: () => import("./get-CWyRAHpz.mjs").then((m) => m.default),
|
|
17
|
-
schemas: () => import("./schemas-CHMD0HrY.mjs").then((m) => m.default),
|
|
18
|
-
"schema-tables": () => import("./schema-tables-CeW0kpA5.mjs").then((m) => m.default),
|
|
19
|
-
"sync-schema": () => import("./sync-schema-CewUOkmm.mjs").then((m) => m.default),
|
|
20
|
-
"rescan-values": () => import("./rescan-values-DAp_avxY.mjs").then((m) => m.default)
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
//#endregion
|
|
24
|
-
export { db_default as default };
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { _ as promptConfirm, c as renderSummary, yt as ConfigError } from "./cli.mjs";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
//#region src/commands/delete-runtime.ts
|
|
4
|
-
const DeleteResult = z.object({
|
|
5
|
-
deleted: z.boolean(),
|
|
6
|
-
aborted: z.boolean(),
|
|
7
|
-
id: z.number().int()
|
|
8
|
-
});
|
|
9
|
-
const deleteResultView = {
|
|
10
|
-
compactPick: DeleteResult,
|
|
11
|
-
tableColumns: [
|
|
12
|
-
{
|
|
13
|
-
key: "id",
|
|
14
|
-
label: "ID"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
key: "deleted",
|
|
18
|
-
label: "Deleted"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
key: "aborted",
|
|
22
|
-
label: "Aborted"
|
|
23
|
-
}
|
|
24
|
-
]
|
|
25
|
-
};
|
|
26
|
-
async function confirmDestructive(args) {
|
|
27
|
-
if (args.yes) return true;
|
|
28
|
-
if (process.stdin.isTTY !== true) throw new ConfigError(`refusing to ${args.action} without confirmation — pass --yes to proceed non-interactively`);
|
|
29
|
-
return promptConfirm({
|
|
30
|
-
message: args.promptMessage,
|
|
31
|
-
initialValue: false
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
async function confirmAndDelete(args) {
|
|
35
|
-
if (!await confirmDestructive({
|
|
36
|
-
yes: args.yes,
|
|
37
|
-
action: `delete ${args.id}`,
|
|
38
|
-
promptMessage: args.promptMessage
|
|
39
|
-
})) {
|
|
40
|
-
renderSummary({
|
|
41
|
-
deleted: false,
|
|
42
|
-
aborted: true,
|
|
43
|
-
id: args.id
|
|
44
|
-
}, deleteResultView, args.abortMessage, args.ctx);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
await args.deleteResource();
|
|
48
|
-
renderSummary({
|
|
49
|
-
deleted: true,
|
|
50
|
-
aborted: false,
|
|
51
|
-
id: args.id
|
|
52
|
-
}, deleteResultView, args.successMessage, args.ctx);
|
|
53
|
-
}
|
|
54
|
-
//#endregion
|
|
55
|
-
export { confirmAndDelete as n, confirmDestructive as r, DeleteResult as t };
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { t as defineCommandGroup } from "./group-81zoQcEP.mjs";
|
|
2
|
-
//#region src/commands/git-sync/index.ts
|
|
3
|
-
var git_sync_default = defineCommandGroup({
|
|
4
|
-
name: "git-sync",
|
|
5
|
-
description: "Sync Metabase content with a git remote",
|
|
6
|
-
skills: [{
|
|
7
|
-
skill: "git-sync",
|
|
8
|
-
purpose: "import/export round-trip and dirty checks"
|
|
9
|
-
}],
|
|
10
|
-
subCommands: {
|
|
11
|
-
status: () => import("./status-BtWz7Pzn.mjs").then((mod) => mod.default),
|
|
12
|
-
"is-dirty": () => import("./is-dirty-TDcfeOde.mjs").then((mod) => mod.default),
|
|
13
|
-
"has-remote-changes": () => import("./has-remote-changes-BsS3XB0P.mjs").then((mod) => mod.default),
|
|
14
|
-
dirty: () => import("./dirty-fYlA1N1e.mjs").then((mod) => mod.default),
|
|
15
|
-
"current-task": () => import("./current-task-nl2z8sHI.mjs").then((mod) => mod.default),
|
|
16
|
-
"cancel-task": () => import("./cancel-task-BiTlDY7a.mjs").then((mod) => mod.default),
|
|
17
|
-
wait: () => import("./wait-BV6JOdgi.mjs").then((mod) => mod.default),
|
|
18
|
-
import: () => import("./import-CXd5HdB0.mjs").then((mod) => mod.default),
|
|
19
|
-
export: () => import("./export-YtdZo7LZ.mjs").then((mod) => mod.default),
|
|
20
|
-
"export-preflight": () => import("./export-preflight-Db_TChn5.mjs").then((mod) => mod.default),
|
|
21
|
-
stash: () => import("./stash-Dj-mnIa4.mjs").then((mod) => mod.default),
|
|
22
|
-
branches: () => import("./branches-lPHtM_xR.mjs").then((mod) => mod.default),
|
|
23
|
-
"create-branch": () => import("./create-branch-CUNFdKyB.mjs").then((mod) => mod.default),
|
|
24
|
-
"add-collection": () => import("./add-collection-BnGPc0Vv.mjs").then((mod) => mod.default),
|
|
25
|
-
"remove-collection": () => import("./remove-collection-cxSSFxza.mjs").then((mod) => mod.default)
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
//#endregion
|
|
29
|
-
export { git_sync_default as default };
|
package/dist/input-AXP2LCOV.mjs
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { jt as isFileNotFoundError, yt as ConfigError } from "./cli.mjs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { setTimeout } from "node:timers/promises";
|
|
4
|
-
//#region src/runtime/input.ts
|
|
5
|
-
const DEFAULT_FLAG_NAME = "--body";
|
|
6
|
-
const SOURCE_LIST = `${DEFAULT_FLAG_NAME}, --file, stdin, or a positional argument`;
|
|
7
|
-
/** The value of a string flag, or `null` when it was left out or passed empty. */
|
|
8
|
-
function givenValue(value) {
|
|
9
|
-
return value === void 0 || value === "" ? null : value;
|
|
10
|
-
}
|
|
11
|
-
async function readInput(sources) {
|
|
12
|
-
if (sources.flag) return sources.flag;
|
|
13
|
-
if (sources.file) return await readFileSource(sources.file);
|
|
14
|
-
if (!process.stdin.isTTY) {
|
|
15
|
-
const piped = await readPipedStdin();
|
|
16
|
-
if (piped) return piped;
|
|
17
|
-
}
|
|
18
|
-
if (sources.positional) return sources.positional;
|
|
19
|
-
if (sources.required ?? true) throw new ConfigError(`input required: provide one of ${SOURCE_LIST}`);
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
async function readFileSource(path) {
|
|
23
|
-
if (path === "-") return await drainStdin();
|
|
24
|
-
try {
|
|
25
|
-
return await readFile(path, "utf8");
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (isFileNotFoundError(error)) throw fileNotFoundError(path);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function fileNotFoundError(path) {
|
|
32
|
-
return new ConfigError(`--file not found: ${path}`);
|
|
33
|
-
}
|
|
34
|
-
async function drainStdin() {
|
|
35
|
-
let data = "";
|
|
36
|
-
for await (const chunk of process.stdin) data += chunk;
|
|
37
|
-
return data;
|
|
38
|
-
}
|
|
39
|
-
const STDIN_FIRST_CHUNK_TIMEOUT_MS = 500;
|
|
40
|
-
const STDIN_IDLE = Symbol("stdin-idle");
|
|
41
|
-
async function readPipedStdin() {
|
|
42
|
-
const iterator = process.stdin[Symbol.asyncIterator]();
|
|
43
|
-
const controller = new AbortController();
|
|
44
|
-
const idle = setTimeout(STDIN_FIRST_CHUNK_TIMEOUT_MS, STDIN_IDLE, { signal: controller.signal }).catch(() => STDIN_IDLE);
|
|
45
|
-
const first = await Promise.race([iterator.next(), idle]);
|
|
46
|
-
if (typeof first === "symbol") {
|
|
47
|
-
process.stdin.pause();
|
|
48
|
-
process.stdin.unref();
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
controller.abort();
|
|
52
|
-
let data = "";
|
|
53
|
-
let chunk = first;
|
|
54
|
-
while (chunk.done !== true) {
|
|
55
|
-
data += chunk.value;
|
|
56
|
-
chunk = await iterator.next();
|
|
57
|
-
}
|
|
58
|
-
return data;
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
export { readInput as i, fileNotFoundError as n, givenValue as r, DEFAULT_FLAG_NAME as t };
|