drizzle-cube 0.9.5 → 0.9.6
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/ActivityGridChart.config-BgAUfqKS.js +2 -0
- package/dist/adapters/ActivityGridChart.config-CGsjbfYL.cjs +1 -0
- package/dist/adapters/AreaChart.config-Bmj24k3G.cjs +1 -0
- package/dist/adapters/AreaChart.config-DqJvquc-.js +2 -0
- package/dist/adapters/BarChart.config-CzGxsUq-.js +2 -0
- package/dist/adapters/BarChart.config-D1uQGV6T.cjs +1 -0
- package/dist/adapters/BoxPlotChart.config-I0S4-aNF.js +2 -0
- package/dist/adapters/BoxPlotChart.config-wfS4FxGI.cjs +1 -0
- package/dist/adapters/BubbleChart.config-BDSknEkc.js +2 -0
- package/dist/adapters/BubbleChart.config-CByvW3Rq.cjs +1 -0
- package/dist/adapters/CandlestickChart.config-08gqS1xj.cjs +1 -0
- package/dist/adapters/CandlestickChart.config-Bkr9bdwM.js +2 -0
- package/dist/adapters/DataTable.config-BFP6xnMc.cjs +1 -0
- package/dist/adapters/DataTable.config-DHYD7PA8.js +2 -0
- package/dist/adapters/DotStripChart.config-DRQybZZu.cjs +1 -0
- package/dist/adapters/DotStripChart.config-Sp0fOxfM.js +2 -0
- package/dist/adapters/FunnelChart.config-ByARozX6.cjs +1 -0
- package/dist/adapters/FunnelChart.config-DhCqap3P.js +2 -0
- package/dist/adapters/GaugeChart.config-CfRicB1o.cjs +1 -0
- package/dist/adapters/GaugeChart.config-PTQ85WM5.js +2 -0
- package/dist/adapters/HeatMapChart.config-0tmGHwm2.cjs +1 -0
- package/dist/adapters/HeatMapChart.config-Devz5A5U.js +2 -0
- package/dist/adapters/KpiDelta.config-CGTdddOM.js +2 -0
- package/dist/adapters/KpiDelta.config-CjWmwXjk.cjs +1 -0
- package/dist/adapters/KpiNumber.config-Cd4jw22d.cjs +1 -0
- package/dist/adapters/KpiNumber.config-D3Et71Zn.js +2 -0
- package/dist/adapters/KpiText.config-B9VATPy2.js +2 -0
- package/dist/adapters/KpiText.config-B_dkrdHj.cjs +1 -0
- package/dist/adapters/LineChart.config-C3p-mc15.cjs +1 -0
- package/dist/adapters/LineChart.config-DXWLMWjn.js +2 -0
- package/dist/adapters/MarkdownChart.config-BQGmd6Iz.js +2 -0
- package/dist/adapters/MarkdownChart.config-CQFu4297.cjs +1 -0
- package/dist/adapters/MeasureProfileChart.config-CXLeprVS.js +2 -0
- package/dist/adapters/MeasureProfileChart.config-KpfD016N.cjs +1 -0
- package/dist/adapters/PieChart.config-j-syrhBx.js +2 -0
- package/dist/adapters/PieChart.config-zzBPh0tx.cjs +1 -0
- package/dist/adapters/ProportionBarChart.config-BD9bf-LZ.js +2 -0
- package/dist/adapters/ProportionBarChart.config-CBLcLV4Z.cjs +1 -0
- package/dist/adapters/RadarChart.config-CW9f_VxU.cjs +1 -0
- package/dist/adapters/RadarChart.config-yKvcZ49V.js +2 -0
- package/dist/adapters/RadialBarChart.config-DwQYejI6.cjs +1 -0
- package/dist/adapters/RadialBarChart.config-xK4AyoOF.js +2 -0
- package/dist/adapters/RecordsTable.config-CquV7Wi0.js +2 -0
- package/dist/adapters/RecordsTable.config-DzrQXBvh.cjs +1 -0
- package/dist/adapters/RetentionCombinedChart.config-BYAFkFb-.cjs +1 -0
- package/dist/adapters/RetentionCombinedChart.config-Ddmb-WwM.js +2 -0
- package/dist/adapters/RetentionHeatmap.config-CSHXD736.cjs +1 -0
- package/dist/adapters/RetentionHeatmap.config-DODcy3gK.js +2 -0
- package/dist/adapters/SankeyChart.config-B56o6GFu.js +2 -0
- package/dist/adapters/SankeyChart.config-DLnhr2k_.cjs +1 -0
- package/dist/adapters/ScatterChart.config-CrIFaC-s.js +2 -0
- package/dist/adapters/ScatterChart.config-Dfj1_af-.cjs +1 -0
- package/dist/adapters/SunburstChart.config-CuLsLj6v.cjs +1 -0
- package/dist/adapters/SunburstChart.config-CvuVUfkp.js +2 -0
- package/dist/adapters/TreeMapChart.config-CvbDr1Ic.js +2 -0
- package/dist/adapters/TreeMapChart.config-Dqjhe8Xk.cjs +1 -0
- package/dist/adapters/WaterfallChart.config-DXFeGg7P.cjs +1 -0
- package/dist/adapters/WaterfallChart.config-MLHPNE5q.js +2 -0
- package/dist/adapters/{compiler-CJVo3oya.cjs → compiler-C5OSdfwn.cjs} +2 -2
- package/dist/adapters/{compiler-DYAP3l3U.js → compiler-CpvetSCm.js} +2 -2
- package/dist/adapters/core/index.cjs +1 -1
- package/dist/adapters/core/index.js +1 -1
- package/dist/adapters/{core-B05onvSN.js → core-BH_56XtM.js} +2 -2
- package/dist/adapters/{core-BkxueWec.cjs → core-CydD6oFu.cjs} +1 -1
- package/dist/adapters/express/index.cjs +1 -1
- package/dist/adapters/express/index.js +5 -5
- package/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.js +5 -5
- package/dist/adapters/{handler-mwSVmP0z.js → handler-CtcALGcY.js} +32 -32
- package/dist/adapters/{handler-CP-6A8X3.cjs → handler-Dp8fpSpk.cjs} +3 -3
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.js +4 -4
- package/dist/adapters/mcp-app-html-C8vG9epH.js +5 -0
- package/dist/adapters/mcp-app-html-DRWnkcx4.cjs +1 -0
- package/dist/adapters/mcp-app-html.cjs +1 -1
- package/dist/adapters/mcp-app-html.js +1 -1
- package/dist/adapters/mcp-tools.cjs +1 -1
- package/dist/adapters/mcp-tools.js +3 -3
- package/dist/adapters/{mcp-transport-BH5avoaA.js → mcp-transport-DDxjqyuK.js} +1 -1
- package/dist/adapters/{mcp-transport-DXgZ-xcU.cjs → mcp-transport-QGYYYSgR.cjs} +1 -1
- package/dist/adapters/nextjs/index.cjs +1 -1
- package/dist/adapters/nextjs/index.js +5 -5
- package/dist/adapters/{openai-B82IDfp4.cjs → openai-Cm4JcLuF.cjs} +1 -1
- package/dist/adapters/{openai-BaNGQmmK.cjs → openai-CwKv0DD-.cjs} +3 -3
- package/dist/adapters/{openai-CeNMQU8r.js → openai-DH5InEPj.js} +3 -3
- package/dist/adapters/{openai-BKDrN7u0.js → openai-pCkHdDh4.js} +1 -1
- package/dist/adapters/{utils-iMkgJr10.cjs → utils-BFGzuSlF.cjs} +10 -10
- package/dist/adapters/{utils-dyUxCOWT.js → utils-C_Mc0qsk.js} +1259 -1279
- package/dist/adapters/utils.cjs +1 -1
- package/dist/adapters/utils.js +1 -1
- package/dist/cjs/client/hooks/queries/useExplainAI.d.ts +3 -2
- package/dist/cjs/client/types.d.ts +7 -0
- package/dist/client/charts.js +12 -12
- package/dist/client/chunks/{DashboardEditModal-3_odkGXU.js → DashboardEditModal-USQFytJw.js} +12 -12
- package/dist/client/chunks/DashboardEditModal-USQFytJw.js.map +1 -0
- package/dist/client/chunks/{DotStripChart-BGpjqpNU.js → DotStripChart-CjSsunHE.js} +3 -3
- package/dist/client/chunks/{DotStripChart-BGpjqpNU.js.map → DotStripChart-CjSsunHE.js.map} +1 -1
- package/dist/client/chunks/{ProportionBarChart-DuC16TT0.js → ProportionBarChart-B8UyZBO_.js} +3 -3
- package/dist/client/chunks/{ProportionBarChart-DuC16TT0.js.map → ProportionBarChart-B8UyZBO_.js.map} +1 -1
- package/dist/client/chunks/{RecordsTable-zy4EWyWx.js → RecordsTable-BSmkwdgd.js} +4 -4
- package/dist/client/chunks/{RecordsTable-zy4EWyWx.js.map → RecordsTable-BSmkwdgd.js.map} +1 -1
- package/dist/client/chunks/{RetentionCombinedChart-BrqSuLXT.js → RetentionCombinedChart-rOdIhUGH.js} +3 -3
- package/dist/client/chunks/{RetentionCombinedChart-BrqSuLXT.js.map → RetentionCombinedChart-rOdIhUGH.js.map} +1 -1
- package/dist/client/chunks/{RetentionHeatmap-DlGScG6X.js → RetentionHeatmap-DVm_7rph.js} +2 -2
- package/dist/client/chunks/{RetentionHeatmap-DlGScG6X.js.map → RetentionHeatmap-DVm_7rph.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-UhPaVPsn.js → analysis-builder-D_tw9BvU.js} +9 -9
- package/dist/client/chunks/{analysis-builder-UhPaVPsn.js.map → analysis-builder-D_tw9BvU.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-shared-DS_eJnM8.js → analysis-builder-shared-BSINKFr2.js} +9 -9
- package/dist/client/chunks/{analysis-builder-shared-DS_eJnM8.js.map → analysis-builder-shared-BSINKFr2.js.map} +1 -1
- package/dist/client/chunks/{chart-activity-grid-cJI-MF_M.js → chart-activity-grid-CSIsJ12e.js} +4 -4
- package/dist/client/chunks/{chart-activity-grid-cJI-MF_M.js.map → chart-activity-grid-CSIsJ12e.js.map} +1 -1
- package/dist/client/chunks/{chart-area-11vKdaTE.js → chart-area-BpvSKH5I.js} +2 -2
- package/dist/client/chunks/{chart-area-11vKdaTE.js.map → chart-area-BpvSKH5I.js.map} +1 -1
- package/dist/client/chunks/{chart-bar-ByOYTFZp.js → chart-bar-Cjzv-LvG.js} +3 -3
- package/dist/client/chunks/{chart-bar-ByOYTFZp.js.map → chart-bar-Cjzv-LvG.js.map} +1 -1
- package/dist/client/chunks/{chart-box-plot-DKTdhdjk.js → chart-box-plot-DHsHfMt6.js} +2 -2
- package/dist/client/chunks/{chart-box-plot-DKTdhdjk.js.map → chart-box-plot-DHsHfMt6.js.map} +1 -1
- package/dist/client/chunks/{chart-bubble-lCNFaxsr.js → chart-bubble-BtWm6x7L.js} +2 -2
- package/dist/client/chunks/{chart-bubble-lCNFaxsr.js.map → chart-bubble-BtWm6x7L.js.map} +1 -1
- package/dist/client/chunks/{chart-candlestick-DQJ8byvB.js → chart-candlestick-Bz0_fLuq.js} +2 -2
- package/dist/client/chunks/{chart-candlestick-DQJ8byvB.js.map → chart-candlestick-Bz0_fLuq.js.map} +1 -1
- package/dist/client/chunks/{chart-data-table-DkkQtuY6.js → chart-data-table-BH1A-1RN.js} +4 -4
- package/dist/client/chunks/{chart-data-table-DkkQtuY6.js.map → chart-data-table-BH1A-1RN.js.map} +1 -1
- package/dist/client/chunks/{chart-funnel-BtjUJRcf.js → chart-funnel-C_-K4RLj.js} +2 -2
- package/dist/client/chunks/{chart-funnel-BtjUJRcf.js.map → chart-funnel-C_-K4RLj.js.map} +1 -1
- package/dist/client/chunks/{chart-gauge-Da-29BzE.js → chart-gauge-F-vdq2BX.js} +3 -3
- package/dist/client/chunks/{chart-gauge-Da-29BzE.js.map → chart-gauge-F-vdq2BX.js.map} +1 -1
- package/dist/client/chunks/{chart-heat-map-DK0IR2al.js → chart-heat-map-D_8TluGo.js} +3 -3
- package/dist/client/chunks/{chart-heat-map-DK0IR2al.js.map → chart-heat-map-D_8TluGo.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-delta-Cih5Vh_Z.js → chart-kpi-delta-DGvQ0BHZ.js} +3 -3
- package/dist/client/chunks/{chart-kpi-delta-Cih5Vh_Z.js.map → chart-kpi-delta-DGvQ0BHZ.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-number-Z5zpxeKR.js → chart-kpi-number-DP6zCh-c.js} +5 -5
- package/dist/client/chunks/{chart-kpi-number-Z5zpxeKR.js.map → chart-kpi-number-DP6zCh-c.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-text-glMTySe4.js → chart-kpi-text-DDbWfMwK.js} +3 -3
- package/dist/client/chunks/{chart-kpi-text-glMTySe4.js.map → chart-kpi-text-DDbWfMwK.js.map} +1 -1
- package/dist/client/chunks/{chart-line-D6Xkq1eW.js → chart-line-DlhmQQ-X.js} +3 -3
- package/dist/client/chunks/{chart-line-D6Xkq1eW.js.map → chart-line-DlhmQQ-X.js.map} +1 -1
- package/dist/client/chunks/{chart-markdown-BHBjzgfc.js → chart-markdown-C5ldCjSN.js} +3 -3
- package/dist/client/chunks/{chart-markdown-BHBjzgfc.js.map → chart-markdown-C5ldCjSN.js.map} +1 -1
- package/dist/client/chunks/{chart-measure-profile-Cl5xQZLF.js → chart-measure-profile-CjErXQI4.js} +3 -3
- package/dist/client/chunks/{chart-measure-profile-Cl5xQZLF.js.map → chart-measure-profile-CjErXQI4.js.map} +1 -1
- package/dist/client/chunks/{chart-pie-X3wVYu9v.js → chart-pie-CmKhmUR-.js} +3 -3
- package/dist/client/chunks/{chart-pie-X3wVYu9v.js.map → chart-pie-CmKhmUR-.js.map} +1 -1
- package/dist/client/chunks/{chart-radar-BExajFiU.js → chart-radar-B2bYTyb0.js} +3 -3
- package/dist/client/chunks/{chart-radar-BExajFiU.js.map → chart-radar-B2bYTyb0.js.map} +1 -1
- package/dist/client/chunks/{chart-radial-bar-CgGmC8WE.js → chart-radial-bar-DvQ-xZS-.js} +3 -3
- package/dist/client/chunks/{chart-radial-bar-CgGmC8WE.js.map → chart-radial-bar-DvQ-xZS-.js.map} +1 -1
- package/dist/client/chunks/{chart-sankey-DKj417mA.js → chart-sankey-DHIrjmhj.js} +2 -2
- package/dist/client/chunks/{chart-sankey-DKj417mA.js.map → chart-sankey-DHIrjmhj.js.map} +1 -1
- package/dist/client/chunks/{chart-scatter-DSNgbnEu.js → chart-scatter-JnoA3CD5.js} +3 -3
- package/dist/client/chunks/{chart-scatter-DSNgbnEu.js.map → chart-scatter-JnoA3CD5.js.map} +1 -1
- package/dist/client/chunks/{chart-sunburst-IUrLGpyo.js → chart-sunburst-BtzRp5bg.js} +3 -3
- package/dist/client/chunks/{chart-sunburst-IUrLGpyo.js.map → chart-sunburst-BtzRp5bg.js.map} +1 -1
- package/dist/client/chunks/{chart-tree-map-DS5X1Wiw.js → chart-tree-map-HQitd39D.js} +3 -3
- package/dist/client/chunks/{chart-tree-map-DS5X1Wiw.js.map → chart-tree-map-HQitd39D.js.map} +1 -1
- package/dist/client/chunks/{chart-waterfall-C1GZfgHw.js → chart-waterfall-Dd3HMz3p.js} +3 -3
- package/dist/client/chunks/{chart-waterfall-C1GZfgHw.js.map → chart-waterfall-Dd3HMz3p.js.map} +1 -1
- package/dist/client/chunks/{charts-core-DQXx0Xk-.js → charts-core-CQFOTTPb.js} +2 -2
- package/dist/client/chunks/{charts-core-DQXx0Xk-.js.map → charts-core-CQFOTTPb.js.map} +1 -1
- package/dist/client/chunks/charts-loader-BlxEsp39.js +87 -0
- package/dist/client/chunks/{charts-loader-DuV9kYN8.js.map → charts-loader-BlxEsp39.js.map} +1 -1
- package/dist/client/chunks/core-BWRP9s8x.js.map +1 -1
- package/dist/client/chunks/dashboardExport-Db3rmUP1.js.map +1 -1
- package/dist/client/chunks/dist-COqN0-Yj.js.map +1 -1
- package/dist/client/chunks/exceljs.min-CdNVdwM3.js.map +1 -1
- package/dist/client/chunks/{schema-visualization-DIn9gy5O.js → schema-visualization-BspQ-XB8.js} +3 -3
- package/dist/client/chunks/{schema-visualization-DIn9gy5O.js.map → schema-visualization-BspQ-XB8.js.map} +1 -1
- package/dist/client/chunks/{useDirtyStateTracking-giMoYB90.js → useDirtyStateTracking-DY5sjvb6.js} +4 -4
- package/dist/client/chunks/{useDirtyStateTracking-giMoYB90.js.map → useDirtyStateTracking-DY5sjvb6.js.map} +1 -1
- package/dist/client/chunks/{useExplainAI-CpFZbdn-.js → useExplainAI-BY0Ti3FZ.js} +5 -5
- package/dist/client/chunks/{useExplainAI-CpFZbdn-.js.map → useExplainAI-BY0Ti3FZ.js.map} +1 -1
- package/dist/client/chunks/{vendor-wyAsikWW.js → vendor-C4pD7rZf.js} +2 -2
- package/dist/client/chunks/{vendor-wyAsikWW.js.map → vendor-C4pD7rZf.js.map} +1 -1
- package/dist/client/components.js +1 -1
- package/dist/client/hooks/queries/useExplainAI.d.ts +3 -2
- package/dist/client/hooks.js +3 -3
- package/dist/client/icons.js +1 -1
- package/dist/client/index.js +13 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/providers.js +1 -1
- package/dist/client/schema.js +1 -1
- package/dist/client/types.d.ts +7 -0
- package/dist/client/utils.js +4 -4
- package/dist/client-bundle-stats.html +1 -1
- package/dist/mcp-app/mcp-app.html +4 -4
- package/dist/server/index.cjs +42 -42
- package/dist/server/index.js +1083 -1103
- package/dist/server/{openai-Cxii1K2N.cjs → openai-C6tdx3yN.cjs} +2 -2
- package/dist/server/{openai-aLi6lmJs.js → openai-D0vh48Oi.js} +2 -2
- package/dist/server/{openai-DC2ye24o.cjs → openai-D346-vmt.cjs} +1 -1
- package/dist/server/{openai-Jt2WUOXm.js → openai-SILGL2q_.js} +1 -1
- package/package.json +2 -2
- package/dist/adapters/ActivityGridChart.config-BXelP_3e.cjs +0 -1
- package/dist/adapters/ActivityGridChart.config-BjOOT9bc.js +0 -2
- package/dist/adapters/AreaChart.config-B9PiH7Kt.js +0 -2
- package/dist/adapters/AreaChart.config-Bv39EDi8.cjs +0 -1
- package/dist/adapters/BarChart.config-CU-XGdDO.cjs +0 -1
- package/dist/adapters/BarChart.config-D9OIP1uN.js +0 -2
- package/dist/adapters/BoxPlotChart.config-C3cLagI6.js +0 -2
- package/dist/adapters/BoxPlotChart.config-CMwswa9H.cjs +0 -1
- package/dist/adapters/BubbleChart.config--rvyga9s.js +0 -2
- package/dist/adapters/BubbleChart.config-DOCLeg91.cjs +0 -1
- package/dist/adapters/CandlestickChart.config-B8h__isO.cjs +0 -1
- package/dist/adapters/CandlestickChart.config-DPzi-8B_.js +0 -2
- package/dist/adapters/DataTable.config-Dn2PyQdy.cjs +0 -1
- package/dist/adapters/DataTable.config-wtCGsLPw.js +0 -2
- package/dist/adapters/DotStripChart.config-BFshbpk0.cjs +0 -1
- package/dist/adapters/DotStripChart.config-c-tbauT6.js +0 -2
- package/dist/adapters/FunnelChart.config-CZYDI4k-.js +0 -2
- package/dist/adapters/FunnelChart.config-Dc2pIfJy.cjs +0 -1
- package/dist/adapters/GaugeChart.config-C9LYrKB1.cjs +0 -1
- package/dist/adapters/GaugeChart.config-DD73ueqk.js +0 -2
- package/dist/adapters/HeatMapChart.config-5DgOpgpV.js +0 -2
- package/dist/adapters/HeatMapChart.config-BZgQ255E.cjs +0 -1
- package/dist/adapters/KpiDelta.config-BEB3XqML.cjs +0 -1
- package/dist/adapters/KpiDelta.config-DeRkPIxD.js +0 -2
- package/dist/adapters/KpiNumber.config-CxpTAKzR.cjs +0 -1
- package/dist/adapters/KpiNumber.config-DM1dRQL-.js +0 -2
- package/dist/adapters/KpiText.config-Dt59uikm.js +0 -2
- package/dist/adapters/KpiText.config-Dt_pW6mK.cjs +0 -1
- package/dist/adapters/LineChart.config-YeXia1kV.cjs +0 -1
- package/dist/adapters/LineChart.config-jX2ulNmq.js +0 -2
- package/dist/adapters/MarkdownChart.config-BAXU79ec.js +0 -2
- package/dist/adapters/MarkdownChart.config-BP6_9ET2.cjs +0 -1
- package/dist/adapters/MeasureProfileChart.config-BW2gdPaM.cjs +0 -1
- package/dist/adapters/MeasureProfileChart.config-CSxeKHnS.js +0 -2
- package/dist/adapters/PieChart.config-Ci3ZPY9U.cjs +0 -1
- package/dist/adapters/PieChart.config-DtCSYlvx.js +0 -2
- package/dist/adapters/ProportionBarChart.config-B71wPMKX.js +0 -2
- package/dist/adapters/ProportionBarChart.config-Bihgdn8v.cjs +0 -1
- package/dist/adapters/RadarChart.config-CC8jIJ78.js +0 -2
- package/dist/adapters/RadarChart.config-D7dCuKRM.cjs +0 -1
- package/dist/adapters/RadialBarChart.config-DC2nQk5q.js +0 -2
- package/dist/adapters/RadialBarChart.config-Dumbvnj1.cjs +0 -1
- package/dist/adapters/RecordsTable.config-BRqLDmY3.cjs +0 -1
- package/dist/adapters/RecordsTable.config-D1wlIO1v.js +0 -2
- package/dist/adapters/RetentionCombinedChart.config-DejP7Hcc.cjs +0 -1
- package/dist/adapters/RetentionCombinedChart.config-yDEDWwJt.js +0 -2
- package/dist/adapters/RetentionHeatmap.config-BSqTkHB8.cjs +0 -1
- package/dist/adapters/RetentionHeatmap.config-GRJjblsW.js +0 -2
- package/dist/adapters/SankeyChart.config-9b1eT_2V.js +0 -2
- package/dist/adapters/SankeyChart.config-n8ro9EZU.cjs +0 -1
- package/dist/adapters/ScatterChart.config-BHqhTxPV.cjs +0 -1
- package/dist/adapters/ScatterChart.config-Bub2V8Ge.js +0 -2
- package/dist/adapters/SunburstChart.config-CxG0NFqN.js +0 -2
- package/dist/adapters/SunburstChart.config-PmN9LEb5.cjs +0 -1
- package/dist/adapters/TreeMapChart.config-D2S3rG6P.js +0 -2
- package/dist/adapters/TreeMapChart.config-TC4dciRa.cjs +0 -1
- package/dist/adapters/WaterfallChart.config-BirJq5VE.cjs +0 -1
- package/dist/adapters/WaterfallChart.config-DYMzte7F.js +0 -2
- package/dist/adapters/mcp-app-html-CUL2sMpA.js +0 -5
- package/dist/adapters/mcp-app-html-Ey6aj9WR.cjs +0 -1
- package/dist/client/chunks/DashboardEditModal-3_odkGXU.js.map +0 -1
- package/dist/client/chunks/charts-loader-DuV9kYN8.js +0 -87
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboardExport-Db3rmUP1.js","names":[],"sources":["../../../src/client/types/analysisConfig.ts","../../../src/client/utils/configMigration.ts","../../../src/client/utils/syntaxHighlighting.ts","../../../src/client/utils/dashboardExport.ts"],"sourcesContent":["/**\n * AnalysisConfig - Versioned, mode-agnostic configuration format\n *\n * This is the canonical format for persisting analysis state to:\n * - localStorage (standalone AnalysisBuilder)\n * - Share URLs\n * - Dashboard portlets (via analysisConfig field)\n *\n * Key design principles:\n * - `query` field IS the executable query (no transformation needed)\n * - Per-mode chart config via `charts[analysisType]` map\n * - No UI state (activeQueryIndex, activeFunnelStepIndex) - those are transient\n */\n\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n} from '../types.js'\nimport type { ServerFunnelQuery } from './funnel.js'\nimport type { ServerFlowQuery } from './flow.js'\nimport type { ServerRetentionQuery } from './retention.js'\n\n// ============================================================================\n// Chart Configuration\n// ============================================================================\n\n/**\n * Chart configuration - shared across all analysis types\n */\nexport interface ChartConfig {\n chartType: ChartType\n chartConfig: ChartAxisConfig\n displayConfig: ChartDisplayConfig\n}\n\n// ============================================================================\n// Analysis Type\n// ============================================================================\n\n/**\n * Analysis type discriminator\n * Future modes: 'cohort'\n */\nexport type AnalysisType = 'query' | 'funnel' | 'flow' | 'retention'\n\n// ============================================================================\n// Base Configuration\n// ============================================================================\n\n/**\n * Base config - common to all analysis types\n */\ninterface AnalysisConfigBase {\n /** Version number for migration support */\n version: 1\n\n /** Which analysis mode this config represents */\n analysisType: AnalysisType\n\n /** Whether to show table or chart view */\n activeView: 'table' | 'chart'\n\n /**\n * Per-mode chart configuration map.\n * Each mode owns its own chart settings, allowing users to configure\n * different chart types for query mode vs funnel mode.\n */\n charts: {\n [K in AnalysisType]?: ChartConfig\n }\n}\n\n// ============================================================================\n// Query Mode Configuration\n// ============================================================================\n\n/**\n * Query mode config - for single queries and multi-query analysis\n *\n * The `query` field is the actual executable query:\n * - Single query: CubeQuery object\n * - Multi-query: MultiQueryConfig with queries array and merge strategy\n */\nexport interface QueryAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'query'\n\n /**\n * The executable query.\n * - CubeQuery: Single query with measures, dimensions, filters\n * - MultiQueryConfig: Multiple queries with merge strategy\n */\n query: CubeQuery | MultiQueryConfig\n}\n\n// ============================================================================\n// Funnel Mode Configuration\n// ============================================================================\n\n/**\n * Funnel mode config - for funnel analysis\n *\n * The `query` field is the ServerFunnelQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FunnelAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'funnel'\n\n /**\n * Server funnel query - executable as-is.\n * Contains bindingKey, timeDimension, steps[], and options.\n */\n query: ServerFunnelQuery\n}\n\n// ============================================================================\n// Flow Mode Configuration\n// ============================================================================\n\n/**\n * Flow mode config - for bidirectional flow analysis\n *\n * The `query` field is the ServerFlowQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FlowAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'flow'\n\n /**\n * Server flow query - executable as-is.\n * Contains bindingKey, timeDimension, eventDimension, startingStep, and depth config.\n */\n query: ServerFlowQuery\n}\n\n// ============================================================================\n// Retention Mode Configuration\n// ============================================================================\n\n/**\n * Retention mode config - for cohort-based retention analysis\n *\n * The `query` field is the ServerRetentionQuery which can be sent\n * directly to the server for execution.\n */\nexport interface RetentionAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'retention'\n\n /**\n * Server retention query - executable as-is.\n * Contains cohortTimeDimension, activityTimeDimension, bindingKey,\n * granularity settings, and period configuration.\n */\n query: ServerRetentionQuery\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/**\n * AnalysisConfig - union of all analysis mode configurations\n */\nexport type AnalysisConfig = QueryAnalysisConfig | FunnelAnalysisConfig | FlowAnalysisConfig | RetentionAnalysisConfig\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if config is for query mode\n */\nexport const isQueryConfig = (c: AnalysisConfig): c is QueryAnalysisConfig =>\n c.analysisType === 'query'\n\n/**\n * Check if config is for funnel mode\n */\nexport const isFunnelConfig = (c: AnalysisConfig): c is FunnelAnalysisConfig =>\n c.analysisType === 'funnel'\n\n/**\n * Check if config is for flow mode\n */\nexport const isFlowConfig = (c: AnalysisConfig): c is FlowAnalysisConfig =>\n c.analysisType === 'flow'\n\n/**\n * Check if config is for retention mode\n */\nexport const isRetentionConfig = (c: AnalysisConfig): c is RetentionAnalysisConfig =>\n c.analysisType === 'retention'\n\n/**\n * Check if a query config contains multiple queries\n */\nexport const isMultiQuery = (config: QueryAnalysisConfig): boolean =>\n 'queries' in config.query &&\n Array.isArray((config.query as MultiQueryConfig).queries)\n\n/**\n * Check if a query config contains a single query\n */\nexport const isSingleQuery = (config: QueryAnalysisConfig): boolean =>\n !isMultiQuery(config)\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisConfig\n */\nexport const isValidAnalysisConfig = (\n config: unknown\n): config is AnalysisConfig => {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n // Check version\n if (c.version !== 1) return false\n\n // Check analysisType\n if (c.analysisType !== 'query' && c.analysisType !== 'funnel' && c.analysisType !== 'flow' && c.analysisType !== 'retention') return false\n\n // Check query exists\n if (!c.query || typeof c.query !== 'object') return false\n\n // Check activeView\n if (c.activeView !== 'table' && c.activeView !== 'chart') return false\n\n return true\n}\n\n// ============================================================================\n// Default Factories\n// ============================================================================\n\n/**\n * Create a default empty query analysis config\n */\nexport const createDefaultQueryConfig = (): QueryAnalysisConfig => ({\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: {\n chartType: 'bar',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n measures: [],\n dimensions: [],\n },\n})\n\n/**\n * Create a default empty funnel analysis config\n */\nexport const createDefaultFunnelConfig = (): FunnelAnalysisConfig => ({\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n})\n\n/**\n * Create a default empty flow analysis config\n */\nexport const createDefaultFlowConfig = (): FlowAnalysisConfig => ({\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n flow: {\n bindingKey: '',\n timeDimension: '',\n eventDimension: '',\n startingStep: {\n name: '',\n filter: undefined,\n },\n stepsBefore: 3,\n stepsAfter: 3,\n joinStrategy: 'auto',\n },\n },\n})\n\n/**\n * Create a default empty retention analysis config\n */\nexport const createDefaultRetentionConfig = (): RetentionAnalysisConfig => ({\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: {\n chartType: 'heatmap',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n retention: {\n timeDimension: '',\n bindingKey: '',\n dateRange: { start: '', end: '' },\n granularity: 'week',\n periods: 12,\n retentionType: 'classic',\n },\n },\n})\n\n/**\n * Create a default config for the given analysis type\n */\nexport const createDefaultConfig = (\n type: AnalysisType = 'query'\n): AnalysisConfig => {\n switch (type) {\n case 'funnel':\n return createDefaultFunnelConfig()\n case 'flow':\n return createDefaultFlowConfig()\n case 'retention':\n return createDefaultRetentionConfig()\n case 'query':\n default:\n return createDefaultQueryConfig()\n }\n}\n\n// ============================================================================\n// Workspace Format (localStorage persistence)\n// ============================================================================\n\n/**\n * AnalysisWorkspace - Multi-mode persistence format for localStorage\n *\n * Unlike AnalysisConfig (which represents a single analysis mode),\n * AnalysisWorkspace preserves state for ALL modes. This prevents state\n * loss when switching between query, funnel, flow, and retention modes.\n *\n * Usage:\n * - localStorage persistence → AnalysisWorkspace (preserves all modes)\n * - Share URLs → AnalysisConfig (shares one specific analysis)\n * - Dashboard portlets → AnalysisConfig (embeds one specific analysis)\n */\nexport interface AnalysisWorkspace {\n /** Version number for migration support */\n version: 1\n\n /** Currently active analysis type */\n activeType: AnalysisType\n\n /**\n * Per-mode configurations.\n * Each mode stores its complete AnalysisConfig independently.\n * Charts are duplicated per-mode but merged on load.\n */\n modes: {\n query?: QueryAnalysisConfig\n funnel?: FunnelAnalysisConfig\n flow?: FlowAnalysisConfig\n retention?: RetentionAnalysisConfig\n }\n}\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisWorkspace\n */\nexport const isValidAnalysisWorkspace = (\n data: unknown\n): data is AnalysisWorkspace => {\n if (!data || typeof data !== 'object') return false\n\n const w = data as Record<string, unknown>\n\n // Check version\n if (w.version !== 1) return false\n\n // Check activeType\n if (w.activeType !== 'query' && w.activeType !== 'funnel' && w.activeType !== 'flow' && w.activeType !== 'retention') return false\n\n // Check modes exists and is an object\n if (!w.modes || typeof w.modes !== 'object') return false\n\n return true\n}\n\n/**\n * Create a default empty workspace with all modes initialized\n */\nexport const createDefaultWorkspace = (): AnalysisWorkspace => ({\n version: 1,\n activeType: 'query',\n modes: {\n query: createDefaultQueryConfig(),\n funnel: createDefaultFunnelConfig(),\n flow: createDefaultFlowConfig(),\n retention: createDefaultRetentionConfig(),\n },\n})\n","/**\n * Config Migration Utilities\n *\n * Handles migration from legacy portlet/share formats to AnalysisConfig.\n * Used for backward compatibility when loading old saved configurations.\n */\n\nimport type {\n AnalysisConfig,\n QueryAnalysisConfig,\n FunnelAnalysisConfig,\n FlowAnalysisConfig,\n RetentionAnalysisConfig,\n ChartConfig,\n} from '../types/analysisConfig.js'\nimport { createDefaultQueryConfig, isValidAnalysisConfig } from '../types/analysisConfig.js'\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n FunnelBindingKey,\n QueryMergeStrategy,\n PortletConfig,\n} from '../types.js'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel.js'\nimport type { ServerFlowQuery } from '../types/flow.js'\nimport { isServerRetentionQuery } from '../types/retention.js'\n\n// ============================================================================\n// Legacy Portlet Format\n// ============================================================================\n\n/**\n * Legacy portlet format (before AnalysisConfig)\n */\nexport interface LegacyPortlet {\n /** JSON string of CubeQuery or MultiQueryConfig */\n query: string\n chartType?: ChartType\n chartConfig?: ChartAxisConfig\n displayConfig?: ChartDisplayConfig\n analysisType?: 'query' | 'funnel'\n // Funnel-specific legacy fields\n funnelChartType?: ChartType\n funnelChartConfig?: ChartAxisConfig\n funnelDisplayConfig?: ChartDisplayConfig\n}\n\n/**\n * Legacy MultiQueryConfig with funnel merge strategy\n * This is a standalone type (not extending MultiQueryConfig) to handle old data\n * where mergeStrategy was 'funnel' before it was removed from QueryMergeStrategy.\n */\nexport interface LegacyFunnelMultiQuery {\n queries: CubeQuery[]\n mergeStrategy: 'funnel'\n mergeKeys?: string[]\n queryLabels?: string[]\n funnelBindingKey?: FunnelBindingKey | null\n stepTimeToConvert?: (string | null)[]\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if a query is a MultiQueryConfig\n */\nfunction isMultiQueryConfig(query: unknown): query is MultiQueryConfig {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as MultiQueryConfig).queries)\n )\n}\n\n/**\n * Check if a query is a legacy funnel multi-query\n * Uses type assertion since 'funnel' is no longer in QueryMergeStrategy\n */\nfunction isLegacyFunnelMultiQuery(\n query: unknown\n): query is LegacyFunnelMultiQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as { queries?: unknown[] }).queries) &&\n 'mergeStrategy' in query &&\n (query as { mergeStrategy?: string }).mergeStrategy === 'funnel'\n )\n}\n\n/**\n * Check if a query is a ServerFunnelQuery\n */\nfunction isServerFunnelQuery(query: unknown): query is ServerFunnelQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'funnel' in query &&\n typeof (query as ServerFunnelQuery).funnel === 'object'\n )\n}\n\n/**\n * Check if a query is a ServerFlowQuery\n */\nfunction isServerFlowQuery(query: unknown): query is ServerFlowQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'flow' in query &&\n typeof (query as ServerFlowQuery).flow === 'object'\n )\n}\n\n// ============================================================================\n// Migration Functions\n// ============================================================================\n\n/**\n * Extract chart config from legacy portlet fields\n */\nfunction extractChartConfig(\n portlet: LegacyPortlet,\n analysisType: 'query' | 'funnel' | 'flow' | 'retention'\n): ChartConfig {\n if (analysisType === 'funnel') {\n return {\n chartType: portlet.funnelChartType || portlet.chartType || 'funnel',\n chartConfig: portlet.funnelChartConfig || portlet.chartConfig || {},\n displayConfig: portlet.funnelDisplayConfig || portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'flow') {\n // Flow uses sankey chart by default\n return {\n chartType: portlet.chartType || 'sankey',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'retention') {\n // Retention uses retentionCombined chart by default\n return {\n chartType: portlet.chartType || 'retentionCombined',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n return {\n chartType: portlet.chartType || 'bar',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n}\n\n/**\n * Migrate a legacy portlet to AnalysisConfig format\n *\n * Handles:\n * - Single CubeQuery → QueryAnalysisConfig\n * - MultiQueryConfig → QueryAnalysisConfig\n * - ServerFunnelQuery → FunnelAnalysisConfig\n * - ServerFlowQuery → FlowAnalysisConfig\n * - Legacy mergeStrategy:'funnel' → FunnelAnalysisConfig (via migrateLegacyFunnelMerge)\n *\n * @param portlet - Legacy portlet with query string\n * @returns AnalysisConfig in new format\n */\nexport function migrateLegacyPortlet(portlet: LegacyPortlet): AnalysisConfig {\n try {\n const query = JSON.parse(portlet.query)\n\n // Check if it's a ServerRetentionQuery\n if (isServerRetentionQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'retention')\n return {\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: chartConfig,\n },\n query,\n } as RetentionAnalysisConfig\n }\n\n // Check if it's a ServerFlowQuery\n if (isServerFlowQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'flow')\n return {\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: chartConfig,\n },\n query,\n } as FlowAnalysisConfig\n }\n\n // Check if it's already a ServerFunnelQuery\n if (isServerFunnelQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query,\n } as FunnelAnalysisConfig\n }\n\n // Check if it's a legacy funnel multi-query\n if (isLegacyFunnelMultiQuery(query)) {\n return migrateLegacyFunnelMerge(query, portlet)\n }\n\n // Check explicit analysisType\n if (portlet.analysisType === 'funnel') {\n // Treat as funnel even if query isn't ServerFunnelQuery format\n // (This handles edge cases where analysisType was set but query wasn't converted)\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: isServerFunnelQuery(query)\n ? query\n : {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n } as FunnelAnalysisConfig\n }\n\n // Handle regular query or multi-query\n const chartConfig = extractChartConfig(portlet, 'query')\n\n // MultiQueryConfig (non-funnel, since isLegacyFunnelMultiQuery was checked earlier)\n if (isMultiQueryConfig(query)) {\n // Defensive: convert any lingering 'funnel' strategy to 'concat'\n const strategy = (query.mergeStrategy as string) === 'funnel' ? 'concat' : query.mergeStrategy\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: {\n queries: query.queries,\n mergeStrategy: strategy as QueryMergeStrategy,\n mergeKeys: query.mergeKeys,\n queryLabels: query.queryLabels,\n },\n } as QueryAnalysisConfig\n }\n\n // Single CubeQuery\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: query as CubeQuery,\n } as QueryAnalysisConfig\n } catch (error) {\n // If parsing fails, return default config\n console.warn('[configMigration] Failed to parse legacy portlet:', error)\n return createDefaultQueryConfig()\n }\n}\n\n/**\n * Migrate legacy mergeStrategy:'funnel' to FunnelAnalysisConfig\n *\n * This handles the old pattern where funnels were created using multi-query\n * with mergeStrategy: 'funnel'. Converts to the new dedicated funnel format.\n *\n * @param legacyQuery - MultiQueryConfig with mergeStrategy: 'funnel'\n * @param portlet - Legacy portlet for chart config\n * @returns FunnelAnalysisConfig in new format\n */\nexport function migrateLegacyFunnelMerge(\n legacyQuery: LegacyFunnelMultiQuery,\n portlet?: LegacyPortlet\n): FunnelAnalysisConfig {\n // Extract binding key\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (legacyQuery.funnelBindingKey?.dimension) {\n if (typeof legacyQuery.funnelBindingKey.dimension === 'string') {\n bindingKey = legacyQuery.funnelBindingKey.dimension\n } else if (Array.isArray(legacyQuery.funnelBindingKey.dimension)) {\n bindingKey = legacyQuery.funnelBindingKey.dimension.map((m) => ({\n cube: m.cube,\n dimension: m.dimension,\n }))\n }\n }\n\n // Extract time dimension from first query\n let timeDimension: string = ''\n if (\n legacyQuery.queries.length > 0 &&\n legacyQuery.queries[0].timeDimensions?.length\n ) {\n timeDimension = legacyQuery.queries[0].timeDimensions[0].dimension\n }\n\n // Convert queries to funnel steps\n const steps: ServerFunnelStep[] = legacyQuery.queries.map((query, index) => {\n const step: ServerFunnelStep = {\n name:\n legacyQuery.queryLabels?.[index] ||\n `Step ${index + 1}`,\n }\n\n // Convert filters\n if (query.filters && query.filters.length > 0) {\n step.filter =\n query.filters.length === 1 ? query.filters[0] : { and: query.filters }\n }\n\n // Add time to convert if specified\n if (\n legacyQuery.stepTimeToConvert &&\n legacyQuery.stepTimeToConvert[index]\n ) {\n step.timeToConvert = legacyQuery.stepTimeToConvert[index] as string\n }\n\n return step\n })\n\n // Build chart config\n const chartConfig: ChartConfig = portlet\n ? extractChartConfig(portlet, 'funnel')\n : {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n }\n\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n },\n }\n}\n\n/**\n * Migrate any config to the latest version\n *\n * Handles:\n * - Already valid AnalysisConfig (returns as-is)\n * - Legacy portlet format (converts to AnalysisConfig)\n * - Unknown format (returns default config)\n *\n * @param config - Unknown config value\n * @returns Valid AnalysisConfig\n */\nexport function migrateConfig(config: unknown): AnalysisConfig {\n // Already valid?\n if (isValidAnalysisConfig(config)) {\n return config\n }\n\n // Check if it looks like a legacy portlet\n if (\n config &&\n typeof config === 'object' &&\n 'query' in config &&\n typeof (config as { query: unknown }).query === 'string'\n ) {\n return migrateLegacyPortlet(config as LegacyPortlet)\n }\n\n // Check if it's a raw query object (not wrapped in portlet)\n if (config && typeof config === 'object') {\n try {\n // Try to wrap it as a portlet and migrate\n const wrapped: LegacyPortlet = {\n query: JSON.stringify(config),\n }\n return migrateLegacyPortlet(wrapped)\n } catch {\n // Fall through to default\n }\n }\n\n // Return default config\n console.warn('[configMigration] Unknown config format, using defaults')\n return createDefaultQueryConfig()\n}\n\n/**\n * Check if a portlet has the new AnalysisConfig format\n */\nexport function hasAnalysisConfig(\n portlet: unknown\n): portlet is { analysisConfig: AnalysisConfig } {\n return (\n typeof portlet === 'object' &&\n portlet !== null &&\n 'analysisConfig' in portlet &&\n isValidAnalysisConfig((portlet as { analysisConfig: unknown }).analysisConfig)\n )\n}\n\n/**\n * Ensure a portlet has analysisConfig, migrating from legacy format if needed.\n *\n * This is the primary entry point for rendering portlets - it guarantees\n * that analysisConfig exists, either by using the existing one or by\n * converting legacy fields on-the-fly.\n *\n * @param portlet - PortletConfig which may or may not have analysisConfig\n * @returns PortletConfig with analysisConfig guaranteed to exist\n */\nexport function ensureAnalysisConfig(\n portlet: PortletConfig\n): PortletConfig & { analysisConfig: AnalysisConfig } {\n // If already has valid analysisConfig, return as-is\n if (hasAnalysisConfig(portlet)) {\n return portlet as PortletConfig & { analysisConfig: AnalysisConfig }\n }\n\n // Migrate from legacy fields\n // Note: 'flow' and 'retention' types don't have legacy format - filter to only query/funnel\n const legacyAnalysisType = portlet.analysisType === 'flow' || portlet.analysisType === 'retention' ? undefined : portlet.analysisType\n const analysisConfig = migrateLegacyPortlet({\n query: portlet.query ?? '{}',\n chartType: portlet.chartType,\n chartConfig: portlet.chartConfig,\n displayConfig: portlet.displayConfig,\n analysisType: legacyAnalysisType,\n funnelChartType: portlet.funnelChartType,\n funnelChartConfig: portlet.funnelChartConfig,\n funnelDisplayConfig: portlet.funnelDisplayConfig,\n })\n\n return { ...portlet, analysisConfig }\n}\n","/**\n * Syntax Highlighting Utility\n *\n * Lazy-loads highlight.js only when needed for syntax highlighting in debug panels.\n * This minimizes bundle size impact since syntax highlighting is not needed on initial load.\n *\n * Usage:\n * await highlightCodeBlocks()\n */\n\nlet highlightJs: any = null\nlet loadingPromise: Promise<void> | null = null\n\n/**\n * Lazy-loads highlight.js and registers supported languages.\n * Only loads once - subsequent calls return immediately.\n */\nexport async function loadSyntaxHighlighter(): Promise<void> {\n // Already loaded\n if (highlightJs) {\n return\n }\n\n // Loading in progress - wait for it\n if (loadingPromise) {\n return loadingPromise\n }\n\n loadingPromise = (async () => {\n try {\n // Dynamic imports to enable code splitting\n const hljs = await import('highlight.js/lib/core')\n const javascript = await import('highlight.js/lib/languages/javascript')\n const sql = await import('highlight.js/lib/languages/sql')\n const json = await import('highlight.js/lib/languages/json')\n\n // Register languages we need\n hljs.default.registerLanguage('javascript', javascript.default)\n hljs.default.registerLanguage('sql', sql.default)\n hljs.default.registerLanguage('json', json.default)\n\n // Store the instance\n highlightJs = hljs.default\n } catch (err) {\n console.error('Failed to load syntax highlighter:', err)\n // Clear loading promise so it can be retried\n loadingPromise = null\n }\n })()\n\n return loadingPromise\n}\n\n/**\n * Highlights all code blocks on the page that haven't been highlighted yet.\n * Gracefully handles cases where highlight.js fails to load.\n */\nexport async function highlightCodeBlocks(): Promise<void> {\n // Load highlighter if not already loaded\n await loadSyntaxHighlighter()\n\n // If loading failed, return silently (code blocks remain unstyled)\n if (!highlightJs) {\n return\n }\n\n // Find all code blocks and highlight them\n document.querySelectorAll('pre code').forEach((block) => {\n // Skip if already highlighted\n if (!block.classList.contains('hljs')) {\n highlightJs.highlightElement(block)\n }\n })\n}\n\n/**\n * Highlights a specific code block element.\n * Useful for dynamically added content.\n *\n * @param element - The code block element to highlight\n */\nexport async function highlightCodeBlock(element: HTMLElement): Promise<void> {\n await loadSyntaxHighlighter()\n\n if (!highlightJs) {\n return\n }\n\n if (!element.classList.contains('hljs')) {\n highlightJs.highlightElement(element)\n }\n}\n\n/**\n * Returns whether syntax highlighting is available.\n * Useful for conditional rendering or feature detection.\n */\nexport function isSyntaxHighlightingAvailable(): boolean {\n return highlightJs !== null\n}\n\n/**\n * Returns the loaded highlight.js instance, if available.\n * Useful for custom highlighting flows that need direct access.\n */\nexport function getSyntaxHighlighter(): any | null {\n return highlightJs\n}\n","/**\n * Dashboard Export / Import Utilities\n *\n * Serialises a DashboardConfig (plus the host's name/description) into a small\n * JSON envelope, and parses such files back with a structural shape check and\n * legacy-portlet migration. Pure functions, no extra dependency.\n *\n * Usage:\n * 1. Enable in CubeProvider: features={{ dashboardImportExport: { enabled: true } }}\n * 2. Optionally pass `dashboardMeta` / `onDashboardMetaChange` to AnalyticsDashboard so\n * exports carry the dashboard name and imports can rename the host's record.\n * 3. Export / Import buttons appear in the dashboard toolbar. Export is offered in view\n * and edit mode and also on read-only dashboards (`editable={false}`); import only\n * while editing. `hideToolbar` still hides both.\n *\n * Hosts that create new dashboards from a file (e.g. on a list page) use\n * `readDashboardExportFile()` directly and persist the returned config themselves.\n */\n\nimport type {\n DashboardConfig,\n DashboardFilter,\n DashboardFilterMappingEntry,\n DashboardGridSettings,\n DashboardLayoutMode,\n DashboardMeta,\n PortletConfig,\n PortletGroup,\n PortletGroupCell,\n RowLayout,\n RowLayoutColumn,\n} from '../types.js'\nimport { isValidAnalysisConfig } from '../types/analysisConfig.js'\nimport {\n ensureAnalysisConfig,\n hasAnalysisConfig,\n migrateLegacyPortlet,\n} from './configMigration.js'\n\n// ============================================================================\n// File format\n// ============================================================================\n\n/**\n * The on-disk shape of a dashboard export: the config plus the host-owned name and\n * description. Portlets carry their own `analysisConfig.version` and migrate on read.\n */\nexport interface DashboardExportFile {\n /** ISO 8601 timestamp of when the file was created */\n exportedAt: string\n name?: string\n description?: string\n config: DashboardConfig\n}\n\n/**\n * Why a file could not be imported. The check is structural only: the file has to\n * have the shape of a dashboard config, and its contents are taken on trust from\n * there. Codes, not sentences: the UI resolves them to translated text\n * (`dashboard.import.error.<code>`).\n */\nexport type DashboardImportError =\n | { code: 'invalidJson' }\n | { code: 'unknownFormat' }\n | { code: 'invalidConfig'; path: string }\n | { code: 'invalidPortlet'; index: number; path: string }\n\n/**\n * Non-fatal findings about an otherwise valid file.\n */\nexport type DashboardImportWarning =\n | { code: 'unknownFilterMapping'; portletId: string; portletTitle: string; filterId: string }\n\nexport type DashboardImportResult =\n | {\n ok: true\n config: DashboardConfig\n name?: string\n description?: string\n warnings: DashboardImportWarning[]\n }\n | { ok: false; errors: DashboardImportError[] }\n\n// ============================================================================\n// Export\n// ============================================================================\n\nconst LEGACY_PORTLET_KEYS = [\n 'query',\n 'chartType',\n 'chartConfig',\n 'displayConfig',\n 'analysisType',\n 'funnelCube',\n 'funnelSteps',\n 'funnelTimeDimension',\n 'funnelBindingKey',\n 'funnelChartType',\n 'funnelChartConfig',\n 'funnelDisplayConfig',\n] as const satisfies ReadonlyArray<keyof PortletConfig>\n\n/**\n * Canonical form of a portlet for the file: `analysisConfig` guaranteed (migrating\n * legacy fields on the way) and the deprecated legacy fields dropped.\n */\nfunction normalizePortletForExport(portlet: PortletConfig): PortletConfig {\n // ensureAnalysisConfig intentionally falls back to a default query when it\n // cannot parse legacy data. An export must not turn a broken chart into a\n // valid-looking empty one, so validate legacy JSON before migrating it.\n if (!hasAnalysisConfig(portlet)) {\n if (typeof portlet.query !== 'string') {\n throw new Error(`Cannot export portlet \"${portlet.id}\": no valid analysis configuration`)\n }\n try {\n JSON.parse(portlet.query)\n } catch {\n throw new Error(`Cannot export portlet \"${portlet.id}\": legacy query is not valid JSON`)\n }\n }\n\n const normalized: PortletConfig = { ...ensureAnalysisConfig(portlet) }\n if (!hasAnalysisConfig(normalized)) {\n throw new Error(`Cannot export portlet \"${portlet.id}\": could not migrate analysis configuration`)\n }\n for (const key of LEGACY_PORTLET_KEYS) {\n delete normalized[key]\n }\n return normalized\n}\n\n/**\n * Prepare a config for export: canonical portlets, transient thumbnail fields removed,\n * everything else untouched.\n */\nexport function normalizeDashboardConfigForExport(config: DashboardConfig): DashboardConfig {\n const normalized: DashboardConfig = {\n ...config,\n portlets: config.portlets.map(normalizePortletForExport),\n }\n delete normalized.thumbnailData\n delete normalized.thumbnailUrl\n return normalized\n}\n\n/**\n * Build the export envelope for a dashboard.\n */\nexport function createDashboardExport(\n config: DashboardConfig,\n meta?: DashboardMeta,\n now: Date = new Date()\n): DashboardExportFile {\n const file: DashboardExportFile = {\n exportedAt: now.toISOString(),\n config: normalizeDashboardConfigForExport(config),\n }\n const name = meta?.name?.trim()\n const description = meta?.description?.trim()\n if (name) file.name = name\n if (description) file.description = description\n return file\n}\n\n/**\n * Pretty-printed JSON for the file, so exports diff well in version control.\n */\nexport function serializeDashboardExport(file: DashboardExportFile): string {\n return JSON.stringify(file, null, 2)\n}\n\nfunction toFileNamePart(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[/\\\\]/g, '-')\n}\n\n/**\n * Filename for an export: `<prefix->?<dashboard-name|dashboard>-<yyyy-mm-dd>.json`.\n * The name is lowercased with spaces turned into dashes — no slugification.\n */\nexport function dashboardExportFilename(\n name?: string,\n date: Date = new Date(),\n prefix?: string\n): string {\n const namePart = toFileNamePart(name ?? '') || 'dashboard'\n const prefixPart = prefix ? toFileNamePart(prefix) : ''\n const day = date.toISOString().slice(0, 10)\n return `${prefixPart ? `${prefixPart}-` : ''}${namePart}-${day}.json`\n}\n\n/**\n * Trigger a browser download of the export file. No-op outside a browser.\n */\nexport function downloadDashboardExport(file: DashboardExportFile, filename?: string): void {\n if (typeof document === 'undefined' || typeof URL === 'undefined') return\n const blob = new Blob([serializeDashboardExport(file)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename ?? dashboardExportFilename(file.name)\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n}\n\n// ============================================================================\n// Import\n// ============================================================================\n\nconst LAYOUT_MODES: readonly DashboardLayoutMode[] = ['grid', 'rows']\n\ntype UnknownRecord = Record<string, unknown>\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value)\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string'\n}\n\nfunction isOptionalBoolean(value: unknown): value is boolean | undefined {\n return value === undefined || typeof value === 'boolean'\n}\n\nfunction isOptionalRecord(value: unknown): value is UnknownRecord | undefined {\n return value === undefined || isRecord(value)\n}\n\nfunction isLayoutMode(value: unknown): value is DashboardLayoutMode {\n return typeof value === 'string' && LAYOUT_MODES.includes(value as DashboardLayoutMode)\n}\n\nfunction isGridSettings(value: unknown): value is DashboardGridSettings {\n return isRecord(value) && (['cols', 'rowHeight', 'minW', 'minH'] as const).every((key) => isFiniteNumber(value[key]))\n}\n\nfunction isFilterMappingEntry(value: unknown): value is string | DashboardFilterMappingEntry {\n if (typeof value === 'string') return true\n return isRecord(value) && isNonEmptyString(value.filterId) && isOptionalString(value.member)\n}\n\nfunction isDashboardFilter(value: unknown): value is DashboardFilter {\n return (\n isRecord(value) &&\n isNonEmptyString(value.id) &&\n typeof value.label === 'string' &&\n isRecord(value.filter) &&\n isOptionalBoolean(value.isUniversalTime)\n )\n}\n\nfunction isGroupCell(value: unknown): value is PortletGroupCell {\n return isRecord(value) && Array.isArray(value.portletIds) && value.portletIds.every((id) => typeof id === 'string')\n}\n\nfunction isRowColumn(value: unknown): value is RowLayoutColumn {\n if (!isRecord(value) || !isFiniteNumber(value.w)) return false\n const hasPortlet = value.portletId !== undefined\n const hasGroup = value.groupId !== undefined\n if (hasPortlet === hasGroup) return false\n return isOptionalString(value.portletId) && isOptionalString(value.groupId)\n}\n\ntype PortletParseResult = { ok: true; portlet: PortletConfig } | { ok: false; error: DashboardImportError }\n\n/**\n * Read one portlet and return it in canonical form (analysisConfig guaranteed,\n * legacy fields migrated and dropped), or the reason its shape was rejected.\n */\nfunction parsePortlet(value: unknown, index: number): PortletParseResult {\n const fail = (path: string): PortletParseResult => ({ ok: false, error: { code: 'invalidPortlet', index, path } })\n\n if (!isRecord(value)) return fail('')\n if (!isNonEmptyString(value.id)) return fail('id')\n if (typeof value.title !== 'string') return fail('title')\n const { x, y, w, h } = value\n if (!isFiniteNumber(x)) return fail('x')\n if (!isFiniteNumber(y)) return fail('y')\n if (!isFiniteNumber(w)) return fail('w')\n if (!isFiniteNumber(h)) return fail('h')\n\n const portlet: PortletConfig = { id: value.id, title: value.title, x, y, w, h }\n\n const mapping = value.dashboardFilterMapping\n if (mapping !== undefined) {\n if (!Array.isArray(mapping) || !mapping.every(isFilterMappingEntry)) return fail('dashboardFilterMapping')\n portlet.dashboardFilterMapping = mapping.filter(isFilterMappingEntry)\n }\n if (!isOptionalBoolean(value.eagerLoad)) return fail('eagerLoad')\n if (value.eagerLoad !== undefined) portlet.eagerLoad = value.eagerLoad\n\n // Query/chart definition: either a valid canonical analysisConfig, or the legacy\n // `query` JSON string (plus its sibling chart fields) that configMigration upgrades.\n if (value.analysisConfig !== undefined) {\n if (!isValidAnalysisConfig(value.analysisConfig)) return fail('analysisConfig')\n portlet.analysisConfig = value.analysisConfig\n return { ok: true, portlet }\n }\n if (typeof value.query !== 'string') return fail('analysisConfig')\n if (!isOptionalString(value.chartType) || !isOptionalString(value.funnelChartType)) return fail('chartType')\n if (!isOptionalRecord(value.chartConfig) || !isOptionalRecord(value.funnelChartConfig)) return fail('chartConfig')\n if (!isOptionalRecord(value.displayConfig) || !isOptionalRecord(value.funnelDisplayConfig)) return fail('displayConfig')\n const analysisType = value.analysisType\n if (analysisType !== undefined && analysisType !== 'query' && analysisType !== 'funnel') return fail('analysisType')\n\n const migrated = migrateLegacyPortlet({\n query: value.query,\n chartType: value.chartType,\n chartConfig: value.chartConfig,\n displayConfig: value.displayConfig,\n analysisType,\n funnelChartType: value.funnelChartType,\n funnelChartConfig: value.funnelChartConfig,\n funnelDisplayConfig: value.funnelDisplayConfig,\n })\n portlet.analysisConfig = migrated\n return { ok: true, portlet }\n}\n\ntype SectionResult<T> = { ok: true; value: T } | { ok: false; error: DashboardImportError }\n\nfunction invalidConfig<T>(path: string): SectionResult<T> {\n return { ok: false, error: { code: 'invalidConfig', path } }\n}\n\nfunction parseFilters(value: unknown): SectionResult<DashboardFilter[]> {\n if (!Array.isArray(value)) return invalidConfig('filters')\n const filters: DashboardFilter[] = []\n for (let i = 0; i < value.length; i++) {\n const filter: unknown = value[i]\n if (!isDashboardFilter(filter)) return invalidConfig(`filters[${i}]`)\n filters.push(filter)\n }\n return { ok: true, value: filters }\n}\n\nfunction parseGroups(value: unknown, portletIds: Set<string>): SectionResult<PortletGroup[]> {\n if (!Array.isArray(value)) return invalidConfig('groups')\n const groups: PortletGroup[] = []\n for (let i = 0; i < value.length; i++) {\n const group: unknown = value[i]\n if (!isRecord(group)) return invalidConfig(`groups[${i}]`)\n if (!isNonEmptyString(group.id)) return invalidConfig(`groups[${i}].id`)\n if (!isOptionalString(group.title)) return invalidConfig(`groups[${i}].title`)\n if (group.direction !== 'row' && group.direction !== 'column') return invalidConfig(`groups[${i}].direction`)\n if (!Array.isArray(group.cells)) return invalidConfig(`groups[${i}].cells`)\n const cells: PortletGroupCell[] = []\n for (let c = 0; c < group.cells.length; c++) {\n const cell: unknown = group.cells[c]\n if (!isGroupCell(cell)) return invalidConfig(`groups[${i}].cells[${c}]`)\n if (cell.portletIds.some((id) => !portletIds.has(id))) return invalidConfig(`groups[${i}].cells[${c}].portletIds`)\n cells.push(cell)\n }\n const parsed: PortletGroup = { id: group.id, direction: group.direction, cells }\n if (group.title !== undefined) parsed.title = group.title\n groups.push(parsed)\n }\n return { ok: true, value: groups }\n}\n\nfunction parseRows(value: unknown, portletIds: Set<string>, groupIds: Set<string>): SectionResult<RowLayout[]> {\n if (!Array.isArray(value)) return invalidConfig('rows')\n const rows: RowLayout[] = []\n for (let i = 0; i < value.length; i++) {\n const row: unknown = value[i]\n if (!isRecord(row)) return invalidConfig(`rows[${i}]`)\n if (!isNonEmptyString(row.id)) return invalidConfig(`rows[${i}].id`)\n if (!isFiniteNumber(row.h)) return invalidConfig(`rows[${i}].h`)\n if (!Array.isArray(row.columns)) return invalidConfig(`rows[${i}].columns`)\n const columns: RowLayoutColumn[] = []\n for (let c = 0; c < row.columns.length; c++) {\n const column: unknown = row.columns[c]\n if (!isRowColumn(column)) return invalidConfig(`rows[${i}].columns[${c}]`)\n if (column.portletId !== undefined && !portletIds.has(column.portletId)) {\n return invalidConfig(`rows[${i}].columns[${c}].portletId`)\n }\n if (column.groupId !== undefined && !groupIds.has(column.groupId)) {\n return invalidConfig(`rows[${i}].columns[${c}].groupId`)\n }\n columns.push(column)\n }\n rows.push({ id: row.id, h: row.h, columns })\n }\n return { ok: true, value: rows }\n}\n\ntype ConfigParseResult =\n | { ok: true; config: DashboardConfig; warnings: DashboardImportWarning[] }\n | { ok: false; errors: DashboardImportError[] }\n\n/**\n * Read a bare DashboardConfig object (the `config` of an envelope, or a raw row\n * pasted from the host's database). Builds a fresh, whitelisted config: unknown keys\n * and the transient thumbnail fields do not survive the import.\n */\nfunction parseConfig(value: unknown): ConfigParseResult {\n if (!isRecord(value)) return { ok: false, errors: [{ code: 'invalidConfig', path: '' }] }\n if (!Array.isArray(value.portlets)) return { ok: false, errors: [{ code: 'invalidConfig', path: 'portlets' }] }\n\n const errors: DashboardImportError[] = []\n const portlets: PortletConfig[] = []\n const portletIds = new Set<string>()\n\n value.portlets.forEach((raw: unknown, index: number) => {\n const result = parsePortlet(raw, index)\n if (!result.ok) {\n errors.push(result.error)\n return\n }\n portletIds.add(result.portlet.id)\n portlets.push(result.portlet)\n })\n\n const config: DashboardConfig = { portlets }\n const invalid = (path: string) => errors.push({ code: 'invalidConfig', path })\n\n if (value.layoutMode !== undefined) {\n if (isLayoutMode(value.layoutMode)) config.layoutMode = value.layoutMode\n else invalid('layoutMode')\n }\n if (value.grid !== undefined) {\n if (isGridSettings(value.grid)) config.grid = value.grid\n else invalid('grid')\n }\n if (value.colorPalette !== undefined) {\n if (typeof value.colorPalette === 'string') config.colorPalette = value.colorPalette\n else invalid('colorPalette')\n }\n if (value.eagerLoad !== undefined) {\n if (typeof value.eagerLoad === 'boolean') config.eagerLoad = value.eagerLoad\n else invalid('eagerLoad')\n }\n if (value.layouts !== undefined) {\n if (isRecord(value.layouts)) config.layouts = value.layouts\n else invalid('layouts')\n }\n if (value.filters !== undefined) {\n const result = parseFilters(value.filters)\n if (result.ok) config.filters = result.value\n else errors.push(result.error)\n }\n\n const groupIds = new Set<string>()\n if (value.groups !== undefined) {\n const result = parseGroups(value.groups, portletIds)\n if (result.ok) {\n config.groups = result.value\n result.value.forEach((group) => groupIds.add(group.id))\n } else {\n errors.push(result.error)\n }\n }\n if (value.rows !== undefined) {\n const result = parseRows(value.rows, portletIds, groupIds)\n if (result.ok) config.rows = result.value\n else errors.push(result.error)\n }\n\n if (errors.length > 0) return { ok: false, errors }\n\n // Warnings: portlets linked to a dashboard filter the file does not define. Not\n // pruned, because hosts may supply those filters programmatically at runtime.\n const warnings: DashboardImportWarning[] = []\n if (config.filters !== undefined) {\n const filterIds = new Set(config.filters.map((filter) => filter.id))\n for (const portlet of portlets) {\n for (const entry of portlet.dashboardFilterMapping ?? []) {\n const filterId = typeof entry === 'string' ? entry : entry.filterId\n if (!filterIds.has(filterId)) {\n warnings.push({ code: 'unknownFilterMapping', portletId: portlet.id, portletTitle: portlet.title, filterId })\n }\n }\n }\n }\n\n return { ok: true, config, warnings }\n}\n\nfunction optionalTrimmed(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed ? trimmed : undefined\n}\n\n/**\n * Parse a dashboard export. Accepts the JSON text or an already-parsed value.\n *\n * Two shapes are recognised: the envelope written by `createDashboardExport` (an\n * object with a `config`), and a bare DashboardConfig (an object with a `portlets`\n * array) so a row copied straight from the host's database imports too. Anything\n * else is `unknownFormat`.\n */\nexport function parseDashboardExport(input: string | unknown): DashboardImportResult {\n let value: unknown = input\n if (typeof input === 'string') {\n try {\n value = JSON.parse(input)\n } catch {\n return { ok: false, errors: [{ code: 'invalidJson' }] }\n }\n }\n\n if (!isRecord(value)) return { ok: false, errors: [{ code: 'unknownFormat' }] }\n\n if ('config' in value) {\n const parsed = parseConfig(value.config)\n if (!parsed.ok) return parsed\n return {\n ok: true,\n config: parsed.config,\n name: optionalTrimmed(value.name),\n description: optionalTrimmed(value.description),\n warnings: parsed.warnings,\n }\n }\n\n if (Array.isArray(value.portlets)) return parseConfig(value)\n\n return { ok: false, errors: [{ code: 'unknownFormat' }] }\n}\n\nfunction readFileText(file: File): Promise<string> {\n if (typeof file.text === 'function') return file.text()\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '')\n reader.onerror = () => reject(reader.error)\n reader.readAsText(file)\n })\n}\n\n/**\n * Read and parse a File picked by the user (e.g. from an `<input type=\"file\">`).\n */\nexport async function readDashboardExportFile(file: File): Promise<DashboardImportResult> {\n let text: string\n try {\n text = await readFileText(file)\n } catch {\n return { ok: false, errors: [{ code: 'invalidJson' }] }\n }\n return parseDashboardExport(text)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dashboardExport-Db3rmUP1.js","names":[],"sources":["../../../src/client/types/analysisConfig.ts","../../../src/client/utils/configMigration.ts","../../../src/client/utils/syntaxHighlighting.ts","../../../src/client/utils/dashboardExport.ts"],"sourcesContent":["/**\n * AnalysisConfig - Versioned, mode-agnostic configuration format\n *\n * This is the canonical format for persisting analysis state to:\n * - localStorage (standalone AnalysisBuilder)\n * - Share URLs\n * - Dashboard portlets (via analysisConfig field)\n *\n * Key design principles:\n * - `query` field IS the executable query (no transformation needed)\n * - Per-mode chart config via `charts[analysisType]` map\n * - No UI state (activeQueryIndex, activeFunnelStepIndex) - those are transient\n */\n\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n} from '../types.js'\nimport type { ServerFunnelQuery } from './funnel.js'\nimport type { ServerFlowQuery } from './flow.js'\nimport type { ServerRetentionQuery } from './retention.js'\n\n// ============================================================================\n// Chart Configuration\n// ============================================================================\n\n/**\n * Chart configuration - shared across all analysis types\n */\nexport interface ChartConfig {\n chartType: ChartType\n chartConfig: ChartAxisConfig\n displayConfig: ChartDisplayConfig\n}\n\n// ============================================================================\n// Analysis Type\n// ============================================================================\n\n/**\n * Analysis type discriminator\n * Future modes: 'cohort'\n */\nexport type AnalysisType = 'query' | 'funnel' | 'flow' | 'retention'\n\n// ============================================================================\n// Base Configuration\n// ============================================================================\n\n/**\n * Base config - common to all analysis types\n */\ninterface AnalysisConfigBase {\n /** Version number for migration support */\n version: 1\n\n /** Which analysis mode this config represents */\n analysisType: AnalysisType\n\n /** Whether to show table or chart view */\n activeView: 'table' | 'chart'\n\n /**\n * Per-mode chart configuration map.\n * Each mode owns its own chart settings, allowing users to configure\n * different chart types for query mode vs funnel mode.\n */\n charts: {\n [K in AnalysisType]?: ChartConfig\n }\n}\n\n// ============================================================================\n// Query Mode Configuration\n// ============================================================================\n\n/**\n * Query mode config - for single queries and multi-query analysis\n *\n * The `query` field is the actual executable query:\n * - Single query: CubeQuery object\n * - Multi-query: MultiQueryConfig with queries array and merge strategy\n */\nexport interface QueryAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'query'\n\n /**\n * The executable query.\n * - CubeQuery: Single query with measures, dimensions, filters\n * - MultiQueryConfig: Multiple queries with merge strategy\n */\n query: CubeQuery | MultiQueryConfig\n}\n\n// ============================================================================\n// Funnel Mode Configuration\n// ============================================================================\n\n/**\n * Funnel mode config - for funnel analysis\n *\n * The `query` field is the ServerFunnelQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FunnelAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'funnel'\n\n /**\n * Server funnel query - executable as-is.\n * Contains bindingKey, timeDimension, steps[], and options.\n */\n query: ServerFunnelQuery\n}\n\n// ============================================================================\n// Flow Mode Configuration\n// ============================================================================\n\n/**\n * Flow mode config - for bidirectional flow analysis\n *\n * The `query` field is the ServerFlowQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FlowAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'flow'\n\n /**\n * Server flow query - executable as-is.\n * Contains bindingKey, timeDimension, eventDimension, startingStep, and depth config.\n */\n query: ServerFlowQuery\n}\n\n// ============================================================================\n// Retention Mode Configuration\n// ============================================================================\n\n/**\n * Retention mode config - for cohort-based retention analysis\n *\n * The `query` field is the ServerRetentionQuery which can be sent\n * directly to the server for execution.\n */\nexport interface RetentionAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'retention'\n\n /**\n * Server retention query - executable as-is.\n * Contains cohortTimeDimension, activityTimeDimension, bindingKey,\n * granularity settings, and period configuration.\n */\n query: ServerRetentionQuery\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/**\n * AnalysisConfig - union of all analysis mode configurations\n */\nexport type AnalysisConfig = QueryAnalysisConfig | FunnelAnalysisConfig | FlowAnalysisConfig | RetentionAnalysisConfig\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if config is for query mode\n */\nexport const isQueryConfig = (c: AnalysisConfig): c is QueryAnalysisConfig =>\n c.analysisType === 'query'\n\n/**\n * Check if config is for funnel mode\n */\nexport const isFunnelConfig = (c: AnalysisConfig): c is FunnelAnalysisConfig =>\n c.analysisType === 'funnel'\n\n/**\n * Check if config is for flow mode\n */\nexport const isFlowConfig = (c: AnalysisConfig): c is FlowAnalysisConfig =>\n c.analysisType === 'flow'\n\n/**\n * Check if config is for retention mode\n */\nexport const isRetentionConfig = (c: AnalysisConfig): c is RetentionAnalysisConfig =>\n c.analysisType === 'retention'\n\n/**\n * Check if a query config contains multiple queries\n */\nexport const isMultiQuery = (config: QueryAnalysisConfig): boolean =>\n 'queries' in config.query &&\n Array.isArray((config.query as MultiQueryConfig).queries)\n\n/**\n * Check if a query config contains a single query\n */\nexport const isSingleQuery = (config: QueryAnalysisConfig): boolean =>\n !isMultiQuery(config)\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisConfig\n */\nexport const isValidAnalysisConfig = (\n config: unknown\n): config is AnalysisConfig => {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n // Check version\n if (c.version !== 1) return false\n\n // Check analysisType\n if (c.analysisType !== 'query' && c.analysisType !== 'funnel' && c.analysisType !== 'flow' && c.analysisType !== 'retention') return false\n\n // Check query exists\n if (!c.query || typeof c.query !== 'object') return false\n\n // Check activeView\n if (c.activeView !== 'table' && c.activeView !== 'chart') return false\n\n return true\n}\n\n// ============================================================================\n// Default Factories\n// ============================================================================\n\n/**\n * Create a default empty query analysis config\n */\nexport const createDefaultQueryConfig = (): QueryAnalysisConfig => ({\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: {\n chartType: 'bar',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n measures: [],\n dimensions: [],\n },\n})\n\n/**\n * Create a default empty funnel analysis config\n */\nexport const createDefaultFunnelConfig = (): FunnelAnalysisConfig => ({\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n})\n\n/**\n * Create a default empty flow analysis config\n */\nexport const createDefaultFlowConfig = (): FlowAnalysisConfig => ({\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n flow: {\n bindingKey: '',\n timeDimension: '',\n eventDimension: '',\n startingStep: {\n name: '',\n filter: undefined,\n },\n stepsBefore: 3,\n stepsAfter: 3,\n joinStrategy: 'auto',\n },\n },\n})\n\n/**\n * Create a default empty retention analysis config\n */\nexport const createDefaultRetentionConfig = (): RetentionAnalysisConfig => ({\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: {\n chartType: 'heatmap',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n retention: {\n timeDimension: '',\n bindingKey: '',\n dateRange: { start: '', end: '' },\n granularity: 'week',\n periods: 12,\n retentionType: 'classic',\n },\n },\n})\n\n/**\n * Create a default config for the given analysis type\n */\nexport const createDefaultConfig = (\n type: AnalysisType = 'query'\n): AnalysisConfig => {\n switch (type) {\n case 'funnel':\n return createDefaultFunnelConfig()\n case 'flow':\n return createDefaultFlowConfig()\n case 'retention':\n return createDefaultRetentionConfig()\n case 'query':\n default:\n return createDefaultQueryConfig()\n }\n}\n\n// ============================================================================\n// Workspace Format (localStorage persistence)\n// ============================================================================\n\n/**\n * AnalysisWorkspace - Multi-mode persistence format for localStorage\n *\n * Unlike AnalysisConfig (which represents a single analysis mode),\n * AnalysisWorkspace preserves state for ALL modes. This prevents state\n * loss when switching between query, funnel, flow, and retention modes.\n *\n * Usage:\n * - localStorage persistence → AnalysisWorkspace (preserves all modes)\n * - Share URLs → AnalysisConfig (shares one specific analysis)\n * - Dashboard portlets → AnalysisConfig (embeds one specific analysis)\n */\nexport interface AnalysisWorkspace {\n /** Version number for migration support */\n version: 1\n\n /** Currently active analysis type */\n activeType: AnalysisType\n\n /**\n * Per-mode configurations.\n * Each mode stores its complete AnalysisConfig independently.\n * Charts are duplicated per-mode but merged on load.\n */\n modes: {\n query?: QueryAnalysisConfig\n funnel?: FunnelAnalysisConfig\n flow?: FlowAnalysisConfig\n retention?: RetentionAnalysisConfig\n }\n}\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisWorkspace\n */\nexport const isValidAnalysisWorkspace = (\n data: unknown\n): data is AnalysisWorkspace => {\n if (!data || typeof data !== 'object') return false\n\n const w = data as Record<string, unknown>\n\n // Check version\n if (w.version !== 1) return false\n\n // Check activeType\n if (w.activeType !== 'query' && w.activeType !== 'funnel' && w.activeType !== 'flow' && w.activeType !== 'retention') return false\n\n // Check modes exists and is an object\n if (!w.modes || typeof w.modes !== 'object') return false\n\n return true\n}\n\n/**\n * Create a default empty workspace with all modes initialized\n */\nexport const createDefaultWorkspace = (): AnalysisWorkspace => ({\n version: 1,\n activeType: 'query',\n modes: {\n query: createDefaultQueryConfig(),\n funnel: createDefaultFunnelConfig(),\n flow: createDefaultFlowConfig(),\n retention: createDefaultRetentionConfig(),\n },\n})\n","/**\n * Config Migration Utilities\n *\n * Handles migration from legacy portlet/share formats to AnalysisConfig.\n * Used for backward compatibility when loading old saved configurations.\n */\n\nimport type {\n AnalysisConfig,\n QueryAnalysisConfig,\n FunnelAnalysisConfig,\n FlowAnalysisConfig,\n RetentionAnalysisConfig,\n ChartConfig,\n} from '../types/analysisConfig.js'\nimport { createDefaultQueryConfig, isValidAnalysisConfig } from '../types/analysisConfig.js'\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n FunnelBindingKey,\n QueryMergeStrategy,\n PortletConfig,\n} from '../types.js'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel.js'\nimport type { ServerFlowQuery } from '../types/flow.js'\nimport { isServerRetentionQuery } from '../types/retention.js'\n\n// ============================================================================\n// Legacy Portlet Format\n// ============================================================================\n\n/**\n * Legacy portlet format (before AnalysisConfig)\n */\nexport interface LegacyPortlet {\n /** JSON string of CubeQuery or MultiQueryConfig */\n query: string\n chartType?: ChartType\n chartConfig?: ChartAxisConfig\n displayConfig?: ChartDisplayConfig\n analysisType?: 'query' | 'funnel'\n // Funnel-specific legacy fields\n funnelChartType?: ChartType\n funnelChartConfig?: ChartAxisConfig\n funnelDisplayConfig?: ChartDisplayConfig\n}\n\n/**\n * Legacy MultiQueryConfig with funnel merge strategy\n * This is a standalone type (not extending MultiQueryConfig) to handle old data\n * where mergeStrategy was 'funnel' before it was removed from QueryMergeStrategy.\n */\nexport interface LegacyFunnelMultiQuery {\n queries: CubeQuery[]\n mergeStrategy: 'funnel'\n mergeKeys?: string[]\n queryLabels?: string[]\n funnelBindingKey?: FunnelBindingKey | null\n stepTimeToConvert?: (string | null)[]\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if a query is a MultiQueryConfig\n */\nfunction isMultiQueryConfig(query: unknown): query is MultiQueryConfig {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as MultiQueryConfig).queries)\n )\n}\n\n/**\n * Check if a query is a legacy funnel multi-query\n * Uses type assertion since 'funnel' is no longer in QueryMergeStrategy\n */\nfunction isLegacyFunnelMultiQuery(\n query: unknown\n): query is LegacyFunnelMultiQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as { queries?: unknown[] }).queries) &&\n 'mergeStrategy' in query &&\n (query as { mergeStrategy?: string }).mergeStrategy === 'funnel'\n )\n}\n\n/**\n * Check if a query is a ServerFunnelQuery\n */\nfunction isServerFunnelQuery(query: unknown): query is ServerFunnelQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'funnel' in query &&\n typeof (query as ServerFunnelQuery).funnel === 'object'\n )\n}\n\n/**\n * Check if a query is a ServerFlowQuery\n */\nfunction isServerFlowQuery(query: unknown): query is ServerFlowQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'flow' in query &&\n typeof (query as ServerFlowQuery).flow === 'object'\n )\n}\n\n// ============================================================================\n// Migration Functions\n// ============================================================================\n\n/**\n * Extract chart config from legacy portlet fields\n */\nfunction extractChartConfig(\n portlet: LegacyPortlet,\n analysisType: 'query' | 'funnel' | 'flow' | 'retention'\n): ChartConfig {\n if (analysisType === 'funnel') {\n return {\n chartType: portlet.funnelChartType || portlet.chartType || 'funnel',\n chartConfig: portlet.funnelChartConfig || portlet.chartConfig || {},\n displayConfig: portlet.funnelDisplayConfig || portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'flow') {\n // Flow uses sankey chart by default\n return {\n chartType: portlet.chartType || 'sankey',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'retention') {\n // Retention uses retentionCombined chart by default\n return {\n chartType: portlet.chartType || 'retentionCombined',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n return {\n chartType: portlet.chartType || 'bar',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n}\n\n/**\n * Migrate a legacy portlet to AnalysisConfig format\n *\n * Handles:\n * - Single CubeQuery → QueryAnalysisConfig\n * - MultiQueryConfig → QueryAnalysisConfig\n * - ServerFunnelQuery → FunnelAnalysisConfig\n * - ServerFlowQuery → FlowAnalysisConfig\n * - Legacy mergeStrategy:'funnel' → FunnelAnalysisConfig (via migrateLegacyFunnelMerge)\n *\n * @param portlet - Legacy portlet with query string\n * @returns AnalysisConfig in new format\n */\nexport function migrateLegacyPortlet(portlet: LegacyPortlet): AnalysisConfig {\n try {\n const query = JSON.parse(portlet.query)\n\n // Check if it's a ServerRetentionQuery\n if (isServerRetentionQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'retention')\n return {\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: chartConfig,\n },\n query,\n } as RetentionAnalysisConfig\n }\n\n // Check if it's a ServerFlowQuery\n if (isServerFlowQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'flow')\n return {\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: chartConfig,\n },\n query,\n } as FlowAnalysisConfig\n }\n\n // Check if it's already a ServerFunnelQuery\n if (isServerFunnelQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query,\n } as FunnelAnalysisConfig\n }\n\n // Check if it's a legacy funnel multi-query\n if (isLegacyFunnelMultiQuery(query)) {\n return migrateLegacyFunnelMerge(query, portlet)\n }\n\n // Check explicit analysisType\n if (portlet.analysisType === 'funnel') {\n // Treat as funnel even if query isn't ServerFunnelQuery format\n // (This handles edge cases where analysisType was set but query wasn't converted)\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: isServerFunnelQuery(query)\n ? query\n : {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n } as FunnelAnalysisConfig\n }\n\n // Handle regular query or multi-query\n const chartConfig = extractChartConfig(portlet, 'query')\n\n // MultiQueryConfig (non-funnel, since isLegacyFunnelMultiQuery was checked earlier)\n if (isMultiQueryConfig(query)) {\n // Defensive: convert any lingering 'funnel' strategy to 'concat'\n const strategy = (query.mergeStrategy as string) === 'funnel' ? 'concat' : query.mergeStrategy\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: {\n queries: query.queries,\n mergeStrategy: strategy as QueryMergeStrategy,\n mergeKeys: query.mergeKeys,\n queryLabels: query.queryLabels,\n },\n } as QueryAnalysisConfig\n }\n\n // Single CubeQuery\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: query as CubeQuery,\n } as QueryAnalysisConfig\n } catch (error) {\n // If parsing fails, return default config\n console.warn('[configMigration] Failed to parse legacy portlet:', error)\n return createDefaultQueryConfig()\n }\n}\n\n/**\n * Migrate legacy mergeStrategy:'funnel' to FunnelAnalysisConfig\n *\n * This handles the old pattern where funnels were created using multi-query\n * with mergeStrategy: 'funnel'. Converts to the new dedicated funnel format.\n *\n * @param legacyQuery - MultiQueryConfig with mergeStrategy: 'funnel'\n * @param portlet - Legacy portlet for chart config\n * @returns FunnelAnalysisConfig in new format\n */\nexport function migrateLegacyFunnelMerge(\n legacyQuery: LegacyFunnelMultiQuery,\n portlet?: LegacyPortlet\n): FunnelAnalysisConfig {\n // Extract binding key\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (legacyQuery.funnelBindingKey?.dimension) {\n if (typeof legacyQuery.funnelBindingKey.dimension === 'string') {\n bindingKey = legacyQuery.funnelBindingKey.dimension\n } else if (Array.isArray(legacyQuery.funnelBindingKey.dimension)) {\n bindingKey = legacyQuery.funnelBindingKey.dimension.map((m) => ({\n cube: m.cube,\n dimension: m.dimension,\n }))\n }\n }\n\n // Extract time dimension from first query\n let timeDimension: string = ''\n if (\n legacyQuery.queries.length > 0 &&\n legacyQuery.queries[0].timeDimensions?.length\n ) {\n timeDimension = legacyQuery.queries[0].timeDimensions[0].dimension\n }\n\n // Convert queries to funnel steps\n const steps: ServerFunnelStep[] = legacyQuery.queries.map((query, index) => {\n const step: ServerFunnelStep = {\n name:\n legacyQuery.queryLabels?.[index] ||\n `Step ${index + 1}`,\n }\n\n // Convert filters\n if (query.filters && query.filters.length > 0) {\n step.filter =\n query.filters.length === 1 ? query.filters[0] : { and: query.filters }\n }\n\n // Add time to convert if specified\n if (\n legacyQuery.stepTimeToConvert &&\n legacyQuery.stepTimeToConvert[index]\n ) {\n step.timeToConvert = legacyQuery.stepTimeToConvert[index] as string\n }\n\n return step\n })\n\n // Build chart config\n const chartConfig: ChartConfig = portlet\n ? extractChartConfig(portlet, 'funnel')\n : {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n }\n\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n },\n }\n}\n\n/**\n * Migrate any config to the latest version\n *\n * Handles:\n * - Already valid AnalysisConfig (returns as-is)\n * - Legacy portlet format (converts to AnalysisConfig)\n * - Unknown format (returns default config)\n *\n * @param config - Unknown config value\n * @returns Valid AnalysisConfig\n */\nexport function migrateConfig(config: unknown): AnalysisConfig {\n // Already valid?\n if (isValidAnalysisConfig(config)) {\n return config\n }\n\n // Check if it looks like a legacy portlet\n if (\n config &&\n typeof config === 'object' &&\n 'query' in config &&\n typeof (config as { query: unknown }).query === 'string'\n ) {\n return migrateLegacyPortlet(config as LegacyPortlet)\n }\n\n // Check if it's a raw query object (not wrapped in portlet)\n if (config && typeof config === 'object') {\n try {\n // Try to wrap it as a portlet and migrate\n const wrapped: LegacyPortlet = {\n query: JSON.stringify(config),\n }\n return migrateLegacyPortlet(wrapped)\n } catch {\n // Fall through to default\n }\n }\n\n // Return default config\n console.warn('[configMigration] Unknown config format, using defaults')\n return createDefaultQueryConfig()\n}\n\n/**\n * Check if a portlet has the new AnalysisConfig format\n */\nexport function hasAnalysisConfig(\n portlet: unknown\n): portlet is { analysisConfig: AnalysisConfig } {\n return (\n typeof portlet === 'object' &&\n portlet !== null &&\n 'analysisConfig' in portlet &&\n isValidAnalysisConfig((portlet as { analysisConfig: unknown }).analysisConfig)\n )\n}\n\n/**\n * Ensure a portlet has analysisConfig, migrating from legacy format if needed.\n *\n * This is the primary entry point for rendering portlets - it guarantees\n * that analysisConfig exists, either by using the existing one or by\n * converting legacy fields on-the-fly.\n *\n * @param portlet - PortletConfig which may or may not have analysisConfig\n * @returns PortletConfig with analysisConfig guaranteed to exist\n */\nexport function ensureAnalysisConfig(\n portlet: PortletConfig\n): PortletConfig & { analysisConfig: AnalysisConfig } {\n // If already has valid analysisConfig, return as-is\n if (hasAnalysisConfig(portlet)) {\n return portlet as PortletConfig & { analysisConfig: AnalysisConfig }\n }\n\n // Migrate from legacy fields\n // Note: 'flow' and 'retention' types don't have legacy format - filter to only query/funnel\n const legacyAnalysisType = portlet.analysisType === 'flow' || portlet.analysisType === 'retention' ? undefined : portlet.analysisType\n const analysisConfig = migrateLegacyPortlet({\n query: portlet.query ?? '{}',\n chartType: portlet.chartType,\n chartConfig: portlet.chartConfig,\n displayConfig: portlet.displayConfig,\n analysisType: legacyAnalysisType,\n funnelChartType: portlet.funnelChartType,\n funnelChartConfig: portlet.funnelChartConfig,\n funnelDisplayConfig: portlet.funnelDisplayConfig,\n })\n\n return { ...portlet, analysisConfig }\n}\n","/**\n * Syntax Highlighting Utility\n *\n * Lazy-loads highlight.js only when needed for syntax highlighting in debug panels.\n * This minimizes bundle size impact since syntax highlighting is not needed on initial load.\n *\n * Usage:\n * await highlightCodeBlocks()\n */\n\nlet highlightJs: any = null\nlet loadingPromise: Promise<void> | null = null\n\n/**\n * Lazy-loads highlight.js and registers supported languages.\n * Only loads once - subsequent calls return immediately.\n */\nexport async function loadSyntaxHighlighter(): Promise<void> {\n // Already loaded\n if (highlightJs) {\n return\n }\n\n // Loading in progress - wait for it\n if (loadingPromise) {\n return loadingPromise\n }\n\n loadingPromise = (async () => {\n try {\n // Dynamic imports to enable code splitting\n const hljs = await import('highlight.js/lib/core')\n const javascript = await import('highlight.js/lib/languages/javascript')\n const sql = await import('highlight.js/lib/languages/sql')\n const json = await import('highlight.js/lib/languages/json')\n\n // Register languages we need\n hljs.default.registerLanguage('javascript', javascript.default)\n hljs.default.registerLanguage('sql', sql.default)\n hljs.default.registerLanguage('json', json.default)\n\n // Store the instance\n highlightJs = hljs.default\n } catch (err) {\n console.error('Failed to load syntax highlighter:', err)\n // Clear loading promise so it can be retried\n loadingPromise = null\n }\n })()\n\n return loadingPromise\n}\n\n/**\n * Highlights all code blocks on the page that haven't been highlighted yet.\n * Gracefully handles cases where highlight.js fails to load.\n */\nexport async function highlightCodeBlocks(): Promise<void> {\n // Load highlighter if not already loaded\n await loadSyntaxHighlighter()\n\n // If loading failed, return silently (code blocks remain unstyled)\n if (!highlightJs) {\n return\n }\n\n // Find all code blocks and highlight them\n document.querySelectorAll('pre code').forEach((block) => {\n // Skip if already highlighted\n if (!block.classList.contains('hljs')) {\n highlightJs.highlightElement(block)\n }\n })\n}\n\n/**\n * Highlights a specific code block element.\n * Useful for dynamically added content.\n *\n * @param element - The code block element to highlight\n */\nexport async function highlightCodeBlock(element: HTMLElement): Promise<void> {\n await loadSyntaxHighlighter()\n\n if (!highlightJs) {\n return\n }\n\n if (!element.classList.contains('hljs')) {\n highlightJs.highlightElement(element)\n }\n}\n\n/**\n * Returns whether syntax highlighting is available.\n * Useful for conditional rendering or feature detection.\n */\nexport function isSyntaxHighlightingAvailable(): boolean {\n return highlightJs !== null\n}\n\n/**\n * Returns the loaded highlight.js instance, if available.\n * Useful for custom highlighting flows that need direct access.\n */\nexport function getSyntaxHighlighter(): any | null {\n return highlightJs\n}\n","/**\n * Dashboard Export / Import Utilities\n *\n * Serialises a DashboardConfig (plus the host's name/description) into a small\n * JSON envelope, and parses such files back with a structural shape check and\n * legacy-portlet migration. Pure functions, no extra dependency.\n *\n * Usage:\n * 1. Enable in CubeProvider: features={{ dashboardImportExport: { enabled: true } }}\n * 2. Optionally pass `dashboardMeta` / `onDashboardMetaChange` to AnalyticsDashboard so\n * exports carry the dashboard name and imports can rename the host's record.\n * 3. Export / Import buttons appear in the dashboard toolbar. Export is offered in view\n * and edit mode and also on read-only dashboards (`editable={false}`); import only\n * while editing. `hideToolbar` still hides both.\n *\n * Hosts that create new dashboards from a file (e.g. on a list page) use\n * `readDashboardExportFile()` directly and persist the returned config themselves.\n */\n\nimport type {\n DashboardConfig,\n DashboardFilter,\n DashboardFilterMappingEntry,\n DashboardGridSettings,\n DashboardLayoutMode,\n DashboardMeta,\n PortletConfig,\n PortletGroup,\n PortletGroupCell,\n RowLayout,\n RowLayoutColumn,\n} from '../types.js'\nimport { isValidAnalysisConfig } from '../types/analysisConfig.js'\nimport {\n ensureAnalysisConfig,\n hasAnalysisConfig,\n migrateLegacyPortlet,\n} from './configMigration.js'\n\n// ============================================================================\n// File format\n// ============================================================================\n\n/**\n * The on-disk shape of a dashboard export: the config plus the host-owned name and\n * description. Portlets carry their own `analysisConfig.version` and migrate on read.\n */\nexport interface DashboardExportFile {\n /** ISO 8601 timestamp of when the file was created */\n exportedAt: string\n name?: string\n description?: string\n config: DashboardConfig\n}\n\n/**\n * Why a file could not be imported. The check is structural only: the file has to\n * have the shape of a dashboard config, and its contents are taken on trust from\n * there. Codes, not sentences: the UI resolves them to translated text\n * (`dashboard.import.error.<code>`).\n */\nexport type DashboardImportError =\n | { code: 'invalidJson' }\n | { code: 'unknownFormat' }\n | { code: 'invalidConfig'; path: string }\n | { code: 'invalidPortlet'; index: number; path: string }\n\n/**\n * Non-fatal findings about an otherwise valid file.\n */\nexport type DashboardImportWarning =\n | { code: 'unknownFilterMapping'; portletId: string; portletTitle: string; filterId: string }\n\nexport type DashboardImportResult =\n | {\n ok: true\n config: DashboardConfig\n name?: string\n description?: string\n warnings: DashboardImportWarning[]\n }\n | { ok: false; errors: DashboardImportError[] }\n\n// ============================================================================\n// Export\n// ============================================================================\n\nconst LEGACY_PORTLET_KEYS = [\n 'query',\n 'chartType',\n 'chartConfig',\n 'displayConfig',\n 'analysisType',\n 'funnelCube',\n 'funnelSteps',\n 'funnelTimeDimension',\n 'funnelBindingKey',\n 'funnelChartType',\n 'funnelChartConfig',\n 'funnelDisplayConfig',\n] as const satisfies ReadonlyArray<keyof PortletConfig>\n\n/**\n * Canonical form of a portlet for the file: `analysisConfig` guaranteed (migrating\n * legacy fields on the way) and the deprecated legacy fields dropped.\n */\nfunction normalizePortletForExport(portlet: PortletConfig): PortletConfig {\n // ensureAnalysisConfig intentionally falls back to a default query when it\n // cannot parse legacy data. An export must not turn a broken chart into a\n // valid-looking empty one, so validate legacy JSON before migrating it.\n if (!hasAnalysisConfig(portlet)) {\n if (typeof portlet.query !== 'string') {\n throw new Error(`Cannot export portlet \"${portlet.id}\": no valid analysis configuration`)\n }\n try {\n JSON.parse(portlet.query)\n } catch {\n throw new Error(`Cannot export portlet \"${portlet.id}\": legacy query is not valid JSON`)\n }\n }\n\n const normalized: PortletConfig = { ...ensureAnalysisConfig(portlet) }\n if (!hasAnalysisConfig(normalized)) {\n throw new Error(`Cannot export portlet \"${portlet.id}\": could not migrate analysis configuration`)\n }\n for (const key of LEGACY_PORTLET_KEYS) {\n delete normalized[key]\n }\n return normalized\n}\n\n/**\n * Prepare a config for export: canonical portlets, transient thumbnail fields removed,\n * everything else untouched.\n */\nexport function normalizeDashboardConfigForExport(config: DashboardConfig): DashboardConfig {\n const normalized: DashboardConfig = {\n ...config,\n portlets: config.portlets.map(normalizePortletForExport),\n }\n delete normalized.thumbnailData\n delete normalized.thumbnailUrl\n return normalized\n}\n\n/**\n * Build the export envelope for a dashboard.\n */\nexport function createDashboardExport(\n config: DashboardConfig,\n meta?: DashboardMeta,\n now: Date = new Date()\n): DashboardExportFile {\n const file: DashboardExportFile = {\n exportedAt: now.toISOString(),\n config: normalizeDashboardConfigForExport(config),\n }\n const name = meta?.name?.trim()\n const description = meta?.description?.trim()\n if (name) file.name = name\n if (description) file.description = description\n return file\n}\n\n/**\n * Pretty-printed JSON for the file, so exports diff well in version control.\n */\nexport function serializeDashboardExport(file: DashboardExportFile): string {\n return JSON.stringify(file, null, 2)\n}\n\nfunction toFileNamePart(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[/\\\\]/g, '-')\n}\n\n/**\n * Filename for an export: `<prefix->?<dashboard-name|dashboard>-<yyyy-mm-dd>.json`.\n * The name is lowercased with spaces turned into dashes — no slugification.\n */\nexport function dashboardExportFilename(\n name?: string,\n date: Date = new Date(),\n prefix?: string\n): string {\n const namePart = toFileNamePart(name ?? '') || 'dashboard'\n const prefixPart = prefix ? toFileNamePart(prefix) : ''\n const day = date.toISOString().slice(0, 10)\n return `${prefixPart ? `${prefixPart}-` : ''}${namePart}-${day}.json`\n}\n\n/**\n * Trigger a browser download of the export file. No-op outside a browser.\n */\nexport function downloadDashboardExport(file: DashboardExportFile, filename?: string): void {\n if (typeof document === 'undefined' || typeof URL === 'undefined') return\n const blob = new Blob([serializeDashboardExport(file)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename ?? dashboardExportFilename(file.name)\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n URL.revokeObjectURL(url)\n}\n\n// ============================================================================\n// Import\n// ============================================================================\n\nconst LAYOUT_MODES: readonly DashboardLayoutMode[] = ['grid', 'rows']\n\ntype UnknownRecord = Record<string, unknown>\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value)\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string'\n}\n\nfunction isOptionalBoolean(value: unknown): value is boolean | undefined {\n return value === undefined || typeof value === 'boolean'\n}\n\nfunction isOptionalRecord(value: unknown): value is UnknownRecord | undefined {\n return value === undefined || isRecord(value)\n}\n\nfunction isLayoutMode(value: unknown): value is DashboardLayoutMode {\n return typeof value === 'string' && LAYOUT_MODES.includes(value as DashboardLayoutMode)\n}\n\nfunction isGridSettings(value: unknown): value is DashboardGridSettings {\n return isRecord(value) && (['cols', 'rowHeight', 'minW', 'minH'] as const).every((key) => isFiniteNumber(value[key]))\n}\n\nfunction isFilterMappingEntry(value: unknown): value is string | DashboardFilterMappingEntry {\n if (typeof value === 'string') return true\n return isRecord(value) && isNonEmptyString(value.filterId) && isOptionalString(value.member)\n}\n\nfunction isDashboardFilter(value: unknown): value is DashboardFilter {\n return (\n isRecord(value) &&\n isNonEmptyString(value.id) &&\n typeof value.label === 'string' &&\n isRecord(value.filter) &&\n isOptionalBoolean(value.isUniversalTime)\n )\n}\n\nfunction isGroupCell(value: unknown): value is PortletGroupCell {\n return isRecord(value) && Array.isArray(value.portletIds) && value.portletIds.every((id) => typeof id === 'string')\n}\n\nfunction isRowColumn(value: unknown): value is RowLayoutColumn {\n if (!isRecord(value) || !isFiniteNumber(value.w)) return false\n const hasPortlet = value.portletId !== undefined\n const hasGroup = value.groupId !== undefined\n if (hasPortlet === hasGroup) return false\n return isOptionalString(value.portletId) && isOptionalString(value.groupId)\n}\n\ntype PortletParseResult = { ok: true; portlet: PortletConfig } | { ok: false; error: DashboardImportError }\n\n/**\n * Read one portlet and return it in canonical form (analysisConfig guaranteed,\n * legacy fields migrated and dropped), or the reason its shape was rejected.\n */\nfunction parsePortlet(value: unknown, index: number): PortletParseResult {\n const fail = (path: string): PortletParseResult => ({ ok: false, error: { code: 'invalidPortlet', index, path } })\n\n if (!isRecord(value)) return fail('')\n if (!isNonEmptyString(value.id)) return fail('id')\n if (typeof value.title !== 'string') return fail('title')\n const { x, y, w, h } = value\n if (!isFiniteNumber(x)) return fail('x')\n if (!isFiniteNumber(y)) return fail('y')\n if (!isFiniteNumber(w)) return fail('w')\n if (!isFiniteNumber(h)) return fail('h')\n\n const portlet: PortletConfig = { id: value.id, title: value.title, x, y, w, h }\n\n const mapping = value.dashboardFilterMapping\n if (mapping !== undefined) {\n if (!Array.isArray(mapping) || !mapping.every(isFilterMappingEntry)) return fail('dashboardFilterMapping')\n portlet.dashboardFilterMapping = mapping.filter(isFilterMappingEntry)\n }\n if (!isOptionalBoolean(value.eagerLoad)) return fail('eagerLoad')\n if (value.eagerLoad !== undefined) portlet.eagerLoad = value.eagerLoad\n\n // Query/chart definition: either a valid canonical analysisConfig, or the legacy\n // `query` JSON string (plus its sibling chart fields) that configMigration upgrades.\n if (value.analysisConfig !== undefined) {\n if (!isValidAnalysisConfig(value.analysisConfig)) return fail('analysisConfig')\n portlet.analysisConfig = value.analysisConfig\n return { ok: true, portlet }\n }\n if (typeof value.query !== 'string') return fail('analysisConfig')\n if (!isOptionalString(value.chartType) || !isOptionalString(value.funnelChartType)) return fail('chartType')\n if (!isOptionalRecord(value.chartConfig) || !isOptionalRecord(value.funnelChartConfig)) return fail('chartConfig')\n if (!isOptionalRecord(value.displayConfig) || !isOptionalRecord(value.funnelDisplayConfig)) return fail('displayConfig')\n const analysisType = value.analysisType\n if (analysisType !== undefined && analysisType !== 'query' && analysisType !== 'funnel') return fail('analysisType')\n\n const migrated = migrateLegacyPortlet({\n query: value.query,\n chartType: value.chartType,\n chartConfig: value.chartConfig,\n displayConfig: value.displayConfig,\n analysisType,\n funnelChartType: value.funnelChartType,\n funnelChartConfig: value.funnelChartConfig,\n funnelDisplayConfig: value.funnelDisplayConfig,\n })\n portlet.analysisConfig = migrated\n return { ok: true, portlet }\n}\n\ntype SectionResult<T> = { ok: true; value: T } | { ok: false; error: DashboardImportError }\n\nfunction invalidConfig<T>(path: string): SectionResult<T> {\n return { ok: false, error: { code: 'invalidConfig', path } }\n}\n\nfunction parseFilters(value: unknown): SectionResult<DashboardFilter[]> {\n if (!Array.isArray(value)) return invalidConfig('filters')\n const filters: DashboardFilter[] = []\n for (let i = 0; i < value.length; i++) {\n const filter: unknown = value[i]\n if (!isDashboardFilter(filter)) return invalidConfig(`filters[${i}]`)\n filters.push(filter)\n }\n return { ok: true, value: filters }\n}\n\nfunction parseGroups(value: unknown, portletIds: Set<string>): SectionResult<PortletGroup[]> {\n if (!Array.isArray(value)) return invalidConfig('groups')\n const groups: PortletGroup[] = []\n for (let i = 0; i < value.length; i++) {\n const group: unknown = value[i]\n if (!isRecord(group)) return invalidConfig(`groups[${i}]`)\n if (!isNonEmptyString(group.id)) return invalidConfig(`groups[${i}].id`)\n if (!isOptionalString(group.title)) return invalidConfig(`groups[${i}].title`)\n if (group.direction !== 'row' && group.direction !== 'column') return invalidConfig(`groups[${i}].direction`)\n if (!Array.isArray(group.cells)) return invalidConfig(`groups[${i}].cells`)\n const cells: PortletGroupCell[] = []\n for (let c = 0; c < group.cells.length; c++) {\n const cell: unknown = group.cells[c]\n if (!isGroupCell(cell)) return invalidConfig(`groups[${i}].cells[${c}]`)\n if (cell.portletIds.some((id) => !portletIds.has(id))) return invalidConfig(`groups[${i}].cells[${c}].portletIds`)\n cells.push(cell)\n }\n const parsed: PortletGroup = { id: group.id, direction: group.direction, cells }\n if (group.title !== undefined) parsed.title = group.title\n groups.push(parsed)\n }\n return { ok: true, value: groups }\n}\n\nfunction parseRows(value: unknown, portletIds: Set<string>, groupIds: Set<string>): SectionResult<RowLayout[]> {\n if (!Array.isArray(value)) return invalidConfig('rows')\n const rows: RowLayout[] = []\n for (let i = 0; i < value.length; i++) {\n const row: unknown = value[i]\n if (!isRecord(row)) return invalidConfig(`rows[${i}]`)\n if (!isNonEmptyString(row.id)) return invalidConfig(`rows[${i}].id`)\n if (!isFiniteNumber(row.h)) return invalidConfig(`rows[${i}].h`)\n if (!Array.isArray(row.columns)) return invalidConfig(`rows[${i}].columns`)\n const columns: RowLayoutColumn[] = []\n for (let c = 0; c < row.columns.length; c++) {\n const column: unknown = row.columns[c]\n if (!isRowColumn(column)) return invalidConfig(`rows[${i}].columns[${c}]`)\n if (column.portletId !== undefined && !portletIds.has(column.portletId)) {\n return invalidConfig(`rows[${i}].columns[${c}].portletId`)\n }\n if (column.groupId !== undefined && !groupIds.has(column.groupId)) {\n return invalidConfig(`rows[${i}].columns[${c}].groupId`)\n }\n columns.push(column)\n }\n rows.push({ id: row.id, h: row.h, columns })\n }\n return { ok: true, value: rows }\n}\n\ntype ConfigParseResult =\n | { ok: true; config: DashboardConfig; warnings: DashboardImportWarning[] }\n | { ok: false; errors: DashboardImportError[] }\n\n/**\n * Read a bare DashboardConfig object (the `config` of an envelope, or a raw row\n * pasted from the host's database). Builds a fresh, whitelisted config: unknown keys\n * and the transient thumbnail fields do not survive the import.\n */\nfunction parseConfig(value: unknown): ConfigParseResult {\n if (!isRecord(value)) return { ok: false, errors: [{ code: 'invalidConfig', path: '' }] }\n if (!Array.isArray(value.portlets)) return { ok: false, errors: [{ code: 'invalidConfig', path: 'portlets' }] }\n\n const errors: DashboardImportError[] = []\n const portlets: PortletConfig[] = []\n const portletIds = new Set<string>()\n\n value.portlets.forEach((raw: unknown, index: number) => {\n const result = parsePortlet(raw, index)\n if (!result.ok) {\n errors.push(result.error)\n return\n }\n portletIds.add(result.portlet.id)\n portlets.push(result.portlet)\n })\n\n const config: DashboardConfig = { portlets }\n const invalid = (path: string) => errors.push({ code: 'invalidConfig', path })\n\n if (value.layoutMode !== undefined) {\n if (isLayoutMode(value.layoutMode)) config.layoutMode = value.layoutMode\n else invalid('layoutMode')\n }\n if (value.grid !== undefined) {\n if (isGridSettings(value.grid)) config.grid = value.grid\n else invalid('grid')\n }\n if (value.colorPalette !== undefined) {\n if (typeof value.colorPalette === 'string') config.colorPalette = value.colorPalette\n else invalid('colorPalette')\n }\n if (value.eagerLoad !== undefined) {\n if (typeof value.eagerLoad === 'boolean') config.eagerLoad = value.eagerLoad\n else invalid('eagerLoad')\n }\n if (value.layouts !== undefined) {\n if (isRecord(value.layouts)) config.layouts = value.layouts\n else invalid('layouts')\n }\n if (value.filters !== undefined) {\n const result = parseFilters(value.filters)\n if (result.ok) config.filters = result.value\n else errors.push(result.error)\n }\n\n const groupIds = new Set<string>()\n if (value.groups !== undefined) {\n const result = parseGroups(value.groups, portletIds)\n if (result.ok) {\n config.groups = result.value\n result.value.forEach((group) => groupIds.add(group.id))\n } else {\n errors.push(result.error)\n }\n }\n if (value.rows !== undefined) {\n const result = parseRows(value.rows, portletIds, groupIds)\n if (result.ok) config.rows = result.value\n else errors.push(result.error)\n }\n\n if (errors.length > 0) return { ok: false, errors }\n\n // Warnings: portlets linked to a dashboard filter the file does not define. Not\n // pruned, because hosts may supply those filters programmatically at runtime.\n const warnings: DashboardImportWarning[] = []\n if (config.filters !== undefined) {\n const filterIds = new Set(config.filters.map((filter) => filter.id))\n for (const portlet of portlets) {\n for (const entry of portlet.dashboardFilterMapping ?? []) {\n const filterId = typeof entry === 'string' ? entry : entry.filterId\n if (!filterIds.has(filterId)) {\n warnings.push({ code: 'unknownFilterMapping', portletId: portlet.id, portletTitle: portlet.title, filterId })\n }\n }\n }\n }\n\n return { ok: true, config, warnings }\n}\n\nfunction optionalTrimmed(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed ? trimmed : undefined\n}\n\n/**\n * Parse a dashboard export. Accepts the JSON text or an already-parsed value.\n *\n * Two shapes are recognised: the envelope written by `createDashboardExport` (an\n * object with a `config`), and a bare DashboardConfig (an object with a `portlets`\n * array) so a row copied straight from the host's database imports too. Anything\n * else is `unknownFormat`.\n */\nexport function parseDashboardExport(input: string | unknown): DashboardImportResult {\n let value: unknown = input\n if (typeof input === 'string') {\n try {\n value = JSON.parse(input)\n } catch {\n return { ok: false, errors: [{ code: 'invalidJson' }] }\n }\n }\n\n if (!isRecord(value)) return { ok: false, errors: [{ code: 'unknownFormat' }] }\n\n if ('config' in value) {\n const parsed = parseConfig(value.config)\n if (!parsed.ok) return parsed\n return {\n ok: true,\n config: parsed.config,\n name: optionalTrimmed(value.name),\n description: optionalTrimmed(value.description),\n warnings: parsed.warnings,\n }\n }\n\n if (Array.isArray(value.portlets)) return parseConfig(value)\n\n return { ok: false, errors: [{ code: 'unknownFormat' }] }\n}\n\nfunction readFileText(file: File): Promise<string> {\n if (typeof file.text === 'function') return file.text()\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '')\n reader.onerror = () => reject(reader.error)\n reader.readAsText(file)\n })\n}\n\n/**\n * Read and parse a File picked by the user (e.g. from an `<input type=\"file\">`).\n */\nexport async function readDashboardExportFile(file: File): Promise<DashboardImportResult> {\n let text: string\n try {\n text = await readFileText(file)\n } catch {\n return { ok: false, errors: [{ code: 'invalidJson' }] }\n }\n return parseDashboardExport(text)\n}\n"],"mappings":"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"}
|