drizzle-cube 0.4.52 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/express/index.cjs +2 -2
- package/dist/adapters/express/index.js +92 -83
- package/dist/adapters/fastify/index.cjs +2 -2
- package/dist/adapters/fastify/index.js +126 -116
- package/dist/adapters/{handler-RItnSaEl.js → handler-3LGcjLtr.js} +617 -612
- package/dist/adapters/handler-BzzbVpcl.cjs +25 -0
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.js +94 -86
- package/dist/adapters/{compiler-D_o2IzHn.js → locale-DTnJrxm1.js} +1363 -1079
- package/dist/adapters/locale-DueXjqMh.cjs +198 -0
- package/dist/adapters/locale.d.ts +8 -0
- package/dist/adapters/mcp-tools.cjs +1 -1
- package/dist/adapters/mcp-tools.js +14 -14
- package/dist/adapters/mcp-transport-45SiFcCH.cjs +39 -0
- package/dist/adapters/mcp-transport-Bxpc4mRy.js +553 -0
- package/dist/adapters/mcp-transport.d.ts +11 -0
- package/dist/adapters/nextjs/index.cjs +1 -1
- package/dist/adapters/nextjs/index.js +138 -122
- package/dist/adapters/utils-DNrj-ryp.cjs +17 -0
- package/dist/adapters/{utils-IH1ePsBd.js → utils-DOg9oGdt.js} +2341 -819
- package/dist/adapters/utils.cjs +1 -1
- package/dist/adapters/utils.d.ts +7 -0
- package/dist/adapters/utils.js +1 -1
- package/dist/client/charts.js +12 -12
- package/dist/client/chunks/{DashboardEditModal-BTdV528l.js → DashboardEditModal-cSSIAZGy.js} +1968 -1973
- package/dist/client/chunks/DashboardEditModal-cSSIAZGy.js.map +1 -0
- package/dist/client/chunks/{FieldSearchModal-D75vy4Wb.js → FieldSearchModal-CZNo4pNK.js} +550 -536
- package/dist/client/chunks/FieldSearchModal-CZNo4pNK.js.map +1 -0
- package/dist/client/chunks/KpiDelta-Dll_eCV1.js +2 -0
- package/dist/client/chunks/KpiNumber-BPlR92hI.js +2 -0
- package/dist/client/chunks/KpiText-BIxq7Jso.js +2 -0
- package/dist/client/chunks/{RetentionCombinedChart-DIhK5pD8.js → RetentionCombinedChart-BD8tGeM_.js} +96 -96
- package/dist/client/chunks/RetentionCombinedChart-BD8tGeM_.js.map +1 -0
- package/dist/client/chunks/{RetentionHeatmap-CyREolyP.js → RetentionHeatmap-B_5sewwi.js} +77 -77
- package/dist/client/chunks/RetentionHeatmap-B_5sewwi.js.map +1 -0
- package/dist/client/chunks/SchemaVisualization-CCICjhvv.js +2 -0
- package/dist/client/chunks/SchemaVisualizationLazy-DraGsMx6.js +2 -0
- package/dist/client/chunks/af-ZA-xDmO5F0s.js +1431 -0
- package/dist/client/chunks/af-ZA-xDmO5F0s.js.map +1 -0
- package/dist/client/chunks/{analysis-builder-C1CJ0c7L.js → analysis-builder-BeVZhiQ5.js} +1519 -1507
- package/dist/client/chunks/analysis-builder-BeVZhiQ5.js.map +1 -0
- package/dist/client/chunks/{analysis-builder-shared-rkjJfWLT.js → analysis-builder-shared-BWc7ZZnG.js} +925 -954
- package/dist/client/chunks/analysis-builder-shared-BWc7ZZnG.js.map +1 -0
- package/dist/client/chunks/chart-activity-grid-CWT0gLv4.js +2376 -0
- package/dist/client/chunks/chart-activity-grid-CWT0gLv4.js.map +1 -0
- package/dist/client/chunks/{chart-area-BwYaflNk.js → chart-area-D63kG8OT.js} +157 -157
- package/dist/client/chunks/chart-area-D63kG8OT.js.map +1 -0
- package/dist/client/chunks/{chart-bar-BiENfFgE.js → chart-bar-BEfsCLjl.js} +78 -78
- package/dist/client/chunks/chart-bar-BEfsCLjl.js.map +1 -0
- package/dist/client/chunks/{chart-box-plot-BJF1tBXC.js → chart-box-plot-o-h9MRX5.js} +111 -114
- package/dist/client/chunks/chart-box-plot-o-h9MRX5.js.map +1 -0
- package/dist/client/chunks/{chart-bubble-DQQhGVDJ.js → chart-bubble-CMDp4Pfm.js} +121 -121
- package/dist/client/chunks/chart-bubble-CMDp4Pfm.js.map +1 -0
- package/dist/client/chunks/chart-candlestick-WyANJ0Ky.js +303 -0
- package/dist/client/chunks/chart-candlestick-WyANJ0Ky.js.map +1 -0
- package/dist/client/chunks/chart-config-activity-grid-C-EkgYoa.js +51 -0
- package/dist/client/chunks/chart-config-activity-grid-C-EkgYoa.js.map +1 -0
- package/dist/client/chunks/chart-config-area-CMZpbIah.js +93 -0
- package/dist/client/chunks/chart-config-area-CMZpbIah.js.map +1 -0
- package/dist/client/chunks/chart-config-bar-B8_V4YLg.js +87 -0
- package/dist/client/chunks/chart-config-bar-B8_V4YLg.js.map +1 -0
- package/dist/client/chunks/chart-config-box-plot-Dwj7sEbU.js +35 -0
- package/dist/client/chunks/chart-config-box-plot-Dwj7sEbU.js.map +1 -0
- package/dist/client/chunks/chart-config-bubble-B0w0ZVp4.js +82 -0
- package/dist/client/chunks/chart-config-bubble-B0w0ZVp4.js.map +1 -0
- package/dist/client/chunks/chart-config-candlestick-Bvo3zeIn.js +72 -0
- package/dist/client/chunks/chart-config-candlestick-Bvo3zeIn.js.map +1 -0
- package/dist/client/chunks/{chart-config-data-table-Bhdx5Hem.js → chart-config-data-table-BQXSn4b_.js} +9 -9
- package/dist/client/chunks/chart-config-data-table-BQXSn4b_.js.map +1 -0
- package/dist/client/chunks/chart-config-funnel-BzEsHmjR.js +93 -0
- package/dist/client/chunks/chart-config-funnel-BzEsHmjR.js.map +1 -0
- package/dist/client/chunks/chart-config-gauge-C5ZiyZy7.js +64 -0
- package/dist/client/chunks/chart-config-gauge-C5ZiyZy7.js.map +1 -0
- package/dist/client/chunks/chart-config-heat-map-Cv8qNnVP.js +91 -0
- package/dist/client/chunks/chart-config-heat-map-Cv8qNnVP.js.map +1 -0
- package/dist/client/chunks/chart-config-kpi-delta-BraHQc2E.js +94 -0
- package/dist/client/chunks/chart-config-kpi-delta-BraHQc2E.js.map +1 -0
- package/dist/client/chunks/chart-config-kpi-number-CeCkx7mC.js +75 -0
- package/dist/client/chunks/chart-config-kpi-number-CeCkx7mC.js.map +1 -0
- package/dist/client/chunks/chart-config-kpi-text-CImM3SvH.js +47 -0
- package/dist/client/chunks/chart-config-kpi-text-CImM3SvH.js.map +1 -0
- package/dist/client/chunks/chart-config-line-BVKapAQK.js +104 -0
- package/dist/client/chunks/chart-config-line-BVKapAQK.js.map +1 -0
- package/dist/client/chunks/chart-config-markdown-C-_g_8te.js +117 -0
- package/dist/client/chunks/chart-config-markdown-C-_g_8te.js.map +1 -0
- package/dist/client/chunks/chart-config-measure-profile-KTVV1gO3.js +82 -0
- package/dist/client/chunks/chart-config-measure-profile-KTVV1gO3.js.map +1 -0
- package/dist/client/chunks/chart-config-pie-BZxVl25X.js +68 -0
- package/dist/client/chunks/chart-config-pie-BZxVl25X.js.map +1 -0
- package/dist/client/chunks/chart-config-radar-B7FByX3t.js +49 -0
- package/dist/client/chunks/chart-config-radar-B7FByX3t.js.map +1 -0
- package/dist/client/chunks/chart-config-radial-bar-UfW_3yyX.js +38 -0
- package/dist/client/chunks/chart-config-radial-bar-UfW_3yyX.js.map +1 -0
- package/dist/client/chunks/chart-config-sankey-DGAThN9i.js +66 -0
- package/dist/client/chunks/chart-config-sankey-DGAThN9i.js.map +1 -0
- package/dist/client/chunks/chart-config-scatter-BVVJuOnt.js +61 -0
- package/dist/client/chunks/chart-config-scatter-BVVJuOnt.js.map +1 -0
- package/dist/client/chunks/chart-config-sunburst-utejM2YS.js +45 -0
- package/dist/client/chunks/chart-config-sunburst-utejM2YS.js.map +1 -0
- package/dist/client/chunks/chart-config-tree-map-IHp97OyV.js +51 -0
- package/dist/client/chunks/chart-config-tree-map-IHp97OyV.js.map +1 -0
- package/dist/client/chunks/chart-config-waterfall-BdqG1V-x.js +59 -0
- package/dist/client/chunks/chart-config-waterfall-BdqG1V-x.js.map +1 -0
- package/dist/client/chunks/{chart-data-table-2iCsn0CF.js → chart-data-table-C3Xh9jwL.js} +1083 -1086
- package/dist/client/chunks/chart-data-table-C3Xh9jwL.js.map +1 -0
- package/dist/client/chunks/{chart-funnel-poyOf7-e.js → chart-funnel-C7pgktN5.js} +132 -132
- package/dist/client/chunks/chart-funnel-C7pgktN5.js.map +1 -0
- package/dist/client/chunks/{chart-gauge-D5J4gRky.js → chart-gauge-D2r2B_AR.js} +150 -150
- package/dist/client/chunks/chart-gauge-D2r2B_AR.js.map +1 -0
- package/dist/client/chunks/{chart-heat-map-BAMVhLGG.js → chart-heat-map-Dw2yjwfO.js} +75 -80
- package/dist/client/chunks/chart-heat-map-Dw2yjwfO.js.map +1 -0
- package/dist/client/chunks/{chart-kpi-delta-KQjUIeal.js → chart-kpi-delta-CYE0S1x_.js} +117 -117
- package/dist/client/chunks/chart-kpi-delta-CYE0S1x_.js.map +1 -0
- package/dist/client/chunks/chart-kpi-number-BlZ79xHW.js +321 -0
- package/dist/client/chunks/chart-kpi-number-BlZ79xHW.js.map +1 -0
- package/dist/client/chunks/chart-kpi-text-DY1BnxPe.js +148 -0
- package/dist/client/chunks/chart-kpi-text-DY1BnxPe.js.map +1 -0
- package/dist/client/chunks/{chart-line-B5_WntY5.js → chart-line-CBsTThTv.js} +123 -123
- package/dist/client/chunks/chart-line-CBsTThTv.js.map +1 -0
- package/dist/client/chunks/chart-markdown-BWaWVkuz.js +3474 -0
- package/dist/client/chunks/chart-markdown-BWaWVkuz.js.map +1 -0
- package/dist/client/chunks/chart-measure-profile-B41qCTBG.js +179 -0
- package/dist/client/chunks/chart-measure-profile-B41qCTBG.js.map +1 -0
- package/dist/client/chunks/chart-pie-Djbu8x2v.js +172 -0
- package/dist/client/chunks/chart-pie-Djbu8x2v.js.map +1 -0
- package/dist/client/chunks/chart-radar-BsTcKV0K.js +154 -0
- package/dist/client/chunks/chart-radar-BsTcKV0K.js.map +1 -0
- package/dist/client/chunks/chart-radial-bar-Du7XNnwE.js +148 -0
- package/dist/client/chunks/chart-radial-bar-Du7XNnwE.js.map +1 -0
- package/dist/client/chunks/{chart-sankey-BOyxfG1Q.js → chart-sankey-WwkZAhLy.js} +73 -73
- package/dist/client/chunks/chart-sankey-WwkZAhLy.js.map +1 -0
- package/dist/client/chunks/chart-scatter-D8krEYsA.js +255 -0
- package/dist/client/chunks/chart-scatter-D8krEYsA.js.map +1 -0
- package/dist/client/chunks/{chart-sunburst-D9lGEOCc.js → chart-sunburst-CIDB_pTl.js} +66 -66
- package/dist/client/chunks/chart-sunburst-CIDB_pTl.js.map +1 -0
- package/dist/client/chunks/chart-tree-map-C5C2iaWM.js +298 -0
- package/dist/client/chunks/chart-tree-map-C5C2iaWM.js.map +1 -0
- package/dist/client/chunks/{chart-waterfall-BCdUx4DC.js → chart-waterfall-BGdPrJ5Y.js} +80 -80
- package/dist/client/chunks/chart-waterfall-BGdPrJ5Y.js.map +1 -0
- package/dist/client/chunks/{charts-core-C5Yokk-x.js → charts-core-BXOqaYFn.js} +92 -92
- package/dist/client/chunks/charts-core-BXOqaYFn.js.map +1 -0
- package/dist/client/chunks/en-US-5xPTdCXg.js +35 -0
- package/dist/client/chunks/en-US-5xPTdCXg.js.map +1 -0
- package/dist/client/chunks/nl-NL-vCifBijs.js +1491 -0
- package/dist/client/chunks/nl-NL-vCifBijs.js.map +1 -0
- package/dist/client/chunks/{schema-visualization-t1KiOORo.js → schema-visualization-Xp60Ff2W.js} +352 -364
- package/dist/client/chunks/schema-visualization-Xp60Ff2W.js.map +1 -0
- package/dist/client/chunks/{useDebounce-CKqkM42n.js → useDebounce-CfmUMFau.js} +85 -85
- package/dist/client/chunks/useDebounce-CfmUMFau.js.map +1 -0
- package/dist/client/chunks/{useExplainAI-DBIfYwz-.js → useExplainAI-BKGmejIj.js} +4 -4
- package/dist/client/chunks/{useExplainAI-DBIfYwz-.js.map → useExplainAI-BKGmejIj.js.map} +1 -1
- package/dist/client/chunks/{utils--qCr8Yn5.js → utils-BldkcRHv.js} +2 -2
- package/dist/client/chunks/{utils--qCr8Yn5.js.map → utils-BldkcRHv.js.map} +1 -1
- package/dist/client/chunks/{vendor-BRlsCGnK.js → vendor-ClXpIiea.js} +2 -2
- package/dist/client/chunks/{vendor-BRlsCGnK.js.map → vendor-ClXpIiea.js.map} +1 -1
- package/dist/client/components/AnalysisBuilder/types.d.ts +6 -6
- package/dist/client/components.js +3 -3
- package/dist/client/hooks/useTranslation.d.ts +21 -0
- package/dist/client/hooks.js +3 -3
- package/dist/client/icons.js +1 -1
- package/dist/client/index.js +493 -488
- package/dist/client/index.js.map +1 -1
- package/dist/client/providers/CubeApiProvider.d.ts +2 -1
- package/dist/client/providers/CubeProvider.d.ts +7 -1
- package/dist/client/providers/I18nProvider.d.ts +18 -0
- package/dist/client/providers.js +1 -1
- package/dist/client/schema.js +1 -1
- package/dist/client/shared/types.d.ts +5 -20
- package/dist/client/styles.css +1 -1
- package/dist/client/utils.js +5 -5
- package/dist/client-bundle-stats.html +1 -1
- package/dist/mcp-app/mcp-app.html +26 -24
- package/dist/server/index.cjs +42 -40
- package/dist/server/index.js +3353 -1567
- package/package.json +5 -2
- package/dist/adapters/compiler-Bbsijr3W.cjs +0 -198
- package/dist/adapters/handler-DumFgnNM.cjs +0 -25
- package/dist/adapters/mcp-transport-C6bsIOSV.js +0 -545
- package/dist/adapters/mcp-transport-DMhuYmFp.cjs +0 -39
- package/dist/adapters/utils-CyBt-as9.cjs +0 -15
- package/dist/client/chunks/DashboardEditModal-BTdV528l.js.map +0 -1
- package/dist/client/chunks/FieldSearchModal-D75vy4Wb.js.map +0 -1
- package/dist/client/chunks/KpiDelta-Bk8bzKYM.js +0 -2
- package/dist/client/chunks/KpiNumber-CKF-8e_T.js +0 -2
- package/dist/client/chunks/KpiText-Iz1vIvu_.js +0 -2
- package/dist/client/chunks/RetentionCombinedChart-DIhK5pD8.js.map +0 -1
- package/dist/client/chunks/RetentionHeatmap-CyREolyP.js.map +0 -1
- package/dist/client/chunks/SchemaVisualization-B1GUT-FM.js +0 -2
- package/dist/client/chunks/SchemaVisualizationLazy-DymwT34e.js +0 -2
- package/dist/client/chunks/analysis-builder-C1CJ0c7L.js.map +0 -1
- package/dist/client/chunks/analysis-builder-shared-rkjJfWLT.js.map +0 -1
- package/dist/client/chunks/chart-activity-grid-DLktOINm.js +0 -806
- package/dist/client/chunks/chart-activity-grid-DLktOINm.js.map +0 -1
- package/dist/client/chunks/chart-area-BwYaflNk.js.map +0 -1
- package/dist/client/chunks/chart-bar-BiENfFgE.js.map +0 -1
- package/dist/client/chunks/chart-box-plot-BJF1tBXC.js.map +0 -1
- package/dist/client/chunks/chart-bubble-DQQhGVDJ.js.map +0 -1
- package/dist/client/chunks/chart-candlestick-C2UuXbLe.js +0 -306
- package/dist/client/chunks/chart-candlestick-C2UuXbLe.js.map +0 -1
- package/dist/client/chunks/chart-config-activity-grid-DJOU3TEz.js +0 -51
- package/dist/client/chunks/chart-config-activity-grid-DJOU3TEz.js.map +0 -1
- package/dist/client/chunks/chart-config-area-CWnWVT6a.js +0 -93
- package/dist/client/chunks/chart-config-area-CWnWVT6a.js.map +0 -1
- package/dist/client/chunks/chart-config-bar-C-7Dr1Ho.js +0 -87
- package/dist/client/chunks/chart-config-bar-C-7Dr1Ho.js.map +0 -1
- package/dist/client/chunks/chart-config-box-plot-mVOwmLdu.js +0 -35
- package/dist/client/chunks/chart-config-box-plot-mVOwmLdu.js.map +0 -1
- package/dist/client/chunks/chart-config-bubble-BPE2CeiD.js +0 -82
- package/dist/client/chunks/chart-config-bubble-BPE2CeiD.js.map +0 -1
- package/dist/client/chunks/chart-config-candlestick-BSB9DRy2.js +0 -72
- package/dist/client/chunks/chart-config-candlestick-BSB9DRy2.js.map +0 -1
- package/dist/client/chunks/chart-config-data-table-Bhdx5Hem.js.map +0 -1
- package/dist/client/chunks/chart-config-funnel-Cl-v-bm1.js +0 -93
- package/dist/client/chunks/chart-config-funnel-Cl-v-bm1.js.map +0 -1
- package/dist/client/chunks/chart-config-gauge-CdrUTJMJ.js +0 -64
- package/dist/client/chunks/chart-config-gauge-CdrUTJMJ.js.map +0 -1
- package/dist/client/chunks/chart-config-heat-map-DGE3NzoF.js +0 -91
- package/dist/client/chunks/chart-config-heat-map-DGE3NzoF.js.map +0 -1
- package/dist/client/chunks/chart-config-kpi-delta-DMrQerUW.js +0 -94
- package/dist/client/chunks/chart-config-kpi-delta-DMrQerUW.js.map +0 -1
- package/dist/client/chunks/chart-config-kpi-number-DCytCytn.js +0 -75
- package/dist/client/chunks/chart-config-kpi-number-DCytCytn.js.map +0 -1
- package/dist/client/chunks/chart-config-kpi-text-KdKQUvHo.js +0 -47
- package/dist/client/chunks/chart-config-kpi-text-KdKQUvHo.js.map +0 -1
- package/dist/client/chunks/chart-config-line-Bl9VDAdz.js +0 -104
- package/dist/client/chunks/chart-config-line-Bl9VDAdz.js.map +0 -1
- package/dist/client/chunks/chart-config-markdown-BX26b94i.js +0 -117
- package/dist/client/chunks/chart-config-markdown-BX26b94i.js.map +0 -1
- package/dist/client/chunks/chart-config-measure-profile-DwtRhEFh.js +0 -82
- package/dist/client/chunks/chart-config-measure-profile-DwtRhEFh.js.map +0 -1
- package/dist/client/chunks/chart-config-pie-BzBcqPMJ.js +0 -68
- package/dist/client/chunks/chart-config-pie-BzBcqPMJ.js.map +0 -1
- package/dist/client/chunks/chart-config-radar-6ZOgt__z.js +0 -49
- package/dist/client/chunks/chart-config-radar-6ZOgt__z.js.map +0 -1
- package/dist/client/chunks/chart-config-radial-bar-Df6Eta7N.js +0 -38
- package/dist/client/chunks/chart-config-radial-bar-Df6Eta7N.js.map +0 -1
- package/dist/client/chunks/chart-config-sankey-DgqKBFvN.js +0 -66
- package/dist/client/chunks/chart-config-sankey-DgqKBFvN.js.map +0 -1
- package/dist/client/chunks/chart-config-scatter-D5nVLDvi.js +0 -61
- package/dist/client/chunks/chart-config-scatter-D5nVLDvi.js.map +0 -1
- package/dist/client/chunks/chart-config-sunburst-Ca3FX9nW.js +0 -45
- package/dist/client/chunks/chart-config-sunburst-Ca3FX9nW.js.map +0 -1
- package/dist/client/chunks/chart-config-tree-map-Bjy4QNa3.js +0 -51
- package/dist/client/chunks/chart-config-tree-map-Bjy4QNa3.js.map +0 -1
- package/dist/client/chunks/chart-config-waterfall-C5K2eqR7.js +0 -59
- package/dist/client/chunks/chart-config-waterfall-C5K2eqR7.js.map +0 -1
- package/dist/client/chunks/chart-data-table-2iCsn0CF.js.map +0 -1
- package/dist/client/chunks/chart-funnel-poyOf7-e.js.map +0 -1
- package/dist/client/chunks/chart-gauge-D5J4gRky.js.map +0 -1
- package/dist/client/chunks/chart-heat-map-BAMVhLGG.js.map +0 -1
- package/dist/client/chunks/chart-kpi-delta-KQjUIeal.js.map +0 -1
- package/dist/client/chunks/chart-kpi-number-CsQgV_x3.js +0 -325
- package/dist/client/chunks/chart-kpi-number-CsQgV_x3.js.map +0 -1
- package/dist/client/chunks/chart-kpi-text-BR0IyeUU.js +0 -148
- package/dist/client/chunks/chart-kpi-text-BR0IyeUU.js.map +0 -1
- package/dist/client/chunks/chart-line-B5_WntY5.js.map +0 -1
- package/dist/client/chunks/chart-markdown-B6bENbel.js +0 -3473
- package/dist/client/chunks/chart-markdown-B6bENbel.js.map +0 -1
- package/dist/client/chunks/chart-measure-profile-yWk-obNb.js +0 -179
- package/dist/client/chunks/chart-measure-profile-yWk-obNb.js.map +0 -1
- package/dist/client/chunks/chart-pie-BodrUoHv.js +0 -172
- package/dist/client/chunks/chart-pie-BodrUoHv.js.map +0 -1
- package/dist/client/chunks/chart-radar-gG3zfLud.js +0 -154
- package/dist/client/chunks/chart-radar-gG3zfLud.js.map +0 -1
- package/dist/client/chunks/chart-radial-bar-C2IPCV8c.js +0 -148
- package/dist/client/chunks/chart-radial-bar-C2IPCV8c.js.map +0 -1
- package/dist/client/chunks/chart-sankey-BOyxfG1Q.js.map +0 -1
- package/dist/client/chunks/chart-scatter-B8OwlsAX.js +0 -255
- package/dist/client/chunks/chart-scatter-B8OwlsAX.js.map +0 -1
- package/dist/client/chunks/chart-sunburst-D9lGEOCc.js.map +0 -1
- package/dist/client/chunks/chart-tree-map-DZaKy9he.js +0 -298
- package/dist/client/chunks/chart-tree-map-DZaKy9he.js.map +0 -1
- package/dist/client/chunks/chart-waterfall-BCdUx4DC.js.map +0 -1
- package/dist/client/chunks/charts-core-C5Yokk-x.js.map +0 -1
- package/dist/client/chunks/schema-visualization-t1KiOORo.js.map +0 -1
- package/dist/client/chunks/useDebounce-CKqkM42n.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDebounce-CfmUMFau.js","names":[],"sources":["../../../src/client/shared/types.ts","../../../src/client/shared/utils.ts","../../../src/client/shared/queryKey.ts","../../../src/client/hooks/useDebounceQuery.ts","../../../src/client/hooks/queries/useCubeLoadQuery.ts","../../../src/client/utils/multiQueryUtils.ts","../../../src/client/hooks/queries/useMultiCubeLoadQuery.ts","../../../src/client/hooks/queries/useFunnelQuery.ts","../../../src/client/hooks/queries/useFlowQuery.ts","../../../src/client/hooks/queries/useRetentionQuery.ts","../../../src/client/hooks/useFilterValues.ts","../../../src/client/hooks/useDebounce.ts"],"sourcesContent":["/**\n * Shared type definitions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, FilterOperator } from '../types'\n\n// ============================================================================\n// Meta endpoint response types\n// ============================================================================\n\nexport interface MetaField {\n name: string // e.g., \"Employees.count\"\n title: string // e.g., \"Total Employees\"\n shortTitle: string // e.g., \"Total Employees\"\n type: string // e.g., \"count\", \"string\", \"time\", \"number\"\n description?: string // Optional description\n}\n\nexport interface MetaCubeRelationship {\n targetCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinFields?: Array<{\n sourceField: string\n targetField: string\n }>\n}\n\nexport interface MetaCube {\n name: string // e.g., \"Employees\"\n title: string // e.g., \"Employee Analytics\"\n description: string // e.g., \"Employee data and metrics\"\n measures: MetaField[] // e.g., \"Employees.count\"\n dimensions: MetaField[] // e.g., \"Employees.name\"\n segments: MetaField[] // Currently empty in examples\n relationships?: MetaCubeRelationship[] // Optional join relationships to other cubes\n}\n\nexport interface MetaResponse {\n cubes: MetaCube[]\n}\n\n// ============================================================================\n// Query warning types (from server-side query planner)\n// ============================================================================\n\n/**\n * Severity level for query warnings\n */\nexport type QueryWarningSeverity = 'info' | 'warning' | 'error'\n\n/**\n * Warning emitted during query planning or execution\n * Provides user-facing feedback about potential query issues\n */\nexport interface QueryWarning {\n /** Unique code for programmatic handling (e.g., 'FAN_OUT_NO_DIMENSIONS') */\n code: string\n /** Human-readable warning message */\n message: string\n /** Severity level for UI styling */\n severity: QueryWarningSeverity\n /** Cubes involved in the warning (if applicable) */\n cubes?: string[]\n /** Measures involved in the warning (if applicable) */\n measures?: string[]\n /** Actionable suggestion for the user */\n suggestion?: string\n}\n\n// ============================================================================\n// Query analysis types for debugging transparency\n// ============================================================================\n\nexport type PrimaryCubeSelectionReason =\n | 'most_dimensions'\n | 'most_connected'\n | 'alphabetical_fallback'\n | 'single_cube'\n\nexport interface PrimaryCubeCandidate {\n cubeName: string\n dimensionCount: number\n joinCount: number\n canReachAll: boolean\n}\n\nexport interface PrimaryCubeAnalysis {\n selectedCube: string\n reason: PrimaryCubeSelectionReason\n explanation: string\n candidates?: PrimaryCubeCandidate[]\n}\n\nexport interface JoinPathStep {\n fromCube: string\n toCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinType: 'inner' | 'left' | 'right' | 'full'\n joinColumns: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n junctionTable?: {\n tableName: string\n sourceColumns: string[]\n targetColumns: string[]\n }\n}\n\nexport interface JoinPathAnalysis {\n targetCube: string\n pathFound: boolean\n path?: JoinPathStep[]\n pathLength?: number\n error?: string\n visitedCubes?: string[]\n selection?: {\n strategy: 'shortest' | 'preferred' | 'fallbackShortest'\n preferredCubes?: string[]\n selectedRank?: number\n selectedScore?: number\n candidates?: Array<{\n rank: number\n score: number\n usesPreferredJoin: boolean\n preferredCubesInPath: number\n usesProcessed: boolean\n scoreBreakdown: {\n preferredJoinBonus: number\n preferredCubeBonus: number\n lengthPenalty: number\n }\n path: JoinPathStep[]\n }>\n }\n}\n\n/**\n * Reason why a cube requires a CTE\n */\nexport type CTEReason = 'hasMany' | 'fanOutPrevention'\n\nexport interface PreAggregationAnalysis {\n cubeName: string\n cteAlias: string\n /** Why this cube needs a CTE (human-readable explanation) */\n reason: string\n /** Typed reason for programmatic use */\n reasonType?: CTEReason\n measures: string[]\n joinKeys: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n}\n\nexport interface QuerySummary {\n queryType: 'single_cube' | 'multi_cube_join' | 'multi_cube_cte'\n measureStrategy?: 'simple' | 'keysDeduplication' | 'ctePreAggregateFallback' | 'multiFactMerge'\n joinCount: number\n cteCount: number\n hasPreAggregation: boolean\n}\n\nexport interface QueryAnalysis {\n timestamp: string\n cubeCount: number\n cubesInvolved: string[]\n primaryCube: PrimaryCubeAnalysis\n joinPaths: JoinPathAnalysis[]\n preAggregations: PreAggregationAnalysis[]\n querySummary: QuerySummary\n warnings?: string[]\n planningTrace?: {\n steps: Array<{\n phase: 'cube_usage' | 'primary_cube_selection' | 'join_planning' | 'cte_planning' | 'measure_strategy' | 'warnings'\n decision: string\n details?: Record<string, unknown>\n }>\n }\n}\n\n// ============================================================================\n// Validation response from /dry-run endpoint\n// ============================================================================\n\nexport interface ValidationResult {\n valid?: boolean // Our custom property (may not be present in official Cube.js)\n error?: string\n query?: CubeQuery\n sql?: {\n sql: string[]\n params: any[]\n }\n queryType?: string // Always present in successful Cube.js responses\n normalizedQueries?: any[]\n queryOrder?: string[]\n transformedQueries?: any[]\n pivotQuery?: any\n complexity?: string\n cubesUsed?: string[]\n joinType?: string\n // Query analysis for debugging transparency\n analysis?: QueryAnalysis\n}\n\n// ============================================================================\n// Filter operator metadata\n// ============================================================================\n\nexport interface FilterOperatorMeta {\n label: string\n description: string\n requiresValues: boolean\n supportsMultipleValues: boolean\n valueType: 'string' | 'number' | 'date' | 'boolean' | 'any'\n fieldTypes: string[] // Which field types support this operator\n}\n\nexport const FILTER_OPERATORS: Record<FilterOperator, FilterOperatorMeta> = {\n // String operators\n equals: {\n label: 'filter.operator.equals.label',\n description: 'filter.operator.equals.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n notEquals: {\n label: 'filter.operator.notEquals.label',\n description: 'filter.operator.notEquals.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n contains: {\n label: 'filter.operator.contains.label',\n description: 'filter.operator.contains.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notContains: {\n label: 'filter.operator.notContains.label',\n description: 'filter.operator.notContains.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n startsWith: {\n label: 'filter.operator.startsWith.label',\n description: 'filter.operator.startsWith.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notStartsWith: {\n label: 'filter.operator.notStartsWith.label',\n description: 'filter.operator.notStartsWith.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n endsWith: {\n label: 'filter.operator.endsWith.label',\n description: 'filter.operator.endsWith.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notEndsWith: {\n label: 'filter.operator.notEndsWith.label',\n description: 'filter.operator.notEndsWith.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n like: {\n label: 'filter.operator.like.label',\n description: 'filter.operator.like.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notLike: {\n label: 'filter.operator.notLike.label',\n description: 'filter.operator.notLike.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n ilike: {\n label: 'filter.operator.ilike.label',\n description: 'filter.operator.ilike.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Numeric operators\n gt: {\n label: 'filter.operator.gt.label',\n description: 'filter.operator.gt.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n gte: {\n label: 'filter.operator.gte.label',\n description: 'filter.operator.gte.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lt: {\n label: 'filter.operator.lt.label',\n description: 'filter.operator.lt.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lte: {\n label: 'filter.operator.lte.label',\n description: 'filter.operator.lte.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n between: {\n label: 'filter.operator.between.label',\n description: 'filter.operator.between.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n notBetween: {\n label: 'filter.operator.notBetween.label',\n description: 'filter.operator.notBetween.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n // Array operators\n in: {\n label: 'filter.operator.in.label',\n description: 'filter.operator.in.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n notIn: {\n label: 'filter.operator.notIn.label',\n description: 'filter.operator.notIn.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n // Null/Empty operators\n set: {\n label: 'filter.operator.set.label',\n description: 'filter.operator.set.description',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n notSet: {\n label: 'filter.operator.notSet.label',\n description: 'filter.operator.notSet.description',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n isEmpty: {\n label: 'filter.operator.isEmpty.label',\n description: 'filter.operator.isEmpty.description',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n isNotEmpty: {\n label: 'filter.operator.isNotEmpty.label',\n description: 'filter.operator.isNotEmpty.description',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Date operators\n inDateRange: {\n label: 'filter.operator.inDateRange.label',\n description: 'filter.operator.inDateRange.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n beforeDate: {\n label: 'filter.operator.beforeDate.label',\n description: 'filter.operator.beforeDate.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n afterDate: {\n label: 'filter.operator.afterDate.label',\n description: 'filter.operator.afterDate.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n // Regex operators\n regex: {\n label: 'filter.operator.regex.label',\n description: 'filter.operator.regex.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notRegex: {\n label: 'filter.operator.notRegex.label',\n description: 'filter.operator.notRegex.description',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // PostgreSQL array operators\n arrayContains: {\n label: 'filter.operator.arrayContains.label',\n description: 'filter.operator.arrayContains.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayOverlaps: {\n label: 'filter.operator.arrayOverlaps.label',\n description: 'filter.operator.arrayOverlaps.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayContained: {\n label: 'filter.operator.arrayContained.label',\n description: 'filter.operator.arrayContained.description',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n }\n}\n\n// ============================================================================\n// Date range types\n// ============================================================================\n\nexport type DateRangeType =\n | 'custom'\n | 'today'\n | 'yesterday'\n | 'this_week'\n | 'this_month'\n | 'this_quarter'\n | 'this_year'\n | 'last_7_days'\n | 'last_30_days'\n | 'last_week'\n | 'last_month'\n | 'last_quarter'\n | 'last_year'\n | 'last_12_months'\n | 'last_n_days'\n | 'last_n_weeks'\n | 'last_n_months'\n | 'last_n_quarters'\n | 'last_n_years'\n\nexport interface DateRangeOption {\n value: DateRangeType\n label: string\n}\n\nexport const DATE_RANGE_OPTIONS: DateRangeOption[] = [\n { value: 'custom', label: 'dateRange.custom' },\n { value: 'today', label: 'dateRange.today' },\n { value: 'yesterday', label: 'dateRange.yesterday' },\n { value: 'this_week', label: 'dateRange.thisWeek' },\n { value: 'this_month', label: 'dateRange.thisMonth' },\n { value: 'this_quarter', label: 'dateRange.thisQuarter' },\n { value: 'this_year', label: 'dateRange.thisYear' },\n { value: 'last_7_days', label: 'dateRange.last7Days' },\n { value: 'last_30_days', label: 'dateRange.last30Days' },\n { value: 'last_n_days', label: 'dateRange.lastNDays' },\n { value: 'last_week', label: 'dateRange.lastWeek' },\n { value: 'last_n_weeks', label: 'dateRange.lastNWeeks' },\n { value: 'last_month', label: 'dateRange.lastMonth' },\n { value: 'last_12_months', label: 'dateRange.last12Months' },\n { value: 'last_n_months', label: 'dateRange.lastNMonths' },\n { value: 'last_quarter', label: 'dateRange.lastQuarter' },\n { value: 'last_n_quarters', label: 'dateRange.lastNQuarters' },\n { value: 'last_year', label: 'dateRange.lastYear' },\n { value: 'last_n_years', label: 'dateRange.lastNYears' }\n]\n\n// ============================================================================\n// Time dimension granularity options\n// ============================================================================\n\nexport const TIME_GRANULARITIES = [\n { value: 'hour', label: 'timeGranularity.hour' },\n { value: 'day', label: 'timeGranularity.day' },\n { value: 'week', label: 'timeGranularity.week' },\n { value: 'month', label: 'timeGranularity.month' },\n { value: 'quarter', label: 'timeGranularity.quarter' },\n { value: 'year', label: 'timeGranularity.year' }\n]\n\nexport type TimeGranularity = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'\n","/**\n * Shared utility functions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, Filter, SimpleFilter, GroupFilter } from '../types'\nimport type { MetaField, MetaResponse } from './types'\nimport { FILTER_OPERATORS } from './types'\n\n// ============================================================================\n// Filter type guards\n// ============================================================================\n\n/**\n * Check if a filter is a simple filter\n */\nexport function isSimpleFilter(filter: Filter): filter is SimpleFilter {\n return 'member' in filter && 'operator' in filter && 'values' in filter\n}\n\n/**\n * Check if a filter is a group filter\n */\nexport function isGroupFilter(filter: Filter): filter is GroupFilter {\n return 'type' in filter && 'filters' in filter\n}\n\n/**\n * Check if a filter is an AND filter\n */\nexport function isAndFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'and'\n}\n\n/**\n * Check if a filter is an OR filter\n */\nexport function isOrFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'or'\n}\n\n// ============================================================================\n// Filter manipulation functions\n// ============================================================================\n\n/**\n * Flatten all simple filters from a hierarchical filter structure\n */\nexport function flattenFilters(filters: Filter[]): SimpleFilter[] {\n const simple: SimpleFilter[] = []\n\n const flatten = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n simple.push(filter)\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(flatten)\n }\n }\n\n filters.forEach(flatten)\n return simple\n}\n\n/**\n * Count total filters in hierarchical structure\n */\nexport function countFilters(filters: Filter[]): number {\n let count = 0\n\n const countFilter = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n count++\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(countFilter)\n }\n }\n\n filters.forEach(countFilter)\n return count\n}\n\n/**\n * Create a new simple filter\n */\nexport function createSimpleFilter(member: string, operator: string = 'equals', values: any[] = []): SimpleFilter {\n return {\n member,\n operator: operator as any,\n values\n }\n}\n\n/**\n * Create a new AND filter group\n */\nexport function createAndFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'and',\n filters\n }\n}\n\n/**\n * Create a new OR filter group\n */\nexport function createOrFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'or',\n filters\n }\n}\n\n/**\n * Clean up filters - backward compatible (returns filters unchanged)\n * @deprecated This function is no longer used as we now support filtering on any schema field\n */\nexport function cleanupFilters(filters: Filter[], _query?: CubeQuery): Filter[] {\n return filters || []\n}\n\n// ============================================================================\n// Filter transformation functions\n// ============================================================================\n\n/**\n * Transform filters from new GroupFilter format to legacy server format\n * Server expects { and: [...] } and { or: [...] } instead of { type: 'and', filters: [...] }\n */\nexport function transformFiltersForServer(filters: Filter[]): any[] {\n const transformFilter = (filter: Filter): any => {\n if (isSimpleFilter(filter)) {\n return filter\n } else if (isGroupFilter(filter)) {\n const transformedSubFilters = filter.filters.map(transformFilter)\n\n if (filter.type === 'and') {\n return { and: transformedSubFilters }\n } else {\n return { or: transformedSubFilters }\n }\n }\n return filter\n }\n\n return filters.map(transformFilter)\n}\n\n/**\n * Transform filters from server/API format to UI format\n * Converts {and: [...]} and {or: [...]} to {type: 'and', filters: [...]} format\n */\nexport function transformFiltersFromServer(filters: any[]): Filter[] {\n return filters.map(filter => {\n if (!filter || typeof filter !== 'object') {\n return filter\n }\n\n // Handle legacy {and: [...]} format\n if ('and' in filter && Array.isArray(filter.and)) {\n return {\n type: 'and',\n filters: transformFiltersFromServer(filter.and)\n } as GroupFilter\n }\n\n // Handle legacy {or: [...]} format\n if ('or' in filter && Array.isArray(filter.or)) {\n return {\n type: 'or',\n filters: transformFiltersFromServer(filter.or)\n } as GroupFilter\n }\n\n // Handle new format {type: 'and', filters: [...]} - process recursively\n if ('type' in filter && 'filters' in filter && Array.isArray(filter.filters)) {\n return {\n type: filter.type,\n filters: transformFiltersFromServer(filter.filters)\n } as GroupFilter\n }\n\n // Simple filter - pass through\n return filter as SimpleFilter\n }).filter(Boolean) // Remove any null/undefined values\n}\n\n// ============================================================================\n// Query utility functions\n// ============================================================================\n\n/**\n * Check if query has any content (measures, dimensions, or timeDimensions)\n */\nexport function hasQueryContent(query: CubeQuery): boolean {\n return Boolean(\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0)\n )\n}\n\n/**\n * Clean query object by removing empty arrays\n */\nexport function cleanQuery(query: CubeQuery): CubeQuery {\n const cleanedQuery: CubeQuery = {}\n\n if (query.measures && query.measures.length > 0) {\n cleanedQuery.measures = query.measures\n }\n\n if (query.dimensions && query.dimensions.length > 0) {\n cleanedQuery.dimensions = query.dimensions\n }\n\n if (query.timeDimensions && query.timeDimensions.length > 0) {\n cleanedQuery.timeDimensions = query.timeDimensions\n }\n\n if (query.filters && query.filters.length > 0) {\n cleanedQuery.filters = query.filters\n }\n\n if (query.order) {\n cleanedQuery.order = query.order\n }\n\n if (query.limit) {\n cleanedQuery.limit = query.limit\n }\n\n if (query.offset) {\n cleanedQuery.offset = query.offset\n }\n\n if (query.segments && query.segments.length > 0) {\n cleanedQuery.segments = query.segments\n }\n\n return cleanedQuery\n}\n\n/**\n * Clean a query and transform filters for server compatibility\n * This version transforms GroupFilter to legacy and/or format\n */\nexport function cleanQueryForServer(query: CubeQuery): CubeQuery {\n const cleanedQuery = cleanQuery(query)\n\n // Apply server transformation to filters\n if (cleanedQuery.filters && cleanedQuery.filters.length > 0) {\n cleanedQuery.filters = transformFiltersForServer(cleanedQuery.filters) as any\n }\n\n return cleanedQuery\n}\n\n/**\n * Transform a Cube.js query from external format to UI internal format\n * This handles format differences between server/API queries and QueryBuilder state\n */\nexport function transformQueryForUI(query: any): CubeQuery {\n if (!query || typeof query !== 'object') {\n return {}\n }\n\n const transformed: CubeQuery = {}\n\n // Copy simple fields if they exist\n if (query.measures) transformed.measures = Array.isArray(query.measures) ? query.measures : []\n if (query.dimensions) transformed.dimensions = Array.isArray(query.dimensions) ? query.dimensions : []\n if (query.timeDimensions) transformed.timeDimensions = Array.isArray(query.timeDimensions) ? query.timeDimensions : []\n if (query.order) transformed.order = query.order\n if (query.limit) transformed.limit = query.limit\n if (query.offset) transformed.offset = query.offset\n if (query.segments) transformed.segments = Array.isArray(query.segments) ? query.segments : []\n\n // Transform filters from server format to UI format\n if (query.filters && Array.isArray(query.filters)) {\n transformed.filters = transformFiltersFromServer(query.filters)\n }\n\n return cleanQuery(transformed)\n}\n\n// ============================================================================\n// Schema utility functions\n// ============================================================================\n\n/**\n * Get cube name from field name (e.g., \"Employees.count\" -> \"Employees\")\n */\nexport function getCubeNameFromField(fieldName: string): string {\n return fieldName.split('.')[0]\n}\n\n/**\n * Get field type from schema\n */\nexport function getFieldType(fieldName: string, schema: MetaResponse): string {\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.type\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.type\n }\n\n return 'string' // Default fallback\n}\n\n/**\n * Get field title from schema metadata, falling back to field name\n */\nexport function getFieldTitle(fieldName: string, schema: MetaResponse | null): string {\n if (!schema) return fieldName\n\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.title || measure.shortTitle || fieldName\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.title || dimension.shortTitle || fieldName\n }\n\n return fieldName // Fallback to field name if not found\n}\n\n/**\n * Get available operators for a field type\n */\nexport function getAvailableOperators(fieldType: string): Array<{operator: string, label: string}> {\n const operators: Array<{operator: string, label: string}> = []\n\n for (const [operator, meta] of Object.entries(FILTER_OPERATORS)) {\n if (meta.fieldTypes.includes(fieldType)) {\n operators.push({\n operator,\n label: meta.label\n })\n }\n }\n\n return operators\n}\n\n/**\n * Get ALL filterable fields from schema\n */\nexport function getAllFilterableFields(schema: MetaResponse): MetaField[] {\n const allFields: MetaField[] = []\n\n schema.cubes.forEach(cube => {\n allFields.push(...cube.measures)\n allFields.push(...cube.dimensions)\n })\n\n return allFields.sort((a, b) => a.name.localeCompare(b.name))\n}\n\n// ============================================================================\n// Date range utility functions\n// ============================================================================\n\n/**\n * Convert DateRangeType to Cube.js compatible date range format\n */\nexport function convertDateRangeTypeToValue(rangeType: string, number?: number): string {\n const typeMap: Record<string, string> = {\n 'today': 'today',\n 'yesterday': 'yesterday',\n 'this_week': 'this week',\n 'this_month': 'this month',\n 'this_quarter': 'this quarter',\n 'this_year': 'this year',\n 'last_7_days': 'last 7 days',\n 'last_30_days': 'last 30 days',\n 'last_week': 'last week',\n 'last_month': 'last month',\n 'last_quarter': 'last quarter',\n 'last_year': 'last year',\n 'last_12_months': 'last 12 months'\n }\n\n // Handle dynamic ranges with number input\n if (rangeType.startsWith('last_n_') && number !== undefined && number > 0) {\n const unit = rangeType.replace('last_n_', '')\n const unitSingular = unit.slice(0, -1) // Remove 's' for singular form\n return number === 1 ? `last ${unitSingular}` : `last ${number} ${unit}`\n }\n\n return typeMap[rangeType] || rangeType\n}\n\n/**\n * Check if a date range type requires a number input\n */\nexport function requiresNumberInput(rangeType: string): boolean {\n return rangeType.startsWith('last_n_')\n}\n\n/**\n * Format date for Cube.js (YYYY-MM-DD)\n */\nexport function formatDateForCube(date: Date): string {\n return date.toISOString().split('T')[0]\n}\n","export function stableStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n\n const stringify = (input: unknown): string => {\n if (input === null || typeof input !== 'object') {\n return JSON.stringify(input)\n }\n\n if (seen.has(input as object)) {\n return '\"[Circular]\"'\n }\n seen.add(input as object)\n\n if (Array.isArray(input)) {\n return `[${input.map((item) => stringify(item)).join(',')}]`\n }\n\n const record = input as Record<string, unknown>\n const keys = Object.keys(record).sort()\n const props = keys.map((key) => `${JSON.stringify(key)}:${stringify(record[key])}`)\n return `{${props.join(',')}}`\n }\n\n return stringify(value)\n}\n","/**\n * useDebounceQuery - Shared debounce logic for query hooks\n *\n * This hook encapsulates the common debouncing pattern used by\n * useCubeLoadQuery and useMultiCubeLoadQuery to prevent excessive API calls\n * when users are actively editing queries.\n *\n * Features:\n * - Debounces value changes with configurable delay\n * - Handles skip-to-unskip transitions (e.g., portlet becoming visible)\n * - Clears debounced value when invalid or skipped\n * - Provides isDebouncing state for UI feedback\n */\n\nimport { useState, useEffect, useRef, useMemo } from 'react'\nimport { stableStringify } from '../shared/queryKey'\n\nexport interface UseDebounceQueryOptions {\n /**\n * Whether the value is valid (has required fields)\n */\n isValid: boolean\n /**\n * Whether to skip the debounced value\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n}\n\nexport interface UseDebounceQueryResult<T> {\n /** The debounced value (null if skipped or invalid) */\n debouncedValue: T | null\n /** Whether the hook is currently debouncing (waiting for timer) */\n isDebouncing: boolean\n}\n\n/**\n * Hook for debouncing query values with skip and validity support\n *\n * Usage:\n * ```tsx\n * const { debouncedValue, isDebouncing } = useDebounceQuery(query, {\n * isValid: isValidCubeQuery(query),\n * skip: !isReady,\n * debounceMs: 300\n * })\n * ```\n */\nexport function useDebounceQuery<T>(\n value: T | null,\n options: UseDebounceQueryOptions\n): UseDebounceQueryResult<T> {\n const { isValid, skip = false, debounceMs = 300 } = options\n\n // Debounced state\n const [debouncedValue, setDebouncedValue] = useState<T | null>(null)\n const [isDebouncing, setIsDebouncing] = useState(false)\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const lastValueStringRef = useRef<string>('')\n const wasSkippedRef = useRef<boolean>(skip)\n\n // Serialize value for comparison\n const valueString = useMemo(() => {\n if (!value) return ''\n return stableStringify(value)\n }, [value])\n\n // Debounce the value changes\n useEffect(() => {\n // Detect skip-to-unskip transition (e.g., portlet becoming visible)\n const wasSkipped = wasSkippedRef.current\n const justBecameUnskipped = wasSkipped && !skip\n wasSkippedRef.current = skip\n\n // Skip if value hasn't actually changed AND we haven't just become unskipped\n // The justBecameUnskipped check ensures we re-trigger when visibility changes\n if (valueString === lastValueStringRef.current && !justBecameUnskipped) {\n return\n }\n\n // Clear existing timer\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n\n // If value is valid, set debouncing state and schedule update\n if (isValid && !skip) {\n setIsDebouncing(true)\n debounceTimerRef.current = setTimeout(() => {\n lastValueStringRef.current = valueString\n setDebouncedValue(value)\n setIsDebouncing(false)\n }, debounceMs)\n } else {\n // Clear debounced value if invalid or skipped\n lastValueStringRef.current = valueString\n setDebouncedValue(null)\n setIsDebouncing(false)\n }\n\n return () => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n }\n }, [valueString, isValid, skip, debounceMs, value])\n\n return {\n debouncedValue,\n isDebouncing,\n }\n}\n","/**\n * useCubeLoadQuery - TanStack Query hook for cube data loading\n *\n * Features:\n * - Built-in debouncing to prevent excessive API calls\n * - Automatic query deduplication\n * - Background refetch support\n * - Proper loading/error states\n * - Query key based on query content for caching\n *\n * This hook replaces the manual debouncing and query execution\n * in useQueryExecution.\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo, useState, useCallback, useEffect, useRef } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport type { CubeQuery, CubeResultSet } from '../../types'\nimport type { QueryWarning } from '../../shared/types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key from a CubeQuery\n * The key includes all query parameters to ensure proper caching\n */\nexport function createQueryKey(query: CubeQuery | null): readonly unknown[] {\n if (!query) return ['cube', 'load', null] as const\n // Use JSON.stringify for deep equality comparison\n return ['cube', 'load', stableStringify(query)] as const\n}\n\nexport interface UseCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\n/** Options for the refetch function */\nexport interface RefetchOptions {\n /** If true, bypasses both client and server caches */\n bustCache?: boolean\n}\n\nexport interface UseCubeLoadQueryResult {\n /** The result set from the query */\n resultSet: CubeResultSet | null\n /** Raw data from the result set */\n rawData: unknown[] | null\n /** Whether the query is loading (initial load) */\n isLoading: boolean\n /** Whether the query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** Error if the query failed */\n error: Error | null\n /** The debounced query that was executed */\n debouncedQuery: CubeQuery | null\n /** Whether the current query is valid */\n isValidQuery: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass caches. */\n refetch: (options?: RefetchOptions) => void\n /** Clear the query cache */\n clearCache: () => void\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n /**\n * Execute the current query (manual refresh mode only).\n * In auto-refresh mode, this is the same as refetch().\n */\n executeQuery: (options?: RefetchOptions) => void\n /** Warnings from query planning (e.g., fan-out without dimensions) */\n warnings: QueryWarning[] | undefined\n}\n\n/**\n * Check if a query is valid (has at least one measure or dimension)\n */\nfunction isValidCubeQuery(query: CubeQuery | null): boolean {\n if (!query) return false\n const hasMeasures = Boolean(query.measures && query.measures.length > 0)\n const hasDimensions = Boolean(query.dimensions && query.dimensions.length > 0)\n const hasTimeDimensions = Boolean(query.timeDimensions && query.timeDimensions.length > 0)\n return hasMeasures || hasDimensions || hasTimeDimensions\n}\n\n/**\n * TanStack Query hook for loading cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { resultSet, rawData, isLoading, error } = useCubeLoadQuery(query, {\n * debounceMs: 300,\n * skip: !isReady\n * })\n * ```\n */\nexport function useCubeLoadQuery(\n query: CubeQuery | null,\n options: UseCubeLoadQueryOptions = {}\n): UseCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n // This is the query that was last sent to the server\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidCubeQuery(query)\n\n // Silence unused variable warning - used for future functionality\n void resetResultSetOnChange\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(query, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Transform query for server (converts filter groups)\n const serverQuery = useMemo(() => {\n if (!debouncedQuery) return null\n return cleanQueryForServer(debouncedQuery)\n }, [debouncedQuery])\n\n // Calculate if the current query differs from the last executed query\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // Ref to track when the next fetch should bust the cache\n // This is used instead of replacing the queryFn to avoid the queryFn getting \"stuck\" with bustCache=true\n const bustCacheRef = useRef(false)\n\n // Execute query with TanStack Query\n const queryResult = useQuery({\n queryKey: createQueryKey(serverQuery),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n\n // Check if this fetch should bust the cache\n const shouldBustCache = bustCacheRef.current\n // Reset the flag immediately so subsequent fetches don't bust cache\n bustCacheRef.current = false\n\n // When busting cache, bypass batch coordinator and make direct API call\n if (shouldBustCache) {\n return cubeApi.load(serverQuery, { bustCache: true })\n }\n\n // Use batch coordinator if enabled (collects queries for 100ms window)\n if (enableBatching && batchCoordinator) {\n return batchCoordinator.register(serverQuery)\n }\n\n // Fall back to direct load when batching disabled\n return cubeApi.load(serverQuery)\n },\n enabled: shouldExecute,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Extract raw data from result set\n const rawData = useMemo(() => {\n if (!queryResult.data) return null\n try {\n return queryResult.data.rawData()\n } catch {\n return null\n }\n }, [queryResult.data])\n\n // Extract warnings from result set\n const warnings = useMemo((): QueryWarning[] | undefined => {\n if (!queryResult.data?.loadResponse) return undefined\n const lr = queryResult.data.loadResponse\n // Handle nested structure: loadResponse.results[0].warnings\n if (lr.results && lr.results[0]?.warnings) {\n return lr.results[0].warnings\n }\n // Handle flat structure: loadResponse.warnings\n return lr.warnings\n }, [queryResult.data])\n\n // Execute query function - for manual refresh mode, triggers execution\n // Also serves as refetch in auto mode\n const executeQuery = useCallback((options?: RefetchOptions) => {\n if (!serverQuery) return\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Set the ref flag so the queryFn knows to bypass cache\n // The flag is reset inside queryFn after reading it\n bustCacheRef.current = true\n }\n\n // Invalidate and refetch - invalidateQueries marks as stale AND triggers refetch\n // when the query is being observed (which it is, via useQuery)\n queryClient.invalidateQueries({ queryKey: createQueryKey(serverQuery) })\n }, [serverQuery, currentQueryKey, queryClient])\n\n // Refetch is an alias for executeQuery for backward compatibility\n const refetch = executeQuery\n\n // Clear cache function\n const clearCache = () => {\n queryClient.removeQueries({ queryKey: ['cube', 'load'] })\n }\n\n // Handle resetResultSetOnChange\n const resultSet = useMemo(() => {\n if (resetResultSetOnChange && isDebouncing) {\n // Keep showing old data while debouncing\n return queryResult.data ?? null\n }\n return queryResult.data ?? null\n }, [queryResult.data, isDebouncing, resetResultSetOnChange])\n\n return {\n resultSet,\n rawData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error,\n debouncedQuery,\n isValidQuery,\n refetch,\n clearCache,\n needsRefresh,\n executeQuery,\n warnings,\n }\n}\n","/**\n * Multi-Query Data Utilities\n * Handles merging results from multiple CubeQuery executions\n *\n * Pattern follows comparisonUtils.ts for metadata injection:\n * - __queryIndex: numeric index of the source query (0-based)\n * - __queryLabel: user-defined or auto-generated label for the query\n */\n\nimport type { CubeResultSet, CubeQuery, QueryMergeStrategy } from '../types'\n\n/**\n * Metadata fields injected into multi-query data\n */\nexport interface MultiQueryMetadata {\n __queryIndex: number\n __queryLabel: string\n}\n\n/**\n * Check if data contains multi-query metadata\n */\nexport function isMultiQueryData(data: unknown[]): boolean {\n return data.length > 0 && typeof data[0] === 'object' && data[0] !== null && '__queryIndex' in data[0]\n}\n\n/**\n * Get unique query labels from multi-query data\n */\nexport function getQueryLabels(data: unknown[]): string[] {\n if (!isMultiQueryData(data)) return []\n\n const labels = new Set<string>()\n for (const row of data) {\n const label = (row as Record<string, unknown>).__queryLabel\n if (typeof label === 'string') {\n labels.add(label)\n }\n }\n return Array.from(labels)\n}\n\n/**\n * Get query indices from multi-query data\n */\nexport function getQueryIndices(data: unknown[]): number[] {\n if (!isMultiQueryData(data)) return []\n\n const indices = new Set<number>()\n for (const row of data) {\n const index = (row as Record<string, unknown>).__queryIndex\n if (typeof index === 'number') {\n indices.add(index)\n }\n }\n return Array.from(indices).sort((a, b) => a - b)\n}\n\n/**\n * Merge results using 'concat' strategy\n * Appends all rows with __queryIndex and __queryLabel metadata\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array with query metadata\n */\nexport function mergeResultsConcat(\n resultSets: CubeResultSet[],\n _queries: CubeQuery[],\n labels?: string[]\n): unknown[] {\n const merged: unknown[] = []\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const label = labels?.[queryIndex] || `Query ${queryIndex + 1}`\n\n data.forEach(row => {\n merged.push({\n ...row,\n __queryIndex: queryIndex,\n __queryLabel: label\n })\n })\n })\n\n return merged\n}\n\n/**\n * Merge results using 'merge' strategy\n * Aligns data by common dimensions (composite key), combining measures from all queries\n *\n * Example:\n * Query 1: [{ date: '2024-01', revenue: 100 }]\n * Query 2: [{ date: '2024-01', cost: 50 }]\n * Result: [{ date: '2024-01', revenue: 100, cost: 50 }]\n *\n * If multiple queries have the same measure, the first query's value is used.\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param mergeKeys - Dimension fields to align data on (composite key)\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Merged data array with combined measures\n */\nexport function mergeResultsByKey(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n mergeKeys: string[],\n _labels?: string[]\n): unknown[] {\n const mergedMap = new Map<string, Record<string, unknown>>()\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const measures = queries[queryIndex].measures || []\n\n data.forEach(row => {\n // Create composite key from all merge dimensions\n const keyValue = mergeKeys.map(k => String(row[k] ?? '')).join('|')\n\n if (!mergedMap.has(keyValue)) {\n // Initialize with all dimension values\n const baseRow: Record<string, unknown> = {}\n mergeKeys.forEach(k => { baseRow[k] = row[k] })\n mergedMap.set(keyValue, baseRow)\n }\n\n const mergedRow = mergedMap.get(keyValue)!\n\n // Add measures using raw field names (no prefix)\n // If same measure exists in multiple queries, first one wins\n measures.forEach(measure => {\n if (!(measure in mergedRow)) {\n mergedRow[measure] = row[measure]\n }\n })\n\n // Copy other dimensions (non-measure, non-merge-key fields) from first query\n if (queryIndex === 0) {\n Object.keys(row).forEach(field => {\n if (!mergeKeys.includes(field) && !measures.includes(field)) {\n if (!(field in mergedRow)) {\n mergedRow[field] = row[field]\n }\n }\n })\n }\n })\n })\n\n // Sort by first merge key for consistent ordering\n return Array.from(mergedMap.values()).sort((a, b) => {\n const aKey = String(a[mergeKeys[0]] ?? '')\n const bKey = String(b[mergeKeys[0]] ?? '')\n return aKey.localeCompare(bKey)\n })\n}\n\n/**\n * Main entry point for merging query results\n * Delegates to appropriate strategy implementation\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param strategy - Merge strategy ('concat' or 'merge')\n * @param mergeKeys - Dimension fields to align on (required for 'merge' strategy)\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array\n */\nexport function mergeQueryResults(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n strategy: QueryMergeStrategy,\n mergeKeys?: string[],\n labels?: string[]\n): unknown[] {\n // Handle edge cases\n if (resultSets.length === 0) return []\n if (resultSets.length === 1) return resultSets[0].rawData()\n\n // Use merge strategy if we have merge keys\n if (strategy === 'merge' && mergeKeys && mergeKeys.length > 0) {\n return mergeResultsByKey(resultSets, queries, mergeKeys, labels)\n }\n\n // Fall back to concat strategy\n return mergeResultsConcat(resultSets, queries, labels)\n}\n\n/**\n * Get combined fields from all queries\n * Used for chart configuration to show all available measures/dimensions\n *\n * @param queries - Array of CubeQuery objects\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Object containing combined measures, dimensions, and time dimensions\n */\nexport function getCombinedFields(\n queries: CubeQuery[],\n _labels?: string[]\n): {\n measures: string[]\n dimensions: string[]\n timeDimensions: string[]\n} {\n const measures = new Set<string>()\n const dimensions = new Set<string>()\n const timeDimensions = new Set<string>()\n\n queries.forEach((query) => {\n // Measures use raw field names (no prefix), de-duplicated\n query.measures?.forEach(m => measures.add(m))\n\n // Dimensions are shared across queries (de-duplicated)\n query.dimensions?.forEach(d => dimensions.add(d))\n\n // Time dimensions are also shared\n query.timeDimensions?.forEach(td => timeDimensions.add(td.dimension))\n })\n\n return {\n measures: Array.from(measures),\n dimensions: Array.from(dimensions),\n timeDimensions: Array.from(timeDimensions)\n }\n}\n\n/**\n * Generate a default label for a query based on its measures\n * Used when user doesn't provide custom labels\n */\nexport function generateQueryLabel(query: CubeQuery, index: number): string {\n // Try to use first measure name without cube prefix\n if (query.measures && query.measures.length > 0) {\n const firstMeasure = query.measures[0]\n const parts = firstMeasure.split('.')\n if (parts.length > 1) {\n return parts[parts.length - 1] // Use measure name without cube prefix\n }\n return firstMeasure\n }\n\n // Fall back to indexed label\n return `Query ${index + 1}`\n}\n\n/**\n * Validate merge key exists in all queries\n * Returns validation result with details\n */\nexport function validateMergeKey(\n queries: CubeQuery[],\n mergeKey: string\n): {\n isValid: boolean\n missingInQueries: number[]\n} {\n const missingInQueries: number[] = []\n\n queries.forEach((query, index) => {\n const allDimensions = [\n ...(query.dimensions || []),\n ...(query.timeDimensions?.map(td => td.dimension) || [])\n ]\n\n if (!allDimensions.includes(mergeKey)) {\n missingInQueries.push(index)\n }\n })\n\n return {\n isValid: missingInQueries.length === 0,\n missingInQueries\n }\n}\n","/**\n * useMultiCubeLoadQuery - TanStack Query hook for multi-cube data loading\n *\n * Features:\n * - Execute multiple cube queries in parallel\n * - Merge results using configurable strategies\n * - Built-in debouncing to prevent excessive API calls\n * - Per-query error tracking\n * - BatchCoordinator integration for dashboard-level batching\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { MultiQueryConfig, CubeResultSet } from '../../types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { mergeQueryResults } from '../../utils/multiQueryUtils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key for multi-query\n */\nexport function createMultiQueryKey(\n config: MultiQueryConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'multiLoad', null] as const\n return ['cube', 'multiLoad', stableStringify(config)] as const\n}\n\nexport interface UseMultiCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\nexport interface UseMultiCubeLoadQueryResult {\n /** Merged data from all queries */\n data: unknown[] | null\n /** Individual result sets from each query */\n resultSets: (CubeResultSet | null)[] | null\n /** Per-query raw data */\n perQueryData: (unknown[] | null)[] | null\n /** Whether any query is still loading (initial load) */\n isLoading: boolean\n /** Whether any query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** First error encountered */\n error: Error | null\n /** Per-query errors */\n errors: (Error | null)[]\n /** The debounced config that was executed */\n debouncedConfig: MultiQueryConfig | null\n /** Whether the current config is valid */\n isValidConfig: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n}\n\n/**\n * Check if a MultiQueryConfig is valid (has at least 2 valid queries)\n */\nfunction isValidMultiQueryConfig(config: MultiQueryConfig | null): boolean {\n if (!config || !config.queries || config.queries.length < 2) return false\n\n const validQueries = config.queries.filter(\n (q) =>\n (q.measures && q.measures.length > 0) ||\n (q.dimensions && q.dimensions.length > 0) ||\n (q.timeDimensions && q.timeDimensions.length > 0)\n )\n\n return validQueries.length >= 2\n}\n\n/**\n * TanStack Query hook for loading multi-cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useMultiCubeLoadQuery(config, {\n * debounceMs: 300,\n * skip: !isMultiQueryMode\n * })\n * ```\n */\nexport function useMultiCubeLoadQuery(\n config: MultiQueryConfig | null,\n options: UseMultiCubeLoadQueryOptions = {}\n): UseMultiCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange: _resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n // Silence unused variable warning - used for future functionality\n void _resetResultSetOnChange\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Validate config\n const isValidConfig = isValidMultiQueryConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Transform queries for server\n const serverConfig = useMemo(() => {\n if (!debouncedConfig) return null\n return {\n ...debouncedConfig,\n queries: debouncedConfig.queries.map((q) => cleanQueryForServer(q)),\n }\n }, [debouncedConfig])\n\n // Execute multi-query with TanStack Query\n const queryResult = useQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n if (!serverConfig) throw new Error('No config provided')\n\n let resultSets: CubeResultSet[]\n\n // Use BatchCoordinator if enabled\n if (enableBatching && batchCoordinator) {\n resultSets = await Promise.all(\n serverConfig.queries.map((query) => batchCoordinator.register(query))\n )\n } else {\n // Direct batch call\n resultSets = await cubeApi.batchLoad(serverConfig.queries)\n }\n\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n\n // Get per-query raw data\n const perQueryData: (unknown[] | null)[] = resultSets.map((rs, i) => {\n if (errors[i]) return null\n try {\n return rs.rawData()\n } catch {\n return null\n }\n })\n\n // Filter successful results for merging\n const successfulResults = resultSets.filter((_, i) => !errors[i])\n const successfulQueries = serverConfig.queries.filter((_, i) => !errors[i])\n\n // Merge results using configured strategy\n const data =\n successfulResults.length > 0\n ? mergeQueryResults(\n successfulResults,\n successfulQueries,\n serverConfig.mergeStrategy,\n serverConfig.mergeKeys,\n serverConfig.queryLabels\n )\n : []\n\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n enabled: !!serverConfig && !skip,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // Refetch function - forces immediate refetch\n // Pass { bustCache: true } to bypass both client and server caches\n const refetch = (options?: { bustCache?: boolean }) => {\n if (serverConfig) {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n // Direct batch call with bustCache\n const resultSets = await cubeApi.batchLoad(\n serverConfig.queries,\n { bustCache: true }\n )\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n // Merge results based on strategy\n const data = serverConfig.mergeStrategy === 'concat'\n ? resultSets.flatMap((rs) => rs?.rawData() || [])\n : resultSets[0]?.rawData() || []\n // Keep per-query data for table views\n const perQueryData = serverConfig.mergeStrategy === 'concat'\n ? resultSets.map((rs) => rs?.rawData() || [])\n : []\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n })\n } else {\n queryClient.refetchQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n }\n }\n }\n\n // Extract data from query result\n const data = queryResult.data?.data ?? null\n const resultSets = queryResult.data?.resultSets ?? null\n const perQueryData = queryResult.data?.perQueryData ?? null\n const errors = queryResult.data?.errors ?? []\n const error = queryResult.data?.firstError ?? queryResult.error\n\n return {\n data,\n resultSets,\n perQueryData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error,\n errors,\n debouncedConfig,\n isValidConfig,\n refetch,\n }\n}\n","/**\n * useFunnelQuery - Hook for server-side funnel query execution\n *\n * Executes funnel queries on the server using a single SQL query with\n * CTE-based generation. This provides:\n * - True temporal ordering (step N must occur AFTER step N-1)\n * - Time window enforcement (timeToConvert constraints)\n * - No binding key value limits\n * - Time-to-convert metrics (avg, median, P90)\n *\n * Previously this hook used client-side sequential execution. The server-side\n * approach is strictly better and the data shapes are compatible.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n FunnelConfig,\n FunnelChartData,\n UseFunnelQueryOptions,\n UseFunnelQueryResult,\n FunnelStepResult,\n FunnelExecutionResult,\n} from '../../types/funnel'\nimport {\n buildServerFunnelQuery,\n transformServerFunnelResult,\n} from '../../utils/funnelExecution'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Check if a FunnelConfig is valid for execution\n */\nfunction isValidFunnelConfig(config: FunnelConfig | null): boolean {\n if (!config) return false\n if (!config.bindingKey) return false\n if (!config.steps || config.steps.length < 2) return false\n\n // Check that binding key dimension is defined\n if (typeof config.bindingKey.dimension === 'string') {\n if (!config.bindingKey.dimension) return false\n } else if (Array.isArray(config.bindingKey.dimension)) {\n if (config.bindingKey.dimension.length === 0) return false\n }\n\n // Check that each step has a valid query\n // For funnels, a step can have:\n // - measures/dimensions/timeDimensions (standard fields), OR\n // - filters only (the binding key dimension is auto-added by buildStepQuery)\n for (const step of config.steps) {\n const query = step.query\n const hasFields =\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0) ||\n (query.filters && query.filters.length > 0)\n if (!hasFields) return false\n }\n\n return true\n}\n\n/**\n * Hook for server-side funnel query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isExecuting, error } = useFunnelQuery(config, {\n * debounceMs: 300,\n * skip: !hasBindingKey\n * })\n *\n * // Results available after single server request\n * <FunnelChart data={chartData} />\n * ```\n */\nexport function useFunnelQuery(\n config: FunnelConfig | null,\n options: UseFunnelQueryOptions = {}\n): UseFunnelQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n prebuiltServerQuery,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate config\n const isValidConfig = isValidFunnelConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Build server query from config (or use prebuilt if provided)\n const serverQuery = useMemo(() => {\n // If prebuiltServerQuery is provided, use it directly\n if (prebuiltServerQuery) {\n return prebuiltServerQuery\n }\n\n // Otherwise build from config (legacy mode)\n if (!debouncedConfig || !isValidConfig) {\n return null\n }\n\n try {\n const result = buildServerFunnelQuery(\n debouncedConfig.steps.map(s => s.query),\n debouncedConfig.bindingKey,\n debouncedConfig.steps.map(s => s.name),\n debouncedConfig.steps.map(s => s.timeToConvert || null),\n true // includeTimeMetrics\n )\n return result\n } catch (error) {\n console.error('Failed to build server funnel query:', error)\n return null\n }\n }, [prebuiltServerQuery, debouncedConfig, isValidConfig])\n\n // Create stable query key\n // Include step count explicitly to ensure cache invalidation when steps change\n const queryKey = useMemo(() => {\n if (!serverQuery) return ['cube', 'funnel', null] as const\n const stepCount = serverQuery.funnel?.steps?.length || 0\n return ['cube', 'funnel', stepCount, JSON.stringify(serverQuery)] as const\n }, [serverQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Execute funnel query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!serverQuery) {\n throw new Error('No server query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send funnel query to server (single request)\n const resultSet = await cubeApi.load(serverQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err, 0)\n throw err\n }\n },\n // Enable when we have a server query (either prebuilt or built from config)\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Get step names from either config or prebuilt server query\n const stepNames = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.map(s => s.name)\n }\n return debouncedConfig?.steps?.map(s => s.name)\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Get expected step count from either config or prebuilt server query\n const expectedStepCount = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.length\n }\n return debouncedConfig?.steps?.length || 0\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Transform server result to chart data\n // Validate step count matches to prevent showing stale data during transitions\n const chartData = useMemo<FunnelChartData[]>(() => {\n if (!queryResult.data?.rawData) return []\n\n // Check if data step count matches expected step count\n const dataStepCount = queryResult.data.rawData.length\n\n if (dataStepCount !== expectedStepCount) {\n // Data is stale (from a different query) - don't return it\n // This prevents showing mismatched step counts while a new query loads\n return []\n }\n\n return transformServerFunnelResult(\n queryResult.data.rawData,\n stepNames\n )\n }, [queryResult.data, expectedStepCount, stepNames])\n\n // Build step results from chart data (for backward compatibility)\n const stepResults = useMemo<FunnelStepResult[]>(() => {\n if (!chartData.length) return []\n\n const firstCount = chartData[0]?.value || 0\n\n return chartData.map((data, index) => ({\n stepIndex: index,\n stepName: data.name,\n // Get step ID from config, or generate one for prebuilt queries\n stepId: debouncedConfig?.steps?.[index]?.id || `step-${index}`,\n data: [], // Raw data not available from server funnel\n bindingKeyValues: [], // Not available from server funnel\n bindingKeyTotalCount: 0,\n count: data.value,\n conversionRate: data.conversionRate !== null ? data.conversionRate / 100 : null,\n cumulativeConversionRate: firstCount > 0 ? data.value / firstCount : 0,\n executionTime: queryResult.data?.executionTime || 0,\n error: null,\n }))\n }, [chartData, debouncedConfig, queryResult.data?.executionTime])\n\n // Build full result for compatibility\n const result = useMemo<FunnelExecutionResult | null>(() => {\n // Need either config or prebuilt query for results\n if (!chartData.length) return null\n if (!debouncedConfig && !prebuiltServerQuery) return null\n\n const firstCount = chartData[0]?.value || 0\n const lastCount = chartData[chartData.length - 1]?.value || 0\n\n // Create a config object (use debouncedConfig if available, else synthesize from prebuilt)\n const effectiveConfig: FunnelConfig = debouncedConfig || {\n id: 'prebuilt-funnel',\n name: 'Funnel Analysis',\n bindingKey: {\n dimension: typeof prebuiltServerQuery?.funnel?.bindingKey === 'string'\n ? prebuiltServerQuery.funnel.bindingKey\n : prebuiltServerQuery?.funnel?.bindingKey?.[0]?.dimension || ''\n },\n steps: (prebuiltServerQuery?.funnel?.steps || []).map((s, i) => ({\n id: `step-${i}`,\n name: s.name,\n query: { filters: s.filter ? [s.filter as unknown as import('../../types').Filter] : [] },\n timeToConvert: s.timeToConvert || undefined,\n })),\n }\n\n const fullResult: FunnelExecutionResult = {\n config: effectiveConfig,\n steps: stepResults,\n summary: {\n totalEntries: firstCount,\n totalCompletions: lastCount,\n overallConversionRate: firstCount > 0 ? lastCount / firstCount : 0,\n totalExecutionTime: queryResult.data?.executionTime || 0,\n },\n chartData,\n status: queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle',\n error: queryResult.error as Error | null,\n currentStepIndex: null,\n }\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(fullResult)\n }\n\n return fullResult\n }, [debouncedConfig, prebuiltServerQuery, chartData, stepResults, queryResult, onComplete])\n\n // Determine current status\n const status: FunnelExecutionResult['status'] = queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle'\n\n /**\n * Manually execute/refetch the funnel query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const execute = useCallback(async (options?: { bustCache?: boolean }): Promise<FunnelExecutionResult | null> => {\n // Allow execution if we have a serverQuery (either from prebuiltServerQuery or built from config)\n if (!serverQuery) return null\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n try {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n serverQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n await queryResult.refetch()\n }\n return result\n } catch {\n return result\n }\n }, [serverQuery, queryResult, result, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Cancel is a no-op for TanStack Query (handled automatically)\n */\n const cancel = useCallback(() => {\n // TanStack Query handles cancellation automatically\n }, [])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n result,\n status,\n isExecuting: queryResult.isLoading || queryResult.isFetching,\n isDebouncing,\n currentStepIndex: null, // Not applicable for server-side execution\n stepLoadingStates: [], // Not applicable for server-side execution\n stepResults,\n chartData,\n error: queryResult.error as Error | null,\n execute,\n cancel,\n reset,\n // Not exposing executedQueries - server builds the query internally\n executedQueries: [],\n // Expose the server query for debug panel display\n // This is the actual { funnel: {...} } query sent to the server\n serverQuery,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for funnel queries\n */\nexport function createFunnelQueryKey(\n config: FunnelConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'funnel', null] as const\n // Create a stable key based on config\n return ['cube', 'funnel', JSON.stringify(config)] as const\n}\n","/**\n * useFlowQuery - Hook for server-side flow query execution\n *\n * Executes flow queries on the server for bidirectional Sankey chart data.\n * Flow queries explore paths BEFORE and AFTER a defined starting step.\n *\n * The server returns { nodes: [], links: [] } structure ready for Sankey visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerFlowQuery,\n FlowChartData,\n} from '../../types/flow'\nimport { isSankeyData } from '../../types/flow'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for useFlowQuery hook\n */\nexport interface UseFlowQueryOptions {\n /** Skip query execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes successfully */\n onComplete?: (data: FlowChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n}\n\n/**\n * Result from useFlowQuery hook\n */\nexport interface UseFlowQueryResult {\n /** Transformed flow chart data (nodes and links) */\n data: FlowChartData | null\n /** Raw data from server */\n rawData: unknown[] | null\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n /** Is initial load in progress */\n isLoading: boolean\n /** Is refetch in progress */\n isFetching: boolean\n /** Is waiting for debounce */\n isDebouncing: boolean\n /** Is executing (loading or fetching) */\n isExecuting: boolean\n /** Error if query failed */\n error: Error | null\n /** Refetch the query. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n /** Reset the query cache */\n reset: () => void\n /** The server query being executed */\n serverQuery: ServerFlowQuery | null\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current flow config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerFlowQuery is valid for execution\n */\nfunction isValidFlowQuery(query: ServerFlowQuery | null): boolean {\n if (!query?.flow) return false\n\n const { flow } = query\n\n // Must have binding key\n if (!flow.bindingKey) return false\n\n // Must have time dimension\n if (!flow.timeDimension) return false\n\n // Must have event dimension\n if (!flow.eventDimension) return false\n\n // Must have starting step with filter\n if (!flow.startingStep?.filter) return false\n\n // Must have valid depth\n if (flow.stepsBefore < 0 || flow.stepsBefore > 5) return false\n if (flow.stepsAfter < 0 || flow.stepsAfter > 5) return false\n\n return true\n}\n\n/**\n * Transform raw server result to FlowChartData\n */\nfunction transformFlowResult(rawData: unknown[]): FlowChartData | null {\n // Server returns a single row with { nodes: [], links: [] } structure\n if (rawData.length === 1) {\n const row = rawData[0]\n if (row && typeof row === 'object' && 'nodes' in row && 'links' in row) {\n return row as FlowChartData\n }\n }\n\n // Alternative: Server might return nodes and links as separate items\n // or the entire array might be the flow result\n if (rawData.length > 0) {\n const firstItem = rawData[0]\n // Check if it looks like Sankey data using type guard\n if (firstItem && typeof firstItem === 'object' && isSankeyData(firstItem)) {\n return firstItem as FlowChartData\n }\n }\n\n return null\n}\n\n/**\n * Hook for server-side flow query execution\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useFlowQuery(serverFlowQuery, {\n * debounceMs: 300,\n * skip: !isConfigured\n * })\n *\n * // Results available after single server request\n * <SankeyChart data={data} />\n * ```\n */\nexport function useFlowQuery(\n query: ServerFlowQuery | null,\n options: UseFlowQueryOptions = {}\n): UseFlowQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValid = isValidFlowQuery(query)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(\n query,\n {\n isValid,\n skip,\n debounceMs,\n }\n )\n\n // Create stable query key string for the debounced query (used for TanStack Query cache key)\n const queryKeyString = useMemo(() => {\n if (!debouncedQuery) return null\n return JSON.stringify(debouncedQuery)\n }, [debouncedQuery])\n\n // Create stable query key string for the RAW input query (used for staleness detection)\n // This detects when the input query has changed but debounce hasn't completed yet\n const rawQueryKeyString = useMemo(() => {\n if (!query) return null\n return JSON.stringify(query)\n }, [query])\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'flow', null] as const\n return ['cube', 'flow', queryKeyString] as const\n }, [debouncedQuery, queryKeyString])\n\n // Calculate current query key for manual refresh tracking (uses raw input query)\n const currentQueryKey = query ? stableStringify(query) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever query is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!isValid || !debouncedQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [isValid, debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && query && !skip && isValid) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, query, skip, isValid, currentQueryKey])\n\n // Execute flow query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No flow query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send flow query to server (single request)\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n // Include query key in result so we can detect stale data\n // (data from a different query key, e.g., sankey vs sunburst mode)\n // We store the RAW query key so we can compare against the current raw query\n queryKeyString: rawQueryKeyString,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Check if data is stale (from a different query key)\n // This happens when switching between sankey/sunburst modes\n // We compare the current RAW query key with the one stored in the data\n // This catches staleness even during debounce (when debouncedQuery hasn't updated yet)\n const isDataStale = rawQueryKeyString !== null &&\n queryResult.data?.queryKeyString !== undefined &&\n queryResult.data.queryKeyString !== rawQueryKeyString\n\n // Transform server result to chart data\n // Return null if data is stale (from a different query) to show loading instead\n const chartData = useMemo<FlowChartData | null>(() => {\n // If data is stale (from different query), return null to show loading\n if (isDataStale) return null\n\n if (!queryResult.data?.rawData) return null\n\n const transformed = transformFlowResult(queryResult.data.rawData)\n\n // Call completion callback\n if (transformed && queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(transformed)\n }\n\n return transformed\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, isDataStale])\n\n /**\n * Refetch the flow query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const refetch = useCallback((options?: { bustCache?: boolean }) => {\n if (debouncedQuery && isValid) {\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n queryResult.refetch()\n }\n }\n }, [debouncedQuery, isValid, queryResult, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n data: chartData,\n rawData: isDataStale ? null : (queryResult.data?.rawData ?? null),\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n isLoading: queryResult.isLoading || isDataStale,\n isFetching: queryResult.isFetching,\n isDebouncing,\n isExecuting: queryResult.isLoading || queryResult.isFetching || isDataStale,\n error: queryResult.error as Error | null,\n refetch,\n reset,\n serverQuery: debouncedQuery,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for flow queries\n */\nexport function createFlowQueryKey(\n query: ServerFlowQuery | null\n): readonly unknown[] {\n if (!query) return ['cube', 'flow', null] as const\n return ['cube', 'flow', JSON.stringify(query)] as const\n}\n","/**\n * useRetentionQuery - Hook for server-side retention query execution\n *\n * Executes retention queries on the server using SQL generation.\n * Returns cohort-based retention data for heatmap visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerRetentionQuery,\n RetentionChartData,\n RetentionResultRow,\n RetentionSummary,\n RetentionGranularity,\n} from '../../types/retention'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for retention query hook\n */\nexport interface UseRetentionQueryOptions {\n /** Skip execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes */\n onComplete?: (result: RetentionChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n /** Function to resolve field names to human-readable display labels */\n getFieldLabel?: (fieldName: string) => string\n}\n\n/**\n * Result from retention query hook\n */\nexport interface UseRetentionQueryResult {\n /** Retention chart data */\n chartData: RetentionChartData | null\n\n /** Raw data rows from server */\n rawData: RetentionResultRow[] | null\n\n /** Current execution status */\n status: 'idle' | 'loading' | 'success' | 'error'\n\n /** Whether currently loading */\n isLoading: boolean\n\n /** Whether fetching (includes refetch) */\n isFetching: boolean\n\n /** Whether waiting for debounce */\n isDebouncing: boolean\n\n /** Error if execution failed */\n error: Error | null\n\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n\n /** Execute the query (for manual refresh mode) */\n execute: (options?: { bustCache?: boolean }) => Promise<RetentionChartData | null>\n\n /** Refetch the query */\n refetch: () => void\n\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerRetentionQuery is valid for execution\n * Uses new simplified Mixpanel-style format with single timeDimension\n */\nfunction isValidRetentionQuery(query: ServerRetentionQuery | null): boolean {\n if (!query) return false\n if (!query.retention) return false\n if (!query.retention.timeDimension) return false\n if (!query.retention.bindingKey) return false\n if (!query.retention.periods || query.retention.periods < 1) return false\n return true\n}\n\n/**\n * Extract the human-readable label from the binding key\n * e.g., \"Users.userId\" → \"userId\", \"Events.customerId\" → \"customerId\"\n */\nfunction extractBindingKeyLabel(\n bindingKey: ServerRetentionQuery['retention']['bindingKey'] | undefined\n): string | undefined {\n if (!bindingKey) return undefined\n\n // String format: \"Cube.dimensionName\" → \"dimensionName\"\n if (typeof bindingKey === 'string') {\n return bindingKey.split('.').pop()\n }\n\n // Array format: [{ cube, dimension }] → extract first dimension's name\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n const firstMapping = bindingKey[0]\n if (firstMapping?.dimension) {\n return firstMapping.dimension.split('.').pop()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract breakdown value from server response\n * Server returns breakdownValues as an object like {\"PREvents.eventType\": \"approved\"}\n * We extract the first (and typically only) value from this object\n */\nfunction extractBreakdownValue(\n breakdownValues: unknown\n): string | null {\n // If it's already a string, return it\n if (typeof breakdownValues === 'string') {\n return breakdownValues\n }\n\n // If it's an object, extract the first value\n if (breakdownValues && typeof breakdownValues === 'object' && !Array.isArray(breakdownValues)) {\n const values = Object.values(breakdownValues as Record<string, unknown>)\n if (values.length > 0 && values[0] != null) {\n return String(values[0])\n }\n }\n\n return null\n}\n\n/**\n * Transform raw server data to RetentionChartData format\n * New simplified format: rows with period, cohortSize, retainedUsers, retentionRate, breakdownValue\n */\nfunction transformRetentionResult(\n rawData: unknown[],\n granularity?: RetentionGranularity,\n bindingKeyLabel?: string\n): RetentionChartData {\n if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {\n return { rows: [], periods: [], granularity, bindingKeyLabel }\n }\n\n const rows: RetentionResultRow[] = rawData.map((row: unknown) => {\n const r = row as Record<string, unknown>\n return {\n period: Number(r.period ?? r.period_number ?? 0),\n cohortSize: Number(r.cohortSize ?? r.cohort_size ?? 0),\n retainedUsers: Number(r.retainedUsers ?? r.retained_users ?? 0),\n retentionRate: Number(r.retentionRate ?? r.retention_rate ?? 0),\n // Server returns breakdownValues (object) or breakdownValue (string) or breakdown_value (snake_case)\n breakdownValue: extractBreakdownValue(r.breakdownValues) ?? r.breakdownValue ?? r.breakdown_value ?? null,\n } as RetentionResultRow\n })\n\n // Extract unique periods and breakdown values\n const periodsSet = new Set<number>()\n const breakdownSet = new Set<string>()\n\n rows.forEach((row) => {\n periodsSet.add(row.period)\n if (row.breakdownValue) {\n breakdownSet.add(row.breakdownValue)\n }\n })\n\n const periods = Array.from(periodsSet).sort((a, b) => a - b)\n const breakdownValues = breakdownSet.size > 0 ? Array.from(breakdownSet).sort() : undefined\n\n // Calculate summary statistics\n const summary = calculateSummary(rows, breakdownValues)\n\n return { rows, periods, breakdownValues, summary, granularity, bindingKeyLabel }\n}\n\n/**\n * Calculate summary statistics from retention data\n */\nfunction calculateSummary(\n rows: RetentionResultRow[],\n breakdownValues?: string[]\n): RetentionSummary {\n const period1Rows = rows.filter((r) => r.period === 1)\n const period1Rates = period1Rows.map((r) => r.retentionRate)\n\n const totalUsers = rows\n .filter((r) => r.period === 0)\n .reduce((sum, r) => sum + r.cohortSize, 0)\n\n return {\n totalUsers,\n avgPeriod1Retention:\n period1Rates.length > 0\n ? period1Rates.reduce((a, b) => a + b, 0) / period1Rates.length\n : 0,\n maxPeriod1Retention: period1Rates.length > 0 ? Math.max(...period1Rates) : 0,\n minPeriod1Retention: period1Rates.length > 0 ? Math.min(...period1Rates) : 0,\n segmentCount: breakdownValues?.length || 1,\n }\n}\n\n/**\n * Hook for server-side retention query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isLoading, error } = useRetentionQuery(serverQuery, {\n * debounceMs: 300,\n * skip: !hasRequiredFields\n * })\n *\n * <RetentionHeatmap data={chartData} />\n * ```\n */\nexport function useRetentionQuery(\n serverQuery: ServerRetentionQuery | null,\n options: UseRetentionQueryOptions = {}\n): UseRetentionQueryResult {\n const { skip = false, debounceMs = DEFAULT_DEBOUNCE_MS, onComplete, onError, getFieldLabel } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidRetentionQuery(serverQuery)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(serverQuery, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'retention', null] as const\n return ['cube', 'retention', JSON.stringify(debouncedQuery)] as const\n }, [debouncedQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = debouncedQuery ? stableStringify(debouncedQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n if (executedQueryKey === null) return false\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n const shouldExecute = useMemo(() => {\n if (!debouncedQuery || skip) return false\n if (!manualRefresh) return true\n if (executedQueryKey === null) return true\n return executedQueryKey === currentQueryKey\n }, [debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n useEffect(() => {\n if (!manualRefresh && debouncedQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, debouncedQuery, skip, currentQueryKey])\n\n // Execute retention query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No retention query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send retention query to server\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n useEffect(() => {\n if (!manualRefresh) return\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Extract granularity and binding key label from server query\n const granularity = serverQuery?.retention?.granularity\n\n // Get the raw binding key field name for label lookup\n const rawBindingKeyField = useMemo(() => {\n const bindingKey = serverQuery?.retention?.bindingKey\n if (!bindingKey) return undefined\n if (typeof bindingKey === 'string') return bindingKey\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n return bindingKey[0]?.dimension\n }\n return undefined\n }, [serverQuery?.retention?.bindingKey])\n\n // Use getFieldLabel if provided, fallback to extractBindingKeyLabel\n const bindingKeyLabel = useMemo(() => {\n if (rawBindingKeyField && getFieldLabel) {\n const label = getFieldLabel(rawBindingKeyField)\n // If getFieldLabel returns the same string, it didn't find a better label\n if (label && label !== rawBindingKeyField) {\n return label\n }\n }\n // Fallback to extracting from the field name\n return extractBindingKeyLabel(serverQuery?.retention?.bindingKey)\n }, [rawBindingKeyField, getFieldLabel, serverQuery?.retention?.bindingKey])\n\n // Transform server result to chart data\n const chartData = useMemo<RetentionChartData | null>(() => {\n if (!queryResult.data?.rawData) return null\n const result = transformRetentionResult(\n queryResult.data.rawData,\n granularity,\n bindingKeyLabel\n )\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(result)\n }\n\n return result\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, granularity, bindingKeyLabel])\n\n // Execute function for manual refresh mode\n const execute = useCallback(\n async (executeOptions?: { bustCache?: boolean }) => {\n if (!debouncedQuery) return null\n\n if (executeOptions?.bustCache) {\n queryClient.removeQueries({ queryKey })\n }\n\n // Update executed query key to trigger execution\n setExecutedQueryKey(currentQueryKey)\n\n // Wait for the query to complete\n const result = await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime: 0, cacheInfo }\n },\n })\n\n return transformRetentionResult(result.rawData, granularity, bindingKeyLabel)\n },\n [debouncedQuery, queryClient, queryKey, cubeApi, currentQueryKey, granularity, bindingKeyLabel]\n )\n\n // Refetch function\n const refetch = useCallback(() => {\n queryResult.refetch()\n }, [queryResult])\n\n // Determine status\n const status = useMemo(() => {\n if (queryResult.isError) return 'error' as const\n if (queryResult.isLoading) return 'loading' as const\n if (queryResult.isSuccess) return 'success' as const\n return 'idle' as const\n }, [queryResult.isError, queryResult.isLoading, queryResult.isSuccess])\n\n return {\n chartData,\n rawData: queryResult.data?.rawData as RetentionResultRow[] | null ?? null,\n status,\n isLoading: queryResult.isLoading,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error as Error | null,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n execute,\n refetch,\n needsRefresh,\n }\n}\n","/**\n * Hook for fetching distinct field values for filter dropdowns\n * Uses TanStack Query via useCubeLoadQuery for data fetching\n */\n\nimport { useState, useCallback, useRef, useMemo, useEffect } from 'react'\nimport { useCubeLoadQuery } from './queries/useCubeLoadQuery'\nimport type { CubeQuery } from '../types'\n\ninterface UseFilterValuesResult {\n values: any[]\n loading: boolean\n error: string | null\n refetch: () => void\n searchValues: (searchTerm: string, force?: boolean) => void\n}\n\n/**\n * Custom hook to fetch distinct values for a field\n *\n * Uses TanStack Query for server state (data fetching, caching, loading).\n * Values are derived via useMemo from query results - NOT stored in useState.\n */\nexport function useFilterValues(\n fieldName: string | null,\n enabled: boolean = true\n): UseFilterValuesResult {\n const [currentQuery, setCurrentQuery] = useState<CubeQuery | null>(null)\n const lastSearchTerm = useRef<string>('')\n\n // Use TanStack Query hook for data fetching\n const {\n resultSet,\n isLoading,\n error: queryError,\n } = useCubeLoadQuery(currentQuery, {\n skip: !currentQuery || !enabled || !fieldName,\n debounceMs: 150, // Quick debounce for filter searches\n keepPreviousData: true,\n })\n\n // Derive values from resultSet using useMemo (NOT useState)\n // This is the correct pattern - server state stays in TanStack Query\n const values = useMemo(() => {\n // Return empty if no result set, loading, or error\n if (!resultSet || isLoading || queryError || !fieldName) {\n return []\n }\n\n try {\n const data = resultSet.tablePivot()\n const uniqueValues = new Set<any>()\n\n data.forEach((row: any) => {\n const value = row[fieldName]\n if (value !== null && value !== undefined && value !== '') {\n uniqueValues.add(value)\n }\n })\n\n // Convert to array - already sorted by query\n return Array.from(uniqueValues)\n } catch (err) {\n console.error('Error extracting values from result set:', err)\n return []\n }\n }, [resultSet, isLoading, queryError, fieldName])\n\n // Reset query when fieldName becomes null or enabled changes\n useEffect(() => {\n if (!fieldName || !enabled) {\n setCurrentQuery(null)\n lastSearchTerm.current = ''\n }\n }, [fieldName, enabled])\n\n // Refetch function\n const refetch = useCallback(() => {\n if (!fieldName) return\n\n lastSearchTerm.current = ''\n\n try {\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating query:', err)\n }\n }, [fieldName])\n\n // Search function for server-side filtering\n const searchValues = useCallback((searchTerm: string, force: boolean = false) => {\n if (!fieldName) {\n return\n }\n\n // Don't create a new query if the search term hasn't changed (unless forced)\n if (!force && searchTerm === lastSearchTerm.current) {\n return\n }\n\n lastSearchTerm.current = searchTerm\n\n try {\n // Create query inline to avoid dependency issues\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n\n if (searchTerm && searchTerm.trim()) {\n query.filters = [{\n member: fieldName,\n operator: 'contains',\n values: [searchTerm.trim()]\n }]\n }\n\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating search query:', err)\n }\n }, [fieldName])\n\n return {\n values,\n loading: isLoading,\n error: queryError ? (queryError instanceof Error ? queryError.message : String(queryError)) : null,\n refetch,\n searchValues\n }\n}\n","/**\n * Custom hook for debouncing values\n * Delays updating the value until after the specified delay has passed\n * since the last change\n */\n\nimport { useState, useEffect } from 'react'\n\n/**\n * Debounces a value by the specified delay\n * @param value The value to debounce\n * @param delay The delay in milliseconds\n * @returns The debounced value\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value)\n\n useEffect(() => {\n // Set up a timer to update the debounced value after the delay\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n // Clean up the timer if the value changes before the delay\n return () => {\n clearTimeout(handler)\n }\n }, [value, delay])\n\n return debouncedValue\n}"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as e, l as t } from "./vendor-
|
|
2
|
-
import { V as n, Z as r, et as i, lt as a } from "./chart-data-table-
|
|
3
|
-
import { S as o, x as s } from "./useDebounce-
|
|
1
|
+
import { c as e, l as t } from "./vendor-ClXpIiea.js";
|
|
2
|
+
import { V as n, Z as r, et as i, lt as a } from "./chart-data-table-C3Xh9jwL.js";
|
|
3
|
+
import { S as o, x as s } from "./useDebounce-CfmUMFau.js";
|
|
4
4
|
import { useCallback as c, useMemo as l, useState as u } from "react";
|
|
5
5
|
//#region src/client/hooks/queries/useDryRunQuery.ts
|
|
6
6
|
function d(e) {
|
|
@@ -203,4 +203,4 @@ function C(t = {}) {
|
|
|
203
203
|
//#endregion
|
|
204
204
|
export { m as a, g as i, _ as n, h as o, x as r, C as t };
|
|
205
205
|
|
|
206
|
-
//# sourceMappingURL=useExplainAI-
|
|
206
|
+
//# sourceMappingURL=useExplainAI-BKGmejIj.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExplainAI-DBIfYwz-.js","names":[],"sources":["../../../src/client/hooks/queries/useDryRunQuery.ts","../../../src/client/hooks/queries/useExplainQuery.ts","../../../src/client/hooks/queries/useExplainAI.ts"],"sourcesContent":["/**\n * useDryRunQuery - TanStack Query hook for dry-run (debug) data\n *\n * Features:\n * - Fetch SQL and analysis data for query debugging\n * - Support for single and multi-query modes\n * - Built-in caching with TanStack Query\n * - Parallel fetching for multiple queries\n *\n * This hook replaces the debug data useEffect in AnalysisBuilder.\n */\n\nimport { useQuery, useQueries } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { CubeQuery } from '../../types'\nimport type { QueryAnalysis } from '../../components/AnalysisBuilder/types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\n\nexport type DryRunMode = 'regular' | 'comparison' | 'funnel' | 'flow' | 'retention'\n\ninterface DryRunResponsePayload {\n sql?: { sql: string; params?: unknown[] }\n analysis?: QueryAnalysis | null\n mode?: DryRunMode\n queryType?: string\n joinType?: string\n cubesUsed?: string[]\n modeMetadata?: unknown\n}\n\n/**\n * Debug data entry for a single query\n */\nexport interface DebugDataEntry {\n /** Generated SQL and parameters */\n sql: { sql: string; params: unknown[] } | null\n /** Query analysis (dimensions, measures, complexity, etc.) */\n analysis: QueryAnalysis | null\n /** Server-reported dry-run mode */\n mode?: DryRunMode | null\n /** Query type label from server */\n queryType?: string | null\n /** Join strategy label from server */\n joinType?: string | null\n /** Referenced cubes from server */\n cubesUsed?: string[]\n /** Mode-specific metadata (replaces funnel/flow/retention split) */\n modeMetadata?: unknown\n /** Whether this entry is loading */\n loading: boolean\n /** Error if fetch failed */\n error: Error | null\n}\n\n/**\n * Create a stable query key for dry-run\n */\nexport function createDryRunQueryKey(\n query: CubeQuery | null\n): readonly unknown[] {\n if (!query) return ['cube', 'dryRun', null] as const\n return ['cube', 'dryRun', stableStringify(query)] as const\n}\n\nexport interface UseDryRunQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Stale time in milliseconds\n * @default 5 * 60 * 1000 (5 minutes)\n */\n staleTime?: number\n}\n\nexport interface UseDryRunQueryResult {\n /** Debug data for the query */\n debugData: DebugDataEntry\n /** Manually refetch */\n refetch: () => void\n}\n\nfunction inferModeFromPayload(payload: DryRunResponsePayload): DryRunMode | null {\n if (payload.mode) return payload.mode\n if (payload.queryType === 'comparisonQuery') return 'comparison'\n if (payload.queryType === 'funnelQuery') return 'funnel'\n if (payload.queryType === 'flowQuery') return 'flow'\n if (payload.queryType === 'retentionQuery') return 'retention'\n if (payload.queryType === 'regularQuery') return 'regular'\n return null\n}\n\nfunction normalizeDryRunResult(payload: DryRunResponsePayload): Omit<DebugDataEntry, 'loading' | 'error'> {\n const mode = inferModeFromPayload(payload)\n return {\n sql: payload.sql ? { sql: payload.sql.sql, params: payload.sql.params || [] } : null,\n analysis: (payload.analysis || null) as QueryAnalysis | null,\n mode,\n queryType: payload.queryType || null,\n joinType: payload.joinType || null,\n cubesUsed: payload.cubesUsed || [],\n modeMetadata: payload.modeMetadata\n }\n}\n\n/**\n * TanStack Query hook for single query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useDryRunQuery(query, { skip: !isValidQuery })\n * ```\n */\nexport function useDryRunQuery(\n query: CubeQuery | null,\n options: UseDryRunQueryOptions = {}\n): UseDryRunQueryResult {\n const { skip = false, staleTime = 5 * 60 * 1000 } = options\n const { cubeApi } = useCubeApi()\n\n // Transform query for server\n const serverQuery = useMemo(() => {\n if (!query) return null\n const modeQuery = query as CubeQuery & { funnel?: unknown; flow?: unknown; retention?: unknown }\n // Preserve specialized mode payloads as-is; cleanQueryForServer strips non-Cube.js keys.\n if (modeQuery.funnel || modeQuery.flow || modeQuery.retention) {\n return query\n }\n return cleanQueryForServer(query)\n }, [query])\n\n const queryResult = useQuery({\n queryKey: createDryRunQueryKey(serverQuery),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n const result = await cubeApi.dryRun(serverQuery)\n return normalizeDryRunResult(result as DryRunResponsePayload)\n },\n enabled: !!serverQuery && !skip,\n staleTime,\n })\n\n const debugData: DebugDataEntry = {\n ...(queryResult.data || {\n sql: null,\n analysis: null,\n mode: null,\n queryType: null,\n joinType: null,\n cubesUsed: [],\n modeMetadata: undefined\n }),\n loading: queryResult.isLoading,\n error: queryResult.error ?? null,\n }\n\n return {\n debugData,\n refetch: () => queryResult.refetch(),\n }\n}\n\nexport interface UseMultiDryRunQueriesOptions {\n /**\n * Whether to skip all queries\n * @default false\n */\n skip?: boolean\n /**\n * Stale time in milliseconds\n * @default 5 * 60 * 1000 (5 minutes)\n */\n staleTime?: number\n}\n\nexport interface UseMultiDryRunQueriesResult {\n /** Debug data for each query */\n debugDataPerQuery: DebugDataEntry[]\n /** Whether any query is loading */\n isLoading: boolean\n /** Manually refetch all */\n refetchAll: () => void\n}\n\n/**\n * TanStack Query hook for multiple query dry-runs (debug) data\n *\n * Fetches debug data for multiple queries in parallel.\n *\n * Usage:\n * ```tsx\n * const { debugDataPerQuery, isLoading } = useMultiDryRunQueries(queries, {\n * skip: !isMultiQueryMode\n * })\n * ```\n */\nexport function useMultiDryRunQueries(\n queries: CubeQuery[],\n options: UseMultiDryRunQueriesOptions = {}\n): UseMultiDryRunQueriesResult {\n const { skip = false, staleTime = 5 * 60 * 1000 } = options\n const { cubeApi } = useCubeApi()\n\n // Transform queries for server\n const serverQueries = useMemo(() => {\n return queries.map((q) => cleanQueryForServer(q))\n }, [queries])\n\n // Use useQueries for parallel fetching\n const queryResults = useQueries({\n queries: serverQueries.map((query) => ({\n queryKey: createDryRunQueryKey(query),\n queryFn: async () => {\n const result = await cubeApi.dryRun(query)\n return normalizeDryRunResult(result as DryRunResponsePayload)\n },\n enabled: !skip,\n staleTime,\n })),\n })\n\n // Transform results to DebugDataEntry array\n const debugDataPerQuery: DebugDataEntry[] = queryResults.map((result) => ({\n ...(result.data || {\n sql: null,\n analysis: null,\n mode: null,\n queryType: null,\n joinType: null,\n cubesUsed: [],\n modeMetadata: undefined\n }),\n loading: result.isLoading,\n error: result.error ?? null,\n }))\n\n // Check if any query is loading\n const isLoading = queryResults.some((r) => r.isLoading)\n\n // Refetch all queries\n const refetchAll = () => {\n queryResults.forEach((r) => r.refetch())\n }\n\n return {\n debugDataPerQuery,\n isLoading,\n refetchAll,\n }\n}\n\n/**\n * Combined hook for single or multi-query dry-run based on mode\n *\n * This is a convenience wrapper that automatically chooses between\n * single and multi-query dry-run based on the number of queries.\n *\n * Usage:\n * ```tsx\n * const { debugDataPerQuery } = useDryRunQueries({\n * queries: isMultiQueryMode ? allQueries : [currentQuery],\n * isMultiQueryMode,\n * skip: !isValidQuery\n * })\n * ```\n */\nexport function useDryRunQueries(options: {\n queries: CubeQuery[]\n isMultiQueryMode: boolean\n skip?: boolean\n staleTime?: number\n}): UseMultiDryRunQueriesResult {\n const { queries, isMultiQueryMode, skip = false, staleTime } = options\n\n // For single query mode, wrap in array for consistent interface\n const queriesToFetch = isMultiQueryMode ? queries : queries.slice(0, 1)\n\n return useMultiDryRunQueries(queriesToFetch, { skip, staleTime })\n}\n\nexport type FunnelDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for funnel query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useFunnelDryRunQuery(serverQuery, { skip: !isFunnelMode })\n * ```\n */\nexport function useFunnelDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: FunnelDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n\nexport type FlowDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for flow query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useFlowDryRunQuery(serverQuery, { skip: !isFlowMode })\n * ```\n */\nexport function useFlowDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: FlowDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n\nexport type RetentionDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for retention query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useRetentionDryRunQuery(serverQuery, { skip: !isRetentionMode })\n * ```\n */\nexport function useRetentionDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: RetentionDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n","/**\n * useExplainQuery - TanStack Query hook for EXPLAIN PLAN execution\n *\n * Features:\n * - Manually triggered (not automatic like useCubeLoadQuery)\n * - Returns normalized execution plan across PostgreSQL, MySQL, and SQLite\n * - Supports EXPLAIN ANALYZE for actual timing data\n * - No caching - always fetches fresh data to reflect schema/index changes\n *\n * Usage:\n * ```tsx\n * const { explainResult, isLoading, error, runExplain } = useExplainQuery(query)\n *\n * // Trigger explain manually\n * <button onClick={() => runExplain({ analyze: true })}>Explain with Timing</button>\n * ```\n */\n\nimport { useQuery } from '@tanstack/react-query'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { CubeQuery, ExplainResult, ExplainOptions } from '../../types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\n\n/**\n * Query type that can be explained - includes standard queries, funnel queries, flow queries, and retention queries\n */\nexport type ExplainableQuery = CubeQuery | { funnel: unknown } | { flow: unknown } | { retention: unknown } | unknown\n\n/**\n * Create a stable query key for explain\n */\nexport function createExplainQueryKey(\n query: ExplainableQuery | null,\n options?: ExplainOptions\n): readonly unknown[] {\n if (!query) return ['cube', 'explain', null, null] as const\n return ['cube', 'explain', stableStringify(query), options?.analyze ?? false] as const\n}\n\nexport interface UseExplainQueryOptions {\n /**\n * Whether to skip the query (prevent execution even when triggered)\n * @default false\n */\n skip?: boolean\n}\n\nexport interface UseExplainQueryResult {\n /** The explain result from the server */\n explainResult: ExplainResult | null\n /** Whether the explain query is loading */\n isLoading: boolean\n /** Whether an explain has been triggered */\n hasRun: boolean\n /** Error if explain failed */\n error: Error | null\n /**\n * Manually trigger the explain query\n * @param options - Optional explain options (e.g., { analyze: true } for actual timing)\n */\n runExplain: (options?: ExplainOptions) => void\n /** Clear the explain result */\n clearExplain: () => void\n}\n\n/**\n * Check if query is a funnel query\n */\nfunction isFunnelQuery(query: unknown): query is { funnel: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'funnel')\n}\n\n/**\n * Check if query is a flow query\n */\nfunction isFlowQuery(query: unknown): query is { flow: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'flow')\n}\n\n/**\n * Check if query is a retention query\n */\nfunction isRetentionQuery(query: unknown): query is { retention: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'retention')\n}\n\n/**\n * TanStack Query hook for EXPLAIN PLAN execution\n *\n * Unlike useDryRunQuery, this hook is manually triggered via `runExplain()`.\n * This is intentional because:\n * 1. EXPLAIN queries have performance overhead\n * 2. EXPLAIN ANALYZE actually executes the query\n * 3. Users should explicitly choose when to run explain\n *\n * Supports standard queries, funnel queries, and flow queries.\n *\n * Usage:\n * ```tsx\n * const { explainResult, isLoading, runExplain } = useExplainQuery(query, { skip: !isValidQuery })\n *\n * // Trigger explain\n * <button onClick={() => runExplain()}>Explain Plan</button>\n *\n * // Trigger explain with timing\n * <button onClick={() => runExplain({ analyze: true })}>Explain with Timing</button>\n * ```\n */\nexport function useExplainQuery(\n query: ExplainableQuery | null,\n options: UseExplainQueryOptions = {}\n): UseExplainQueryResult {\n const { skip = false } = options\n const { cubeApi } = useCubeApi()\n\n // Track whether explain has been triggered and with what options\n const [explainOptions, setExplainOptions] = useState<ExplainOptions | null>(null)\n const [hasTriggered, setHasTriggered] = useState(false)\n\n // Transform query for server\n // For funnel/flow/retention queries, pass them directly without cleaning\n const serverQuery = useMemo(() => {\n if (!query) return null\n // Funnel, flow, and retention queries have their own format, don't clean them\n if (isFunnelQuery(query) || isFlowQuery(query) || isRetentionQuery(query)) {\n return query\n }\n // Standard queries get cleaned\n return cleanQueryForServer(query as CubeQuery)\n }, [query])\n\n // Query is enabled only when:\n // 1. We have a query\n // 2. Skip is false\n // 3. User has triggered the explain\n const queryEnabled = !!serverQuery && !skip && hasTriggered\n\n const queryResult = useQuery({\n queryKey: createExplainQueryKey(serverQuery, explainOptions ?? undefined),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n const result = await cubeApi.explain(serverQuery, explainOptions ?? undefined)\n return result\n },\n enabled: queryEnabled,\n // EXPLAIN queries should never be cached - always fetch fresh data\n // This ensures users see the latest execution plan after schema/index changes\n staleTime: 0,\n gcTime: 0,\n // Don't refetch automatically - this is manually triggered\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n refetchOnMount: false,\n })\n\n // Manual trigger function\n const runExplain = useCallback((opts?: ExplainOptions) => {\n if (skip || !serverQuery) return\n setExplainOptions(opts ?? null)\n setHasTriggered(true)\n // If already triggered with same options, refetch\n if (hasTriggered) {\n queryResult.refetch()\n }\n }, [skip, serverQuery, hasTriggered, queryResult])\n\n // Clear function\n const clearExplain = useCallback(() => {\n setHasTriggered(false)\n setExplainOptions(null)\n }, [])\n\n return {\n explainResult: queryResult.data ?? null,\n isLoading: queryResult.isLoading || queryResult.isFetching,\n hasRun: hasTriggered,\n error: queryResult.error ?? null,\n runExplain,\n clearExplain,\n }\n}\n","/**\n * useExplainAI - TanStack Query hook for AI analysis of EXPLAIN plans\n *\n * Features:\n * - Manually triggered via useMutation (not automatic)\n * - Analyzes execution plans and provides actionable recommendations\n * - Returns structured recommendations (indexes, table changes, cube improvements)\n * - No caching - always fetches fresh analysis\n * - Uses the /api/ai/explain/analyze endpoint\n *\n * Usage:\n * ```tsx\n * const { analysis, isAnalyzing, error, analyze, clearAnalysis } = useExplainAI()\n *\n * // After getting an explain result, trigger AI analysis\n * <button onClick={() => analyze(explainResult, query)}>Analyze with AI</button>\n * ```\n */\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport type { ExplainResult, AIExplainAnalysis } from '../../types'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\n\nexport interface UseExplainAIOptions {\n /**\n * Custom AI endpoint for explain analysis\n * @default '/api/ai/explain/analyze'\n */\n aiEndpoint?: string\n}\n\nexport interface UseExplainAIResult {\n /** The AI analysis result */\n analysis: AIExplainAnalysis | null\n /** Whether AI analysis is in progress */\n isAnalyzing: boolean\n /** Error if AI analysis failed */\n error: Error | null\n /**\n * Trigger AI analysis of an explain result\n * @param explainResult - The EXPLAIN result to analyze\n * @param query - The original semantic query\n */\n analyze: (explainResult: ExplainResult, query: unknown) => void\n /** Clear the analysis result */\n clearAnalysis: () => void\n}\n\n/**\n * Query key for AI explain analysis\n */\nexport const EXPLAIN_AI_QUERY_KEY = ['cube', 'explain', 'ai'] as const\n\n/**\n * TanStack Query hook for AI analysis of EXPLAIN plans\n *\n * This hook uses useMutation to call the AI endpoint and analyze execution plans,\n * providing actionable recommendations for performance improvement.\n *\n * Recommendations focus on what users CAN control:\n * - Index creation (with CREATE INDEX statements)\n * - Table structure changes\n * - Cube definition improvements (segments, pre-aggregations, joins)\n *\n * Usage:\n * ```tsx\n * const { analysis, isAnalyzing, analyze } = useExplainAI()\n *\n * // After running EXPLAIN\n * if (explainResult) {\n * <button onClick={() => analyze(explainResult, query)}>\n * {isAnalyzing ? 'Analyzing...' : 'Analyze with AI'}\n * </button>\n * }\n *\n * // Display recommendations\n * {analysis?.recommendations.map(rec => (\n * <div key={rec.title}>\n * <h4>{rec.title}</h4>\n * <p>{rec.description}</p>\n * {rec.sql && <pre>{rec.sql}</pre>}\n * {rec.cubeCode && <pre>{rec.cubeCode}</pre>}\n * </div>\n * ))}\n * ```\n */\nexport function useExplainAI(options: UseExplainAIOptions = {}): UseExplainAIResult {\n const { features } = useCubeFeatures()\n const { apiOptions } = useCubeApi()\n const enableAI = features.enableAI ?? true\n const queryClient = useQueryClient()\n\n // AI endpoint - defaults to /api/ai/explain/analyze\n // This is the standard path for AI routes in the dev server\n const aiEndpoint = options.aiEndpoint ?? '/api/ai/explain/analyze'\n\n const mutation = useMutation({\n mutationKey: EXPLAIN_AI_QUERY_KEY,\n mutationFn: async ({ explainResult, query }: { explainResult: ExplainResult; query: unknown }) => {\n // Check if AI is enabled\n if (!enableAI) {\n throw new Error('AI features are disabled')\n }\n\n const response = await fetch(aiEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...apiOptions?.headers,\n },\n credentials: 'include',\n body: JSON.stringify({\n explainResult,\n query,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n throw new Error(\n errorData.error ||\n errorData.message ||\n `AI analysis failed: ${response.status} ${response.statusText}`\n )\n }\n\n const result: AIExplainAnalysis = await response.json()\n return result\n },\n // No caching - each mutation is independent\n gcTime: 0,\n })\n\n const analyze = useCallback((explainResult: ExplainResult, query: unknown) => {\n mutation.mutate({ explainResult, query })\n }, [mutation])\n\n const clearAnalysis = useCallback(() => {\n mutation.reset()\n // Also invalidate any cached queries\n queryClient.removeQueries({ queryKey: EXPLAIN_AI_QUERY_KEY })\n }, [mutation, queryClient])\n\n return {\n analysis: mutation.data ?? null,\n isAnalyzing: mutation.isPending,\n error: mutation.error ?? null,\n analyze,\n clearAnalysis,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useExplainAI-BKGmejIj.js","names":[],"sources":["../../../src/client/hooks/queries/useDryRunQuery.ts","../../../src/client/hooks/queries/useExplainQuery.ts","../../../src/client/hooks/queries/useExplainAI.ts"],"sourcesContent":["/**\n * useDryRunQuery - TanStack Query hook for dry-run (debug) data\n *\n * Features:\n * - Fetch SQL and analysis data for query debugging\n * - Support for single and multi-query modes\n * - Built-in caching with TanStack Query\n * - Parallel fetching for multiple queries\n *\n * This hook replaces the debug data useEffect in AnalysisBuilder.\n */\n\nimport { useQuery, useQueries } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { CubeQuery } from '../../types'\nimport type { QueryAnalysis } from '../../components/AnalysisBuilder/types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\n\nexport type DryRunMode = 'regular' | 'comparison' | 'funnel' | 'flow' | 'retention'\n\ninterface DryRunResponsePayload {\n sql?: { sql: string; params?: unknown[] }\n analysis?: QueryAnalysis | null\n mode?: DryRunMode\n queryType?: string\n joinType?: string\n cubesUsed?: string[]\n modeMetadata?: unknown\n}\n\n/**\n * Debug data entry for a single query\n */\nexport interface DebugDataEntry {\n /** Generated SQL and parameters */\n sql: { sql: string; params: unknown[] } | null\n /** Query analysis (dimensions, measures, complexity, etc.) */\n analysis: QueryAnalysis | null\n /** Server-reported dry-run mode */\n mode?: DryRunMode | null\n /** Query type label from server */\n queryType?: string | null\n /** Join strategy label from server */\n joinType?: string | null\n /** Referenced cubes from server */\n cubesUsed?: string[]\n /** Mode-specific metadata (replaces funnel/flow/retention split) */\n modeMetadata?: unknown\n /** Whether this entry is loading */\n loading: boolean\n /** Error if fetch failed */\n error: Error | null\n}\n\n/**\n * Create a stable query key for dry-run\n */\nexport function createDryRunQueryKey(\n query: CubeQuery | null\n): readonly unknown[] {\n if (!query) return ['cube', 'dryRun', null] as const\n return ['cube', 'dryRun', stableStringify(query)] as const\n}\n\nexport interface UseDryRunQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Stale time in milliseconds\n * @default 5 * 60 * 1000 (5 minutes)\n */\n staleTime?: number\n}\n\nexport interface UseDryRunQueryResult {\n /** Debug data for the query */\n debugData: DebugDataEntry\n /** Manually refetch */\n refetch: () => void\n}\n\nfunction inferModeFromPayload(payload: DryRunResponsePayload): DryRunMode | null {\n if (payload.mode) return payload.mode\n if (payload.queryType === 'comparisonQuery') return 'comparison'\n if (payload.queryType === 'funnelQuery') return 'funnel'\n if (payload.queryType === 'flowQuery') return 'flow'\n if (payload.queryType === 'retentionQuery') return 'retention'\n if (payload.queryType === 'regularQuery') return 'regular'\n return null\n}\n\nfunction normalizeDryRunResult(payload: DryRunResponsePayload): Omit<DebugDataEntry, 'loading' | 'error'> {\n const mode = inferModeFromPayload(payload)\n return {\n sql: payload.sql ? { sql: payload.sql.sql, params: payload.sql.params || [] } : null,\n analysis: (payload.analysis || null) as QueryAnalysis | null,\n mode,\n queryType: payload.queryType || null,\n joinType: payload.joinType || null,\n cubesUsed: payload.cubesUsed || [],\n modeMetadata: payload.modeMetadata\n }\n}\n\n/**\n * TanStack Query hook for single query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useDryRunQuery(query, { skip: !isValidQuery })\n * ```\n */\nexport function useDryRunQuery(\n query: CubeQuery | null,\n options: UseDryRunQueryOptions = {}\n): UseDryRunQueryResult {\n const { skip = false, staleTime = 5 * 60 * 1000 } = options\n const { cubeApi } = useCubeApi()\n\n // Transform query for server\n const serverQuery = useMemo(() => {\n if (!query) return null\n const modeQuery = query as CubeQuery & { funnel?: unknown; flow?: unknown; retention?: unknown }\n // Preserve specialized mode payloads as-is; cleanQueryForServer strips non-Cube.js keys.\n if (modeQuery.funnel || modeQuery.flow || modeQuery.retention) {\n return query\n }\n return cleanQueryForServer(query)\n }, [query])\n\n const queryResult = useQuery({\n queryKey: createDryRunQueryKey(serverQuery),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n const result = await cubeApi.dryRun(serverQuery)\n return normalizeDryRunResult(result as DryRunResponsePayload)\n },\n enabled: !!serverQuery && !skip,\n staleTime,\n })\n\n const debugData: DebugDataEntry = {\n ...(queryResult.data || {\n sql: null,\n analysis: null,\n mode: null,\n queryType: null,\n joinType: null,\n cubesUsed: [],\n modeMetadata: undefined\n }),\n loading: queryResult.isLoading,\n error: queryResult.error ?? null,\n }\n\n return {\n debugData,\n refetch: () => queryResult.refetch(),\n }\n}\n\nexport interface UseMultiDryRunQueriesOptions {\n /**\n * Whether to skip all queries\n * @default false\n */\n skip?: boolean\n /**\n * Stale time in milliseconds\n * @default 5 * 60 * 1000 (5 minutes)\n */\n staleTime?: number\n}\n\nexport interface UseMultiDryRunQueriesResult {\n /** Debug data for each query */\n debugDataPerQuery: DebugDataEntry[]\n /** Whether any query is loading */\n isLoading: boolean\n /** Manually refetch all */\n refetchAll: () => void\n}\n\n/**\n * TanStack Query hook for multiple query dry-runs (debug) data\n *\n * Fetches debug data for multiple queries in parallel.\n *\n * Usage:\n * ```tsx\n * const { debugDataPerQuery, isLoading } = useMultiDryRunQueries(queries, {\n * skip: !isMultiQueryMode\n * })\n * ```\n */\nexport function useMultiDryRunQueries(\n queries: CubeQuery[],\n options: UseMultiDryRunQueriesOptions = {}\n): UseMultiDryRunQueriesResult {\n const { skip = false, staleTime = 5 * 60 * 1000 } = options\n const { cubeApi } = useCubeApi()\n\n // Transform queries for server\n const serverQueries = useMemo(() => {\n return queries.map((q) => cleanQueryForServer(q))\n }, [queries])\n\n // Use useQueries for parallel fetching\n const queryResults = useQueries({\n queries: serverQueries.map((query) => ({\n queryKey: createDryRunQueryKey(query),\n queryFn: async () => {\n const result = await cubeApi.dryRun(query)\n return normalizeDryRunResult(result as DryRunResponsePayload)\n },\n enabled: !skip,\n staleTime,\n })),\n })\n\n // Transform results to DebugDataEntry array\n const debugDataPerQuery: DebugDataEntry[] = queryResults.map((result) => ({\n ...(result.data || {\n sql: null,\n analysis: null,\n mode: null,\n queryType: null,\n joinType: null,\n cubesUsed: [],\n modeMetadata: undefined\n }),\n loading: result.isLoading,\n error: result.error ?? null,\n }))\n\n // Check if any query is loading\n const isLoading = queryResults.some((r) => r.isLoading)\n\n // Refetch all queries\n const refetchAll = () => {\n queryResults.forEach((r) => r.refetch())\n }\n\n return {\n debugDataPerQuery,\n isLoading,\n refetchAll,\n }\n}\n\n/**\n * Combined hook for single or multi-query dry-run based on mode\n *\n * This is a convenience wrapper that automatically chooses between\n * single and multi-query dry-run based on the number of queries.\n *\n * Usage:\n * ```tsx\n * const { debugDataPerQuery } = useDryRunQueries({\n * queries: isMultiQueryMode ? allQueries : [currentQuery],\n * isMultiQueryMode,\n * skip: !isValidQuery\n * })\n * ```\n */\nexport function useDryRunQueries(options: {\n queries: CubeQuery[]\n isMultiQueryMode: boolean\n skip?: boolean\n staleTime?: number\n}): UseMultiDryRunQueriesResult {\n const { queries, isMultiQueryMode, skip = false, staleTime } = options\n\n // For single query mode, wrap in array for consistent interface\n const queriesToFetch = isMultiQueryMode ? queries : queries.slice(0, 1)\n\n return useMultiDryRunQueries(queriesToFetch, { skip, staleTime })\n}\n\nexport type FunnelDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for funnel query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useFunnelDryRunQuery(serverQuery, { skip: !isFunnelMode })\n * ```\n */\nexport function useFunnelDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: FunnelDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n\nexport type FlowDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for flow query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useFlowDryRunQuery(serverQuery, { skip: !isFlowMode })\n * ```\n */\nexport function useFlowDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: FlowDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n\nexport type RetentionDebugDataEntry = DebugDataEntry\n\n/**\n * TanStack Query hook for retention query dry-run (debug) data\n *\n * Usage:\n * ```tsx\n * const { debugData } = useRetentionDryRunQuery(serverQuery, { skip: !isRetentionMode })\n * ```\n */\nexport function useRetentionDryRunQuery(\n serverQuery: unknown | null,\n options: UseDryRunQueryOptions = {}\n): { debugData: RetentionDebugDataEntry; refetch: () => void } {\n return useDryRunQuery(serverQuery as CubeQuery | null, options)\n}\n","/**\n * useExplainQuery - TanStack Query hook for EXPLAIN PLAN execution\n *\n * Features:\n * - Manually triggered (not automatic like useCubeLoadQuery)\n * - Returns normalized execution plan across PostgreSQL, MySQL, and SQLite\n * - Supports EXPLAIN ANALYZE for actual timing data\n * - No caching - always fetches fresh data to reflect schema/index changes\n *\n * Usage:\n * ```tsx\n * const { explainResult, isLoading, error, runExplain } = useExplainQuery(query)\n *\n * // Trigger explain manually\n * <button onClick={() => runExplain({ analyze: true })}>Explain with Timing</button>\n * ```\n */\n\nimport { useQuery } from '@tanstack/react-query'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { CubeQuery, ExplainResult, ExplainOptions } from '../../types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\n\n/**\n * Query type that can be explained - includes standard queries, funnel queries, flow queries, and retention queries\n */\nexport type ExplainableQuery = CubeQuery | { funnel: unknown } | { flow: unknown } | { retention: unknown } | unknown\n\n/**\n * Create a stable query key for explain\n */\nexport function createExplainQueryKey(\n query: ExplainableQuery | null,\n options?: ExplainOptions\n): readonly unknown[] {\n if (!query) return ['cube', 'explain', null, null] as const\n return ['cube', 'explain', stableStringify(query), options?.analyze ?? false] as const\n}\n\nexport interface UseExplainQueryOptions {\n /**\n * Whether to skip the query (prevent execution even when triggered)\n * @default false\n */\n skip?: boolean\n}\n\nexport interface UseExplainQueryResult {\n /** The explain result from the server */\n explainResult: ExplainResult | null\n /** Whether the explain query is loading */\n isLoading: boolean\n /** Whether an explain has been triggered */\n hasRun: boolean\n /** Error if explain failed */\n error: Error | null\n /**\n * Manually trigger the explain query\n * @param options - Optional explain options (e.g., { analyze: true } for actual timing)\n */\n runExplain: (options?: ExplainOptions) => void\n /** Clear the explain result */\n clearExplain: () => void\n}\n\n/**\n * Check if query is a funnel query\n */\nfunction isFunnelQuery(query: unknown): query is { funnel: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'funnel')\n}\n\n/**\n * Check if query is a flow query\n */\nfunction isFlowQuery(query: unknown): query is { flow: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'flow')\n}\n\n/**\n * Check if query is a retention query\n */\nfunction isRetentionQuery(query: unknown): query is { retention: unknown } {\n if (!query || typeof query !== 'object') return false\n return Object.prototype.hasOwnProperty.call(query, 'retention')\n}\n\n/**\n * TanStack Query hook for EXPLAIN PLAN execution\n *\n * Unlike useDryRunQuery, this hook is manually triggered via `runExplain()`.\n * This is intentional because:\n * 1. EXPLAIN queries have performance overhead\n * 2. EXPLAIN ANALYZE actually executes the query\n * 3. Users should explicitly choose when to run explain\n *\n * Supports standard queries, funnel queries, and flow queries.\n *\n * Usage:\n * ```tsx\n * const { explainResult, isLoading, runExplain } = useExplainQuery(query, { skip: !isValidQuery })\n *\n * // Trigger explain\n * <button onClick={() => runExplain()}>Explain Plan</button>\n *\n * // Trigger explain with timing\n * <button onClick={() => runExplain({ analyze: true })}>Explain with Timing</button>\n * ```\n */\nexport function useExplainQuery(\n query: ExplainableQuery | null,\n options: UseExplainQueryOptions = {}\n): UseExplainQueryResult {\n const { skip = false } = options\n const { cubeApi } = useCubeApi()\n\n // Track whether explain has been triggered and with what options\n const [explainOptions, setExplainOptions] = useState<ExplainOptions | null>(null)\n const [hasTriggered, setHasTriggered] = useState(false)\n\n // Transform query for server\n // For funnel/flow/retention queries, pass them directly without cleaning\n const serverQuery = useMemo(() => {\n if (!query) return null\n // Funnel, flow, and retention queries have their own format, don't clean them\n if (isFunnelQuery(query) || isFlowQuery(query) || isRetentionQuery(query)) {\n return query\n }\n // Standard queries get cleaned\n return cleanQueryForServer(query as CubeQuery)\n }, [query])\n\n // Query is enabled only when:\n // 1. We have a query\n // 2. Skip is false\n // 3. User has triggered the explain\n const queryEnabled = !!serverQuery && !skip && hasTriggered\n\n const queryResult = useQuery({\n queryKey: createExplainQueryKey(serverQuery, explainOptions ?? undefined),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n const result = await cubeApi.explain(serverQuery, explainOptions ?? undefined)\n return result\n },\n enabled: queryEnabled,\n // EXPLAIN queries should never be cached - always fetch fresh data\n // This ensures users see the latest execution plan after schema/index changes\n staleTime: 0,\n gcTime: 0,\n // Don't refetch automatically - this is manually triggered\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n refetchOnMount: false,\n })\n\n // Manual trigger function\n const runExplain = useCallback((opts?: ExplainOptions) => {\n if (skip || !serverQuery) return\n setExplainOptions(opts ?? null)\n setHasTriggered(true)\n // If already triggered with same options, refetch\n if (hasTriggered) {\n queryResult.refetch()\n }\n }, [skip, serverQuery, hasTriggered, queryResult])\n\n // Clear function\n const clearExplain = useCallback(() => {\n setHasTriggered(false)\n setExplainOptions(null)\n }, [])\n\n return {\n explainResult: queryResult.data ?? null,\n isLoading: queryResult.isLoading || queryResult.isFetching,\n hasRun: hasTriggered,\n error: queryResult.error ?? null,\n runExplain,\n clearExplain,\n }\n}\n","/**\n * useExplainAI - TanStack Query hook for AI analysis of EXPLAIN plans\n *\n * Features:\n * - Manually triggered via useMutation (not automatic)\n * - Analyzes execution plans and provides actionable recommendations\n * - Returns structured recommendations (indexes, table changes, cube improvements)\n * - No caching - always fetches fresh analysis\n * - Uses the /api/ai/explain/analyze endpoint\n *\n * Usage:\n * ```tsx\n * const { analysis, isAnalyzing, error, analyze, clearAnalysis } = useExplainAI()\n *\n * // After getting an explain result, trigger AI analysis\n * <button onClick={() => analyze(explainResult, query)}>Analyze with AI</button>\n * ```\n */\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport type { ExplainResult, AIExplainAnalysis } from '../../types'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\n\nexport interface UseExplainAIOptions {\n /**\n * Custom AI endpoint for explain analysis\n * @default '/api/ai/explain/analyze'\n */\n aiEndpoint?: string\n}\n\nexport interface UseExplainAIResult {\n /** The AI analysis result */\n analysis: AIExplainAnalysis | null\n /** Whether AI analysis is in progress */\n isAnalyzing: boolean\n /** Error if AI analysis failed */\n error: Error | null\n /**\n * Trigger AI analysis of an explain result\n * @param explainResult - The EXPLAIN result to analyze\n * @param query - The original semantic query\n */\n analyze: (explainResult: ExplainResult, query: unknown) => void\n /** Clear the analysis result */\n clearAnalysis: () => void\n}\n\n/**\n * Query key for AI explain analysis\n */\nexport const EXPLAIN_AI_QUERY_KEY = ['cube', 'explain', 'ai'] as const\n\n/**\n * TanStack Query hook for AI analysis of EXPLAIN plans\n *\n * This hook uses useMutation to call the AI endpoint and analyze execution plans,\n * providing actionable recommendations for performance improvement.\n *\n * Recommendations focus on what users CAN control:\n * - Index creation (with CREATE INDEX statements)\n * - Table structure changes\n * - Cube definition improvements (segments, pre-aggregations, joins)\n *\n * Usage:\n * ```tsx\n * const { analysis, isAnalyzing, analyze } = useExplainAI()\n *\n * // After running EXPLAIN\n * if (explainResult) {\n * <button onClick={() => analyze(explainResult, query)}>\n * {isAnalyzing ? 'Analyzing...' : 'Analyze with AI'}\n * </button>\n * }\n *\n * // Display recommendations\n * {analysis?.recommendations.map(rec => (\n * <div key={rec.title}>\n * <h4>{rec.title}</h4>\n * <p>{rec.description}</p>\n * {rec.sql && <pre>{rec.sql}</pre>}\n * {rec.cubeCode && <pre>{rec.cubeCode}</pre>}\n * </div>\n * ))}\n * ```\n */\nexport function useExplainAI(options: UseExplainAIOptions = {}): UseExplainAIResult {\n const { features } = useCubeFeatures()\n const { apiOptions } = useCubeApi()\n const enableAI = features.enableAI ?? true\n const queryClient = useQueryClient()\n\n // AI endpoint - defaults to /api/ai/explain/analyze\n // This is the standard path for AI routes in the dev server\n const aiEndpoint = options.aiEndpoint ?? '/api/ai/explain/analyze'\n\n const mutation = useMutation({\n mutationKey: EXPLAIN_AI_QUERY_KEY,\n mutationFn: async ({ explainResult, query }: { explainResult: ExplainResult; query: unknown }) => {\n // Check if AI is enabled\n if (!enableAI) {\n throw new Error('AI features are disabled')\n }\n\n const response = await fetch(aiEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...apiOptions?.headers,\n },\n credentials: 'include',\n body: JSON.stringify({\n explainResult,\n query,\n }),\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n throw new Error(\n errorData.error ||\n errorData.message ||\n `AI analysis failed: ${response.status} ${response.statusText}`\n )\n }\n\n const result: AIExplainAnalysis = await response.json()\n return result\n },\n // No caching - each mutation is independent\n gcTime: 0,\n })\n\n const analyze = useCallback((explainResult: ExplainResult, query: unknown) => {\n mutation.mutate({ explainResult, query })\n }, [mutation])\n\n const clearAnalysis = useCallback(() => {\n mutation.reset()\n // Also invalidate any cached queries\n queryClient.removeQueries({ queryKey: EXPLAIN_AI_QUERY_KEY })\n }, [mutation, queryClient])\n\n return {\n analysis: mutation.data ?? null,\n isAnalyzing: mutation.isPending,\n error: mutation.error ?? null,\n analyze,\n clearAnalysis,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as e } from "./chart-data-table-
|
|
1
|
+
import { P as e } from "./chart-data-table-C3Xh9jwL.js";
|
|
2
2
|
import "react";
|
|
3
3
|
import { jsx as t } from "react/jsx-runtime";
|
|
4
4
|
//#region src/client/utils/measureIcons.tsx
|
|
@@ -125,4 +125,4 @@ function u() {
|
|
|
125
125
|
//#endregion
|
|
126
126
|
export { s as a, r as c, o as i, n as l, u as n, a as o, c as r, l as s, i as t };
|
|
127
127
|
|
|
128
|
-
//# sourceMappingURL=utils
|
|
128
|
+
//# sourceMappingURL=utils-BldkcRHv.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils
|
|
1
|
+
{"version":3,"file":"utils-BldkcRHv.js","names":[],"sources":["../../../src/client/utils/measureIcons.tsx","../../../src/client/utils/index.ts"],"sourcesContent":["import React from 'react'\nimport { getMeasureTypeIcon } from '../icons'\n\n/**\n * Get the appropriate icon component for a given measure type\n * All icons use amber coloring for consistency\n */\nexport function getMeasureIcon(\n measureType: string | undefined,\n className: string = 'w-4 h-4'\n): React.ReactElement {\n const IconComponent = getMeasureTypeIcon(measureType)\n return <IconComponent className={className} />\n}\n\n/**\n * Get all available measure type icons\n * Useful for documentation or UI that shows all measure types\n */\nexport function getAllMeasureIcons(): Record<string, React.ReactElement> {\n const types = ['count', 'countDistinct', 'countDistinctApprox', 'sum', 'avg', 'min', 'max', 'runningTotal', 'calculated', 'number']\n const icons: Record<string, React.ReactElement> = {}\n\n for (const type of types) {\n const IconComponent = getMeasureTypeIcon(type)\n icons[type] = <IconComponent className=\"dc:w-4 dc:h-4\" />\n }\n\n return icons\n}\n","/**\n * Utility functions for drizzle-cube client\n */\n\nimport type { PortletConfig, DashboardConfig } from '../types'\n\n// Re-export chart utilities\nexport * from './chartUtils'\nexport * from './chartConstants'\nexport * from './measureIcons'\nexport * from './periodUtils'\nexport * from './pivotUtils'\nexport * from './syntaxHighlighting'\nexport * from './comparisonUtils'\n\n// Thumbnail utilities (requires html2canvas peer dependency)\nexport { captureThumbnail, isThumbnailCaptureAvailable } from './thumbnail'\n\n// XLSX export utilities (requires exceljs peer dependency)\nexport { exportPortletToXlsx, isExportAvailable } from './exportXlsx'\n\n/**\n * Create a dashboard layout from portlet configurations\n */\nexport function createDashboardLayout(portlets: PortletConfig[]): DashboardConfig {\n const layouts = generateResponsiveLayouts(portlets)\n \n return {\n portlets,\n layouts\n }\n}\n\n/**\n * Generate responsive layouts for different breakpoints\n */\nexport function generateResponsiveLayouts(portlets: PortletConfig[]) {\n const gridLayout = portlets.map(portlet => ({\n i: portlet.id,\n x: portlet.x,\n y: portlet.y,\n w: portlet.w,\n h: portlet.h,\n minW: 3,\n minH: 3\n }))\n\n return {\n lg: gridLayout,\n md: gridLayout.map(item => ({ ...item, w: Math.min(item.w, 8) })),\n sm: gridLayout.map(item => ({ ...item, w: Math.min(item.w, 6) })),\n xs: gridLayout.map(item => ({ ...item, w: Math.min(item.w, 4) })),\n xxs: gridLayout.map(item => ({ ...item, w: 2 }))\n }\n}\n\n/**\n * Format chart data for display\n */\nexport function formatChartData(data: any[], options: {\n formatNumbers?: boolean\n precision?: number\n} = {}): any[] {\n const { formatNumbers = true, precision = 2 } = options\n\n if (!formatNumbers) return data\n\n return data.map(row => {\n const formattedRow: any = {}\n \n for (const [key, value] of Object.entries(row)) {\n if (typeof value === 'number') {\n formattedRow[key] = Number(value.toFixed(precision))\n } else {\n formattedRow[key] = value\n }\n }\n \n return formattedRow\n })\n}\n\n/**\n * Generate a unique ID for new portlets\n */\nexport function generatePortletId(): string {\n return `portlet-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n}\n\n/**\n * Find the next available position in a grid\n */\nexport function findNextPosition(existingPortlets: PortletConfig[], _w: number = 6, _h: number = 4): { x: number; y: number } {\n if (existingPortlets.length === 0) {\n return { x: 0, y: 0 }\n }\n\n // Find the maximum Y position and height\n const maxY = Math.max(...existingPortlets.map(p => p.y + p.h))\n \n return { x: 0, y: maxY }\n}\n\n/**\n * Validate a cube query JSON string\n */\nexport function validateCubeQuery(queryString: string): { valid: boolean; error?: string; query?: any } {\n try {\n const query = JSON.parse(queryString)\n \n // Basic validation\n if (typeof query !== 'object' || query === null) {\n return { valid: false, error: 'Query must be a JSON object' }\n }\n\n // Check for required fields\n if (!query.measures && !query.dimensions) {\n return { valid: false, error: 'Query must have at least measures or dimensions' }\n }\n\n return { valid: true, query }\n } catch {\n return { valid: false, error: 'Invalid JSON format' }\n }\n}\n\n/**\n * Create a sample portlet configuration\n */\nexport function createSamplePortlet(): Omit<PortletConfig, 'id'> {\n return {\n title: 'Sample Chart',\n query: JSON.stringify({\n measures: ['count'],\n dimensions: ['category']\n }, null, 2),\n chartType: 'bar',\n chartConfig: {\n x: 'category',\n y: ['count']\n },\n displayConfig: {\n showLegend: true,\n showGrid: true,\n showTooltip: true\n },\n w: 6,\n h: 4,\n x: 0,\n y: 0\n }\n}"],"mappings":";;;;AAOA,SAAgB,EACd,GACA,IAAoB,WACA;AAEpB,QAAO,kBADe,EAAmB,EAAY,EAC9C,EAA0B,cAAa,CAAA;;AAOhD,SAAgB,IAAyD;CACvE,IAAM,IAAQ;EAAC;EAAS;EAAiB;EAAuB;EAAO;EAAO;EAAO;EAAO;EAAgB;EAAc;EAAS,EAC7H,IAA4C,EAAE;AAEpD,MAAK,IAAM,KAAQ,EAEjB,GAAM,KAAQ,kBADQ,EAAmB,EAAK,EAChC,EAAe,WAAU,iBAAkB,CAAA;AAG3D,QAAO;;;;ACJT,SAAgB,EAAsB,GAA4C;AAGhF,QAAO;EACL;EACA,SAJc,EAA0B,EAAS;EAKlD;;AAMH,SAAgB,EAA0B,GAA2B;CACnE,IAAM,IAAa,EAAS,KAAI,OAAY;EAC1C,GAAG,EAAQ;EACX,GAAG,EAAQ;EACX,GAAG,EAAQ;EACX,GAAG,EAAQ;EACX,GAAG,EAAQ;EACX,MAAM;EACN,MAAM;EACP,EAAE;AAEH,QAAO;EACL,IAAI;EACJ,IAAI,EAAW,KAAI,OAAS;GAAE,GAAG;GAAM,GAAG,KAAK,IAAI,EAAK,GAAG,EAAE;GAAE,EAAE;EACjE,IAAI,EAAW,KAAI,OAAS;GAAE,GAAG;GAAM,GAAG,KAAK,IAAI,EAAK,GAAG,EAAE;GAAE,EAAE;EACjE,IAAI,EAAW,KAAI,OAAS;GAAE,GAAG;GAAM,GAAG,KAAK,IAAI,EAAK,GAAG,EAAE;GAAE,EAAE;EACjE,KAAK,EAAW,KAAI,OAAS;GAAE,GAAG;GAAM,GAAG;GAAG,EAAE;EACjD;;AAMH,SAAgB,EAAgB,GAAa,IAGzC,EAAE,EAAS;CACb,IAAM,EAAE,mBAAgB,IAAM,eAAY,MAAM;AAIhD,QAFK,IAEE,EAAK,KAAI,MAAO;EACrB,IAAM,IAAoB,EAAE;AAE5B,OAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,EAAI,CAC5C,CAAI,OAAO,KAAU,WACnB,EAAa,KAAO,OAAO,EAAM,QAAQ,EAAU,CAAC,GAEpD,EAAa,KAAO;AAIxB,SAAO;GACP,GAdyB;;AAoB7B,SAAgB,IAA4B;AAC1C,QAAO,WAAW,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,OAAO,GAAG,EAAE;;AAMzE,SAAgB,EAAiB,GAAmC,IAAa,GAAG,IAAa,GAA6B;AAQ5H,QAPI,EAAiB,WAAW,IACvB;EAAE,GAAG;EAAG,GAAG;EAAG,GAMhB;EAAE,GAAG;EAAG,GAFF,KAAK,IAAI,GAAG,EAAiB,KAAI,MAAK,EAAE,IAAI,EAAE,EAAE,CAAC;EAEtC;;AAM1B,SAAgB,EAAkB,GAAsE;AACtG,KAAI;EACF,IAAM,IAAQ,KAAK,MAAM,EAAY;AAYrC,SATI,OAAO,KAAU,aAAY,IACxB;GAAE,OAAO;GAAO,OAAO;GAA+B,GAI3D,CAAC,EAAM,YAAY,CAAC,EAAM,aACrB;GAAE,OAAO;GAAO,OAAO;GAAmD,GAG5E;GAAE,OAAO;GAAM;GAAO;SACvB;AACN,SAAO;GAAE,OAAO;GAAO,OAAO;GAAuB;;;AAOzD,SAAgB,IAAiD;AAC/D,QAAO;EACL,OAAO;EACP,OAAO,KAAK,UAAU;GACpB,UAAU,CAAC,QAAQ;GACnB,YAAY,CAAC,WAAW;GACzB,EAAE,MAAM,EAAE;EACX,WAAW;EACX,aAAa;GACX,GAAG;GACH,GAAG,CAAC,QAAQ;GACb;EACD,eAAe;GACb,YAAY;GACZ,UAAU;GACV,aAAa;GACd;EACD,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACJ"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./rolldown-runtime-CCl2IeXn.js";
|
|
2
|
-
import { _t as t, at as n, ct as r, dt as i, ft as a, gt as o, ht as s, it as c, lt as l, mt as u, nt as d, ot as f, pt as p, rt as m, st as h, tt as g, ut as _, vt as v } from "./chart-data-table-
|
|
2
|
+
import { _t as t, at as n, ct as r, dt as i, ft as a, gt as o, ht as s, it as c, lt as l, mt as u, nt as d, ot as f, pt as p, rt as m, st as h, tt as g, ut as _, vt as v } from "./chart-data-table-C3Xh9jwL.js";
|
|
3
3
|
import * as y from "react";
|
|
4
4
|
import b from "react";
|
|
5
5
|
//#region node_modules/react-intersection-observer/dist/index.mjs
|
|
@@ -825,4 +825,4 @@ var ie = /* @__PURE__ */ e(((e, t) => {
|
|
|
825
825
|
//#endregion
|
|
826
826
|
export { q as a, F as c, X as i, P as l, re as n, R as o, G as r, ee as s, ie as t, k as u };
|
|
827
827
|
|
|
828
|
-
//# sourceMappingURL=vendor-
|
|
828
|
+
//# sourceMappingURL=vendor-ClXpIiea.js.map
|