drizzle-cube 0.4.21 → 0.4.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/express/index.cjs +1 -1
- package/dist/adapters/express/index.js +2 -2
- package/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.js +2 -2
- package/dist/adapters/{handler-B-tEntiU.cjs → handler-C3hT7g2W.cjs} +1 -1
- package/dist/adapters/{handler-9Rdn7zM2.js → handler-t7Qd1IYi.js} +1 -1
- package/dist/adapters/hono/index.cjs +6 -6
- package/dist/adapters/hono/index.d.ts +13 -6
- package/dist/adapters/hono/index.js +65 -65
- package/dist/adapters/{mcp-transport-m1X1GtwG.js → mcp-transport-B6ZudTSk.js} +7 -0
- package/dist/adapters/{mcp-transport-8u9G5oNa.cjs → mcp-transport-DCiSGtp1.cjs} +1 -1
- package/dist/adapters/nextjs/index.cjs +2 -2
- package/dist/adapters/nextjs/index.js +2 -2
- package/dist/client/charts.js +12 -12
- package/dist/client/chunks/{RetentionCombinedChart-CLq89aOJ.js → RetentionCombinedChart-BK3NPsHP.js} +2 -2
- package/dist/client/chunks/{RetentionCombinedChart-CLq89aOJ.js.map → RetentionCombinedChart-BK3NPsHP.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-3z9fHE2F.js → analysis-builder-DVrv9Q4n.js} +9 -9
- package/dist/client/chunks/{analysis-builder-3z9fHE2F.js.map → analysis-builder-DVrv9Q4n.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-shared-Da-vlQa_.js → analysis-builder-shared-CrENEvEk.js} +6 -6
- package/dist/client/chunks/{analysis-builder-shared-Da-vlQa_.js.map → analysis-builder-shared-CrENEvEk.js.map} +1 -1
- package/dist/client/chunks/{chart-activity-grid-CPGcTSuh.js → chart-activity-grid-OG6he4YS.js} +2 -2
- package/dist/client/chunks/{chart-activity-grid-CPGcTSuh.js.map → chart-activity-grid-OG6he4YS.js.map} +1 -1
- package/dist/client/chunks/{chart-area-ByJQ7NZd.js → chart-area-TawAd2k9.js} +3 -3
- package/dist/client/chunks/{chart-area-ByJQ7NZd.js.map → chart-area-TawAd2k9.js.map} +1 -1
- package/dist/client/chunks/{chart-bar-dj14frMt.js → chart-bar-D3vtCNQG.js} +2 -2
- package/dist/client/chunks/{chart-bar-dj14frMt.js.map → chart-bar-D3vtCNQG.js.map} +1 -1
- package/dist/client/chunks/{chart-box-plot-ZatBpatq.js → chart-box-plot-BXwN2rO5.js} +2 -2
- package/dist/client/chunks/{chart-box-plot-ZatBpatq.js.map → chart-box-plot-BXwN2rO5.js.map} +1 -1
- package/dist/client/chunks/{chart-bubble-CemotLx-.js → chart-bubble-ZfNe8t5k.js} +2 -2
- package/dist/client/chunks/{chart-bubble-CemotLx-.js.map → chart-bubble-ZfNe8t5k.js.map} +1 -1
- package/dist/client/chunks/{chart-candlestick-BIR4uGGt.js → chart-candlestick-DmF3haFu.js} +2 -2
- package/dist/client/chunks/{chart-candlestick-BIR4uGGt.js.map → chart-candlestick-DmF3haFu.js.map} +1 -1
- package/dist/client/chunks/{chart-data-table-BsAjHe7o.js → chart-data-table-DJZPkArt.js} +4 -4
- package/dist/client/chunks/{chart-data-table-BsAjHe7o.js.map → chart-data-table-DJZPkArt.js.map} +1 -1
- package/dist/client/chunks/{chart-funnel-dofnhD24.js → chart-funnel-CE9x0Io9.js} +2 -2
- package/dist/client/chunks/{chart-funnel-dofnhD24.js.map → chart-funnel-CE9x0Io9.js.map} +1 -1
- package/dist/client/chunks/{chart-gauge-CKJJ8m3b.js → chart-gauge-Djs5FWxB.js} +2 -2
- package/dist/client/chunks/{chart-gauge-CKJJ8m3b.js.map → chart-gauge-Djs5FWxB.js.map} +1 -1
- package/dist/client/chunks/{chart-heat-map-BVuPUKHT.js → chart-heat-map-CqtMkdxd.js} +2 -2
- package/dist/client/chunks/{chart-heat-map-BVuPUKHT.js.map → chart-heat-map-CqtMkdxd.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-delta-DUD3f8vL.js → chart-kpi-delta-DEzA74cL.js} +4 -4
- package/dist/client/chunks/{chart-kpi-delta-DUD3f8vL.js.map → chart-kpi-delta-DEzA74cL.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-number-iJh-PzsM.js → chart-kpi-number-Bab-BZtX.js} +3 -3
- package/dist/client/chunks/{chart-kpi-number-iJh-PzsM.js.map → chart-kpi-number-Bab-BZtX.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-text-x6pV9v9Q.js → chart-kpi-text-BkTgckDJ.js} +3 -3
- package/dist/client/chunks/{chart-kpi-text-x6pV9v9Q.js.map → chart-kpi-text-BkTgckDJ.js.map} +1 -1
- package/dist/client/chunks/{chart-line-DzyZkugh.js → chart-line-DhM-Hvu0.js} +3 -3
- package/dist/client/chunks/{chart-line-DzyZkugh.js.map → chart-line-DhM-Hvu0.js.map} +1 -1
- package/dist/client/chunks/{chart-measure-profile-C2IkBG3V.js → chart-measure-profile-4vQxFm69.js} +3 -3
- package/dist/client/chunks/{chart-measure-profile-C2IkBG3V.js.map → chart-measure-profile-4vQxFm69.js.map} +1 -1
- package/dist/client/chunks/{chart-pie-akbfRfb9.js → chart-pie-B86KRcHI.js} +2 -2
- package/dist/client/chunks/{chart-pie-akbfRfb9.js.map → chart-pie-B86KRcHI.js.map} +1 -1
- package/dist/client/chunks/{chart-radar-BaN-Kjww.js → chart-radar-BhDBmJRh.js} +2 -2
- package/dist/client/chunks/{chart-radar-BaN-Kjww.js.map → chart-radar-BhDBmJRh.js.map} +1 -1
- package/dist/client/chunks/{chart-radial-bar-DpptEL3s.js → chart-radial-bar-Brugya8X.js} +2 -2
- package/dist/client/chunks/{chart-radial-bar-DpptEL3s.js.map → chart-radial-bar-Brugya8X.js.map} +1 -1
- package/dist/client/chunks/{chart-sankey-CG-3hHmX.js → chart-sankey-D2L8ympI.js} +2 -2
- package/dist/client/chunks/{chart-sankey-CG-3hHmX.js.map → chart-sankey-D2L8ympI.js.map} +1 -1
- package/dist/client/chunks/{chart-scatter-l_yTVxF3.js → chart-scatter-CAkbBDkK.js} +2 -2
- package/dist/client/chunks/{chart-scatter-l_yTVxF3.js.map → chart-scatter-CAkbBDkK.js.map} +1 -1
- package/dist/client/chunks/{chart-sunburst-KhDcKhmZ.js → chart-sunburst-DaxJ-cob.js} +2 -2
- package/dist/client/chunks/{chart-sunburst-KhDcKhmZ.js.map → chart-sunburst-DaxJ-cob.js.map} +1 -1
- package/dist/client/chunks/{chart-tree-map-CBbiaBXV.js → chart-tree-map-CrDJAvZU.js} +2 -2
- package/dist/client/chunks/{chart-tree-map-CBbiaBXV.js.map → chart-tree-map-CrDJAvZU.js.map} +1 -1
- package/dist/client/chunks/{chart-waterfall-CX3vx_lI.js → chart-waterfall-BBwSfEKT.js} +3 -3
- package/dist/client/chunks/{chart-waterfall-CX3vx_lI.js.map → chart-waterfall-BBwSfEKT.js.map} +1 -1
- package/dist/client/chunks/{charts-core-CU9u_HtL.js → charts-core-B4Rbfdcn.js} +2 -2
- package/dist/client/chunks/{charts-core-CU9u_HtL.js.map → charts-core-B4Rbfdcn.js.map} +1 -1
- package/dist/client/chunks/{charts-loader-B3tt5oKG.js → charts-loader-DbrwgvCK.js} +25 -25
- package/dist/client/chunks/{charts-loader-B3tt5oKG.js.map → charts-loader-DbrwgvCK.js.map} +1 -1
- package/dist/client/chunks/{components-CMGGxqOB.js → components-GzooQM5J.js} +9 -9
- package/dist/client/chunks/{components-CMGGxqOB.js.map → components-GzooQM5J.js.map} +1 -1
- package/dist/client/chunks/{core-D_8mkGpQ.js → core-Y9e-sNfb.js} +2 -2
- package/dist/client/chunks/{core-D_8mkGpQ.js.map → core-Y9e-sNfb.js.map} +1 -1
- package/dist/client/chunks/{icons-M7shurcH.js → icons-DFJw-2HU.js} +6 -6
- package/dist/client/chunks/{icons-M7shurcH.js.map → icons-DFJw-2HU.js.map} +1 -1
- package/dist/client/chunks/{providers-CgxXm6Ll.js → providers-CCw8Kjlc.js} +2 -2
- package/dist/client/chunks/{providers-CgxXm6Ll.js.map → providers-CCw8Kjlc.js.map} +1 -1
- package/dist/client/chunks/{syntaxHighlighting-BQfjio-i.js → syntaxHighlighting-DAMSW_A6.js} +2 -2
- package/dist/client/chunks/{syntaxHighlighting-BQfjio-i.js.map → syntaxHighlighting-DAMSW_A6.js.map} +1 -1
- package/dist/client/chunks/{useDirtyStateTracking-Cu1HSjmo.js → useDirtyStateTracking-CjhwBXRw.js} +20 -20
- package/dist/client/chunks/{useDirtyStateTracking-Cu1HSjmo.js.map → useDirtyStateTracking-CjhwBXRw.js.map} +1 -1
- package/dist/client/chunks/useExplainAI-IiW55BaQ.js +182 -0
- package/dist/client/chunks/useExplainAI-IiW55BaQ.js.map +1 -0
- package/dist/client/chunks/{vendor-AVsJ2ni0.js → vendor-B2EH3V58.js} +7 -7
- package/dist/client/chunks/{vendor-AVsJ2ni0.js.map → vendor-B2EH3V58.js.map} +1 -1
- package/dist/client/components.js +1 -1
- package/dist/client/hooks/useNotebookLayout.d.ts +8 -0
- package/dist/client/hooks.d.ts +2 -0
- package/dist/client/hooks.js +51 -190
- package/dist/client/hooks.js.map +1 -1
- package/dist/client/icons.js +1 -1
- package/dist/client/index.js +871 -742
- package/dist/client/index.js.map +1 -1
- package/dist/client/providers.js +1 -1
- package/dist/client/styles.css +1 -1
- package/dist/client/utils.js +7 -7
- package/dist/client-bundle-stats.html +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +7 -0
- package/package.json +1 -1
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
`)}}]},ht={name:"drizzle-cube-date-filtering",description:"CRITICAL: How to correctly filter by date vs group by time period - the #1 source of query mistakes",messages:[{role:"user",content:{type:"text",text:["# Date Filtering vs Time Grouping - CRITICAL GUIDE","","This is the most common mistake when building queries. These are TWO DIFFERENT operations.","","## Quick Decision Tree","","```","User wants data over a time period?",'├── Wants AGGREGATED TOTALS (e.g., "total sales last month")',"│ └── Use: filters with inDateRange operator","│",'└── Wants TIME SERIES breakdown (e.g., "daily sales last month")'," └── Use: timeDimensions with granularity","```","","## For Aggregated Totals (MOST COMMON)","",'When user says: "last 3 months", "over the past year", "in Q1", "since January"',"","```json","{",' "measures": ["Sales.totalRevenue"],',' "dimensions": ["Products.category"],',' "filters": [',' { "member": "Sales.date", "operator": "inDateRange", "values": ["last 3 months"] }'," ]","}","```","","Result: One row per category with TOTAL revenue over the 3-month period.","","## For Time Series (Trend Analysis)","",'When user says: "by month", "per week", "daily trend", "over time"',"","```json","{",' "measures": ["Sales.totalRevenue"],',' "timeDimensions": [',' { "dimension": "Sales.date", "dateRange": "last 3 months", "granularity": "month" }'," ]","}","```","","Result: Multiple rows, one per month.","","## WRONG: timeDimensions Without Granularity","","```json","// This returns ~90 rows (daily) instead of aggregates!","{",' "timeDimensions": [{ "dimension": "Sales.date", "dateRange": "last 3 months" }]',"}","```","","## Both: Filter AND Group","",'When user wants: "monthly breakdown for last quarter"',"","```json","{",' "measures": ["Sales.totalRevenue"],',' "filters": [',' { "member": "Sales.date", "operator": "inDateRange", "values": ["last quarter"] }'," ],",' "timeDimensions": [',' { "dimension": "Sales.date", "granularity": "month" }'," ]","}","```","","## Summary Table","","| User Request | Use | Example |","|-------------|-----|---------|",'| "total for last 3 months" | filters + inDateRange | { filters: [{ operator: "inDateRange", values: ["last 3 months"] }] } |','| "top 5 last quarter" | filters + inDateRange | Same as above + order + limit |','| "monthly trend" | timeDimensions + granularity | { timeDimensions: [{ granularity: "month" }] } |','| "daily breakdown last week" | timeDimensions | { timeDimensions: [{ dateRange: "last week", granularity: "day" }] } |'].join(`
|
|
198
198
|
`)}}]},Bn=[mt,ft,pt,ht];function kn(){return Bn}class Ie{cubes=new Map;dbExecutor;metadataCache;cacheConfig;constructor(e){e?.databaseExecutor?this.dbExecutor=e.databaseExecutor:e?.drizzle&&(this.dbExecutor=Ge(e.drizzle,e.schema,e.engineType)),this.cacheConfig=e?.cache}setDatabaseExecutor(e){this.dbExecutor=e}getEngineType(){return this.dbExecutor?.getEngineType()}setDrizzle(e,t,n){this.dbExecutor=Ge(e,t,n)}hasExecutor(){return!!this.dbExecutor}requireExecutor(){if(!this.dbExecutor)throw new Error("Database executor not configured");return this.dbExecutor}createQueryExecutor(e=!1){const t=this.requireExecutor();return new Pn(t,e?this.cacheConfig:void 0)}formatSqlResult(e){const t=this.requireExecutor().getEngineType();return{sql:Y.formatSqlString(e.sql,t),params:e.params}}registerCube(e){this.validateCalculatedMeasures(e),new X(this.cubes).populateDependencies(e),this.cubes.set(e.name,e),this.invalidateMetadataCache()}validateCalculatedMeasures(e){const t=[];for(const[n,i]of Object.entries(e.measures))if(i.type==="calculated"){if(!i.calculatedSql){t.push(`Calculated measure '${e.name}.${n}' must have calculatedSql property`);continue}const s=dn(i.calculatedSql);if(!s.isValid){t.push(`Invalid calculatedSql syntax in '${e.name}.${n}': ${s.errors.join(", ")}`);continue}const r=new Map(this.cubes);r.set(e.name,e);const a=new X(r);try{a.validateDependencies(e)}catch(u){t.push(u instanceof Error?u.message:String(u))}}if(t.length===0){const n=new Map(this.cubes);n.set(e.name,e);const i=new X(n);i.buildGraph(e);const s=i.detectCycle();s&&t.push(`Circular dependency detected in calculated measures: ${s.join(" -> ")}`)}if(t.length>0)throw new Error(`Calculated measure validation failed for cube '${e.name}':
|
|
199
199
|
${t.join(`
|
|
200
|
-
`)}`)}getCube(e){return this.cubes.get(e)}getAllCubes(){return Array.from(this.cubes.values())}getAllCubesMap(){return this.cubes}async execute(e,t,n){return this.createQueryExecutor(!0).execute(this.cubes,e,t,n)}async executeMultiCubeQuery(e,t,n){return this.execute(e,t,n)}async executeQuery(e,t,n){if(!this.cubes.get(e))throw new Error(`Cube '${e}' not found`);return this.execute(t,n)}getMetadata(){return this.metadataCache?this.metadataCache:(this.metadataCache=Array.from(this.cubes.values()).map(e=>this.generateCubeMetadata(e)),this.metadataCache)}getColumnName(e){if(e&&e.name||e&&e.columnType&&e.name)return e.name;if(typeof e=="string")return e;if(e&&typeof e=="object"){if(e._.name)return e._.name;if(e.name)return e.name;if(e.columnName)return e.columnName}return"unknown_column"}static DEFAULT_TIME_GRANULARITIES=["year","quarter","month","week","day","hour"];generateCubeMetadata(e){const t=Object.keys(e.measures),n=Object.keys(e.dimensions),i=new Array(t.length),s=new Array(n.length);for(let c=0;c<t.length;c++){const l=t[c],m=e.measures[l];let p;m.drillMembers&&m.drillMembers.length>0&&(p=m.drillMembers.map(f=>f.includes(".")?f:`${e.name}.${f}`)),i[c]={name:`${e.name}.${l}`,title:m.title||l,shortTitle:m.title||l,type:m.type,format:void 0,description:m.description,synonyms:m.synonyms,drillMembers:p}}for(let c=0;c<n.length;c++){const l=n[c],m=e.dimensions[l];let p;m.type==="time"&&(p=m.granularities||Ie.DEFAULT_TIME_GRANULARITIES),s[c]={name:`${e.name}.${l}`,title:m.title||l,shortTitle:m.title||l,type:m.type,format:void 0,description:m.description,synonyms:m.synonyms,granularities:p}}const r=[];if(e.joins)for(const[,c]of Object.entries(e.joins)){const l=typeof c.targetCube=="function"?c.targetCube():c.targetCube;r.push({targetCube:l.name,relationship:c.relationship,joinFields:c.on.map(m=>({sourceField:this.getColumnName(m.source),targetField:this.getColumnName(m.target)}))})}const a=[];if(e.hierarchies)for(const[,c]of Object.entries(e.hierarchies))a.push({name:c.name,title:c.title||c.name,cubeName:e.name,levels:c.levels.map(l=>l.includes(".")?l:`${e.name}.${l}`)});return{name:e.name,title:e.title||e.name,description:e.description,exampleQuestions:e.exampleQuestions,measures:i,dimensions:s,segments:[],relationships:r.length>0?r:void 0,hierarchies:a.length>0?a:void 0,meta:e.meta}}async generateSQL(e,t,n){const i=this.getCube(e);if(!i)throw new Error(`Cube '${e}' not found`);const r=await this.createQueryExecutor().generateSQL(i,t,n);return this.formatSqlResult(r)}async generateMultiCubeSQL(e,t){const i=await this.createQueryExecutor().generateMultiCubeSQL(this.cubes,e,t);return this.formatSqlResult(i)}async dryRun(e,t){const i=await this.createQueryExecutor().dryRunSQL(this.cubes,e,t);return this.formatSqlResult(i)}async dryRunFunnel(e,t){return this.dryRun(e,t)}async dryRunFlow(e,t){return this.dryRun(e,t)}async dryRunRetention(e,t){return this.dryRun(e,t)}async explainQuery(e,t,n){return this.createQueryExecutor().explainQuery(this.cubes,e,t,n)}hasCube(e){return this.cubes.has(e)}removeCube(e){const t=this.cubes.delete(e);return t&&this.invalidateMetadataCache(),t}clearCubes(){this.cubes.clear(),this.invalidateMetadataCache()}invalidateMetadataCache(){this.metadataCache=void 0}getCubeNames(){return Array.from(this.cubes.keys())}validateQuery(e){return gt(this.cubes,e)}analyzeQuery(e,t){return this.createQueryExecutor(!0).analyzeQuery(this.cubes,e,t)}}function Un(d){const e=[];return d.timeDimensions?.some(t=>t.compareDateRange&&t.compareDateRange.length>=2)&&e.push("comparison"),d.funnel!==void 0&&d.funnel.steps?.length>=2&&e.push("funnel"),d.flow!==void 0&&d.flow.startingStep!==void 0&&d.flow.eventDimension!==void 0&&e.push("flow"),d.retention!==void 0&&d.retention.timeDimension!=null&&d.retention.bindingKey!=null&&e.push("retention"),e.length===0?[]:e}function gt(d,e){const t=[],n=Un(e);if(n.length>1)return t.push(`Query contains multiple query modes: ${n.join(", ")}`),{isValid:!1,errors:t};const i={funnel:()=>{const r=e.funnel.bindingKey;if(typeof r=="string"){const[a]=r.split(".");a&&!d.has(a)&&t.push(`Funnel binding key cube not found: ${a}`)}else if(Array.isArray(r))for(const a of r)d.has(a.cube)||t.push(`Funnel binding key cube not found: ${a.cube}`)},flow:()=>{const r=e.flow.bindingKey;if(typeof r=="string"){const[a]=r.split(".");a&&!d.has(a)&&t.push(`Flow binding key cube not found: ${a}`)}},retention:()=>{const r=e.retention,a=xn(r.timeDimension);a&&!d.has(a)&&t.push(`Retention cube not found: ${a}`);const u=r.bindingKey;if(typeof u=="string"){const[c]=u.split(".");c&&!d.has(c)&&t.push(`Retention binding key cube not found: ${c}`)}else if(Array.isArray(u))for(const c of u)d.has(c.cube)||t.push(`Retention binding key cube not found: ${c.cube}`);if(r.breakdownDimensions&&Array.isArray(r.breakdownDimensions))for(const c of r.breakdownDimensions){const[l]=c.split(".");l&&!d.has(l)&&t.push(`Retention breakdown cube not found: ${l}`)}}};if(n.length===1&&n[0]!=="comparison"){const r=n[0];return i[r](),{isValid:t.length===0,errors:t}}const s=new Set;if(e.measures)for(const r of e.measures){const[a,u]=r.split(".");if(!a||!u){t.push(`Invalid measure format: ${r}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in measure '${r}')`);continue}if(!c.measures[u]){const l=u===a?`. Did you mean one of: ${Object.keys(c.measures).slice(0,5).map(m=>`'${a}.${m}'`).join(", ")}?`:"";t.push(`Measure '${u}' not found on cube '${a}'${l}`)}}if(e.dimensions)for(const r of e.dimensions){const[a,u]=r.split(".");if(!a||!u){t.push(`Invalid dimension format: ${r}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in dimension '${r}')`);continue}if(!c.dimensions[u]){const l=u===a?`. Did you mean one of: ${Object.keys(c.dimensions).slice(0,5).map(m=>`'${a}.${m}'`).join(", ")}?`:"";t.push(`Dimension '${u}' not found on cube '${a}'${l}`)}}if(e.timeDimensions)for(const r of e.timeDimensions){const[a,u]=r.dimension.split(".");if(!a||!u){t.push(`Invalid timeDimension format: ${r.dimension}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in timeDimension '${r.dimension}')`);continue}c.dimensions[u]||t.push(`TimeDimension '${u}' not found on cube '${a}' (must be a dimension with time type)`)}if(e.filters)for(const r of e.filters)bt(r,d,t,s);return s.size===0&&t.push("Query must reference at least one cube through measures, dimensions, or filters"),{isValid:t.length===0,errors:t}}function bt(d,e,t,n){if("and"in d||"or"in d){const a=d.and||d.or||[];for(const u of a)bt(u,e,t,n);return}if(!("member"in d)){t.push("Filter must have a member field");return}const[i,s]=d.member.split(".");if(!i||!s){t.push(`Invalid filter member format: ${d.member}. Expected format: 'CubeName.fieldName'`);return}n.add(i);const r=e.get(i);if(!r){t.push(`Cube '${i}' not found (referenced in filter '${d.member}')`);return}if(!r.dimensions[s]&&!r.measures[s]){const a=s===i?`. Did you mean one of: ${[...Object.keys(r.dimensions),...Object.keys(r.measures)].slice(0,5).map(u=>`'${i}.${u}'`).join(", ")}?`:"";t.push(`Filter field '${s}' not found on cube '${i}' (must be a dimension or measure)${a}`)}}function xn(d){if(typeof d=="string"){const[e]=d.split(".");return e||null}return d.cube}const le=["2025-11-25","2025-06-18","2025-03-26"],yt="2025-11-25";function Qn(d){const t=Xn(d["mcp-protocol-version"])||yt;return{ok:le.includes(t),negotiated:le.includes(t)?t:null,supported:le}}function Kn(d){if(!d)return!1;const e=d.split(",").map(i=>i.trim().toLowerCase()),t=e.includes("text/event-stream"),n=e.includes("application/json");return t&&!n}const Wn="mcp-session-id";function Jn(d){if(!d)return!1;const e=d.split(",").map(i=>i.trim().toLowerCase().split(";")[0]),t=e.some(i=>i==="application/json"),n=e.some(i=>i==="text/event-stream");return t&&n}function zn(d,e={}){const{allowMissingOrigin:t=!0,allowedOrigins:n}=e;if(!d)return t?{valid:!0}:{valid:!1,reason:"Origin header is required"};if(!n||n.length===0)return{valid:!0};let i;try{i=new URL(d)}catch{return{valid:!1,reason:"Invalid Origin header format"}}return n.map(r=>{try{return new URL(r).origin}catch{return r}}).includes(i.origin)?{valid:!0}:{valid:!1,reason:"Origin not in allowed list"}}function qn(d,e,t){const n=[];return e&&n.push(`id: ${e}`),t&&t>0&&n.push(`retry: ${t}`),n.push("event: message"),n.push(`data: ${JSON.stringify(d)}`),n.push(""),n.join(`
|
|
200
|
+
`)}`)}getCube(e){return this.cubes.get(e)}getAllCubes(){return Array.from(this.cubes.values())}getAllCubesMap(){return this.cubes}async execute(e,t,n){return this.createQueryExecutor(!0).execute(this.cubes,e,t,n)}async executeMultiCubeQuery(e,t,n){return this.execute(e,t,n)}async executeQuery(e,t,n){if(!this.cubes.get(e))throw new Error(`Cube '${e}' not found`);return this.execute(t,n)}getMetadata(){return this.metadataCache?this.metadataCache:(this.metadataCache=Array.from(this.cubes.values()).map(e=>this.generateCubeMetadata(e)),this.metadataCache)}getColumnName(e){if(e&&e.name||e&&e.columnType&&e.name)return e.name;if(typeof e=="string")return e;if(e&&typeof e=="object"){if(e._.name)return e._.name;if(e.name)return e.name;if(e.columnName)return e.columnName}return"unknown_column"}static DEFAULT_TIME_GRANULARITIES=["year","quarter","month","week","day","hour"];generateCubeMetadata(e){const t=Object.keys(e.measures),n=Object.keys(e.dimensions),i=new Array(t.length),s=new Array(n.length);for(let c=0;c<t.length;c++){const l=t[c],m=e.measures[l];let p;m.drillMembers&&m.drillMembers.length>0&&(p=m.drillMembers.map(f=>f.includes(".")?f:`${e.name}.${f}`)),i[c]={name:`${e.name}.${l}`,title:m.title||l,shortTitle:m.title||l,type:m.type,format:void 0,description:m.description,synonyms:m.synonyms,drillMembers:p}}for(let c=0;c<n.length;c++){const l=n[c],m=e.dimensions[l];let p;m.type==="time"&&(p=m.granularities||Ie.DEFAULT_TIME_GRANULARITIES),s[c]={name:`${e.name}.${l}`,title:m.title||l,shortTitle:m.title||l,type:m.type,format:void 0,description:m.description,synonyms:m.synonyms,granularities:p}}const r=[];if(e.joins)for(const[,c]of Object.entries(e.joins)){const l=typeof c.targetCube=="function"?c.targetCube():c.targetCube;r.push({targetCube:l.name,relationship:c.relationship,joinFields:c.on.map(m=>({sourceField:this.getColumnName(m.source),targetField:this.getColumnName(m.target)}))})}const a=[];if(e.hierarchies)for(const[,c]of Object.entries(e.hierarchies))a.push({name:c.name,title:c.title||c.name,cubeName:e.name,levels:c.levels.map(l=>l.includes(".")?l:`${e.name}.${l}`)});return{name:e.name,title:e.title||e.name,description:e.description,exampleQuestions:e.exampleQuestions,measures:i,dimensions:s,segments:[],relationships:r.length>0?r:void 0,hierarchies:a.length>0?a:void 0,meta:e.meta}}async generateSQL(e,t,n){const i=this.getCube(e);if(!i)throw new Error(`Cube '${e}' not found`);const r=await this.createQueryExecutor().generateSQL(i,t,n);return this.formatSqlResult(r)}async generateMultiCubeSQL(e,t){const i=await this.createQueryExecutor().generateMultiCubeSQL(this.cubes,e,t);return this.formatSqlResult(i)}async dryRun(e,t){const i=await this.createQueryExecutor().dryRunSQL(this.cubes,e,t);return this.formatSqlResult(i)}async dryRunFunnel(e,t){return this.dryRun(e,t)}async dryRunFlow(e,t){return this.dryRun(e,t)}async dryRunRetention(e,t){return this.dryRun(e,t)}async explainQuery(e,t,n){return this.createQueryExecutor().explainQuery(this.cubes,e,t,n)}hasCube(e){return this.cubes.has(e)}unregisterCube(e){return this.removeCube(e)}removeCube(e){const t=this.cubes.delete(e);return t&&this.invalidateMetadataCache(),t}clearCubes(){this.cubes.clear(),this.invalidateMetadataCache()}invalidateMetadataCache(){this.metadataCache=void 0}getCubeNames(){return Array.from(this.cubes.keys())}validateQuery(e){return gt(this.cubes,e)}analyzeQuery(e,t){return this.createQueryExecutor(!0).analyzeQuery(this.cubes,e,t)}}function Un(d){const e=[];return d.timeDimensions?.some(t=>t.compareDateRange&&t.compareDateRange.length>=2)&&e.push("comparison"),d.funnel!==void 0&&d.funnel.steps?.length>=2&&e.push("funnel"),d.flow!==void 0&&d.flow.startingStep!==void 0&&d.flow.eventDimension!==void 0&&e.push("flow"),d.retention!==void 0&&d.retention.timeDimension!=null&&d.retention.bindingKey!=null&&e.push("retention"),e.length===0?[]:e}function gt(d,e){const t=[],n=Un(e);if(n.length>1)return t.push(`Query contains multiple query modes: ${n.join(", ")}`),{isValid:!1,errors:t};const i={funnel:()=>{const r=e.funnel.bindingKey;if(typeof r=="string"){const[a]=r.split(".");a&&!d.has(a)&&t.push(`Funnel binding key cube not found: ${a}`)}else if(Array.isArray(r))for(const a of r)d.has(a.cube)||t.push(`Funnel binding key cube not found: ${a.cube}`)},flow:()=>{const r=e.flow.bindingKey;if(typeof r=="string"){const[a]=r.split(".");a&&!d.has(a)&&t.push(`Flow binding key cube not found: ${a}`)}},retention:()=>{const r=e.retention,a=xn(r.timeDimension);a&&!d.has(a)&&t.push(`Retention cube not found: ${a}`);const u=r.bindingKey;if(typeof u=="string"){const[c]=u.split(".");c&&!d.has(c)&&t.push(`Retention binding key cube not found: ${c}`)}else if(Array.isArray(u))for(const c of u)d.has(c.cube)||t.push(`Retention binding key cube not found: ${c.cube}`);if(r.breakdownDimensions&&Array.isArray(r.breakdownDimensions))for(const c of r.breakdownDimensions){const[l]=c.split(".");l&&!d.has(l)&&t.push(`Retention breakdown cube not found: ${l}`)}}};if(n.length===1&&n[0]!=="comparison"){const r=n[0];return i[r](),{isValid:t.length===0,errors:t}}const s=new Set;if(e.measures)for(const r of e.measures){const[a,u]=r.split(".");if(!a||!u){t.push(`Invalid measure format: ${r}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in measure '${r}')`);continue}if(!c.measures[u]){const l=u===a?`. Did you mean one of: ${Object.keys(c.measures).slice(0,5).map(m=>`'${a}.${m}'`).join(", ")}?`:"";t.push(`Measure '${u}' not found on cube '${a}'${l}`)}}if(e.dimensions)for(const r of e.dimensions){const[a,u]=r.split(".");if(!a||!u){t.push(`Invalid dimension format: ${r}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in dimension '${r}')`);continue}if(!c.dimensions[u]){const l=u===a?`. Did you mean one of: ${Object.keys(c.dimensions).slice(0,5).map(m=>`'${a}.${m}'`).join(", ")}?`:"";t.push(`Dimension '${u}' not found on cube '${a}'${l}`)}}if(e.timeDimensions)for(const r of e.timeDimensions){const[a,u]=r.dimension.split(".");if(!a||!u){t.push(`Invalid timeDimension format: ${r.dimension}. Expected format: 'CubeName.fieldName'`);continue}s.add(a);const c=d.get(a);if(!c){t.push(`Cube '${a}' not found (referenced in timeDimension '${r.dimension}')`);continue}c.dimensions[u]||t.push(`TimeDimension '${u}' not found on cube '${a}' (must be a dimension with time type)`)}if(e.filters)for(const r of e.filters)bt(r,d,t,s);return s.size===0&&t.push("Query must reference at least one cube through measures, dimensions, or filters"),{isValid:t.length===0,errors:t}}function bt(d,e,t,n){if("and"in d||"or"in d){const a=d.and||d.or||[];for(const u of a)bt(u,e,t,n);return}if(!("member"in d)){t.push("Filter must have a member field");return}const[i,s]=d.member.split(".");if(!i||!s){t.push(`Invalid filter member format: ${d.member}. Expected format: 'CubeName.fieldName'`);return}n.add(i);const r=e.get(i);if(!r){t.push(`Cube '${i}' not found (referenced in filter '${d.member}')`);return}if(!r.dimensions[s]&&!r.measures[s]){const a=s===i?`. Did you mean one of: ${[...Object.keys(r.dimensions),...Object.keys(r.measures)].slice(0,5).map(u=>`'${i}.${u}'`).join(", ")}?`:"";t.push(`Filter field '${s}' not found on cube '${i}' (must be a dimension or measure)${a}`)}}function xn(d){if(typeof d=="string"){const[e]=d.split(".");return e||null}return d.cube}const le=["2025-11-25","2025-06-18","2025-03-26"],yt="2025-11-25";function Qn(d){const t=Xn(d["mcp-protocol-version"])||yt;return{ok:le.includes(t),negotiated:le.includes(t)?t:null,supported:le}}function Kn(d){if(!d)return!1;const e=d.split(",").map(i=>i.trim().toLowerCase()),t=e.includes("text/event-stream"),n=e.includes("application/json");return t&&!n}const Wn="mcp-session-id";function Jn(d){if(!d)return!1;const e=d.split(",").map(i=>i.trim().toLowerCase().split(";")[0]),t=e.some(i=>i==="application/json"),n=e.some(i=>i==="text/event-stream");return t&&n}function zn(d,e={}){const{allowMissingOrigin:t=!0,allowedOrigins:n}=e;if(!d)return t?{valid:!0}:{valid:!1,reason:"Origin header is required"};if(!n||n.length===0)return{valid:!0};let i;try{i=new URL(d)}catch{return{valid:!1,reason:"Invalid Origin header format"}}return n.map(r=>{try{return new URL(r).origin}catch{return r}}).includes(i.origin)?{valid:!0}:{valid:!1,reason:"Origin not in allowed list"}}function qn(d,e,t){const n=[];return e&&n.push(`id: ${e}`),t&&t>0&&n.push(`retry: ${t}`),n.push("event: message"),n.push(`data: ${JSON.stringify(d)}`),n.push(""),n.join(`
|
|
201
201
|
`)}function Vn(d,e,t,n){return{jsonrpc:"2.0",id:d??null,error:{code:e,message:t,...n!==void 0?{data:n}:{}}}}function Gn(d,e){return{jsonrpc:"2.0",id:d??null,result:e}}function Hn(d){if(!d||typeof d!="object")return null;const e=d;return e.jsonrpc!=="2.0"||typeof e.method!="string"?null:{jsonrpc:"2.0",method:e.method,id:e.id,params:e.params}}async function Yn(d,e,t){const{semanticLayer:n,extractSecurityContext:i,rawRequest:s,rawResponse:r}=t,a=t.prompts??wt,u=t.resources??$t;switch(d){case"initialize":{const c=e?.protocolVersion;let l;return c&&le.includes(c)?l=c:l=yt,{protocolVersion:l,capabilities:{tools:{listChanged:!1},resources:{listChanged:!1},prompts:{listChanged:!1},sampling:{}},sessionId:Ct(),serverInfo:{name:"drizzle-cube",version:typeof process<"u"?process.env?.npm_package_version||"dev":"worker"}}}case"list_tools":case"tools/list":return{tools:ei(),nextCursor:""};case"call_tool":case"tools/call":return ti(e,t);case"resources/list":return{resources:u.map(({uri:c,name:l,description:m,mimeType:p})=>({uri:c,name:l,description:m,mimeType:p})),nextCursor:""};case"resources/templates/list":return{resourceTemplates:[],nextCursor:""};case"resources/read":{const c=e?.uri,l=u.find(m=>m.uri===c)||u[0];if(!l)throw G(-32602,"resource not found");return{contents:[{uri:l.uri,mimeType:l.mimeType,text:l.text}]}}case"prompts/list":return{prompts:a.map(({name:c,description:l})=>({name:c,description:l})),nextCursor:""};case"ping":return{};case"notifications/initialized":return{};case"prompts/get":{const c=e?.name,l=a.find(m=>m.name===c)||a[0];if(!l)throw G(-32602,"prompt not found");return{name:l.name,description:l.description,messages:l.messages}}case"discover":return Y.handleDiscover(n,e||{});case"validate":{const c=e||{};if(!c.query)throw G(-32602,"query is required");return Y.handleValidate(n,c)}case"load":{const c=e||{};if(!c.query)throw G(-32602,"query is required");const l=await i(s,r);return Y.handleLoad(n,l,c)}default:throw G(-32601,`Unknown MCP method: ${d}`)}}function G(d,e,t){const n=new Error(e);return n.code=d,n}function Xn(d){return d?Array.isArray(d)?d[0]||null:d:null}function Zn(d){return d.id===void 0||d.id===null}function Ct(){return`evt-${Y.generateRequestId()}`}function ei(){return[{name:"discover",description:`Find relevant cubes based on topic or intent. Call this FIRST to understand available data.
|
|
202
202
|
|
|
203
203
|
Returns cubes with:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("next/server"),f=require("../mcp-transport-8u9G5oNa.cjs"),i=require("../utils.cjs");function v(n){const{cubes:o,drizzle:s,schema:c,engineType:d,cache:a}=n;if(!o||o.length===0)throw new Error("At least one cube must be provided in the cubes array");const t=new f.SemanticLayerCompiler({drizzle:s,schema:c,engineType:d,cache:a});return o.forEach(e=>{t.registerCube(e)}),t}function h(n,o){const s=n.headers.get("origin"),c={};return o.origin&&(typeof o.origin=="string"?c["Access-Control-Allow-Origin"]=o.origin:Array.isArray(o.origin)?s&&o.origin.includes(s)&&(c["Access-Control-Allow-Origin"]=s):typeof o.origin=="function"&&s&&o.origin(s)&&(c["Access-Control-Allow-Origin"]=s)),o.methods&&(c["Access-Control-Allow-Methods"]=o.methods.join(", ")),o.allowedHeaders&&(c["Access-Control-Allow-Headers"]=o.allowedHeaders.join(", ")),o.credentials&&(c["Access-Control-Allow-Credentials"]="true"),c}function J(n){return async function(s){const c=h(s,n);return new Response(null,{status:200,headers:c})}}function T(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const N=await a.json();e=N.query||N}else if(a.method==="GET"){const N=a.nextUrl.searchParams.get("query");if(!N)return r.NextResponse.json(i.formatErrorResponse("Query parameter is required",400),{status:400});try{e=JSON.parse(N)}catch{return r.NextResponse.json(i.formatErrorResponse("Invalid JSON in query parameter",400),{status:400})}}else return r.NextResponse.json(i.formatErrorResponse("Method not allowed",405),{status:405});const l=await o(a,t),y=c.validateQuery(e);if(!y.isValid)return r.NextResponse.json(i.formatErrorResponse(`Query validation failed: ${y.errors.join(", ")}`,400),{status:400});const u=a.headers.get("x-cache-control")==="no-cache",p=await c.executeMultiCubeQuery(e,l,{skipCache:u}),w=i.formatCubeResponse(e,p,c);return r.NextResponse.json(w,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js load handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Query execution failed",500),{status:500})}}}function A(n){const{cors:o}=n,s=v(n);return async function(d,a){try{const t=s.getMetadata(),e=i.formatMetaResponse(t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js meta handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Failed to fetch metadata",500),{status:500})}}}function M(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const C=await a.json();e=C.query||C}else if(a.method==="GET"){const C=a.nextUrl.searchParams.get("query");if(!C)return r.NextResponse.json(i.formatErrorResponse("Query parameter is required",400),{status:400});try{e=JSON.parse(C)}catch{return r.NextResponse.json(i.formatErrorResponse("Invalid JSON in query parameter",400),{status:400})}}else return r.NextResponse.json(i.formatErrorResponse("Method not allowed",405),{status:405});const l=await o(a,t),y=c.validateQuery(e);if(!y.isValid)return r.NextResponse.json(i.formatErrorResponse(`Query validation failed: ${y.errors.join(", ")}`,400),{status:400});const u=e.measures?.[0]||e.dimensions?.[0];if(!u)return r.NextResponse.json(i.formatErrorResponse("No measures or dimensions specified",400),{status:400});const p=u.split(".")[0],w=await c.generateSQL(p,e,l),N=i.formatSqlResponse(e,w);return r.NextResponse.json(N,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js SQL handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"SQL generation failed",500),{status:500})}}}function L(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const u=await a.json();e=u.query||u}else if(a.method==="GET"){const u=a.nextUrl.searchParams.get("query");if(!u)return r.NextResponse.json({error:"Query parameter is required",valid:!1},{status:400});try{e=JSON.parse(u)}catch{return r.NextResponse.json({error:"Invalid JSON in query parameter",valid:!1},{status:400})}}else return r.NextResponse.json({error:"Method not allowed",valid:!1},{status:405});const l=await o(a,t),y=await i.handleDryRun(e,l,c);return r.NextResponse.json(y,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js dry-run handler error:",e),r.NextResponse.json({error:e instanceof Error?e.message:"Dry-run validation failed",valid:!1},{status:400})}}}function D(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=await a.json(),{queries:l}=e;if(!l||!Array.isArray(l))return r.NextResponse.json(i.formatErrorResponse('Request body must contain a "queries" array',400),{status:400});if(l.length===0)return r.NextResponse.json(i.formatErrorResponse("Queries array cannot be empty",400),{status:400});const y=await o(a,t),u=a.headers.get("x-cache-control")==="no-cache",p=await i.handleBatchRequest(l,y,c,{skipCache:u});return r.NextResponse.json(p,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js batch handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Batch execution failed",500),{status:500})}}}function V(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json({error:"Method not allowed"},{status:405});const e=await a.json(),l=e.query||e,y=e.options||{},u=await o(a,t),p=c.validateQuery(l);if(!p.isValid)return r.NextResponse.json({error:`Query validation failed: ${p.errors.join(", ")}`},{status:400});const w=await c.explainQuery(l,u,y);return r.NextResponse.json(w,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js explain handler error:",e),r.NextResponse.json({error:e instanceof Error?e.message:"Explain query failed"},{status:500})}}}function k(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json(),e=await i.handleDiscover(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js discover handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Discovery failed",500),{status:500})}}}function K(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json();if(!t.naturalLanguage)return r.NextResponse.json(i.formatErrorResponse("naturalLanguage field is required",400),{status:400});const e=await i.handleSuggest(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js suggest handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Query suggestion failed",500),{status:500})}}}function z(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json();if(!t.query)return r.NextResponse.json(i.formatErrorResponse("query field is required",400),{status:400});const e=await i.handleValidate(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js validate handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Query validation failed",500),{status:500})}}}function $(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=await a.json();if(!e.query)return r.NextResponse.json(i.formatErrorResponse("query field is required",400),{status:400});const l=await o(a,t),y=await i.handleLoad(c,l,e);return r.NextResponse.json(y,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js MCP load handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Query execution failed",500),{status:500})}}}function _(n){const{extractSecurityContext:o,cors:s,mcp:c={enabled:!0}}=n,d=v(n);return async function(t){if(t.method==="DELETE")return r.NextResponse.json({error:"Session termination not supported"},{status:405});if(t.method==="GET"){const m=new TextEncoder,S=f.primeEventId(),j=new ReadableStream({start(R){R.enqueue(m.encode(f.serializeSseEvent({jsonrpc:"2.0",method:"mcp/ready",params:{protocol:"streamable-http"}},S,15e3)))}}),H=new Headers({"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});if(s){const R=h(t,s);Object.entries(R).forEach(([b,O])=>H.set(b,O))}return new r.NextResponse(j,{status:200,headers:H})}if(t.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=f.validateOriginHeader(t.headers.get("origin"),c.allowedOrigins?{allowedOrigins:c.allowedOrigins}:{});if(!e.valid)return r.NextResponse.json(f.buildJsonRpcError(null,-32600,e.reason),{status:403});const l=t.headers.get("accept");if(!f.validateAcceptHeader(l))return r.NextResponse.json(f.buildJsonRpcError(null,-32600,"Accept header must include both application/json and text/event-stream"),{status:400});const y=f.negotiateProtocol(Object.fromEntries(t.headers.entries()));if(!y.ok)return r.NextResponse.json({error:"Unsupported MCP protocol version",supported:y.supported},{status:426});let u;try{u=await t.json()}catch{u=null}const p=f.parseJsonRpc(u);if(!p)return r.NextResponse.json(f.buildJsonRpcError(null,-32600,"Invalid JSON-RPC 2.0 request"),{status:400});const w=f.wantsEventStream(l),N=p.method==="initialize",C=(m,S=200,j={})=>r.NextResponse.json(m,{status:S,headers:{...s?h(t,s):{},...j}});try{const m=await f.dispatchMcpMethod(p.method,p.params,{semanticLayer:d,extractSecurityContext:R=>o(R),rawRequest:t,rawResponse:null});if(f.isNotification(p))return new r.NextResponse(null,{status:202});const S=N&&m&&typeof m=="object"&&"sessionId"in m?m.sessionId:void 0,j={};S&&(j[f.MCP_SESSION_ID_HEADER]=S);const H=f.buildJsonRpcResult(p.id??null,m);if(w){const R=new TextEncoder,b=f.primeEventId(),O=new ReadableStream({start(x){x.enqueue(R.encode(`id: ${b}
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("next/server"),f=require("../mcp-transport-DCiSGtp1.cjs"),i=require("../utils.cjs");function v(n){const{cubes:o,drizzle:s,schema:c,engineType:d,cache:a}=n;if(!o||o.length===0)throw new Error("At least one cube must be provided in the cubes array");const t=new f.SemanticLayerCompiler({drizzle:s,schema:c,engineType:d,cache:a});return o.forEach(e=>{t.registerCube(e)}),t}function h(n,o){const s=n.headers.get("origin"),c={};return o.origin&&(typeof o.origin=="string"?c["Access-Control-Allow-Origin"]=o.origin:Array.isArray(o.origin)?s&&o.origin.includes(s)&&(c["Access-Control-Allow-Origin"]=s):typeof o.origin=="function"&&s&&o.origin(s)&&(c["Access-Control-Allow-Origin"]=s)),o.methods&&(c["Access-Control-Allow-Methods"]=o.methods.join(", ")),o.allowedHeaders&&(c["Access-Control-Allow-Headers"]=o.allowedHeaders.join(", ")),o.credentials&&(c["Access-Control-Allow-Credentials"]="true"),c}function J(n){return async function(s){const c=h(s,n);return new Response(null,{status:200,headers:c})}}function T(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const N=await a.json();e=N.query||N}else if(a.method==="GET"){const N=a.nextUrl.searchParams.get("query");if(!N)return r.NextResponse.json(i.formatErrorResponse("Query parameter is required",400),{status:400});try{e=JSON.parse(N)}catch{return r.NextResponse.json(i.formatErrorResponse("Invalid JSON in query parameter",400),{status:400})}}else return r.NextResponse.json(i.formatErrorResponse("Method not allowed",405),{status:405});const l=await o(a,t),y=c.validateQuery(e);if(!y.isValid)return r.NextResponse.json(i.formatErrorResponse(`Query validation failed: ${y.errors.join(", ")}`,400),{status:400});const u=a.headers.get("x-cache-control")==="no-cache",p=await c.executeMultiCubeQuery(e,l,{skipCache:u}),w=i.formatCubeResponse(e,p,c);return r.NextResponse.json(w,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js load handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Query execution failed",500),{status:500})}}}function A(n){const{cors:o}=n,s=v(n);return async function(d,a){try{const t=s.getMetadata(),e=i.formatMetaResponse(t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js meta handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Failed to fetch metadata",500),{status:500})}}}function M(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const C=await a.json();e=C.query||C}else if(a.method==="GET"){const C=a.nextUrl.searchParams.get("query");if(!C)return r.NextResponse.json(i.formatErrorResponse("Query parameter is required",400),{status:400});try{e=JSON.parse(C)}catch{return r.NextResponse.json(i.formatErrorResponse("Invalid JSON in query parameter",400),{status:400})}}else return r.NextResponse.json(i.formatErrorResponse("Method not allowed",405),{status:405});const l=await o(a,t),y=c.validateQuery(e);if(!y.isValid)return r.NextResponse.json(i.formatErrorResponse(`Query validation failed: ${y.errors.join(", ")}`,400),{status:400});const u=e.measures?.[0]||e.dimensions?.[0];if(!u)return r.NextResponse.json(i.formatErrorResponse("No measures or dimensions specified",400),{status:400});const p=u.split(".")[0],w=await c.generateSQL(p,e,l),N=i.formatSqlResponse(e,w);return r.NextResponse.json(N,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js SQL handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"SQL generation failed",500),{status:500})}}}function L(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{let e;if(a.method==="POST"){const u=await a.json();e=u.query||u}else if(a.method==="GET"){const u=a.nextUrl.searchParams.get("query");if(!u)return r.NextResponse.json({error:"Query parameter is required",valid:!1},{status:400});try{e=JSON.parse(u)}catch{return r.NextResponse.json({error:"Invalid JSON in query parameter",valid:!1},{status:400})}}else return r.NextResponse.json({error:"Method not allowed",valid:!1},{status:405});const l=await o(a,t),y=await i.handleDryRun(e,l,c);return r.NextResponse.json(y,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js dry-run handler error:",e),r.NextResponse.json({error:e instanceof Error?e.message:"Dry-run validation failed",valid:!1},{status:400})}}}function D(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=await a.json(),{queries:l}=e;if(!l||!Array.isArray(l))return r.NextResponse.json(i.formatErrorResponse('Request body must contain a "queries" array',400),{status:400});if(l.length===0)return r.NextResponse.json(i.formatErrorResponse("Queries array cannot be empty",400),{status:400});const y=await o(a,t),u=a.headers.get("x-cache-control")==="no-cache",p=await i.handleBatchRequest(l,y,c,{skipCache:u});return r.NextResponse.json(p,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js batch handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Batch execution failed",500),{status:500})}}}function V(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json({error:"Method not allowed"},{status:405});const e=await a.json(),l=e.query||e,y=e.options||{},u=await o(a,t),p=c.validateQuery(l);if(!p.isValid)return r.NextResponse.json({error:`Query validation failed: ${p.errors.join(", ")}`},{status:400});const w=await c.explainQuery(l,u,y);return r.NextResponse.json(w,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js explain handler error:",e),r.NextResponse.json({error:e instanceof Error?e.message:"Explain query failed"},{status:500})}}}function k(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json(),e=await i.handleDiscover(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js discover handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Discovery failed",500),{status:500})}}}function K(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json();if(!t.naturalLanguage)return r.NextResponse.json(i.formatErrorResponse("naturalLanguage field is required",400),{status:400});const e=await i.handleSuggest(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js suggest handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Query suggestion failed",500),{status:500})}}}function z(n){const{cors:o}=n,s=v(n);return async function(d,a){try{if(d.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const t=await d.json();if(!t.query)return r.NextResponse.json(i.formatErrorResponse("query field is required",400),{status:400});const e=await i.handleValidate(s,t);return r.NextResponse.json(e,{headers:o?h(d,o):{}})}catch(t){return process.env.NODE_ENV!=="test"&&console.error("Next.js validate handler error:",t),r.NextResponse.json(i.formatErrorResponse(t instanceof Error?t.message:"Query validation failed",500),{status:500})}}}function $(n){const{extractSecurityContext:o,cors:s}=n,c=v(n);return async function(a,t){try{if(a.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=await a.json();if(!e.query)return r.NextResponse.json(i.formatErrorResponse("query field is required",400),{status:400});const l=await o(a,t),y=await i.handleLoad(c,l,e);return r.NextResponse.json(y,{headers:s?h(a,s):{}})}catch(e){return process.env.NODE_ENV!=="test"&&console.error("Next.js MCP load handler error:",e),r.NextResponse.json(i.formatErrorResponse(e instanceof Error?e.message:"Query execution failed",500),{status:500})}}}function _(n){const{extractSecurityContext:o,cors:s,mcp:c={enabled:!0}}=n,d=v(n);return async function(t){if(t.method==="DELETE")return r.NextResponse.json({error:"Session termination not supported"},{status:405});if(t.method==="GET"){const m=new TextEncoder,S=f.primeEventId(),j=new ReadableStream({start(R){R.enqueue(m.encode(f.serializeSseEvent({jsonrpc:"2.0",method:"mcp/ready",params:{protocol:"streamable-http"}},S,15e3)))}}),H=new Headers({"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});if(s){const R=h(t,s);Object.entries(R).forEach(([b,O])=>H.set(b,O))}return new r.NextResponse(j,{status:200,headers:H})}if(t.method!=="POST")return r.NextResponse.json(i.formatErrorResponse("Method not allowed - use POST",405),{status:405});const e=f.validateOriginHeader(t.headers.get("origin"),c.allowedOrigins?{allowedOrigins:c.allowedOrigins}:{});if(!e.valid)return r.NextResponse.json(f.buildJsonRpcError(null,-32600,e.reason),{status:403});const l=t.headers.get("accept");if(!f.validateAcceptHeader(l))return r.NextResponse.json(f.buildJsonRpcError(null,-32600,"Accept header must include both application/json and text/event-stream"),{status:400});const y=f.negotiateProtocol(Object.fromEntries(t.headers.entries()));if(!y.ok)return r.NextResponse.json({error:"Unsupported MCP protocol version",supported:y.supported},{status:426});let u;try{u=await t.json()}catch{u=null}const p=f.parseJsonRpc(u);if(!p)return r.NextResponse.json(f.buildJsonRpcError(null,-32600,"Invalid JSON-RPC 2.0 request"),{status:400});const w=f.wantsEventStream(l),N=p.method==="initialize",C=(m,S=200,j={})=>r.NextResponse.json(m,{status:S,headers:{...s?h(t,s):{},...j}});try{const m=await f.dispatchMcpMethod(p.method,p.params,{semanticLayer:d,extractSecurityContext:R=>o(R),rawRequest:t,rawResponse:null});if(f.isNotification(p))return new r.NextResponse(null,{status:202});const S=N&&m&&typeof m=="object"&&"sessionId"in m?m.sessionId:void 0,j={};S&&(j[f.MCP_SESSION_ID_HEADER]=S);const H=f.buildJsonRpcResult(p.id??null,m);if(w){const R=new TextEncoder,b=f.primeEventId(),O=new ReadableStream({start(x){x.enqueue(R.encode(`id: ${b}
|
|
2
2
|
|
|
3
3
|
`)),x.enqueue(R.encode(f.serializeSseEvent(H,b))),x.close()}}),E=new Headers({"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive",...j});if(s){const x=h(t,s);Object.entries(x).forEach(([g,P])=>E.set(g,P))}return new r.NextResponse(O,{status:200,headers:E})}return C(H,200,j)}catch(m){if(f.isNotification(p))return process.env.NODE_ENV!=="test"&&console.error("Next.js MCP notification processing error:",m),new r.NextResponse(null,{status:202});process.env.NODE_ENV!=="test"&&console.error("Next.js MCP RPC handler error:",m);const S=m?.code??-32603,j=m?.data,H=m.message||"MCP request failed",R=f.buildJsonRpcError(p.id??null,S,H,j);if(w){const b=new TextEncoder,O=f.primeEventId(),E=new ReadableStream({start(g){g.enqueue(b.encode(`id: ${O}
|
|
4
4
|
|
|
5
|
-
`)),g.enqueue(b.encode(f.serializeSseEvent(R,O))),g.close()}}),x=new Headers({"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});if(s){const g=h(t,s);Object.entries(g).forEach(([P,I])=>x.set(P,I))}return new r.NextResponse(E,{status:200,headers:x})}return C(R,200)}}}function Q(n){const{extractSecurityContext:o,cors:s,agent:c}=n;if(!c)throw new Error("agent config is required for createAgentChatHandler");const d=v(n);return async function(t,e){try{if(t.method!=="POST")return r.NextResponse.json({error:"Method not allowed - use POST"},{status:405});const{handleAgentChat:l}=await Promise.resolve().then(()=>require("../handler-
|
|
5
|
+
`)),g.enqueue(b.encode(f.serializeSseEvent(R,O))),g.close()}}),x=new Headers({"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"});if(s){const g=h(t,s);Object.entries(g).forEach(([P,I])=>x.set(P,I))}return new r.NextResponse(E,{status:200,headers:x})}return C(R,200)}}}function Q(n){const{extractSecurityContext:o,cors:s,agent:c}=n;if(!c)throw new Error("agent config is required for createAgentChatHandler");const d=v(n);return async function(t,e){try{if(t.method!=="POST")return r.NextResponse.json({error:"Method not allowed - use POST"},{status:405});const{handleAgentChat:l}=await Promise.resolve().then(()=>require("../handler-C3hT7g2W.cjs")),y=await t.json(),{message:u,sessionId:p,history:w}=y;if(!u||typeof u!="string")return r.NextResponse.json({error:"message is required and must be a string"},{status:400});let N=(c.apiKey||"").trim();if(c.allowClientApiKey){const E=t.headers.get("x-agent-api-key");E&&(N=E.trim())}if(!N)return r.NextResponse.json({error:"No API key configured. Set agent.apiKey in server config or send X-Agent-Api-Key header."},{status:401});const C=c.allowClientApiKey&&t.headers.get("x-agent-provider")||void 0,m=c.allowClientApiKey&&t.headers.get("x-agent-model")||void 0,S=c.allowClientApiKey&&t.headers.get("x-agent-provider-endpoint")||void 0,j=await o(t,e),H=c.buildSystemContext?.(j),R=new TextEncoder,b=new ReadableStream({async start(E){try{const x=l({message:u,sessionId:p,history:w,semanticLayer:d,securityContext:j,agentConfig:c,apiKey:N,systemContext:H,providerOverride:C,modelOverride:m,baseURLOverride:S});for await(const g of x){const P=`data: ${JSON.stringify(g)}
|
|
6
6
|
|
|
7
7
|
`;E.enqueue(R.encode(P))}}catch(x){const g={type:"error",data:{message:x instanceof Error?x.message:"Stream failed"}};E.enqueue(R.encode(`data: ${JSON.stringify(g)}
|
|
8
8
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse as r } from "next/server";
|
|
2
|
-
import { e as T, s as A, v as D, b as R, a as _, n as Q, p as V, w as I, d as J, i as M, M as k, c as K, S as $ } from "../mcp-transport-
|
|
2
|
+
import { e as T, s as A, v as D, b as R, a as _, n as Q, p as V, w as I, d as J, i as M, M as k, c as K, S as $ } from "../mcp-transport-B6ZudTSk.js";
|
|
3
3
|
import { formatErrorResponse as i, formatCubeResponse as z, formatMetaResponse as U, formatSqlResponse as G, handleDryRun as B, handleBatchRequest as F, handleDiscover as X, handleSuggest as q, handleValidate as W, handleLoad as Y } from "../utils.js";
|
|
4
4
|
function E(n) {
|
|
5
5
|
const { cubes: a, drizzle: s, schema: c, engineType: d, cache: o } = n;
|
|
@@ -519,7 +519,7 @@ function oe(n) {
|
|
|
519
519
|
{ error: "Method not allowed - use POST" },
|
|
520
520
|
{ status: 405 }
|
|
521
521
|
);
|
|
522
|
-
const { handleAgentChat: l } = await import("../handler-
|
|
522
|
+
const { handleAgentChat: l } = await import("../handler-t7Qd1IYi.js"), y = await t.json(), { message: u, sessionId: f, history: C } = y;
|
|
523
523
|
if (!u || typeof u != "string")
|
|
524
524
|
return r.json(
|
|
525
525
|
{ error: "message is required and must be a string" },
|
package/dist/client/charts.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { L as D, g as L, c as P, b as T, i as O, p as S, a as N } from "./chunks/charts-loader-
|
|
1
|
+
import { L as D, g as L, c as P, b as T, i as O, p as S, a as N } from "./chunks/charts-loader-DbrwgvCK.js";
|
|
2
2
|
import { useState as f, useEffect as c } from "react";
|
|
3
3
|
import { f as B } from "./chunks/index-CApFCBF9.js";
|
|
4
|
-
import { c as M, C as E, N as H, P as I } from "./chunks/charts-core-
|
|
5
|
-
import { B as _ } from "./chunks/chart-bar-
|
|
6
|
-
import { L as j } from "./chunks/chart-line-
|
|
7
|
-
import { default as U } from "./chunks/chart-area-
|
|
8
|
-
import { default as Y } from "./chunks/chart-pie-
|
|
9
|
-
import { default as $ } from "./chunks/chart-scatter-
|
|
10
|
-
import { default as J } from "./chunks/chart-radar-
|
|
11
|
-
import { default as Q } from "./chunks/chart-radial-bar-
|
|
12
|
-
import { default as ee } from "./chunks/chart-tree-map-
|
|
13
|
-
import { default as ae } from "./chunks/chart-funnel-
|
|
14
|
-
import { D as oe } from "./chunks/chart-data-table-
|
|
4
|
+
import { c as M, C as E, N as H, P as I } from "./chunks/charts-core-B4Rbfdcn.js";
|
|
5
|
+
import { B as _ } from "./chunks/chart-bar-D3vtCNQG.js";
|
|
6
|
+
import { L as j } from "./chunks/chart-line-DhM-Hvu0.js";
|
|
7
|
+
import { default as U } from "./chunks/chart-area-TawAd2k9.js";
|
|
8
|
+
import { default as Y } from "./chunks/chart-pie-B86KRcHI.js";
|
|
9
|
+
import { default as $ } from "./chunks/chart-scatter-CAkbBDkK.js";
|
|
10
|
+
import { default as J } from "./chunks/chart-radar-BhDBmJRh.js";
|
|
11
|
+
import { default as Q } from "./chunks/chart-radial-bar-Brugya8X.js";
|
|
12
|
+
import { default as ee } from "./chunks/chart-tree-map-CrDJAvZU.js";
|
|
13
|
+
import { default as ae } from "./chunks/chart-funnel-CE9x0Io9.js";
|
|
14
|
+
import { D as oe } from "./chunks/chart-data-table-DJZPkArt.js";
|
|
15
15
|
const s = {
|
|
16
16
|
dropZones: [
|
|
17
17
|
{
|
package/dist/client/chunks/{RetentionCombinedChart-CLq89aOJ.js → RetentionCombinedChart-BK3NPsHP.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as e, jsxs as l } from "react/jsx-runtime";
|
|
2
2
|
import E, { useState as M, useMemo as T } from "react";
|
|
3
3
|
import { ComposedChart as O, CartesianGrid as W, XAxis as F, YAxis as B, Legend as I, Line as X } from "recharts";
|
|
4
|
-
import { a as Y, C as q, b as J, c as $ } from "./charts-core-
|
|
4
|
+
import { a as Y, C as q, b as J, c as $ } from "./charts-core-B4Rbfdcn.js";
|
|
5
5
|
import { i as K } from "./retention-CzCo8262.js";
|
|
6
6
|
function Q(d) {
|
|
7
7
|
return `rgba(34, 197, 94, ${0.1 + Math.max(0, Math.min(1, d)) * 0.7})`;
|
|
@@ -253,4 +253,4 @@ const ae = E.memo(function({
|
|
|
253
253
|
export {
|
|
254
254
|
ae as default
|
|
255
255
|
};
|
|
256
|
-
//# sourceMappingURL=RetentionCombinedChart-
|
|
256
|
+
//# sourceMappingURL=RetentionCombinedChart-BK3NPsHP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RetentionCombinedChart-CLq89aOJ.js","sources":["../../../src/client/components/charts/RetentionCombinedChart.tsx"],"sourcesContent":["/**\n * RetentionCombinedChart Component\n *\n * Combined visualization for retention analysis data.\n * Supports multiple display modes: heatmap, line chart, or combined view.\n *\n * Features:\n * - X-axis: Period numbers (P0, P1, P2...)\n * - Y-axis: Retention % (0-100%)\n * - Lines: One per breakdown value (or single if no breakdown)\n * - Display modes: 'heatmap' | 'line' | 'combined'\n * - Heatmap shows color-coded retention matrix\n * - Line chart shows retention curves over periods\n */\n\nimport React, { useMemo, useState } from 'react'\nimport {\n ComposedChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Legend,\n} from 'recharts'\nimport ChartContainer from './ChartContainer'\nimport ChartTooltip from './ChartTooltip'\nimport { CHART_COLORS, CHART_MARGINS } from '../../utils/chartConstants'\nimport type { ChartProps } from '../../types'\nimport type { RetentionChartData, RetentionResultRow, RetentionGranularity } from '../../types/retention'\nimport { isRetentionData } from '../../types/retention'\n\n/**\n * Retention display mode\n * - 'heatmap': Show retention as color-coded bars\n * - 'line': Show retention as line curves\n * - 'combined': Show both heatmap background and line overlay\n */\nexport type RetentionDisplayMode = 'heatmap' | 'line' | 'combined'\n\n/**\n * Get color with opacity based on retention rate for heatmap cells\n * Uses a green gradient: higher retention = more saturated green\n */\nfunction getRetentionColor(rate: number): string {\n const clampedRate = Math.max(0, Math.min(1, rate))\n const alpha = 0.1 + clampedRate * 0.7\n return `rgba(34, 197, 94, ${alpha})`\n}\n\n/**\n * Format percentage for display\n */\nfunction formatPercentage(rate: number): string {\n return `${Math.round(rate * 100)}%`\n}\n\n/**\n * Format period label based on granularity\n * Period 0 shows \"< 1 Day\" / \"< 1 Week\" etc. to indicate the initial cohort\n * e.g., Period 0 with 'week' granularity → \"< 1 Week\", Period 1 → \"Week 1\"\n */\nfunction formatPeriodLabel(period: number, granularity?: RetentionGranularity): string {\n const prefix = granularity === 'day' ? 'Day'\n : granularity === 'week' ? 'Week'\n : granularity === 'month' ? 'Month'\n : 'P' // Fallback to P0, P1, etc.\n\n // Period 0 is special - shows \"< 1 Day\" / \"< 1 Week\" etc.\n if (period === 0) {\n return granularity ? `< 1 ${prefix}` : 'P0'\n }\n\n return granularity ? `${prefix} ${period}` : `P${period}`\n}\n\n/**\n * Get display label for the cohort total column\n * Shows \"Total\" regardless of binding key - it's the cohort size count\n */\nfunction getCohortLabel(_bindingKeyLabel?: string): string {\n return 'Total'\n}\n\n/**\n * Get default series name based on binding key\n * e.g., \"userId\" → \"userId Retention\", null → \"Retention\"\n */\nfunction getDefaultSeriesName(bindingKeyLabel?: string): string {\n if (!bindingKeyLabel) return 'Retention'\n return `${bindingKeyLabel} Retention`\n}\n\n/**\n * Transform retention data for chart display\n * Groups data by period with breakdown values as series\n */\nfunction transformRetentionData(\n rows: RetentionResultRow[],\n periods: number[],\n breakdownValues?: string[],\n granularity?: RetentionGranularity,\n bindingKeyLabel?: string\n): { chartData: any[]; seriesKeys: string[]; defaultSeriesName: string } {\n const defaultSeriesName = getDefaultSeriesName(bindingKeyLabel)\n\n // If no breakdown, single series\n if (!breakdownValues || breakdownValues.length === 0) {\n const chartData = periods.map((period) => {\n const row = rows.find((r) => r.period === period && !r.breakdownValue)\n return {\n period,\n periodLabel: formatPeriodLabel(period, granularity),\n [defaultSeriesName]: row ? row.retentionRate : null,\n cohortSize: row?.cohortSize ?? 0,\n retainedUsers: row?.retainedUsers ?? 0,\n }\n })\n return { chartData, seriesKeys: [defaultSeriesName], defaultSeriesName }\n }\n\n // With breakdown, create series per breakdown value\n const chartData = periods.map((period) => {\n const dataPoint: any = {\n period,\n periodLabel: formatPeriodLabel(period, granularity),\n }\n\n breakdownValues.forEach((bv) => {\n const row = rows.find((r) => r.period === period && r.breakdownValue === bv)\n dataPoint[bv] = row ? row.retentionRate : null\n dataPoint[`${bv}_cohortSize`] = row?.cohortSize ?? 0\n dataPoint[`${bv}_retainedUsers`] = row?.retainedUsers ?? 0\n })\n\n return dataPoint\n })\n\n return { chartData, seriesKeys: breakdownValues, defaultSeriesName }\n}\n\ninterface TooltipData {\n period: number\n breakdownValue?: string | null\n cohortSize: number\n retainedUsers: number\n retentionRate: number\n x: number\n y: number\n}\n\n/**\n * RetentionCombinedChart Component\n */\nconst RetentionCombinedChart = React.memo(function RetentionCombinedChart({\n data,\n height = '100%',\n displayConfig,\n colorPalette,\n}: ChartProps) {\n const [hoveredLegend, setHoveredLegend] = useState<string | null>(null)\n const [heatmapTooltip, setHeatmapTooltip] = useState<TooltipData | null>(null)\n\n // Parse retention data\n const retentionData = useMemo<RetentionChartData | null>(() => {\n if (!data) return null\n\n // Check if data is already in RetentionChartData format\n if (isRetentionData(data)) {\n return data\n }\n\n // If data is an array of RetentionResultRow, convert it\n if (Array.isArray(data) && data.length > 0) {\n const rows = data as RetentionResultRow[]\n const periods = [...new Set(rows.map((r) => r.period))].sort((a, b) => a - b)\n const breakdownValues = [\n ...new Set(rows.filter((r) => r.breakdownValue).map((r) => r.breakdownValue!)),\n ]\n\n return {\n rows,\n periods,\n breakdownValues: breakdownValues.length > 0 ? breakdownValues : undefined,\n }\n }\n\n return null\n }, [data])\n\n // Transform data for chart\n const { chartData, seriesKeys, defaultSeriesName } = useMemo(() => {\n if (!retentionData) {\n return { chartData: [], seriesKeys: [], defaultSeriesName: 'Retention' }\n }\n return transformRetentionData(\n retentionData.rows,\n retentionData.periods,\n retentionData.breakdownValues,\n retentionData.granularity,\n retentionData.bindingKeyLabel\n )\n }, [retentionData])\n\n // Get cohort label for heatmap column header\n const cohortLabel = getCohortLabel(retentionData?.bindingKeyLabel)\n\n // Display mode from config\n const displayMode: RetentionDisplayMode =\n (displayConfig as any)?.retentionDisplayMode || 'line'\n\n const showLegend = displayConfig?.showLegend ?? true\n const showGrid = displayConfig?.showGrid ?? true\n const showTooltip = displayConfig?.showTooltip ?? true\n\n // Handle empty/loading states\n if (!data || (Array.isArray(data) && data.length === 0)) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n Configure retention analysis to see results\n </div>\n </div>\n </div>\n )\n }\n\n if (!chartData || chartData.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Unable to render retention data</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">Data format may be incorrect</div>\n </div>\n </div>\n )\n }\n\n // Render line chart component (reused in line and combined modes)\n const renderLineChart = (chartHeight: string | number) => {\n const chartMargins = {\n ...CHART_MARGINS,\n left: 50,\n right: 20,\n }\n\n return (\n <ChartContainer height={chartHeight}>\n <ComposedChart data={chartData} margin={chartMargins} accessibilityLayer={false}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" />}\n <XAxis\n dataKey=\"periodLabel\"\n tick={{ fontSize: 12 }}\n axisLine={{ stroke: 'var(--dc-border)' }}\n tickLine={{ stroke: 'var(--dc-border)' }}\n />\n <YAxis\n domain={[0, 1]}\n tickFormatter={(value) => formatPercentage(value)}\n tick={{ fontSize: 12 }}\n axisLine={{ stroke: 'var(--dc-border)' }}\n tickLine={{ stroke: 'var(--dc-border)' }}\n label={{\n value: 'Retention %',\n angle: -90,\n position: 'insideLeft',\n style: { textAnchor: 'middle', fontSize: '12px', fill: 'var(--dc-text-secondary)' },\n }}\n />\n {showTooltip && (\n <ChartTooltip\n formatter={(value: any, name: string) => {\n if (value === null || value === undefined) {\n return ['No data', name]\n }\n return [formatPercentage(value), name]\n }}\n labelFormatter={(label: string) => label}\n />\n )}\n {showLegend && (\n <Legend\n wrapperStyle={{ fontSize: '12px', paddingTop: '10px' }}\n iconType=\"line\"\n iconSize={8}\n layout=\"horizontal\"\n align=\"center\"\n verticalAlign=\"bottom\"\n onMouseEnter={(o) => setHoveredLegend(String(o.dataKey || ''))}\n onMouseLeave={() => setHoveredLegend(null)}\n />\n )}\n\n {/* Render lines */}\n {seriesKeys.map((seriesKey, index) => (\n <Line\n key={seriesKey}\n type=\"monotone\"\n dataKey={seriesKey}\n stroke={\n (colorPalette?.colors && colorPalette.colors[index % colorPalette.colors.length]) ||\n CHART_COLORS[index % CHART_COLORS.length]\n }\n strokeWidth={2}\n dot={{ r: 4, strokeWidth: 2 }}\n activeDot={{ r: 6 }}\n strokeOpacity={hoveredLegend ? (hoveredLegend === seriesKey ? 1 : 0.3) : 1}\n connectNulls={false}\n />\n ))}\n </ComposedChart>\n </ChartContainer>\n )\n }\n\n // Render heatmap table component (reused in heatmap and combined modes)\n const renderHeatmapTable = () => (\n <table className=\"dc:w-full dc:border-collapse dc:text-sm\">\n <thead className=\"dc:sticky dc:top-0 bg-dc-bg dc:z-10\">\n <tr>\n <th className=\"dc:text-left dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[100px] dc:whitespace-nowrap\">\n {retentionData?.breakdownValues?.length ? 'Segment' : 'Cohort'}\n </th>\n <th className=\"dc:text-right dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[60px] dc:whitespace-nowrap\">\n {cohortLabel}\n </th>\n {retentionData?.periods.map((period) => (\n <th\n key={period}\n className=\"dc:text-center dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[70px] dc:whitespace-nowrap\"\n >\n {formatPeriodLabel(period, retentionData?.granularity)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {seriesKeys.map((seriesKey, rowIndex) => {\n const period0Data = chartData.find((d) => d.period === 0)\n const isDefaultSeries = seriesKey === defaultSeriesName\n const cohortSize = isDefaultSeries\n ? period0Data?.cohortSize ?? 0\n : period0Data?.[`${seriesKey}_cohortSize`] ?? 0\n\n return (\n <tr\n key={seriesKey}\n className={rowIndex % 2 === 0 ? 'bg-dc-bg' : 'bg-dc-surface-secondary'}\n >\n <td className=\"dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:whitespace-nowrap\">\n {seriesKey}\n </td>\n <td className=\"dc:p-2 dc:text-right text-dc-text-secondary dc:border-b border-dc-border\">\n {cohortSize.toLocaleString()}\n </td>\n {retentionData?.periods.map((period) => {\n const dataPoint = chartData.find((d) => d.period === period)\n const rate = dataPoint?.[seriesKey] ?? 0\n const bgColor = rate > 0 ? getRetentionColor(rate) : 'transparent'\n const textColor = rate > 0.5 ? '#ffffff' : 'var(--dc-text)'\n\n return (\n <td\n key={period}\n className=\"dc:p-2 dc:text-center dc:border-b border-dc-border dc:cursor-default dc:transition-opacity dc:hover:opacity-80\"\n style={{ backgroundColor: bgColor, color: textColor }}\n onMouseEnter={(e) => {\n const rect = e.currentTarget.getBoundingClientRect()\n const retainedUsers = isDefaultSeries\n ? dataPoint?.retainedUsers ?? 0\n : dataPoint?.[`${seriesKey}_retainedUsers`] ?? 0\n setHeatmapTooltip({\n period,\n breakdownValue: isDefaultSeries ? null : seriesKey,\n cohortSize,\n retainedUsers,\n retentionRate: rate,\n x: rect.left + rect.width / 2,\n y: rect.top,\n })\n }}\n onMouseLeave={() => setHeatmapTooltip(null)}\n >\n {rate > 0 ? formatPercentage(rate) : '-'}\n </td>\n )\n })}\n </tr>\n )\n })}\n </tbody>\n </table>\n )\n\n // Render heatmap tooltip (shared between heatmap and combined modes)\n const renderHeatmapTooltip = () =>\n heatmapTooltip && (\n <div\n className=\"dc:fixed dc:z-50 dc:px-3 dc:py-2 bg-dc-surface dc:border border-dc-border dc:rounded dc:shadow-lg dc:text-sm dc:pointer-events-none\"\n style={{\n left: heatmapTooltip.x,\n top: heatmapTooltip.y - 10,\n transform: 'translate(-50%, -100%)',\n }}\n >\n <div className=\"dc:font-medium text-dc-text dc:mb-1\">\n {heatmapTooltip.breakdownValue\n ? `${heatmapTooltip.breakdownValue} - ${formatPeriodLabel(heatmapTooltip.period, retentionData?.granularity)}`\n : formatPeriodLabel(heatmapTooltip.period, retentionData?.granularity)}\n </div>\n <div className=\"text-dc-text-secondary dc:space-y-0.5\">\n <div>Cohort Size: {heatmapTooltip.cohortSize.toLocaleString()}</div>\n <div>Retained: {heatmapTooltip.retainedUsers.toLocaleString()}</div>\n <div className=\"dc:font-medium text-dc-text\">\n Rate: {formatPercentage(heatmapTooltip.retentionRate)}\n </div>\n </div>\n </div>\n )\n\n // Render heatmap mode (table-based only)\n if (displayMode === 'heatmap') {\n return (\n <div className=\"dc:relative dc:w-full dc:h-full dc:overflow-auto\" style={{ height }}>\n {renderHeatmapTable()}\n {renderHeatmapTooltip()}\n </div>\n )\n }\n\n // Combined mode: line chart on top, heatmap table below\n if (displayMode === 'combined') {\n return (\n <div className=\"dc:flex dc:flex-col dc:w-full dc:h-full\" style={{ height }}>\n {/* Line chart - takes remaining space after heatmap */}\n <div className=\"dc:flex-1 dc:min-h-[200px]\">\n {renderLineChart('100%')}\n </div>\n {/* Heatmap table - auto-height based on content, scrolls if needed */}\n <div className=\"dc:flex-shrink-0 dc:max-h-[40%] dc:overflow-auto dc:border-t border-dc-border\">\n {renderHeatmapTable()}\n </div>\n {/* Shared heatmap tooltip */}\n {renderHeatmapTooltip()}\n </div>\n )\n }\n\n // Line mode: just the line chart\n return renderLineChart(height)\n})\n\nexport default RetentionCombinedChart\n"],"names":["getRetentionColor","rate","formatPercentage","formatPeriodLabel","period","granularity","prefix","getCohortLabel","_bindingKeyLabel","getDefaultSeriesName","bindingKeyLabel","transformRetentionData","rows","periods","breakdownValues","defaultSeriesName","row","r","dataPoint","bv","RetentionCombinedChart","React","data","height","displayConfig","colorPalette","hoveredLegend","setHoveredLegend","useState","heatmapTooltip","setHeatmapTooltip","retentionData","useMemo","isRetentionData","a","b","chartData","seriesKeys","cohortLabel","displayMode","showLegend","showGrid","showTooltip","jsx","jsxs","renderLineChart","chartHeight","chartMargins","CHART_MARGINS","ChartContainer","ComposedChart","CartesianGrid","XAxis","YAxis","value","ChartTooltip","name","label","Legend","o","seriesKey","index","Line","CHART_COLORS","renderHeatmapTable","rowIndex","period0Data","d","isDefaultSeries","cohortSize","bgColor","textColor","e","rect","retainedUsers","renderHeatmapTooltip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RetentionCombinedChart-BK3NPsHP.js","sources":["../../../src/client/components/charts/RetentionCombinedChart.tsx"],"sourcesContent":["/**\n * RetentionCombinedChart Component\n *\n * Combined visualization for retention analysis data.\n * Supports multiple display modes: heatmap, line chart, or combined view.\n *\n * Features:\n * - X-axis: Period numbers (P0, P1, P2...)\n * - Y-axis: Retention % (0-100%)\n * - Lines: One per breakdown value (or single if no breakdown)\n * - Display modes: 'heatmap' | 'line' | 'combined'\n * - Heatmap shows color-coded retention matrix\n * - Line chart shows retention curves over periods\n */\n\nimport React, { useMemo, useState } from 'react'\nimport {\n ComposedChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Legend,\n} from 'recharts'\nimport ChartContainer from './ChartContainer'\nimport ChartTooltip from './ChartTooltip'\nimport { CHART_COLORS, CHART_MARGINS } from '../../utils/chartConstants'\nimport type { ChartProps } from '../../types'\nimport type { RetentionChartData, RetentionResultRow, RetentionGranularity } from '../../types/retention'\nimport { isRetentionData } from '../../types/retention'\n\n/**\n * Retention display mode\n * - 'heatmap': Show retention as color-coded bars\n * - 'line': Show retention as line curves\n * - 'combined': Show both heatmap background and line overlay\n */\nexport type RetentionDisplayMode = 'heatmap' | 'line' | 'combined'\n\n/**\n * Get color with opacity based on retention rate for heatmap cells\n * Uses a green gradient: higher retention = more saturated green\n */\nfunction getRetentionColor(rate: number): string {\n const clampedRate = Math.max(0, Math.min(1, rate))\n const alpha = 0.1 + clampedRate * 0.7\n return `rgba(34, 197, 94, ${alpha})`\n}\n\n/**\n * Format percentage for display\n */\nfunction formatPercentage(rate: number): string {\n return `${Math.round(rate * 100)}%`\n}\n\n/**\n * Format period label based on granularity\n * Period 0 shows \"< 1 Day\" / \"< 1 Week\" etc. to indicate the initial cohort\n * e.g., Period 0 with 'week' granularity → \"< 1 Week\", Period 1 → \"Week 1\"\n */\nfunction formatPeriodLabel(period: number, granularity?: RetentionGranularity): string {\n const prefix = granularity === 'day' ? 'Day'\n : granularity === 'week' ? 'Week'\n : granularity === 'month' ? 'Month'\n : 'P' // Fallback to P0, P1, etc.\n\n // Period 0 is special - shows \"< 1 Day\" / \"< 1 Week\" etc.\n if (period === 0) {\n return granularity ? `< 1 ${prefix}` : 'P0'\n }\n\n return granularity ? `${prefix} ${period}` : `P${period}`\n}\n\n/**\n * Get display label for the cohort total column\n * Shows \"Total\" regardless of binding key - it's the cohort size count\n */\nfunction getCohortLabel(_bindingKeyLabel?: string): string {\n return 'Total'\n}\n\n/**\n * Get default series name based on binding key\n * e.g., \"userId\" → \"userId Retention\", null → \"Retention\"\n */\nfunction getDefaultSeriesName(bindingKeyLabel?: string): string {\n if (!bindingKeyLabel) return 'Retention'\n return `${bindingKeyLabel} Retention`\n}\n\n/**\n * Transform retention data for chart display\n * Groups data by period with breakdown values as series\n */\nfunction transformRetentionData(\n rows: RetentionResultRow[],\n periods: number[],\n breakdownValues?: string[],\n granularity?: RetentionGranularity,\n bindingKeyLabel?: string\n): { chartData: any[]; seriesKeys: string[]; defaultSeriesName: string } {\n const defaultSeriesName = getDefaultSeriesName(bindingKeyLabel)\n\n // If no breakdown, single series\n if (!breakdownValues || breakdownValues.length === 0) {\n const chartData = periods.map((period) => {\n const row = rows.find((r) => r.period === period && !r.breakdownValue)\n return {\n period,\n periodLabel: formatPeriodLabel(period, granularity),\n [defaultSeriesName]: row ? row.retentionRate : null,\n cohortSize: row?.cohortSize ?? 0,\n retainedUsers: row?.retainedUsers ?? 0,\n }\n })\n return { chartData, seriesKeys: [defaultSeriesName], defaultSeriesName }\n }\n\n // With breakdown, create series per breakdown value\n const chartData = periods.map((period) => {\n const dataPoint: any = {\n period,\n periodLabel: formatPeriodLabel(period, granularity),\n }\n\n breakdownValues.forEach((bv) => {\n const row = rows.find((r) => r.period === period && r.breakdownValue === bv)\n dataPoint[bv] = row ? row.retentionRate : null\n dataPoint[`${bv}_cohortSize`] = row?.cohortSize ?? 0\n dataPoint[`${bv}_retainedUsers`] = row?.retainedUsers ?? 0\n })\n\n return dataPoint\n })\n\n return { chartData, seriesKeys: breakdownValues, defaultSeriesName }\n}\n\ninterface TooltipData {\n period: number\n breakdownValue?: string | null\n cohortSize: number\n retainedUsers: number\n retentionRate: number\n x: number\n y: number\n}\n\n/**\n * RetentionCombinedChart Component\n */\nconst RetentionCombinedChart = React.memo(function RetentionCombinedChart({\n data,\n height = '100%',\n displayConfig,\n colorPalette,\n}: ChartProps) {\n const [hoveredLegend, setHoveredLegend] = useState<string | null>(null)\n const [heatmapTooltip, setHeatmapTooltip] = useState<TooltipData | null>(null)\n\n // Parse retention data\n const retentionData = useMemo<RetentionChartData | null>(() => {\n if (!data) return null\n\n // Check if data is already in RetentionChartData format\n if (isRetentionData(data)) {\n return data\n }\n\n // If data is an array of RetentionResultRow, convert it\n if (Array.isArray(data) && data.length > 0) {\n const rows = data as RetentionResultRow[]\n const periods = [...new Set(rows.map((r) => r.period))].sort((a, b) => a - b)\n const breakdownValues = [\n ...new Set(rows.filter((r) => r.breakdownValue).map((r) => r.breakdownValue!)),\n ]\n\n return {\n rows,\n periods,\n breakdownValues: breakdownValues.length > 0 ? breakdownValues : undefined,\n }\n }\n\n return null\n }, [data])\n\n // Transform data for chart\n const { chartData, seriesKeys, defaultSeriesName } = useMemo(() => {\n if (!retentionData) {\n return { chartData: [], seriesKeys: [], defaultSeriesName: 'Retention' }\n }\n return transformRetentionData(\n retentionData.rows,\n retentionData.periods,\n retentionData.breakdownValues,\n retentionData.granularity,\n retentionData.bindingKeyLabel\n )\n }, [retentionData])\n\n // Get cohort label for heatmap column header\n const cohortLabel = getCohortLabel(retentionData?.bindingKeyLabel)\n\n // Display mode from config\n const displayMode: RetentionDisplayMode =\n (displayConfig as any)?.retentionDisplayMode || 'line'\n\n const showLegend = displayConfig?.showLegend ?? true\n const showGrid = displayConfig?.showGrid ?? true\n const showTooltip = displayConfig?.showTooltip ?? true\n\n // Handle empty/loading states\n if (!data || (Array.isArray(data) && data.length === 0)) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n Configure retention analysis to see results\n </div>\n </div>\n </div>\n )\n }\n\n if (!chartData || chartData.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full text-dc-text-muted\"\n style={{ height }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">Unable to render retention data</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">Data format may be incorrect</div>\n </div>\n </div>\n )\n }\n\n // Render line chart component (reused in line and combined modes)\n const renderLineChart = (chartHeight: string | number) => {\n const chartMargins = {\n ...CHART_MARGINS,\n left: 50,\n right: 20,\n }\n\n return (\n <ChartContainer height={chartHeight}>\n <ComposedChart data={chartData} margin={chartMargins} accessibilityLayer={false}>\n {showGrid && <CartesianGrid strokeDasharray=\"3 3\" />}\n <XAxis\n dataKey=\"periodLabel\"\n tick={{ fontSize: 12 }}\n axisLine={{ stroke: 'var(--dc-border)' }}\n tickLine={{ stroke: 'var(--dc-border)' }}\n />\n <YAxis\n domain={[0, 1]}\n tickFormatter={(value) => formatPercentage(value)}\n tick={{ fontSize: 12 }}\n axisLine={{ stroke: 'var(--dc-border)' }}\n tickLine={{ stroke: 'var(--dc-border)' }}\n label={{\n value: 'Retention %',\n angle: -90,\n position: 'insideLeft',\n style: { textAnchor: 'middle', fontSize: '12px', fill: 'var(--dc-text-secondary)' },\n }}\n />\n {showTooltip && (\n <ChartTooltip\n formatter={(value: any, name: string) => {\n if (value === null || value === undefined) {\n return ['No data', name]\n }\n return [formatPercentage(value), name]\n }}\n labelFormatter={(label: string) => label}\n />\n )}\n {showLegend && (\n <Legend\n wrapperStyle={{ fontSize: '12px', paddingTop: '10px' }}\n iconType=\"line\"\n iconSize={8}\n layout=\"horizontal\"\n align=\"center\"\n verticalAlign=\"bottom\"\n onMouseEnter={(o) => setHoveredLegend(String(o.dataKey || ''))}\n onMouseLeave={() => setHoveredLegend(null)}\n />\n )}\n\n {/* Render lines */}\n {seriesKeys.map((seriesKey, index) => (\n <Line\n key={seriesKey}\n type=\"monotone\"\n dataKey={seriesKey}\n stroke={\n (colorPalette?.colors && colorPalette.colors[index % colorPalette.colors.length]) ||\n CHART_COLORS[index % CHART_COLORS.length]\n }\n strokeWidth={2}\n dot={{ r: 4, strokeWidth: 2 }}\n activeDot={{ r: 6 }}\n strokeOpacity={hoveredLegend ? (hoveredLegend === seriesKey ? 1 : 0.3) : 1}\n connectNulls={false}\n />\n ))}\n </ComposedChart>\n </ChartContainer>\n )\n }\n\n // Render heatmap table component (reused in heatmap and combined modes)\n const renderHeatmapTable = () => (\n <table className=\"dc:w-full dc:border-collapse dc:text-sm\">\n <thead className=\"dc:sticky dc:top-0 bg-dc-bg dc:z-10\">\n <tr>\n <th className=\"dc:text-left dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[100px] dc:whitespace-nowrap\">\n {retentionData?.breakdownValues?.length ? 'Segment' : 'Cohort'}\n </th>\n <th className=\"dc:text-right dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[60px] dc:whitespace-nowrap\">\n {cohortLabel}\n </th>\n {retentionData?.periods.map((period) => (\n <th\n key={period}\n className=\"dc:text-center dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:min-w-[70px] dc:whitespace-nowrap\"\n >\n {formatPeriodLabel(period, retentionData?.granularity)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {seriesKeys.map((seriesKey, rowIndex) => {\n const period0Data = chartData.find((d) => d.period === 0)\n const isDefaultSeries = seriesKey === defaultSeriesName\n const cohortSize = isDefaultSeries\n ? period0Data?.cohortSize ?? 0\n : period0Data?.[`${seriesKey}_cohortSize`] ?? 0\n\n return (\n <tr\n key={seriesKey}\n className={rowIndex % 2 === 0 ? 'bg-dc-bg' : 'bg-dc-surface-secondary'}\n >\n <td className=\"dc:p-2 dc:font-medium text-dc-text dc:border-b border-dc-border dc:whitespace-nowrap\">\n {seriesKey}\n </td>\n <td className=\"dc:p-2 dc:text-right text-dc-text-secondary dc:border-b border-dc-border\">\n {cohortSize.toLocaleString()}\n </td>\n {retentionData?.periods.map((period) => {\n const dataPoint = chartData.find((d) => d.period === period)\n const rate = dataPoint?.[seriesKey] ?? 0\n const bgColor = rate > 0 ? getRetentionColor(rate) : 'transparent'\n const textColor = rate > 0.5 ? '#ffffff' : 'var(--dc-text)'\n\n return (\n <td\n key={period}\n className=\"dc:p-2 dc:text-center dc:border-b border-dc-border dc:cursor-default dc:transition-opacity dc:hover:opacity-80\"\n style={{ backgroundColor: bgColor, color: textColor }}\n onMouseEnter={(e) => {\n const rect = e.currentTarget.getBoundingClientRect()\n const retainedUsers = isDefaultSeries\n ? dataPoint?.retainedUsers ?? 0\n : dataPoint?.[`${seriesKey}_retainedUsers`] ?? 0\n setHeatmapTooltip({\n period,\n breakdownValue: isDefaultSeries ? null : seriesKey,\n cohortSize,\n retainedUsers,\n retentionRate: rate,\n x: rect.left + rect.width / 2,\n y: rect.top,\n })\n }}\n onMouseLeave={() => setHeatmapTooltip(null)}\n >\n {rate > 0 ? formatPercentage(rate) : '-'}\n </td>\n )\n })}\n </tr>\n )\n })}\n </tbody>\n </table>\n )\n\n // Render heatmap tooltip (shared between heatmap and combined modes)\n const renderHeatmapTooltip = () =>\n heatmapTooltip && (\n <div\n className=\"dc:fixed dc:z-50 dc:px-3 dc:py-2 bg-dc-surface dc:border border-dc-border dc:rounded dc:shadow-lg dc:text-sm dc:pointer-events-none\"\n style={{\n left: heatmapTooltip.x,\n top: heatmapTooltip.y - 10,\n transform: 'translate(-50%, -100%)',\n }}\n >\n <div className=\"dc:font-medium text-dc-text dc:mb-1\">\n {heatmapTooltip.breakdownValue\n ? `${heatmapTooltip.breakdownValue} - ${formatPeriodLabel(heatmapTooltip.period, retentionData?.granularity)}`\n : formatPeriodLabel(heatmapTooltip.period, retentionData?.granularity)}\n </div>\n <div className=\"text-dc-text-secondary dc:space-y-0.5\">\n <div>Cohort Size: {heatmapTooltip.cohortSize.toLocaleString()}</div>\n <div>Retained: {heatmapTooltip.retainedUsers.toLocaleString()}</div>\n <div className=\"dc:font-medium text-dc-text\">\n Rate: {formatPercentage(heatmapTooltip.retentionRate)}\n </div>\n </div>\n </div>\n )\n\n // Render heatmap mode (table-based only)\n if (displayMode === 'heatmap') {\n return (\n <div className=\"dc:relative dc:w-full dc:h-full dc:overflow-auto\" style={{ height }}>\n {renderHeatmapTable()}\n {renderHeatmapTooltip()}\n </div>\n )\n }\n\n // Combined mode: line chart on top, heatmap table below\n if (displayMode === 'combined') {\n return (\n <div className=\"dc:flex dc:flex-col dc:w-full dc:h-full\" style={{ height }}>\n {/* Line chart - takes remaining space after heatmap */}\n <div className=\"dc:flex-1 dc:min-h-[200px]\">\n {renderLineChart('100%')}\n </div>\n {/* Heatmap table - auto-height based on content, scrolls if needed */}\n <div className=\"dc:flex-shrink-0 dc:max-h-[40%] dc:overflow-auto dc:border-t border-dc-border\">\n {renderHeatmapTable()}\n </div>\n {/* Shared heatmap tooltip */}\n {renderHeatmapTooltip()}\n </div>\n )\n }\n\n // Line mode: just the line chart\n return renderLineChart(height)\n})\n\nexport default RetentionCombinedChart\n"],"names":["getRetentionColor","rate","formatPercentage","formatPeriodLabel","period","granularity","prefix","getCohortLabel","_bindingKeyLabel","getDefaultSeriesName","bindingKeyLabel","transformRetentionData","rows","periods","breakdownValues","defaultSeriesName","row","r","dataPoint","bv","RetentionCombinedChart","React","data","height","displayConfig","colorPalette","hoveredLegend","setHoveredLegend","useState","heatmapTooltip","setHeatmapTooltip","retentionData","useMemo","isRetentionData","a","b","chartData","seriesKeys","cohortLabel","displayMode","showLegend","showGrid","showTooltip","jsx","jsxs","renderLineChart","chartHeight","chartMargins","CHART_MARGINS","ChartContainer","ComposedChart","CartesianGrid","XAxis","YAxis","value","ChartTooltip","name","label","Legend","o","seriesKey","index","Line","CHART_COLORS","renderHeatmapTable","rowIndex","period0Data","d","isDefaultSeries","cohortSize","bgColor","textColor","e","rect","retainedUsers","renderHeatmapTooltip"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsxs as t, jsx as e, Fragment as ie } from "react/jsx-runtime";
|
|
2
2
|
import { useState as _, useCallback as F, memo as me, useEffect as Y, useRef as Z, useMemo as z, Component as xd, forwardRef as Qc, useImperativeHandle as gd } from "react";
|
|
3
|
-
import { u as bd, g as yd } from "./providers-
|
|
4
|
-
import { F as vd, E as ac, C as Ct, D as Nd, G as wd, q as Cd, H as Sd, u as Dd, I as kd } from "./analysis-builder-shared-
|
|
5
|
-
import { a0 as Wt, a1 as Td, h as $d, a6 as Ad, $ as Ce, a7 as Gc, a8 as _c, a9 as Hc, aa as Id, ab as St } from "./components-
|
|
6
|
-
import { i as Rd, L as ic } from "./charts-loader-
|
|
7
|
-
import {
|
|
8
|
-
import { a as $, g as nc,
|
|
9
|
-
import { S as ue } from "./charts-core-
|
|
10
|
-
import {
|
|
3
|
+
import { u as bd, g as yd } from "./providers-CCw8Kjlc.js";
|
|
4
|
+
import { F as vd, E as ac, C as Ct, D as Nd, G as wd, q as Cd, H as Sd, u as Dd, I as kd } from "./analysis-builder-shared-CrENEvEk.js";
|
|
5
|
+
import { a0 as Wt, a1 as Td, h as $d, a6 as Ad, $ as Ce, a7 as Gc, a8 as _c, a9 as Hc, aa as Id, ab as St } from "./components-GzooQM5J.js";
|
|
6
|
+
import { i as Rd, L as ic } from "./charts-loader-DbrwgvCK.js";
|
|
7
|
+
import { c as mt, e as Fd } from "./useExplainAI-IiW55BaQ.js";
|
|
8
|
+
import { a as $, g as nc, b as oc } from "./icons-DFJw-2HU.js";
|
|
9
|
+
import { S as ue } from "./charts-core-B4Rbfdcn.js";
|
|
10
|
+
import { q as Ed, r as Od, t as Ld, D as Ut, w as ht, o as Ve, F as Kc } from "./useDirtyStateTracking-CjhwBXRw.js";
|
|
11
11
|
import { barChartConfig as Bd } from "./chart-config-bar-C8uzktxl.js";
|
|
12
12
|
import { lineChartConfig as Pd } from "./chart-config-line-JNagi9tf.js";
|
|
13
13
|
import { areaChartConfig as Md } from "./chart-config-area-CK_GVApT.js";
|
|
@@ -6168,4 +6168,4 @@ Mr.displayName = "AnalysisBuilder";
|
|
|
6168
6168
|
export {
|
|
6169
6169
|
Mr as default
|
|
6170
6170
|
};
|
|
6171
|
-
//# sourceMappingURL=analysis-builder-
|
|
6171
|
+
//# sourceMappingURL=analysis-builder-DVrv9Q4n.js.map
|