drizzle-cube 0.4.21 → 0.4.22
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/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/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDirtyStateTracking-Cu1HSjmo.js","sources":["../../../src/client/shared/types.ts","../../../src/client/shared/utils.ts","../../../src/client/shared/queryKey.ts","../../../src/client/hooks/useDebounceQuery.ts","../../../src/client/hooks/queries/useCubeLoadQuery.ts","../../../src/client/utils/multiQueryUtils.ts","../../../src/client/hooks/queries/useMultiCubeLoadQuery.ts","../../../src/client/hooks/queries/useFunnelQuery.ts","../../../src/client/hooks/queries/useFlowQuery.ts","../../../src/client/hooks/queries/useRetentionQuery.ts","../../../src/client/hooks/useFilterValues.ts","../../../src/client/hooks/useDebounce.ts","../../../src/client/hooks/useResponsiveDashboard.ts","../../../src/client/hooks/useDirtyStateTracking.ts"],"sourcesContent":["/**\n * Shared type definitions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, FilterOperator } from '../types'\n\n// ============================================================================\n// Meta endpoint response types\n// ============================================================================\n\nexport interface MetaField {\n name: string // e.g., \"Employees.count\"\n title: string // e.g., \"Total Employees\"\n shortTitle: string // e.g., \"Total Employees\"\n type: string // e.g., \"count\", \"string\", \"time\", \"number\"\n description?: string // Optional description\n}\n\nexport interface MetaCubeRelationship {\n targetCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinFields?: Array<{\n sourceField: string\n targetField: string\n }>\n}\n\nexport interface MetaCube {\n name: string // e.g., \"Employees\"\n title: string // e.g., \"Employee Analytics\"\n description: string // e.g., \"Employee data and metrics\"\n measures: MetaField[] // e.g., \"Employees.count\"\n dimensions: MetaField[] // e.g., \"Employees.name\"\n segments: MetaField[] // Currently empty in examples\n relationships?: MetaCubeRelationship[] // Optional join relationships to other cubes\n}\n\nexport interface MetaResponse {\n cubes: MetaCube[]\n}\n\n// ============================================================================\n// Query warning types (from server-side query planner)\n// ============================================================================\n\n/**\n * Severity level for query warnings\n */\nexport type QueryWarningSeverity = 'info' | 'warning' | 'error'\n\n/**\n * Warning emitted during query planning or execution\n * Provides user-facing feedback about potential query issues\n */\nexport interface QueryWarning {\n /** Unique code for programmatic handling (e.g., 'FAN_OUT_NO_DIMENSIONS') */\n code: string\n /** Human-readable warning message */\n message: string\n /** Severity level for UI styling */\n severity: QueryWarningSeverity\n /** Cubes involved in the warning (if applicable) */\n cubes?: string[]\n /** Measures involved in the warning (if applicable) */\n measures?: string[]\n /** Actionable suggestion for the user */\n suggestion?: string\n}\n\n// ============================================================================\n// Query analysis types for debugging transparency\n// ============================================================================\n\nexport type PrimaryCubeSelectionReason =\n | 'most_dimensions'\n | 'most_connected'\n | 'alphabetical_fallback'\n | 'single_cube'\n\nexport interface PrimaryCubeCandidate {\n cubeName: string\n dimensionCount: number\n joinCount: number\n canReachAll: boolean\n}\n\nexport interface PrimaryCubeAnalysis {\n selectedCube: string\n reason: PrimaryCubeSelectionReason\n explanation: string\n candidates?: PrimaryCubeCandidate[]\n}\n\nexport interface JoinPathStep {\n fromCube: string\n toCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinType: 'inner' | 'left' | 'right' | 'full'\n joinColumns: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n junctionTable?: {\n tableName: string\n sourceColumns: string[]\n targetColumns: string[]\n }\n}\n\nexport interface JoinPathAnalysis {\n targetCube: string\n pathFound: boolean\n path?: JoinPathStep[]\n pathLength?: number\n error?: string\n visitedCubes?: string[]\n selection?: {\n strategy: 'shortest' | 'preferred' | 'fallbackShortest'\n preferredCubes?: string[]\n selectedRank?: number\n selectedScore?: number\n candidates?: Array<{\n rank: number\n score: number\n usesPreferredJoin: boolean\n preferredCubesInPath: number\n usesProcessed: boolean\n scoreBreakdown: {\n preferredJoinBonus: number\n preferredCubeBonus: number\n lengthPenalty: number\n }\n path: JoinPathStep[]\n }>\n }\n}\n\n/**\n * Reason why a cube requires a CTE\n */\nexport type CTEReason = 'hasMany' | 'fanOutPrevention'\n\nexport interface PreAggregationAnalysis {\n cubeName: string\n cteAlias: string\n /** Why this cube needs a CTE (human-readable explanation) */\n reason: string\n /** Typed reason for programmatic use */\n reasonType?: CTEReason\n measures: string[]\n joinKeys: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n}\n\nexport interface QuerySummary {\n queryType: 'single_cube' | 'multi_cube_join' | 'multi_cube_cte'\n measureStrategy?: 'simple' | 'keysDeduplication' | 'ctePreAggregateFallback' | 'multiFactMerge'\n joinCount: number\n cteCount: number\n hasPreAggregation: boolean\n}\n\nexport interface QueryAnalysis {\n timestamp: string\n cubeCount: number\n cubesInvolved: string[]\n primaryCube: PrimaryCubeAnalysis\n joinPaths: JoinPathAnalysis[]\n preAggregations: PreAggregationAnalysis[]\n querySummary: QuerySummary\n warnings?: string[]\n planningTrace?: {\n steps: Array<{\n phase: 'cube_usage' | 'primary_cube_selection' | 'join_planning' | 'cte_planning' | 'measure_strategy' | 'warnings'\n decision: string\n details?: Record<string, unknown>\n }>\n }\n}\n\n// ============================================================================\n// Validation response from /dry-run endpoint\n// ============================================================================\n\nexport interface ValidationResult {\n valid?: boolean // Our custom property (may not be present in official Cube.js)\n error?: string\n query?: CubeQuery\n sql?: {\n sql: string[]\n params: any[]\n }\n queryType?: string // Always present in successful Cube.js responses\n normalizedQueries?: any[]\n queryOrder?: string[]\n transformedQueries?: any[]\n pivotQuery?: any\n complexity?: string\n cubesUsed?: string[]\n joinType?: string\n // Query analysis for debugging transparency\n analysis?: QueryAnalysis\n}\n\n// ============================================================================\n// Filter operator metadata\n// ============================================================================\n\nexport interface FilterOperatorMeta {\n label: string\n description: string\n requiresValues: boolean\n supportsMultipleValues: boolean\n valueType: 'string' | 'number' | 'date' | 'boolean' | 'any'\n fieldTypes: string[] // Which field types support this operator\n}\n\nexport const FILTER_OPERATORS: Record<FilterOperator, FilterOperatorMeta> = {\n // String operators\n equals: {\n label: 'equals',\n description: 'Exact match',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n notEquals: {\n label: 'not equals',\n description: 'Does not match',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n contains: {\n label: 'contains',\n description: 'Contains text (case insensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notContains: {\n label: 'not contains',\n description: 'Does not contain text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n startsWith: {\n label: 'starts with',\n description: 'Starts with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notStartsWith: {\n label: 'not starts with',\n description: 'Does not start with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n endsWith: {\n label: 'ends with',\n description: 'Ends with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notEndsWith: {\n label: 'not ends with',\n description: 'Does not end with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n like: {\n label: 'like',\n description: 'SQL LIKE pattern matching (case sensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notLike: {\n label: 'not like',\n description: 'SQL NOT LIKE pattern matching (case sensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n ilike: {\n label: 'ilike',\n description: 'SQL ILIKE pattern matching (case insensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Numeric operators\n gt: {\n label: 'greater than',\n description: 'Greater than value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n gte: {\n label: 'greater than or equal',\n description: 'Greater than or equal to value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lt: {\n label: 'less than',\n description: 'Less than value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lte: {\n label: 'less than or equal',\n description: 'Less than or equal to value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n between: {\n label: 'between',\n description: 'Between two values (inclusive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n notBetween: {\n label: 'not between',\n description: 'Not between two values',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n // Array operators\n in: {\n label: 'in',\n description: 'Matches any of the provided values',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n notIn: {\n label: 'not in',\n description: 'Does not match any of the provided values',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n // Null/Empty operators\n set: {\n label: 'is set',\n description: 'Is not null/empty',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n notSet: {\n label: 'is not set',\n description: 'Is null/empty',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n isEmpty: {\n label: 'is empty',\n description: 'Is empty string or null',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n isNotEmpty: {\n label: 'is not empty',\n description: 'Is not empty string and not null',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Date operators\n inDateRange: {\n label: 'in date range',\n description: 'Between two dates',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n beforeDate: {\n label: 'before date',\n description: 'Before specified date',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n afterDate: {\n label: 'after date',\n description: 'After specified date',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n // Regex operators\n regex: {\n label: 'matches regex',\n description: 'Matches regular expression pattern',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notRegex: {\n label: 'not matches regex',\n description: 'Does not match regular expression pattern',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // PostgreSQL array operators\n arrayContains: {\n label: 'array contains all',\n description: 'Array field contains all specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayOverlaps: {\n label: 'array contains any',\n description: 'Array field contains any of the specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayContained: {\n label: 'array values in',\n description: 'All array field values are within specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n }\n}\n\n// ============================================================================\n// Date range types\n// ============================================================================\n\nexport type DateRangeType =\n | 'custom'\n | 'today'\n | 'yesterday'\n | 'this_week'\n | 'this_month'\n | 'this_quarter'\n | 'this_year'\n | 'last_7_days'\n | 'last_30_days'\n | 'last_week'\n | 'last_month'\n | 'last_quarter'\n | 'last_year'\n | 'last_12_months'\n | 'last_n_days'\n | 'last_n_weeks'\n | 'last_n_months'\n | 'last_n_quarters'\n | 'last_n_years'\n\nexport interface DateRangeOption {\n value: DateRangeType\n label: string\n}\n\nexport const DATE_RANGE_OPTIONS: DateRangeOption[] = [\n { value: 'custom', label: 'Custom' },\n { value: 'today', label: 'Today' },\n { value: 'yesterday', label: 'Yesterday' },\n { value: 'this_week', label: 'This week' },\n { value: 'this_month', label: 'This month' },\n { value: 'this_quarter', label: 'This quarter' },\n { value: 'this_year', label: 'This year' },\n { value: 'last_7_days', label: 'Last 7 days' },\n { value: 'last_30_days', label: 'Last 30 days' },\n { value: 'last_n_days', label: 'Last N days' },\n { value: 'last_week', label: 'Last week' },\n { value: 'last_n_weeks', label: 'Last N weeks' },\n { value: 'last_month', label: 'Last month' },\n { value: 'last_12_months', label: 'Last 12 months' },\n { value: 'last_n_months', label: 'Last N months' },\n { value: 'last_quarter', label: 'Last quarter' },\n { value: 'last_n_quarters', label: 'Last N quarters' },\n { value: 'last_year', label: 'Last year' },\n { value: 'last_n_years', label: 'Last N years' }\n] as const\n\n// ============================================================================\n// Time dimension granularity options\n// ============================================================================\n\nexport const TIME_GRANULARITIES = [\n { value: 'hour', label: 'Hour' },\n { value: 'day', label: 'Day' },\n { value: 'week', label: 'Week' },\n { value: 'month', label: 'Month' },\n { value: 'quarter', label: 'Quarter' },\n { value: 'year', label: 'Year' }\n] as const\n\nexport type TimeGranularity = typeof TIME_GRANULARITIES[number]['value']\n","/**\n * Shared utility functions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, Filter, SimpleFilter, GroupFilter } from '../types'\nimport type { MetaField, MetaResponse } from './types'\nimport { FILTER_OPERATORS } from './types'\n\n// ============================================================================\n// Filter type guards\n// ============================================================================\n\n/**\n * Check if a filter is a simple filter\n */\nexport function isSimpleFilter(filter: Filter): filter is SimpleFilter {\n return 'member' in filter && 'operator' in filter && 'values' in filter\n}\n\n/**\n * Check if a filter is a group filter\n */\nexport function isGroupFilter(filter: Filter): filter is GroupFilter {\n return 'type' in filter && 'filters' in filter\n}\n\n/**\n * Check if a filter is an AND filter\n */\nexport function isAndFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'and'\n}\n\n/**\n * Check if a filter is an OR filter\n */\nexport function isOrFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'or'\n}\n\n// ============================================================================\n// Filter manipulation functions\n// ============================================================================\n\n/**\n * Flatten all simple filters from a hierarchical filter structure\n */\nexport function flattenFilters(filters: Filter[]): SimpleFilter[] {\n const simple: SimpleFilter[] = []\n\n const flatten = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n simple.push(filter)\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(flatten)\n }\n }\n\n filters.forEach(flatten)\n return simple\n}\n\n/**\n * Count total filters in hierarchical structure\n */\nexport function countFilters(filters: Filter[]): number {\n let count = 0\n\n const countFilter = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n count++\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(countFilter)\n }\n }\n\n filters.forEach(countFilter)\n return count\n}\n\n/**\n * Create a new simple filter\n */\nexport function createSimpleFilter(member: string, operator: string = 'equals', values: any[] = []): SimpleFilter {\n return {\n member,\n operator: operator as any,\n values\n }\n}\n\n/**\n * Create a new AND filter group\n */\nexport function createAndFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'and',\n filters\n }\n}\n\n/**\n * Create a new OR filter group\n */\nexport function createOrFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'or',\n filters\n }\n}\n\n/**\n * Clean up filters - backward compatible (returns filters unchanged)\n * @deprecated This function is no longer used as we now support filtering on any schema field\n */\nexport function cleanupFilters(filters: Filter[], _query?: CubeQuery): Filter[] {\n return filters || []\n}\n\n// ============================================================================\n// Filter transformation functions\n// ============================================================================\n\n/**\n * Transform filters from new GroupFilter format to legacy server format\n * Server expects { and: [...] } and { or: [...] } instead of { type: 'and', filters: [...] }\n */\nexport function transformFiltersForServer(filters: Filter[]): any[] {\n const transformFilter = (filter: Filter): any => {\n if (isSimpleFilter(filter)) {\n return filter\n } else if (isGroupFilter(filter)) {\n const transformedSubFilters = filter.filters.map(transformFilter)\n\n if (filter.type === 'and') {\n return { and: transformedSubFilters }\n } else {\n return { or: transformedSubFilters }\n }\n }\n return filter\n }\n\n return filters.map(transformFilter)\n}\n\n/**\n * Transform filters from server/API format to UI format\n * Converts {and: [...]} and {or: [...]} to {type: 'and', filters: [...]} format\n */\nexport function transformFiltersFromServer(filters: any[]): Filter[] {\n return filters.map(filter => {\n if (!filter || typeof filter !== 'object') {\n return filter\n }\n\n // Handle legacy {and: [...]} format\n if ('and' in filter && Array.isArray(filter.and)) {\n return {\n type: 'and',\n filters: transformFiltersFromServer(filter.and)\n } as GroupFilter\n }\n\n // Handle legacy {or: [...]} format\n if ('or' in filter && Array.isArray(filter.or)) {\n return {\n type: 'or',\n filters: transformFiltersFromServer(filter.or)\n } as GroupFilter\n }\n\n // Handle new format {type: 'and', filters: [...]} - process recursively\n if ('type' in filter && 'filters' in filter && Array.isArray(filter.filters)) {\n return {\n type: filter.type,\n filters: transformFiltersFromServer(filter.filters)\n } as GroupFilter\n }\n\n // Simple filter - pass through\n return filter as SimpleFilter\n }).filter(Boolean) // Remove any null/undefined values\n}\n\n// ============================================================================\n// Query utility functions\n// ============================================================================\n\n/**\n * Check if query has any content (measures, dimensions, or timeDimensions)\n */\nexport function hasQueryContent(query: CubeQuery): boolean {\n return Boolean(\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0)\n )\n}\n\n/**\n * Clean query object by removing empty arrays\n */\nexport function cleanQuery(query: CubeQuery): CubeQuery {\n const cleanedQuery: CubeQuery = {}\n\n if (query.measures && query.measures.length > 0) {\n cleanedQuery.measures = query.measures\n }\n\n if (query.dimensions && query.dimensions.length > 0) {\n cleanedQuery.dimensions = query.dimensions\n }\n\n if (query.timeDimensions && query.timeDimensions.length > 0) {\n cleanedQuery.timeDimensions = query.timeDimensions\n }\n\n if (query.filters && query.filters.length > 0) {\n cleanedQuery.filters = query.filters\n }\n\n if (query.order) {\n cleanedQuery.order = query.order\n }\n\n if (query.limit) {\n cleanedQuery.limit = query.limit\n }\n\n if (query.offset) {\n cleanedQuery.offset = query.offset\n }\n\n if (query.segments && query.segments.length > 0) {\n cleanedQuery.segments = query.segments\n }\n\n return cleanedQuery\n}\n\n/**\n * Clean a query and transform filters for server compatibility\n * This version transforms GroupFilter to legacy and/or format\n */\nexport function cleanQueryForServer(query: CubeQuery): CubeQuery {\n const cleanedQuery = cleanQuery(query)\n\n // Apply server transformation to filters\n if (cleanedQuery.filters && cleanedQuery.filters.length > 0) {\n cleanedQuery.filters = transformFiltersForServer(cleanedQuery.filters) as any\n }\n\n return cleanedQuery\n}\n\n/**\n * Transform a Cube.js query from external format to UI internal format\n * This handles format differences between server/API queries and QueryBuilder state\n */\nexport function transformQueryForUI(query: any): CubeQuery {\n if (!query || typeof query !== 'object') {\n return {}\n }\n\n const transformed: CubeQuery = {}\n\n // Copy simple fields if they exist\n if (query.measures) transformed.measures = Array.isArray(query.measures) ? query.measures : []\n if (query.dimensions) transformed.dimensions = Array.isArray(query.dimensions) ? query.dimensions : []\n if (query.timeDimensions) transformed.timeDimensions = Array.isArray(query.timeDimensions) ? query.timeDimensions : []\n if (query.order) transformed.order = query.order\n if (query.limit) transformed.limit = query.limit\n if (query.offset) transformed.offset = query.offset\n if (query.segments) transformed.segments = Array.isArray(query.segments) ? query.segments : []\n\n // Transform filters from server format to UI format\n if (query.filters && Array.isArray(query.filters)) {\n transformed.filters = transformFiltersFromServer(query.filters)\n }\n\n return cleanQuery(transformed)\n}\n\n// ============================================================================\n// Schema utility functions\n// ============================================================================\n\n/**\n * Get cube name from field name (e.g., \"Employees.count\" -> \"Employees\")\n */\nexport function getCubeNameFromField(fieldName: string): string {\n return fieldName.split('.')[0]\n}\n\n/**\n * Get field type from schema\n */\nexport function getFieldType(fieldName: string, schema: MetaResponse): string {\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.type\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.type\n }\n\n return 'string' // Default fallback\n}\n\n/**\n * Get field title from schema metadata, falling back to field name\n */\nexport function getFieldTitle(fieldName: string, schema: MetaResponse | null): string {\n if (!schema) return fieldName\n\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.title || measure.shortTitle || fieldName\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.title || dimension.shortTitle || fieldName\n }\n\n return fieldName // Fallback to field name if not found\n}\n\n/**\n * Get available operators for a field type\n */\nexport function getAvailableOperators(fieldType: string): Array<{operator: string, label: string}> {\n const operators: Array<{operator: string, label: string}> = []\n\n for (const [operator, meta] of Object.entries(FILTER_OPERATORS)) {\n if (meta.fieldTypes.includes(fieldType)) {\n operators.push({\n operator,\n label: meta.label\n })\n }\n }\n\n return operators\n}\n\n/**\n * Get ALL filterable fields from schema\n */\nexport function getAllFilterableFields(schema: MetaResponse): MetaField[] {\n const allFields: MetaField[] = []\n\n schema.cubes.forEach(cube => {\n allFields.push(...cube.measures)\n allFields.push(...cube.dimensions)\n })\n\n return allFields.sort((a, b) => a.name.localeCompare(b.name))\n}\n\n// ============================================================================\n// Date range utility functions\n// ============================================================================\n\n/**\n * Convert DateRangeType to Cube.js compatible date range format\n */\nexport function convertDateRangeTypeToValue(rangeType: string, number?: number): string {\n const typeMap: Record<string, string> = {\n 'today': 'today',\n 'yesterday': 'yesterday',\n 'this_week': 'this week',\n 'this_month': 'this month',\n 'this_quarter': 'this quarter',\n 'this_year': 'this year',\n 'last_7_days': 'last 7 days',\n 'last_30_days': 'last 30 days',\n 'last_week': 'last week',\n 'last_month': 'last month',\n 'last_quarter': 'last quarter',\n 'last_year': 'last year',\n 'last_12_months': 'last 12 months'\n }\n\n // Handle dynamic ranges with number input\n if (rangeType.startsWith('last_n_') && number !== undefined && number > 0) {\n const unit = rangeType.replace('last_n_', '')\n const unitSingular = unit.slice(0, -1) // Remove 's' for singular form\n return number === 1 ? `last ${unitSingular}` : `last ${number} ${unit}`\n }\n\n return typeMap[rangeType] || rangeType\n}\n\n/**\n * Check if a date range type requires a number input\n */\nexport function requiresNumberInput(rangeType: string): boolean {\n return rangeType.startsWith('last_n_')\n}\n\n/**\n * Format date for Cube.js (YYYY-MM-DD)\n */\nexport function formatDateForCube(date: Date): string {\n return date.toISOString().split('T')[0]\n}\n","export function stableStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n\n const stringify = (input: unknown): string => {\n if (input === null || typeof input !== 'object') {\n return JSON.stringify(input)\n }\n\n if (seen.has(input as object)) {\n return '\"[Circular]\"'\n }\n seen.add(input as object)\n\n if (Array.isArray(input)) {\n return `[${input.map((item) => stringify(item)).join(',')}]`\n }\n\n const record = input as Record<string, unknown>\n const keys = Object.keys(record).sort()\n const props = keys.map((key) => `${JSON.stringify(key)}:${stringify(record[key])}`)\n return `{${props.join(',')}}`\n }\n\n return stringify(value)\n}\n","/**\n * useDebounceQuery - Shared debounce logic for query hooks\n *\n * This hook encapsulates the common debouncing pattern used by\n * useCubeLoadQuery and useMultiCubeLoadQuery to prevent excessive API calls\n * when users are actively editing queries.\n *\n * Features:\n * - Debounces value changes with configurable delay\n * - Handles skip-to-unskip transitions (e.g., portlet becoming visible)\n * - Clears debounced value when invalid or skipped\n * - Provides isDebouncing state for UI feedback\n */\n\nimport { useState, useEffect, useRef, useMemo } from 'react'\nimport { stableStringify } from '../shared/queryKey'\n\nexport interface UseDebounceQueryOptions {\n /**\n * Whether the value is valid (has required fields)\n */\n isValid: boolean\n /**\n * Whether to skip the debounced value\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n}\n\nexport interface UseDebounceQueryResult<T> {\n /** The debounced value (null if skipped or invalid) */\n debouncedValue: T | null\n /** Whether the hook is currently debouncing (waiting for timer) */\n isDebouncing: boolean\n}\n\n/**\n * Hook for debouncing query values with skip and validity support\n *\n * Usage:\n * ```tsx\n * const { debouncedValue, isDebouncing } = useDebounceQuery(query, {\n * isValid: isValidCubeQuery(query),\n * skip: !isReady,\n * debounceMs: 300\n * })\n * ```\n */\nexport function useDebounceQuery<T>(\n value: T | null,\n options: UseDebounceQueryOptions\n): UseDebounceQueryResult<T> {\n const { isValid, skip = false, debounceMs = 300 } = options\n\n // Debounced state\n const [debouncedValue, setDebouncedValue] = useState<T | null>(null)\n const [isDebouncing, setIsDebouncing] = useState(false)\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const lastValueStringRef = useRef<string>('')\n const wasSkippedRef = useRef<boolean>(skip)\n\n // Serialize value for comparison\n const valueString = useMemo(() => {\n if (!value) return ''\n return stableStringify(value)\n }, [value])\n\n // Debounce the value changes\n useEffect(() => {\n // Detect skip-to-unskip transition (e.g., portlet becoming visible)\n const wasSkipped = wasSkippedRef.current\n const justBecameUnskipped = wasSkipped && !skip\n wasSkippedRef.current = skip\n\n // Skip if value hasn't actually changed AND we haven't just become unskipped\n // The justBecameUnskipped check ensures we re-trigger when visibility changes\n if (valueString === lastValueStringRef.current && !justBecameUnskipped) {\n return\n }\n\n // Clear existing timer\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n\n // If value is valid, set debouncing state and schedule update\n if (isValid && !skip) {\n setIsDebouncing(true)\n debounceTimerRef.current = setTimeout(() => {\n lastValueStringRef.current = valueString\n setDebouncedValue(value)\n setIsDebouncing(false)\n }, debounceMs)\n } else {\n // Clear debounced value if invalid or skipped\n lastValueStringRef.current = valueString\n setDebouncedValue(null)\n setIsDebouncing(false)\n }\n\n return () => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n }\n }, [valueString, isValid, skip, debounceMs, value])\n\n return {\n debouncedValue,\n isDebouncing,\n }\n}\n","/**\n * useCubeLoadQuery - TanStack Query hook for cube data loading\n *\n * Features:\n * - Built-in debouncing to prevent excessive API calls\n * - Automatic query deduplication\n * - Background refetch support\n * - Proper loading/error states\n * - Query key based on query content for caching\n *\n * This hook replaces the manual debouncing and query execution\n * in useQueryExecution.\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo, useState, useCallback, useEffect, useRef } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport type { CubeQuery, CubeResultSet } from '../../types'\nimport type { QueryWarning } from '../../shared/types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key from a CubeQuery\n * The key includes all query parameters to ensure proper caching\n */\nexport function createQueryKey(query: CubeQuery | null): readonly unknown[] {\n if (!query) return ['cube', 'load', null] as const\n // Use JSON.stringify for deep equality comparison\n return ['cube', 'load', stableStringify(query)] as const\n}\n\nexport interface UseCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\n/** Options for the refetch function */\nexport interface RefetchOptions {\n /** If true, bypasses both client and server caches */\n bustCache?: boolean\n}\n\nexport interface UseCubeLoadQueryResult {\n /** The result set from the query */\n resultSet: CubeResultSet | null\n /** Raw data from the result set */\n rawData: unknown[] | null\n /** Whether the query is loading (initial load) */\n isLoading: boolean\n /** Whether the query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** Error if the query failed */\n error: Error | null\n /** The debounced query that was executed */\n debouncedQuery: CubeQuery | null\n /** Whether the current query is valid */\n isValidQuery: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass caches. */\n refetch: (options?: RefetchOptions) => void\n /** Clear the query cache */\n clearCache: () => void\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n /**\n * Execute the current query (manual refresh mode only).\n * In auto-refresh mode, this is the same as refetch().\n */\n executeQuery: (options?: RefetchOptions) => void\n /** Warnings from query planning (e.g., fan-out without dimensions) */\n warnings: QueryWarning[] | undefined\n}\n\n/**\n * Check if a query is valid (has at least one measure or dimension)\n */\nfunction isValidCubeQuery(query: CubeQuery | null): boolean {\n if (!query) return false\n const hasMeasures = Boolean(query.measures && query.measures.length > 0)\n const hasDimensions = Boolean(query.dimensions && query.dimensions.length > 0)\n const hasTimeDimensions = Boolean(query.timeDimensions && query.timeDimensions.length > 0)\n return hasMeasures || hasDimensions || hasTimeDimensions\n}\n\n/**\n * TanStack Query hook for loading cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { resultSet, rawData, isLoading, error } = useCubeLoadQuery(query, {\n * debounceMs: 300,\n * skip: !isReady\n * })\n * ```\n */\nexport function useCubeLoadQuery(\n query: CubeQuery | null,\n options: UseCubeLoadQueryOptions = {}\n): UseCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n // This is the query that was last sent to the server\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidCubeQuery(query)\n\n // Silence unused variable warning - used for future functionality\n void resetResultSetOnChange\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(query, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Transform query for server (converts filter groups)\n const serverQuery = useMemo(() => {\n if (!debouncedQuery) return null\n return cleanQueryForServer(debouncedQuery)\n }, [debouncedQuery])\n\n // Calculate if the current query differs from the last executed query\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // Ref to track when the next fetch should bust the cache\n // This is used instead of replacing the queryFn to avoid the queryFn getting \"stuck\" with bustCache=true\n const bustCacheRef = useRef(false)\n\n // Execute query with TanStack Query\n const queryResult = useQuery({\n queryKey: createQueryKey(serverQuery),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n\n // Check if this fetch should bust the cache\n const shouldBustCache = bustCacheRef.current\n // Reset the flag immediately so subsequent fetches don't bust cache\n bustCacheRef.current = false\n\n // When busting cache, bypass batch coordinator and make direct API call\n if (shouldBustCache) {\n return cubeApi.load(serverQuery, { bustCache: true })\n }\n\n // Use batch coordinator if enabled (collects queries for 100ms window)\n if (enableBatching && batchCoordinator) {\n return batchCoordinator.register(serverQuery)\n }\n\n // Fall back to direct load when batching disabled\n return cubeApi.load(serverQuery)\n },\n enabled: shouldExecute,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Extract raw data from result set\n const rawData = useMemo(() => {\n if (!queryResult.data) return null\n try {\n return queryResult.data.rawData()\n } catch {\n return null\n }\n }, [queryResult.data])\n\n // Extract warnings from result set\n const warnings = useMemo((): QueryWarning[] | undefined => {\n if (!queryResult.data?.loadResponse) return undefined\n const lr = queryResult.data.loadResponse\n // Handle nested structure: loadResponse.results[0].warnings\n if (lr.results && lr.results[0]?.warnings) {\n return lr.results[0].warnings\n }\n // Handle flat structure: loadResponse.warnings\n return lr.warnings\n }, [queryResult.data])\n\n // Execute query function - for manual refresh mode, triggers execution\n // Also serves as refetch in auto mode\n const executeQuery = useCallback((options?: RefetchOptions) => {\n if (!serverQuery) return\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Set the ref flag so the queryFn knows to bypass cache\n // The flag is reset inside queryFn after reading it\n bustCacheRef.current = true\n }\n\n // Invalidate and refetch - invalidateQueries marks as stale AND triggers refetch\n // when the query is being observed (which it is, via useQuery)\n queryClient.invalidateQueries({ queryKey: createQueryKey(serverQuery) })\n }, [serverQuery, currentQueryKey, queryClient])\n\n // Refetch is an alias for executeQuery for backward compatibility\n const refetch = executeQuery\n\n // Clear cache function\n const clearCache = () => {\n queryClient.removeQueries({ queryKey: ['cube', 'load'] })\n }\n\n // Handle resetResultSetOnChange\n const resultSet = useMemo(() => {\n if (resetResultSetOnChange && isDebouncing) {\n // Keep showing old data while debouncing\n return queryResult.data ?? null\n }\n return queryResult.data ?? null\n }, [queryResult.data, isDebouncing, resetResultSetOnChange])\n\n return {\n resultSet,\n rawData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error,\n debouncedQuery,\n isValidQuery,\n refetch,\n clearCache,\n needsRefresh,\n executeQuery,\n warnings,\n }\n}\n","/**\n * Multi-Query Data Utilities\n * Handles merging results from multiple CubeQuery executions\n *\n * Pattern follows comparisonUtils.ts for metadata injection:\n * - __queryIndex: numeric index of the source query (0-based)\n * - __queryLabel: user-defined or auto-generated label for the query\n */\n\nimport type { CubeResultSet, CubeQuery, QueryMergeStrategy } from '../types'\n\n/**\n * Metadata fields injected into multi-query data\n */\nexport interface MultiQueryMetadata {\n __queryIndex: number\n __queryLabel: string\n}\n\n/**\n * Check if data contains multi-query metadata\n */\nexport function isMultiQueryData(data: unknown[]): boolean {\n return data.length > 0 && typeof data[0] === 'object' && data[0] !== null && '__queryIndex' in data[0]\n}\n\n/**\n * Get unique query labels from multi-query data\n */\nexport function getQueryLabels(data: unknown[]): string[] {\n if (!isMultiQueryData(data)) return []\n\n const labels = new Set<string>()\n for (const row of data) {\n const label = (row as Record<string, unknown>).__queryLabel\n if (typeof label === 'string') {\n labels.add(label)\n }\n }\n return Array.from(labels)\n}\n\n/**\n * Get query indices from multi-query data\n */\nexport function getQueryIndices(data: unknown[]): number[] {\n if (!isMultiQueryData(data)) return []\n\n const indices = new Set<number>()\n for (const row of data) {\n const index = (row as Record<string, unknown>).__queryIndex\n if (typeof index === 'number') {\n indices.add(index)\n }\n }\n return Array.from(indices).sort((a, b) => a - b)\n}\n\n/**\n * Merge results using 'concat' strategy\n * Appends all rows with __queryIndex and __queryLabel metadata\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array with query metadata\n */\nexport function mergeResultsConcat(\n resultSets: CubeResultSet[],\n _queries: CubeQuery[],\n labels?: string[]\n): unknown[] {\n const merged: unknown[] = []\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const label = labels?.[queryIndex] || `Query ${queryIndex + 1}`\n\n data.forEach(row => {\n merged.push({\n ...row,\n __queryIndex: queryIndex,\n __queryLabel: label\n })\n })\n })\n\n return merged\n}\n\n/**\n * Merge results using 'merge' strategy\n * Aligns data by common dimensions (composite key), combining measures from all queries\n *\n * Example:\n * Query 1: [{ date: '2024-01', revenue: 100 }]\n * Query 2: [{ date: '2024-01', cost: 50 }]\n * Result: [{ date: '2024-01', revenue: 100, cost: 50 }]\n *\n * If multiple queries have the same measure, the first query's value is used.\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param mergeKeys - Dimension fields to align data on (composite key)\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Merged data array with combined measures\n */\nexport function mergeResultsByKey(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n mergeKeys: string[],\n _labels?: string[]\n): unknown[] {\n const mergedMap = new Map<string, Record<string, unknown>>()\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const measures = queries[queryIndex].measures || []\n\n data.forEach(row => {\n // Create composite key from all merge dimensions\n const keyValue = mergeKeys.map(k => String(row[k] ?? '')).join('|')\n\n if (!mergedMap.has(keyValue)) {\n // Initialize with all dimension values\n const baseRow: Record<string, unknown> = {}\n mergeKeys.forEach(k => { baseRow[k] = row[k] })\n mergedMap.set(keyValue, baseRow)\n }\n\n const mergedRow = mergedMap.get(keyValue)!\n\n // Add measures using raw field names (no prefix)\n // If same measure exists in multiple queries, first one wins\n measures.forEach(measure => {\n if (!(measure in mergedRow)) {\n mergedRow[measure] = row[measure]\n }\n })\n\n // Copy other dimensions (non-measure, non-merge-key fields) from first query\n if (queryIndex === 0) {\n Object.keys(row).forEach(field => {\n if (!mergeKeys.includes(field) && !measures.includes(field)) {\n if (!(field in mergedRow)) {\n mergedRow[field] = row[field]\n }\n }\n })\n }\n })\n })\n\n // Sort by first merge key for consistent ordering\n return Array.from(mergedMap.values()).sort((a, b) => {\n const aKey = String(a[mergeKeys[0]] ?? '')\n const bKey = String(b[mergeKeys[0]] ?? '')\n return aKey.localeCompare(bKey)\n })\n}\n\n/**\n * Main entry point for merging query results\n * Delegates to appropriate strategy implementation\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param strategy - Merge strategy ('concat' or 'merge')\n * @param mergeKeys - Dimension fields to align on (required for 'merge' strategy)\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array\n */\nexport function mergeQueryResults(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n strategy: QueryMergeStrategy,\n mergeKeys?: string[],\n labels?: string[]\n): unknown[] {\n // Handle edge cases\n if (resultSets.length === 0) return []\n if (resultSets.length === 1) return resultSets[0].rawData()\n\n // Use merge strategy if we have merge keys\n if (strategy === 'merge' && mergeKeys && mergeKeys.length > 0) {\n return mergeResultsByKey(resultSets, queries, mergeKeys, labels)\n }\n\n // Fall back to concat strategy\n return mergeResultsConcat(resultSets, queries, labels)\n}\n\n/**\n * Get combined fields from all queries\n * Used for chart configuration to show all available measures/dimensions\n *\n * @param queries - Array of CubeQuery objects\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Object containing combined measures, dimensions, and time dimensions\n */\nexport function getCombinedFields(\n queries: CubeQuery[],\n _labels?: string[]\n): {\n measures: string[]\n dimensions: string[]\n timeDimensions: string[]\n} {\n const measures = new Set<string>()\n const dimensions = new Set<string>()\n const timeDimensions = new Set<string>()\n\n queries.forEach((query) => {\n // Measures use raw field names (no prefix), de-duplicated\n query.measures?.forEach(m => measures.add(m))\n\n // Dimensions are shared across queries (de-duplicated)\n query.dimensions?.forEach(d => dimensions.add(d))\n\n // Time dimensions are also shared\n query.timeDimensions?.forEach(td => timeDimensions.add(td.dimension))\n })\n\n return {\n measures: Array.from(measures),\n dimensions: Array.from(dimensions),\n timeDimensions: Array.from(timeDimensions)\n }\n}\n\n/**\n * Generate a default label for a query based on its measures\n * Used when user doesn't provide custom labels\n */\nexport function generateQueryLabel(query: CubeQuery, index: number): string {\n // Try to use first measure name without cube prefix\n if (query.measures && query.measures.length > 0) {\n const firstMeasure = query.measures[0]\n const parts = firstMeasure.split('.')\n if (parts.length > 1) {\n return parts[parts.length - 1] // Use measure name without cube prefix\n }\n return firstMeasure\n }\n\n // Fall back to indexed label\n return `Query ${index + 1}`\n}\n\n/**\n * Validate merge key exists in all queries\n * Returns validation result with details\n */\nexport function validateMergeKey(\n queries: CubeQuery[],\n mergeKey: string\n): {\n isValid: boolean\n missingInQueries: number[]\n} {\n const missingInQueries: number[] = []\n\n queries.forEach((query, index) => {\n const allDimensions = [\n ...(query.dimensions || []),\n ...(query.timeDimensions?.map(td => td.dimension) || [])\n ]\n\n if (!allDimensions.includes(mergeKey)) {\n missingInQueries.push(index)\n }\n })\n\n return {\n isValid: missingInQueries.length === 0,\n missingInQueries\n }\n}\n","/**\n * useMultiCubeLoadQuery - TanStack Query hook for multi-cube data loading\n *\n * Features:\n * - Execute multiple cube queries in parallel\n * - Merge results using configurable strategies\n * - Built-in debouncing to prevent excessive API calls\n * - Per-query error tracking\n * - BatchCoordinator integration for dashboard-level batching\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { MultiQueryConfig, CubeResultSet } from '../../types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { mergeQueryResults } from '../../utils/multiQueryUtils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key for multi-query\n */\nexport function createMultiQueryKey(\n config: MultiQueryConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'multiLoad', null] as const\n return ['cube', 'multiLoad', stableStringify(config)] as const\n}\n\nexport interface UseMultiCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\nexport interface UseMultiCubeLoadQueryResult {\n /** Merged data from all queries */\n data: unknown[] | null\n /** Individual result sets from each query */\n resultSets: (CubeResultSet | null)[] | null\n /** Per-query raw data */\n perQueryData: (unknown[] | null)[] | null\n /** Whether any query is still loading (initial load) */\n isLoading: boolean\n /** Whether any query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** First error encountered */\n error: Error | null\n /** Per-query errors */\n errors: (Error | null)[]\n /** The debounced config that was executed */\n debouncedConfig: MultiQueryConfig | null\n /** Whether the current config is valid */\n isValidConfig: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n}\n\n/**\n * Check if a MultiQueryConfig is valid (has at least 2 valid queries)\n */\nfunction isValidMultiQueryConfig(config: MultiQueryConfig | null): boolean {\n if (!config || !config.queries || config.queries.length < 2) return false\n\n const validQueries = config.queries.filter(\n (q) =>\n (q.measures && q.measures.length > 0) ||\n (q.dimensions && q.dimensions.length > 0) ||\n (q.timeDimensions && q.timeDimensions.length > 0)\n )\n\n return validQueries.length >= 2\n}\n\n/**\n * TanStack Query hook for loading multi-cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useMultiCubeLoadQuery(config, {\n * debounceMs: 300,\n * skip: !isMultiQueryMode\n * })\n * ```\n */\nexport function useMultiCubeLoadQuery(\n config: MultiQueryConfig | null,\n options: UseMultiCubeLoadQueryOptions = {}\n): UseMultiCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange: _resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n // Silence unused variable warning - used for future functionality\n void _resetResultSetOnChange\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Validate config\n const isValidConfig = isValidMultiQueryConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Transform queries for server\n const serverConfig = useMemo(() => {\n if (!debouncedConfig) return null\n return {\n ...debouncedConfig,\n queries: debouncedConfig.queries.map((q) => cleanQueryForServer(q)),\n }\n }, [debouncedConfig])\n\n // Execute multi-query with TanStack Query\n const queryResult = useQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n if (!serverConfig) throw new Error('No config provided')\n\n let resultSets: CubeResultSet[]\n\n // Use BatchCoordinator if enabled\n if (enableBatching && batchCoordinator) {\n resultSets = await Promise.all(\n serverConfig.queries.map((query) => batchCoordinator.register(query))\n )\n } else {\n // Direct batch call\n resultSets = await cubeApi.batchLoad(serverConfig.queries)\n }\n\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n\n // Get per-query raw data\n const perQueryData: (unknown[] | null)[] = resultSets.map((rs, i) => {\n if (errors[i]) return null\n try {\n return rs.rawData()\n } catch {\n return null\n }\n })\n\n // Filter successful results for merging\n const successfulResults = resultSets.filter((_, i) => !errors[i])\n const successfulQueries = serverConfig.queries.filter((_, i) => !errors[i])\n\n // Merge results using configured strategy\n const data =\n successfulResults.length > 0\n ? mergeQueryResults(\n successfulResults,\n successfulQueries,\n serverConfig.mergeStrategy,\n serverConfig.mergeKeys,\n serverConfig.queryLabels\n )\n : []\n\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n enabled: !!serverConfig && !skip,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // Refetch function - forces immediate refetch\n // Pass { bustCache: true } to bypass both client and server caches\n const refetch = (options?: { bustCache?: boolean }) => {\n if (serverConfig) {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n // Direct batch call with bustCache\n const resultSets = await cubeApi.batchLoad(\n serverConfig.queries,\n { bustCache: true }\n )\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n // Merge results based on strategy\n const data = serverConfig.mergeStrategy === 'concat'\n ? resultSets.flatMap((rs) => rs?.rawData() || [])\n : resultSets[0]?.rawData() || []\n // Keep per-query data for table views\n const perQueryData = serverConfig.mergeStrategy === 'concat'\n ? resultSets.map((rs) => rs?.rawData() || [])\n : []\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n })\n } else {\n queryClient.refetchQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n }\n }\n }\n\n // Extract data from query result\n const data = queryResult.data?.data ?? null\n const resultSets = queryResult.data?.resultSets ?? null\n const perQueryData = queryResult.data?.perQueryData ?? null\n const errors = queryResult.data?.errors ?? []\n const error = queryResult.data?.firstError ?? queryResult.error\n\n return {\n data,\n resultSets,\n perQueryData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error,\n errors,\n debouncedConfig,\n isValidConfig,\n refetch,\n }\n}\n","/**\n * useFunnelQuery - Hook for server-side funnel query execution\n *\n * Executes funnel queries on the server using a single SQL query with\n * CTE-based generation. This provides:\n * - True temporal ordering (step N must occur AFTER step N-1)\n * - Time window enforcement (timeToConvert constraints)\n * - No binding key value limits\n * - Time-to-convert metrics (avg, median, P90)\n *\n * Previously this hook used client-side sequential execution. The server-side\n * approach is strictly better and the data shapes are compatible.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n FunnelConfig,\n FunnelChartData,\n UseFunnelQueryOptions,\n UseFunnelQueryResult,\n FunnelStepResult,\n FunnelExecutionResult,\n} from '../../types/funnel'\nimport {\n buildServerFunnelQuery,\n transformServerFunnelResult,\n} from '../../utils/funnelExecution'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Check if a FunnelConfig is valid for execution\n */\nfunction isValidFunnelConfig(config: FunnelConfig | null): boolean {\n if (!config) return false\n if (!config.bindingKey) return false\n if (!config.steps || config.steps.length < 2) return false\n\n // Check that binding key dimension is defined\n if (typeof config.bindingKey.dimension === 'string') {\n if (!config.bindingKey.dimension) return false\n } else if (Array.isArray(config.bindingKey.dimension)) {\n if (config.bindingKey.dimension.length === 0) return false\n }\n\n // Check that each step has a valid query\n // For funnels, a step can have:\n // - measures/dimensions/timeDimensions (standard fields), OR\n // - filters only (the binding key dimension is auto-added by buildStepQuery)\n for (const step of config.steps) {\n const query = step.query\n const hasFields =\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0) ||\n (query.filters && query.filters.length > 0)\n if (!hasFields) return false\n }\n\n return true\n}\n\n/**\n * Hook for server-side funnel query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isExecuting, error } = useFunnelQuery(config, {\n * debounceMs: 300,\n * skip: !hasBindingKey\n * })\n *\n * // Results available after single server request\n * <FunnelChart data={chartData} />\n * ```\n */\nexport function useFunnelQuery(\n config: FunnelConfig | null,\n options: UseFunnelQueryOptions = {}\n): UseFunnelQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n prebuiltServerQuery,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate config\n const isValidConfig = isValidFunnelConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Build server query from config (or use prebuilt if provided)\n const serverQuery = useMemo(() => {\n // If prebuiltServerQuery is provided, use it directly\n if (prebuiltServerQuery) {\n return prebuiltServerQuery\n }\n\n // Otherwise build from config (legacy mode)\n if (!debouncedConfig || !isValidConfig) {\n return null\n }\n\n try {\n const result = buildServerFunnelQuery(\n debouncedConfig.steps.map(s => s.query),\n debouncedConfig.bindingKey,\n debouncedConfig.steps.map(s => s.name),\n debouncedConfig.steps.map(s => s.timeToConvert || null),\n true // includeTimeMetrics\n )\n return result\n } catch (error) {\n console.error('Failed to build server funnel query:', error)\n return null\n }\n }, [prebuiltServerQuery, debouncedConfig, isValidConfig])\n\n // Create stable query key\n // Include step count explicitly to ensure cache invalidation when steps change\n const queryKey = useMemo(() => {\n if (!serverQuery) return ['cube', 'funnel', null] as const\n const stepCount = serverQuery.funnel?.steps?.length || 0\n return ['cube', 'funnel', stepCount, JSON.stringify(serverQuery)] as const\n }, [serverQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Execute funnel query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!serverQuery) {\n throw new Error('No server query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send funnel query to server (single request)\n const resultSet = await cubeApi.load(serverQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err, 0)\n throw err\n }\n },\n // Enable when we have a server query (either prebuilt or built from config)\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Get step names from either config or prebuilt server query\n const stepNames = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.map(s => s.name)\n }\n return debouncedConfig?.steps?.map(s => s.name)\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Get expected step count from either config or prebuilt server query\n const expectedStepCount = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.length\n }\n return debouncedConfig?.steps?.length || 0\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Transform server result to chart data\n // Validate step count matches to prevent showing stale data during transitions\n const chartData = useMemo<FunnelChartData[]>(() => {\n if (!queryResult.data?.rawData) return []\n\n // Check if data step count matches expected step count\n const dataStepCount = queryResult.data.rawData.length\n\n if (dataStepCount !== expectedStepCount) {\n // Data is stale (from a different query) - don't return it\n // This prevents showing mismatched step counts while a new query loads\n return []\n }\n\n return transformServerFunnelResult(\n queryResult.data.rawData,\n stepNames\n )\n }, [queryResult.data, expectedStepCount, stepNames])\n\n // Build step results from chart data (for backward compatibility)\n const stepResults = useMemo<FunnelStepResult[]>(() => {\n if (!chartData.length) return []\n\n const firstCount = chartData[0]?.value || 0\n\n return chartData.map((data, index) => ({\n stepIndex: index,\n stepName: data.name,\n // Get step ID from config, or generate one for prebuilt queries\n stepId: debouncedConfig?.steps?.[index]?.id || `step-${index}`,\n data: [], // Raw data not available from server funnel\n bindingKeyValues: [], // Not available from server funnel\n bindingKeyTotalCount: 0,\n count: data.value,\n conversionRate: data.conversionRate !== null ? data.conversionRate / 100 : null,\n cumulativeConversionRate: firstCount > 0 ? data.value / firstCount : 0,\n executionTime: queryResult.data?.executionTime || 0,\n error: null,\n }))\n }, [chartData, debouncedConfig, queryResult.data?.executionTime])\n\n // Build full result for compatibility\n const result = useMemo<FunnelExecutionResult | null>(() => {\n // Need either config or prebuilt query for results\n if (!chartData.length) return null\n if (!debouncedConfig && !prebuiltServerQuery) return null\n\n const firstCount = chartData[0]?.value || 0\n const lastCount = chartData[chartData.length - 1]?.value || 0\n\n // Create a config object (use debouncedConfig if available, else synthesize from prebuilt)\n const effectiveConfig: FunnelConfig = debouncedConfig || {\n id: 'prebuilt-funnel',\n name: 'Funnel Analysis',\n bindingKey: {\n dimension: typeof prebuiltServerQuery?.funnel?.bindingKey === 'string'\n ? prebuiltServerQuery.funnel.bindingKey\n : prebuiltServerQuery?.funnel?.bindingKey?.[0]?.dimension || ''\n },\n steps: (prebuiltServerQuery?.funnel?.steps || []).map((s, i) => ({\n id: `step-${i}`,\n name: s.name,\n query: { filters: s.filter ? [s.filter as unknown as import('../../types').Filter] : [] },\n timeToConvert: s.timeToConvert || undefined,\n })),\n }\n\n const fullResult: FunnelExecutionResult = {\n config: effectiveConfig,\n steps: stepResults,\n summary: {\n totalEntries: firstCount,\n totalCompletions: lastCount,\n overallConversionRate: firstCount > 0 ? lastCount / firstCount : 0,\n totalExecutionTime: queryResult.data?.executionTime || 0,\n },\n chartData,\n status: queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle',\n error: queryResult.error as Error | null,\n currentStepIndex: null,\n }\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(fullResult)\n }\n\n return fullResult\n }, [debouncedConfig, prebuiltServerQuery, chartData, stepResults, queryResult, onComplete])\n\n // Determine current status\n const status: FunnelExecutionResult['status'] = queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle'\n\n /**\n * Manually execute/refetch the funnel query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const execute = useCallback(async (options?: { bustCache?: boolean }): Promise<FunnelExecutionResult | null> => {\n // Allow execution if we have a serverQuery (either from prebuiltServerQuery or built from config)\n if (!serverQuery) return null\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n try {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n serverQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n await queryResult.refetch()\n }\n return result\n } catch {\n return result\n }\n }, [serverQuery, queryResult, result, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Cancel is a no-op for TanStack Query (handled automatically)\n */\n const cancel = useCallback(() => {\n // TanStack Query handles cancellation automatically\n }, [])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n result,\n status,\n isExecuting: queryResult.isLoading || queryResult.isFetching,\n isDebouncing,\n currentStepIndex: null, // Not applicable for server-side execution\n stepLoadingStates: [], // Not applicable for server-side execution\n stepResults,\n chartData,\n error: queryResult.error as Error | null,\n execute,\n cancel,\n reset,\n // Not exposing executedQueries - server builds the query internally\n executedQueries: [],\n // Expose the server query for debug panel display\n // This is the actual { funnel: {...} } query sent to the server\n serverQuery,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for funnel queries\n */\nexport function createFunnelQueryKey(\n config: FunnelConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'funnel', null] as const\n // Create a stable key based on config\n return ['cube', 'funnel', JSON.stringify(config)] as const\n}\n","/**\n * useFlowQuery - Hook for server-side flow query execution\n *\n * Executes flow queries on the server for bidirectional Sankey chart data.\n * Flow queries explore paths BEFORE and AFTER a defined starting step.\n *\n * The server returns { nodes: [], links: [] } structure ready for Sankey visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerFlowQuery,\n FlowChartData,\n} from '../../types/flow'\nimport { isSankeyData } from '../../types/flow'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for useFlowQuery hook\n */\nexport interface UseFlowQueryOptions {\n /** Skip query execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes successfully */\n onComplete?: (data: FlowChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n}\n\n/**\n * Result from useFlowQuery hook\n */\nexport interface UseFlowQueryResult {\n /** Transformed flow chart data (nodes and links) */\n data: FlowChartData | null\n /** Raw data from server */\n rawData: unknown[] | null\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n /** Is initial load in progress */\n isLoading: boolean\n /** Is refetch in progress */\n isFetching: boolean\n /** Is waiting for debounce */\n isDebouncing: boolean\n /** Is executing (loading or fetching) */\n isExecuting: boolean\n /** Error if query failed */\n error: Error | null\n /** Refetch the query. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n /** Reset the query cache */\n reset: () => void\n /** The server query being executed */\n serverQuery: ServerFlowQuery | null\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current flow config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerFlowQuery is valid for execution\n */\nfunction isValidFlowQuery(query: ServerFlowQuery | null): boolean {\n if (!query?.flow) return false\n\n const { flow } = query\n\n // Must have binding key\n if (!flow.bindingKey) return false\n\n // Must have time dimension\n if (!flow.timeDimension) return false\n\n // Must have event dimension\n if (!flow.eventDimension) return false\n\n // Must have starting step with filter\n if (!flow.startingStep?.filter) return false\n\n // Must have valid depth\n if (flow.stepsBefore < 0 || flow.stepsBefore > 5) return false\n if (flow.stepsAfter < 0 || flow.stepsAfter > 5) return false\n\n return true\n}\n\n/**\n * Transform raw server result to FlowChartData\n */\nfunction transformFlowResult(rawData: unknown[]): FlowChartData | null {\n // Server returns a single row with { nodes: [], links: [] } structure\n if (rawData.length === 1) {\n const row = rawData[0]\n if (row && typeof row === 'object' && 'nodes' in row && 'links' in row) {\n return row as FlowChartData\n }\n }\n\n // Alternative: Server might return nodes and links as separate items\n // or the entire array might be the flow result\n if (rawData.length > 0) {\n const firstItem = rawData[0]\n // Check if it looks like Sankey data using type guard\n if (firstItem && typeof firstItem === 'object' && isSankeyData(firstItem)) {\n return firstItem as FlowChartData\n }\n }\n\n return null\n}\n\n/**\n * Hook for server-side flow query execution\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useFlowQuery(serverFlowQuery, {\n * debounceMs: 300,\n * skip: !isConfigured\n * })\n *\n * // Results available after single server request\n * <SankeyChart data={data} />\n * ```\n */\nexport function useFlowQuery(\n query: ServerFlowQuery | null,\n options: UseFlowQueryOptions = {}\n): UseFlowQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValid = isValidFlowQuery(query)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(\n query,\n {\n isValid,\n skip,\n debounceMs,\n }\n )\n\n // Create stable query key string for the debounced query (used for TanStack Query cache key)\n const queryKeyString = useMemo(() => {\n if (!debouncedQuery) return null\n return JSON.stringify(debouncedQuery)\n }, [debouncedQuery])\n\n // Create stable query key string for the RAW input query (used for staleness detection)\n // This detects when the input query has changed but debounce hasn't completed yet\n const rawQueryKeyString = useMemo(() => {\n if (!query) return null\n return JSON.stringify(query)\n }, [query])\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'flow', null] as const\n return ['cube', 'flow', queryKeyString] as const\n }, [debouncedQuery, queryKeyString])\n\n // Calculate current query key for manual refresh tracking (uses raw input query)\n const currentQueryKey = query ? stableStringify(query) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever query is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!isValid || !debouncedQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [isValid, debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && query && !skip && isValid) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, query, skip, isValid, currentQueryKey])\n\n // Execute flow query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No flow query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send flow query to server (single request)\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n // Include query key in result so we can detect stale data\n // (data from a different query key, e.g., sankey vs sunburst mode)\n // We store the RAW query key so we can compare against the current raw query\n queryKeyString: rawQueryKeyString,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Check if data is stale (from a different query key)\n // This happens when switching between sankey/sunburst modes\n // We compare the current RAW query key with the one stored in the data\n // This catches staleness even during debounce (when debouncedQuery hasn't updated yet)\n const isDataStale = rawQueryKeyString !== null &&\n queryResult.data?.queryKeyString !== undefined &&\n queryResult.data.queryKeyString !== rawQueryKeyString\n\n // Transform server result to chart data\n // Return null if data is stale (from a different query) to show loading instead\n const chartData = useMemo<FlowChartData | null>(() => {\n // If data is stale (from different query), return null to show loading\n if (isDataStale) return null\n\n if (!queryResult.data?.rawData) return null\n\n const transformed = transformFlowResult(queryResult.data.rawData)\n\n // Call completion callback\n if (transformed && queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(transformed)\n }\n\n return transformed\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, isDataStale])\n\n /**\n * Refetch the flow query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const refetch = useCallback((options?: { bustCache?: boolean }) => {\n if (debouncedQuery && isValid) {\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n queryResult.refetch()\n }\n }\n }, [debouncedQuery, isValid, queryResult, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n data: chartData,\n rawData: isDataStale ? null : (queryResult.data?.rawData ?? null),\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n isLoading: queryResult.isLoading || isDataStale,\n isFetching: queryResult.isFetching,\n isDebouncing,\n isExecuting: queryResult.isLoading || queryResult.isFetching || isDataStale,\n error: queryResult.error as Error | null,\n refetch,\n reset,\n serverQuery: debouncedQuery,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for flow queries\n */\nexport function createFlowQueryKey(\n query: ServerFlowQuery | null\n): readonly unknown[] {\n if (!query) return ['cube', 'flow', null] as const\n return ['cube', 'flow', JSON.stringify(query)] as const\n}\n","/**\n * useRetentionQuery - Hook for server-side retention query execution\n *\n * Executes retention queries on the server using SQL generation.\n * Returns cohort-based retention data for heatmap visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerRetentionQuery,\n RetentionChartData,\n RetentionResultRow,\n RetentionSummary,\n RetentionGranularity,\n} from '../../types/retention'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for retention query hook\n */\nexport interface UseRetentionQueryOptions {\n /** Skip execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes */\n onComplete?: (result: RetentionChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n /** Function to resolve field names to human-readable display labels */\n getFieldLabel?: (fieldName: string) => string\n}\n\n/**\n * Result from retention query hook\n */\nexport interface UseRetentionQueryResult {\n /** Retention chart data */\n chartData: RetentionChartData | null\n\n /** Raw data rows from server */\n rawData: RetentionResultRow[] | null\n\n /** Current execution status */\n status: 'idle' | 'loading' | 'success' | 'error'\n\n /** Whether currently loading */\n isLoading: boolean\n\n /** Whether fetching (includes refetch) */\n isFetching: boolean\n\n /** Whether waiting for debounce */\n isDebouncing: boolean\n\n /** Error if execution failed */\n error: Error | null\n\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n\n /** Execute the query (for manual refresh mode) */\n execute: (options?: { bustCache?: boolean }) => Promise<RetentionChartData | null>\n\n /** Refetch the query */\n refetch: () => void\n\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerRetentionQuery is valid for execution\n * Uses new simplified Mixpanel-style format with single timeDimension\n */\nfunction isValidRetentionQuery(query: ServerRetentionQuery | null): boolean {\n if (!query) return false\n if (!query.retention) return false\n if (!query.retention.timeDimension) return false\n if (!query.retention.bindingKey) return false\n if (!query.retention.periods || query.retention.periods < 1) return false\n return true\n}\n\n/**\n * Extract the human-readable label from the binding key\n * e.g., \"Users.userId\" → \"userId\", \"Events.customerId\" → \"customerId\"\n */\nfunction extractBindingKeyLabel(\n bindingKey: ServerRetentionQuery['retention']['bindingKey'] | undefined\n): string | undefined {\n if (!bindingKey) return undefined\n\n // String format: \"Cube.dimensionName\" → \"dimensionName\"\n if (typeof bindingKey === 'string') {\n return bindingKey.split('.').pop()\n }\n\n // Array format: [{ cube, dimension }] → extract first dimension's name\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n const firstMapping = bindingKey[0]\n if (firstMapping?.dimension) {\n return firstMapping.dimension.split('.').pop()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract breakdown value from server response\n * Server returns breakdownValues as an object like {\"PREvents.eventType\": \"approved\"}\n * We extract the first (and typically only) value from this object\n */\nfunction extractBreakdownValue(\n breakdownValues: unknown\n): string | null {\n // If it's already a string, return it\n if (typeof breakdownValues === 'string') {\n return breakdownValues\n }\n\n // If it's an object, extract the first value\n if (breakdownValues && typeof breakdownValues === 'object' && !Array.isArray(breakdownValues)) {\n const values = Object.values(breakdownValues as Record<string, unknown>)\n if (values.length > 0 && values[0] != null) {\n return String(values[0])\n }\n }\n\n return null\n}\n\n/**\n * Transform raw server data to RetentionChartData format\n * New simplified format: rows with period, cohortSize, retainedUsers, retentionRate, breakdownValue\n */\nfunction transformRetentionResult(\n rawData: unknown[],\n granularity?: RetentionGranularity,\n bindingKeyLabel?: string\n): RetentionChartData {\n if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {\n return { rows: [], periods: [], granularity, bindingKeyLabel }\n }\n\n const rows: RetentionResultRow[] = rawData.map((row: unknown) => {\n const r = row as Record<string, unknown>\n return {\n period: Number(r.period ?? r.period_number ?? 0),\n cohortSize: Number(r.cohortSize ?? r.cohort_size ?? 0),\n retainedUsers: Number(r.retainedUsers ?? r.retained_users ?? 0),\n retentionRate: Number(r.retentionRate ?? r.retention_rate ?? 0),\n // Server returns breakdownValues (object) or breakdownValue (string) or breakdown_value (snake_case)\n breakdownValue: extractBreakdownValue(r.breakdownValues) ?? r.breakdownValue ?? r.breakdown_value ?? null,\n } as RetentionResultRow\n })\n\n // Extract unique periods and breakdown values\n const periodsSet = new Set<number>()\n const breakdownSet = new Set<string>()\n\n rows.forEach((row) => {\n periodsSet.add(row.period)\n if (row.breakdownValue) {\n breakdownSet.add(row.breakdownValue)\n }\n })\n\n const periods = Array.from(periodsSet).sort((a, b) => a - b)\n const breakdownValues = breakdownSet.size > 0 ? Array.from(breakdownSet).sort() : undefined\n\n // Calculate summary statistics\n const summary = calculateSummary(rows, breakdownValues)\n\n return { rows, periods, breakdownValues, summary, granularity, bindingKeyLabel }\n}\n\n/**\n * Calculate summary statistics from retention data\n */\nfunction calculateSummary(\n rows: RetentionResultRow[],\n breakdownValues?: string[]\n): RetentionSummary {\n const period1Rows = rows.filter((r) => r.period === 1)\n const period1Rates = period1Rows.map((r) => r.retentionRate)\n\n const totalUsers = rows\n .filter((r) => r.period === 0)\n .reduce((sum, r) => sum + r.cohortSize, 0)\n\n return {\n totalUsers,\n avgPeriod1Retention:\n period1Rates.length > 0\n ? period1Rates.reduce((a, b) => a + b, 0) / period1Rates.length\n : 0,\n maxPeriod1Retention: period1Rates.length > 0 ? Math.max(...period1Rates) : 0,\n minPeriod1Retention: period1Rates.length > 0 ? Math.min(...period1Rates) : 0,\n segmentCount: breakdownValues?.length || 1,\n }\n}\n\n/**\n * Hook for server-side retention query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isLoading, error } = useRetentionQuery(serverQuery, {\n * debounceMs: 300,\n * skip: !hasRequiredFields\n * })\n *\n * <RetentionHeatmap data={chartData} />\n * ```\n */\nexport function useRetentionQuery(\n serverQuery: ServerRetentionQuery | null,\n options: UseRetentionQueryOptions = {}\n): UseRetentionQueryResult {\n const { skip = false, debounceMs = DEFAULT_DEBOUNCE_MS, onComplete, onError, getFieldLabel } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidRetentionQuery(serverQuery)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(serverQuery, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'retention', null] as const\n return ['cube', 'retention', JSON.stringify(debouncedQuery)] as const\n }, [debouncedQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = debouncedQuery ? stableStringify(debouncedQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n if (executedQueryKey === null) return false\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n const shouldExecute = useMemo(() => {\n if (!debouncedQuery || skip) return false\n if (!manualRefresh) return true\n if (executedQueryKey === null) return true\n return executedQueryKey === currentQueryKey\n }, [debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n useEffect(() => {\n if (!manualRefresh && debouncedQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, debouncedQuery, skip, currentQueryKey])\n\n // Execute retention query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No retention query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send retention query to server\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n useEffect(() => {\n if (!manualRefresh) return\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Extract granularity and binding key label from server query\n const granularity = serverQuery?.retention?.granularity\n\n // Get the raw binding key field name for label lookup\n const rawBindingKeyField = useMemo(() => {\n const bindingKey = serverQuery?.retention?.bindingKey\n if (!bindingKey) return undefined\n if (typeof bindingKey === 'string') return bindingKey\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n return bindingKey[0]?.dimension\n }\n return undefined\n }, [serverQuery?.retention?.bindingKey])\n\n // Use getFieldLabel if provided, fallback to extractBindingKeyLabel\n const bindingKeyLabel = useMemo(() => {\n if (rawBindingKeyField && getFieldLabel) {\n const label = getFieldLabel(rawBindingKeyField)\n // If getFieldLabel returns the same string, it didn't find a better label\n if (label && label !== rawBindingKeyField) {\n return label\n }\n }\n // Fallback to extracting from the field name\n return extractBindingKeyLabel(serverQuery?.retention?.bindingKey)\n }, [rawBindingKeyField, getFieldLabel, serverQuery?.retention?.bindingKey])\n\n // Transform server result to chart data\n const chartData = useMemo<RetentionChartData | null>(() => {\n if (!queryResult.data?.rawData) return null\n const result = transformRetentionResult(\n queryResult.data.rawData,\n granularity,\n bindingKeyLabel\n )\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(result)\n }\n\n return result\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, granularity, bindingKeyLabel])\n\n // Execute function for manual refresh mode\n const execute = useCallback(\n async (executeOptions?: { bustCache?: boolean }) => {\n if (!debouncedQuery) return null\n\n if (executeOptions?.bustCache) {\n queryClient.removeQueries({ queryKey })\n }\n\n // Update executed query key to trigger execution\n setExecutedQueryKey(currentQueryKey)\n\n // Wait for the query to complete\n const result = await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime: 0, cacheInfo }\n },\n })\n\n return transformRetentionResult(result.rawData, granularity, bindingKeyLabel)\n },\n [debouncedQuery, queryClient, queryKey, cubeApi, currentQueryKey, granularity, bindingKeyLabel]\n )\n\n // Refetch function\n const refetch = useCallback(() => {\n queryResult.refetch()\n }, [queryResult])\n\n // Determine status\n const status = useMemo(() => {\n if (queryResult.isError) return 'error' as const\n if (queryResult.isLoading) return 'loading' as const\n if (queryResult.isSuccess) return 'success' as const\n return 'idle' as const\n }, [queryResult.isError, queryResult.isLoading, queryResult.isSuccess])\n\n return {\n chartData,\n rawData: queryResult.data?.rawData as RetentionResultRow[] | null ?? null,\n status,\n isLoading: queryResult.isLoading,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error as Error | null,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n execute,\n refetch,\n needsRefresh,\n }\n}\n","/**\n * Hook for fetching distinct field values for filter dropdowns\n * Uses TanStack Query via useCubeLoadQuery for data fetching\n */\n\nimport { useState, useCallback, useRef, useMemo, useEffect } from 'react'\nimport { useCubeLoadQuery } from './queries/useCubeLoadQuery'\nimport type { CubeQuery } from '../types'\n\ninterface UseFilterValuesResult {\n values: any[]\n loading: boolean\n error: string | null\n refetch: () => void\n searchValues: (searchTerm: string, force?: boolean) => void\n}\n\n/**\n * Custom hook to fetch distinct values for a field\n *\n * Uses TanStack Query for server state (data fetching, caching, loading).\n * Values are derived via useMemo from query results - NOT stored in useState.\n */\nexport function useFilterValues(\n fieldName: string | null,\n enabled: boolean = true\n): UseFilterValuesResult {\n const [currentQuery, setCurrentQuery] = useState<CubeQuery | null>(null)\n const lastSearchTerm = useRef<string>('')\n\n // Use TanStack Query hook for data fetching\n const {\n resultSet,\n isLoading,\n error: queryError,\n } = useCubeLoadQuery(currentQuery, {\n skip: !currentQuery || !enabled || !fieldName,\n debounceMs: 150, // Quick debounce for filter searches\n keepPreviousData: true,\n })\n\n // Derive values from resultSet using useMemo (NOT useState)\n // This is the correct pattern - server state stays in TanStack Query\n const values = useMemo(() => {\n // Return empty if no result set, loading, or error\n if (!resultSet || isLoading || queryError || !fieldName) {\n return []\n }\n\n try {\n const data = resultSet.tablePivot()\n const uniqueValues = new Set<any>()\n\n data.forEach((row: any) => {\n const value = row[fieldName]\n if (value !== null && value !== undefined && value !== '') {\n uniqueValues.add(value)\n }\n })\n\n // Convert to array - already sorted by query\n return Array.from(uniqueValues)\n } catch (err) {\n console.error('Error extracting values from result set:', err)\n return []\n }\n }, [resultSet, isLoading, queryError, fieldName])\n\n // Reset query when fieldName becomes null or enabled changes\n useEffect(() => {\n if (!fieldName || !enabled) {\n setCurrentQuery(null)\n lastSearchTerm.current = ''\n }\n }, [fieldName, enabled])\n\n // Refetch function\n const refetch = useCallback(() => {\n if (!fieldName) return\n\n lastSearchTerm.current = ''\n\n try {\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating query:', err)\n }\n }, [fieldName])\n\n // Search function for server-side filtering\n const searchValues = useCallback((searchTerm: string, force: boolean = false) => {\n if (!fieldName) {\n return\n }\n\n // Don't create a new query if the search term hasn't changed (unless forced)\n if (!force && searchTerm === lastSearchTerm.current) {\n return\n }\n\n lastSearchTerm.current = searchTerm\n\n try {\n // Create query inline to avoid dependency issues\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n\n if (searchTerm && searchTerm.trim()) {\n query.filters = [{\n member: fieldName,\n operator: 'contains',\n values: [searchTerm.trim()]\n }]\n }\n\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating search query:', err)\n }\n }, [fieldName])\n\n return {\n values,\n loading: isLoading,\n error: queryError ? (queryError instanceof Error ? queryError.message : String(queryError)) : null,\n refetch,\n searchValues\n }\n}\n","/**\n * Custom hook for debouncing values\n * Delays updating the value until after the specified delay has passed\n * since the last change\n */\n\nimport { useState, useEffect } from 'react'\n\n/**\n * Debounces a value by the specified delay\n * @param value The value to debounce\n * @param delay The delay in milliseconds\n * @returns The debounced value\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value)\n\n useEffect(() => {\n // Set up a timer to update the debounced value after the delay\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n // Clean up the timer if the value changes before the delay\n return () => {\n clearTimeout(handler)\n }\n }, [value, delay])\n\n return debouncedValue\n}","/**\n * Custom hook for responsive dashboard layout management\n * Implements a three-tier responsive strategy:\n * - Desktop (1200px+): Normal grid layout with full editing\n * - Scaled (768-1199px): CSS transform scaling, read-only\n * - Mobile (<768px): Single-column stacked layout, read-only\n */\n\nimport { useState, useEffect, useRef, useMemo, useCallback, RefCallback } from 'react'\n\nexport type DashboardDisplayMode = 'desktop' | 'scaled' | 'mobile'\n\nconst DESIGN_WIDTH = 1200\nconst MOBILE_THRESHOLD = 768\n\nexport interface UseResponsiveDashboardResult {\n containerRef: RefCallback<HTMLDivElement>\n containerWidth: number\n displayMode: DashboardDisplayMode\n scaleFactor: number\n isEditable: boolean\n designWidth: number\n}\n\n/**\n * Hook for managing responsive dashboard layouts\n * Uses ResizeObserver for accurate width detection and calculates\n * the appropriate display mode and scale factor\n */\nexport function useResponsiveDashboard(): UseResponsiveDashboardResult {\n // Start with window width as initial estimate\n const [containerWidth, setContainerWidth] = useState(() =>\n typeof window !== 'undefined' ? window.innerWidth : DESIGN_WIDTH\n )\n const observerRef = useRef<ResizeObserver | null>(null)\n const elementRef = useRef<HTMLDivElement | null>(null)\n\n // Ref callback - called when element is attached/detached\n // This is key: unlike useEffect, this fires immediately when the DOM element exists\n const containerRef = useCallback((node: HTMLDivElement | null) => {\n // Cleanup previous observer\n if (observerRef.current) {\n observerRef.current.disconnect()\n observerRef.current = null\n }\n\n elementRef.current = node\n\n if (node) {\n // Get initial width immediately (synchronously when element attaches)\n const initialWidth = node.offsetWidth\n if (initialWidth > 0) {\n setContainerWidth(initialWidth)\n }\n\n // Set up ResizeObserver for ongoing changes\n observerRef.current = new ResizeObserver((entries) => {\n const width = entries[0]?.contentRect.width\n if (width && width > 0) {\n setContainerWidth(width)\n }\n })\n observerRef.current.observe(node)\n }\n }, [])\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect()\n }\n }\n }, [])\n\n // Fallback: window resize listener to catch resize events that ResizeObserver might miss\n // This is particularly important for containers in flex/grid layouts or deeply nested elements\n useEffect(() => {\n const handleWindowResize = () => {\n if (elementRef.current) {\n const width = elementRef.current.offsetWidth\n if (width > 0) {\n setContainerWidth(width)\n }\n }\n }\n\n window.addEventListener('resize', handleWindowResize)\n\n // Also measure after a short delay to catch late layout calculations\n const timeoutId = setTimeout(handleWindowResize, 100)\n\n return () => {\n window.removeEventListener('resize', handleWindowResize)\n clearTimeout(timeoutId)\n }\n }, [])\n\n const displayMode = useMemo<DashboardDisplayMode>(() => {\n if (containerWidth >= DESIGN_WIDTH) return 'desktop'\n if (containerWidth >= MOBILE_THRESHOLD) return 'scaled'\n return 'mobile'\n }, [containerWidth])\n\n const scaleFactor = useMemo(() => {\n if (displayMode !== 'scaled') return 1\n return containerWidth / DESIGN_WIDTH\n }, [containerWidth, displayMode])\n\n const isEditable = displayMode === 'desktop'\n\n return {\n containerRef,\n containerWidth,\n displayMode,\n scaleFactor,\n isEditable,\n designWidth: DESIGN_WIDTH\n }\n}\n","/**\n * useDirtyStateTracking - Track configuration changes and dirty state\n *\n * Extracts dirty state tracking logic from AnalyticsDashboard:\n * - Tracks initial config to prevent saves during initial load\n * - Detects meaningful changes from initial state\n * - Manages dirty state through onDirtyStateChange callback\n *\n * @example\n * const { handleConfigChange, handleSave } = useDirtyStateTracking({\n * initialConfig: config,\n * onConfigChange,\n * onSave,\n * onDirtyStateChange,\n * })\n */\n\nimport { useCallback, useRef } from 'react'\n\nexport interface UseDirtyStateTrackingOptions<T> {\n /** Initial configuration to compare against */\n initialConfig: T\n /** Original config change handler */\n onConfigChange?: (config: T) => void\n /** Original save handler */\n onSave?: (config: T) => Promise<void> | void\n /** Dirty state change callback */\n onDirtyStateChange?: (isDirty: boolean) => void\n}\n\nexport interface UseDirtyStateTrackingResult<T> {\n /** Wrapped config change handler that tracks dirty state */\n handleConfigChange: (config: T) => void\n /** Wrapped save handler that tracks dirty state */\n handleSave: (config: T) => Promise<void>\n /** Whether config has changed from initial */\n hasChanged: () => boolean\n /** Reset the initial config reference (e.g., after external config update) */\n resetInitialConfig: (config: T) => void\n}\n\nexport function useDirtyStateTracking<T>({\n initialConfig,\n onConfigChange,\n onSave,\n onDirtyStateChange,\n}: UseDirtyStateTrackingOptions<T>): UseDirtyStateTrackingResult<T> {\n // Track initial config to prevent saves during initial load\n const initialConfigRef = useRef(initialConfig)\n const hasConfigChangedFromInitial = useRef(false)\n\n // Enhanced save handler that tracks dirty state and prevents saves during initial load\n const handleSave = useCallback(\n async (config: T) => {\n // Don't save if this config hasn't actually changed from the initial load\n if (!hasConfigChangedFromInitial.current) {\n return // Prevent saves during initial load/responsive changes\n }\n\n if (onDirtyStateChange) {\n onDirtyStateChange(true) // Mark as dirty when save starts\n }\n\n try {\n if (onSave) {\n await onSave(config)\n }\n\n // Update our reference point after successful save\n initialConfigRef.current = config\n\n // Mark as clean after successful save\n if (onDirtyStateChange) {\n onDirtyStateChange(false)\n }\n } catch (error) {\n // Keep dirty state if save failed\n console.error('Save failed:', error)\n throw error\n }\n },\n [onSave, onDirtyStateChange]\n )\n\n // Enhanced config change handler that marks as dirty (only after initial load)\n const handleConfigChange = useCallback(\n (config: T) => {\n if (onConfigChange) {\n onConfigChange(config)\n }\n\n // Check if this is a meaningful change from the initial config\n const configString = JSON.stringify(config)\n const initialConfigString = JSON.stringify(initialConfigRef.current)\n\n if (configString !== initialConfigString) {\n hasConfigChangedFromInitial.current = true\n\n if (onDirtyStateChange) {\n onDirtyStateChange(true)\n }\n }\n },\n [onConfigChange, onDirtyStateChange]\n )\n\n // Check if config has changed from initial\n const hasChanged = useCallback(() => {\n return hasConfigChangedFromInitial.current\n }, [])\n\n // Reset initial config reference (useful when config is updated externally)\n const resetInitialConfig = useCallback((config: T) => {\n initialConfigRef.current = config\n hasConfigChangedFromInitial.current = false\n }, [])\n\n return {\n handleConfigChange,\n handleSave,\n hasChanged,\n resetInitialConfig,\n }\n}\n"],"names":["FILTER_OPERATORS","DATE_RANGE_OPTIONS","isSimpleFilter","filter","isGroupFilter","transformFiltersForServer","filters","transformFilter","transformedSubFilters","cleanQuery","query","cleanedQuery","cleanQueryForServer","getAvailableOperators","fieldType","operators","operator","meta","convertDateRangeTypeToValue","rangeType","number","typeMap","unit","unitSingular","requiresNumberInput","formatDateForCube","date","stableStringify","value","seen","stringify","input","item","record","key","useDebounceQuery","options","isValid","skip","debounceMs","debouncedValue","setDebouncedValue","useState","isDebouncing","setIsDebouncing","debounceTimerRef","useRef","lastValueStringRef","wasSkippedRef","valueString","useMemo","useEffect","justBecameUnskipped","DEFAULT_DEBOUNCE_MS","createQueryKey","isValidCubeQuery","hasMeasures","hasDimensions","hasTimeDimensions","useCubeLoadQuery","resetResultSetOnChange","staleTime","keepPreviousData","cubeApi","batchCoordinator","enableBatching","useCubeApi","queryClient","useQueryClient","features","useCubeFeatures","manualRefresh","executedQueryKey","setExecutedQueryKey","isValidQuery","debouncedQuery","serverQuery","currentQueryKey","needsRefresh","shouldExecute","bustCacheRef","queryResult","useQuery","shouldBustCache","prevData","rawData","warnings","lr","executeQuery","useCallback","refetch","clearCache","isMultiQueryData","data","getQueryLabels","labels","row","label","getQueryIndices","indices","index","a","b","mergeResultsConcat","resultSets","_queries","merged","resultSet","queryIndex","mergeResultsByKey","queries","mergeKeys","_labels","mergedMap","measures","keyValue","k","baseRow","mergedRow","measure","field","aKey","bKey","mergeQueryResults","strategy","getCombinedFields","dimensions","timeDimensions","m","d","td","generateQueryLabel","firstMeasure","parts","validateMergeKey","mergeKey","missingInQueries","createMultiQueryKey","config","isValidMultiQueryConfig","q","useMultiCubeLoadQuery","isValidConfig","debouncedConfig","serverConfig","errors","rs","perQueryData","i","successfulResults","_","successfulQueries","e","error","isValidFunnelConfig","step","useFunnelQuery","onComplete","onError","prebuiltServerQuery","buildServerFunnelQuery","s","queryKey","startTime","executionTime","cacheInfo","err","stepNames","expectedStepCount","chartData","transformServerFunnelResult","stepResults","firstCount","result","lastCount","fullResult","status","execute","cancel","reset","createFunnelQueryKey","isValidFlowQuery","flow","transformFlowResult","firstItem","isSankeyData","useFlowQuery","queryKeyString","rawQueryKeyString","isDataStale","transformed","createFlowQueryKey","isValidRetentionQuery","extractBindingKeyLabel","bindingKey","firstMapping","extractBreakdownValue","breakdownValues","values","transformRetentionResult","granularity","bindingKeyLabel","rows","r","periodsSet","breakdownSet","periods","summary","calculateSummary","period1Rates","sum","useRetentionQuery","getFieldLabel","rawBindingKeyField","executeOptions","useFilterValues","fieldName","enabled","currentQuery","setCurrentQuery","lastSearchTerm","isLoading","queryError","uniqueValues","searchValues","searchTerm","force","useDebounce","delay","handler","DESIGN_WIDTH","MOBILE_THRESHOLD","useResponsiveDashboard","containerWidth","setContainerWidth","observerRef","elementRef","containerRef","node","initialWidth","entries","width","handleWindowResize","timeoutId","displayMode","scaleFactor","useDirtyStateTracking","initialConfig","onConfigChange","onSave","onDirtyStateChange","initialConfigRef","hasConfigChangedFromInitial","handleSave","handleConfigChange","configString","initialConfigString","hasChanged","resetInitialConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useDirtyStateTracking-CjhwBXRw.js","sources":["../../../src/client/shared/types.ts","../../../src/client/shared/utils.ts","../../../src/client/shared/queryKey.ts","../../../src/client/hooks/useDebounceQuery.ts","../../../src/client/hooks/queries/useCubeLoadQuery.ts","../../../src/client/utils/multiQueryUtils.ts","../../../src/client/hooks/queries/useMultiCubeLoadQuery.ts","../../../src/client/hooks/queries/useFunnelQuery.ts","../../../src/client/hooks/queries/useFlowQuery.ts","../../../src/client/hooks/queries/useRetentionQuery.ts","../../../src/client/hooks/useFilterValues.ts","../../../src/client/hooks/useDebounce.ts","../../../src/client/hooks/useResponsiveDashboard.ts","../../../src/client/hooks/useDirtyStateTracking.ts"],"sourcesContent":["/**\n * Shared type definitions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, FilterOperator } from '../types'\n\n// ============================================================================\n// Meta endpoint response types\n// ============================================================================\n\nexport interface MetaField {\n name: string // e.g., \"Employees.count\"\n title: string // e.g., \"Total Employees\"\n shortTitle: string // e.g., \"Total Employees\"\n type: string // e.g., \"count\", \"string\", \"time\", \"number\"\n description?: string // Optional description\n}\n\nexport interface MetaCubeRelationship {\n targetCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinFields?: Array<{\n sourceField: string\n targetField: string\n }>\n}\n\nexport interface MetaCube {\n name: string // e.g., \"Employees\"\n title: string // e.g., \"Employee Analytics\"\n description: string // e.g., \"Employee data and metrics\"\n measures: MetaField[] // e.g., \"Employees.count\"\n dimensions: MetaField[] // e.g., \"Employees.name\"\n segments: MetaField[] // Currently empty in examples\n relationships?: MetaCubeRelationship[] // Optional join relationships to other cubes\n}\n\nexport interface MetaResponse {\n cubes: MetaCube[]\n}\n\n// ============================================================================\n// Query warning types (from server-side query planner)\n// ============================================================================\n\n/**\n * Severity level for query warnings\n */\nexport type QueryWarningSeverity = 'info' | 'warning' | 'error'\n\n/**\n * Warning emitted during query planning or execution\n * Provides user-facing feedback about potential query issues\n */\nexport interface QueryWarning {\n /** Unique code for programmatic handling (e.g., 'FAN_OUT_NO_DIMENSIONS') */\n code: string\n /** Human-readable warning message */\n message: string\n /** Severity level for UI styling */\n severity: QueryWarningSeverity\n /** Cubes involved in the warning (if applicable) */\n cubes?: string[]\n /** Measures involved in the warning (if applicable) */\n measures?: string[]\n /** Actionable suggestion for the user */\n suggestion?: string\n}\n\n// ============================================================================\n// Query analysis types for debugging transparency\n// ============================================================================\n\nexport type PrimaryCubeSelectionReason =\n | 'most_dimensions'\n | 'most_connected'\n | 'alphabetical_fallback'\n | 'single_cube'\n\nexport interface PrimaryCubeCandidate {\n cubeName: string\n dimensionCount: number\n joinCount: number\n canReachAll: boolean\n}\n\nexport interface PrimaryCubeAnalysis {\n selectedCube: string\n reason: PrimaryCubeSelectionReason\n explanation: string\n candidates?: PrimaryCubeCandidate[]\n}\n\nexport interface JoinPathStep {\n fromCube: string\n toCube: string\n relationship: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany'\n joinType: 'inner' | 'left' | 'right' | 'full'\n joinColumns: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n junctionTable?: {\n tableName: string\n sourceColumns: string[]\n targetColumns: string[]\n }\n}\n\nexport interface JoinPathAnalysis {\n targetCube: string\n pathFound: boolean\n path?: JoinPathStep[]\n pathLength?: number\n error?: string\n visitedCubes?: string[]\n selection?: {\n strategy: 'shortest' | 'preferred' | 'fallbackShortest'\n preferredCubes?: string[]\n selectedRank?: number\n selectedScore?: number\n candidates?: Array<{\n rank: number\n score: number\n usesPreferredJoin: boolean\n preferredCubesInPath: number\n usesProcessed: boolean\n scoreBreakdown: {\n preferredJoinBonus: number\n preferredCubeBonus: number\n lengthPenalty: number\n }\n path: JoinPathStep[]\n }>\n }\n}\n\n/**\n * Reason why a cube requires a CTE\n */\nexport type CTEReason = 'hasMany' | 'fanOutPrevention'\n\nexport interface PreAggregationAnalysis {\n cubeName: string\n cteAlias: string\n /** Why this cube needs a CTE (human-readable explanation) */\n reason: string\n /** Typed reason for programmatic use */\n reasonType?: CTEReason\n measures: string[]\n joinKeys: Array<{\n sourceColumn: string\n targetColumn: string\n }>\n}\n\nexport interface QuerySummary {\n queryType: 'single_cube' | 'multi_cube_join' | 'multi_cube_cte'\n measureStrategy?: 'simple' | 'keysDeduplication' | 'ctePreAggregateFallback' | 'multiFactMerge'\n joinCount: number\n cteCount: number\n hasPreAggregation: boolean\n}\n\nexport interface QueryAnalysis {\n timestamp: string\n cubeCount: number\n cubesInvolved: string[]\n primaryCube: PrimaryCubeAnalysis\n joinPaths: JoinPathAnalysis[]\n preAggregations: PreAggregationAnalysis[]\n querySummary: QuerySummary\n warnings?: string[]\n planningTrace?: {\n steps: Array<{\n phase: 'cube_usage' | 'primary_cube_selection' | 'join_planning' | 'cte_planning' | 'measure_strategy' | 'warnings'\n decision: string\n details?: Record<string, unknown>\n }>\n }\n}\n\n// ============================================================================\n// Validation response from /dry-run endpoint\n// ============================================================================\n\nexport interface ValidationResult {\n valid?: boolean // Our custom property (may not be present in official Cube.js)\n error?: string\n query?: CubeQuery\n sql?: {\n sql: string[]\n params: any[]\n }\n queryType?: string // Always present in successful Cube.js responses\n normalizedQueries?: any[]\n queryOrder?: string[]\n transformedQueries?: any[]\n pivotQuery?: any\n complexity?: string\n cubesUsed?: string[]\n joinType?: string\n // Query analysis for debugging transparency\n analysis?: QueryAnalysis\n}\n\n// ============================================================================\n// Filter operator metadata\n// ============================================================================\n\nexport interface FilterOperatorMeta {\n label: string\n description: string\n requiresValues: boolean\n supportsMultipleValues: boolean\n valueType: 'string' | 'number' | 'date' | 'boolean' | 'any'\n fieldTypes: string[] // Which field types support this operator\n}\n\nexport const FILTER_OPERATORS: Record<FilterOperator, FilterOperatorMeta> = {\n // String operators\n equals: {\n label: 'equals',\n description: 'Exact match',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n notEquals: {\n label: 'not equals',\n description: 'Does not match',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean', 'time']\n },\n contains: {\n label: 'contains',\n description: 'Contains text (case insensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notContains: {\n label: 'not contains',\n description: 'Does not contain text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n startsWith: {\n label: 'starts with',\n description: 'Starts with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notStartsWith: {\n label: 'not starts with',\n description: 'Does not start with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n endsWith: {\n label: 'ends with',\n description: 'Ends with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notEndsWith: {\n label: 'not ends with',\n description: 'Does not end with text',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n like: {\n label: 'like',\n description: 'SQL LIKE pattern matching (case sensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notLike: {\n label: 'not like',\n description: 'SQL NOT LIKE pattern matching (case sensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n ilike: {\n label: 'ilike',\n description: 'SQL ILIKE pattern matching (case insensitive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Numeric operators\n gt: {\n label: 'greater than',\n description: 'Greater than value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n gte: {\n label: 'greater than or equal',\n description: 'Greater than or equal to value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lt: {\n label: 'less than',\n description: 'Less than value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n lte: {\n label: 'less than or equal',\n description: 'Less than or equal to value',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n between: {\n label: 'between',\n description: 'Between two values (inclusive)',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n notBetween: {\n label: 'not between',\n description: 'Not between two values',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'number',\n fieldTypes: ['number', 'count', 'sum', 'avg', 'min', 'max']\n },\n // Array operators\n in: {\n label: 'in',\n description: 'Matches any of the provided values',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n notIn: {\n label: 'not in',\n description: 'Does not match any of the provided values',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'boolean']\n },\n // Null/Empty operators\n set: {\n label: 'is set',\n description: 'Is not null/empty',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n notSet: {\n label: 'is not set',\n description: 'Is null/empty',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'any',\n fieldTypes: ['string', 'number', 'time', 'boolean']\n },\n isEmpty: {\n label: 'is empty',\n description: 'Is empty string or null',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n isNotEmpty: {\n label: 'is not empty',\n description: 'Is not empty string and not null',\n requiresValues: false,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // Date operators\n inDateRange: {\n label: 'in date range',\n description: 'Between two dates',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n beforeDate: {\n label: 'before date',\n description: 'Before specified date',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n afterDate: {\n label: 'after date',\n description: 'After specified date',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'date',\n fieldTypes: ['time']\n },\n // Regex operators\n regex: {\n label: 'matches regex',\n description: 'Matches regular expression pattern',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n notRegex: {\n label: 'not matches regex',\n description: 'Does not match regular expression pattern',\n requiresValues: true,\n supportsMultipleValues: false,\n valueType: 'string',\n fieldTypes: ['string']\n },\n // PostgreSQL array operators\n arrayContains: {\n label: 'array contains all',\n description: 'Array field contains all specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayOverlaps: {\n label: 'array contains any',\n description: 'Array field contains any of the specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n },\n arrayContained: {\n label: 'array values in',\n description: 'All array field values are within specified values (PostgreSQL only)',\n requiresValues: true,\n supportsMultipleValues: true,\n valueType: 'string',\n fieldTypes: ['string']\n }\n}\n\n// ============================================================================\n// Date range types\n// ============================================================================\n\nexport type DateRangeType =\n | 'custom'\n | 'today'\n | 'yesterday'\n | 'this_week'\n | 'this_month'\n | 'this_quarter'\n | 'this_year'\n | 'last_7_days'\n | 'last_30_days'\n | 'last_week'\n | 'last_month'\n | 'last_quarter'\n | 'last_year'\n | 'last_12_months'\n | 'last_n_days'\n | 'last_n_weeks'\n | 'last_n_months'\n | 'last_n_quarters'\n | 'last_n_years'\n\nexport interface DateRangeOption {\n value: DateRangeType\n label: string\n}\n\nexport const DATE_RANGE_OPTIONS: DateRangeOption[] = [\n { value: 'custom', label: 'Custom' },\n { value: 'today', label: 'Today' },\n { value: 'yesterday', label: 'Yesterday' },\n { value: 'this_week', label: 'This week' },\n { value: 'this_month', label: 'This month' },\n { value: 'this_quarter', label: 'This quarter' },\n { value: 'this_year', label: 'This year' },\n { value: 'last_7_days', label: 'Last 7 days' },\n { value: 'last_30_days', label: 'Last 30 days' },\n { value: 'last_n_days', label: 'Last N days' },\n { value: 'last_week', label: 'Last week' },\n { value: 'last_n_weeks', label: 'Last N weeks' },\n { value: 'last_month', label: 'Last month' },\n { value: 'last_12_months', label: 'Last 12 months' },\n { value: 'last_n_months', label: 'Last N months' },\n { value: 'last_quarter', label: 'Last quarter' },\n { value: 'last_n_quarters', label: 'Last N quarters' },\n { value: 'last_year', label: 'Last year' },\n { value: 'last_n_years', label: 'Last N years' }\n] as const\n\n// ============================================================================\n// Time dimension granularity options\n// ============================================================================\n\nexport const TIME_GRANULARITIES = [\n { value: 'hour', label: 'Hour' },\n { value: 'day', label: 'Day' },\n { value: 'week', label: 'Week' },\n { value: 'month', label: 'Month' },\n { value: 'quarter', label: 'Quarter' },\n { value: 'year', label: 'Year' }\n] as const\n\nexport type TimeGranularity = typeof TIME_GRANULARITIES[number]['value']\n","/**\n * Shared utility functions used across QueryBuilder and AnalysisBuilder\n */\n\nimport type { CubeQuery, Filter, SimpleFilter, GroupFilter } from '../types'\nimport type { MetaField, MetaResponse } from './types'\nimport { FILTER_OPERATORS } from './types'\n\n// ============================================================================\n// Filter type guards\n// ============================================================================\n\n/**\n * Check if a filter is a simple filter\n */\nexport function isSimpleFilter(filter: Filter): filter is SimpleFilter {\n return 'member' in filter && 'operator' in filter && 'values' in filter\n}\n\n/**\n * Check if a filter is a group filter\n */\nexport function isGroupFilter(filter: Filter): filter is GroupFilter {\n return 'type' in filter && 'filters' in filter\n}\n\n/**\n * Check if a filter is an AND filter\n */\nexport function isAndFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'and'\n}\n\n/**\n * Check if a filter is an OR filter\n */\nexport function isOrFilter(filter: Filter): filter is GroupFilter {\n return isGroupFilter(filter) && filter.type === 'or'\n}\n\n// ============================================================================\n// Filter manipulation functions\n// ============================================================================\n\n/**\n * Flatten all simple filters from a hierarchical filter structure\n */\nexport function flattenFilters(filters: Filter[]): SimpleFilter[] {\n const simple: SimpleFilter[] = []\n\n const flatten = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n simple.push(filter)\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(flatten)\n }\n }\n\n filters.forEach(flatten)\n return simple\n}\n\n/**\n * Count total filters in hierarchical structure\n */\nexport function countFilters(filters: Filter[]): number {\n let count = 0\n\n const countFilter = (filter: Filter) => {\n if (isSimpleFilter(filter)) {\n count++\n } else if (isGroupFilter(filter)) {\n filter.filters.forEach(countFilter)\n }\n }\n\n filters.forEach(countFilter)\n return count\n}\n\n/**\n * Create a new simple filter\n */\nexport function createSimpleFilter(member: string, operator: string = 'equals', values: any[] = []): SimpleFilter {\n return {\n member,\n operator: operator as any,\n values\n }\n}\n\n/**\n * Create a new AND filter group\n */\nexport function createAndFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'and',\n filters\n }\n}\n\n/**\n * Create a new OR filter group\n */\nexport function createOrFilter(filters: Filter[] = []): GroupFilter {\n return {\n type: 'or',\n filters\n }\n}\n\n/**\n * Clean up filters - backward compatible (returns filters unchanged)\n * @deprecated This function is no longer used as we now support filtering on any schema field\n */\nexport function cleanupFilters(filters: Filter[], _query?: CubeQuery): Filter[] {\n return filters || []\n}\n\n// ============================================================================\n// Filter transformation functions\n// ============================================================================\n\n/**\n * Transform filters from new GroupFilter format to legacy server format\n * Server expects { and: [...] } and { or: [...] } instead of { type: 'and', filters: [...] }\n */\nexport function transformFiltersForServer(filters: Filter[]): any[] {\n const transformFilter = (filter: Filter): any => {\n if (isSimpleFilter(filter)) {\n return filter\n } else if (isGroupFilter(filter)) {\n const transformedSubFilters = filter.filters.map(transformFilter)\n\n if (filter.type === 'and') {\n return { and: transformedSubFilters }\n } else {\n return { or: transformedSubFilters }\n }\n }\n return filter\n }\n\n return filters.map(transformFilter)\n}\n\n/**\n * Transform filters from server/API format to UI format\n * Converts {and: [...]} and {or: [...]} to {type: 'and', filters: [...]} format\n */\nexport function transformFiltersFromServer(filters: any[]): Filter[] {\n return filters.map(filter => {\n if (!filter || typeof filter !== 'object') {\n return filter\n }\n\n // Handle legacy {and: [...]} format\n if ('and' in filter && Array.isArray(filter.and)) {\n return {\n type: 'and',\n filters: transformFiltersFromServer(filter.and)\n } as GroupFilter\n }\n\n // Handle legacy {or: [...]} format\n if ('or' in filter && Array.isArray(filter.or)) {\n return {\n type: 'or',\n filters: transformFiltersFromServer(filter.or)\n } as GroupFilter\n }\n\n // Handle new format {type: 'and', filters: [...]} - process recursively\n if ('type' in filter && 'filters' in filter && Array.isArray(filter.filters)) {\n return {\n type: filter.type,\n filters: transformFiltersFromServer(filter.filters)\n } as GroupFilter\n }\n\n // Simple filter - pass through\n return filter as SimpleFilter\n }).filter(Boolean) // Remove any null/undefined values\n}\n\n// ============================================================================\n// Query utility functions\n// ============================================================================\n\n/**\n * Check if query has any content (measures, dimensions, or timeDimensions)\n */\nexport function hasQueryContent(query: CubeQuery): boolean {\n return Boolean(\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0)\n )\n}\n\n/**\n * Clean query object by removing empty arrays\n */\nexport function cleanQuery(query: CubeQuery): CubeQuery {\n const cleanedQuery: CubeQuery = {}\n\n if (query.measures && query.measures.length > 0) {\n cleanedQuery.measures = query.measures\n }\n\n if (query.dimensions && query.dimensions.length > 0) {\n cleanedQuery.dimensions = query.dimensions\n }\n\n if (query.timeDimensions && query.timeDimensions.length > 0) {\n cleanedQuery.timeDimensions = query.timeDimensions\n }\n\n if (query.filters && query.filters.length > 0) {\n cleanedQuery.filters = query.filters\n }\n\n if (query.order) {\n cleanedQuery.order = query.order\n }\n\n if (query.limit) {\n cleanedQuery.limit = query.limit\n }\n\n if (query.offset) {\n cleanedQuery.offset = query.offset\n }\n\n if (query.segments && query.segments.length > 0) {\n cleanedQuery.segments = query.segments\n }\n\n return cleanedQuery\n}\n\n/**\n * Clean a query and transform filters for server compatibility\n * This version transforms GroupFilter to legacy and/or format\n */\nexport function cleanQueryForServer(query: CubeQuery): CubeQuery {\n const cleanedQuery = cleanQuery(query)\n\n // Apply server transformation to filters\n if (cleanedQuery.filters && cleanedQuery.filters.length > 0) {\n cleanedQuery.filters = transformFiltersForServer(cleanedQuery.filters) as any\n }\n\n return cleanedQuery\n}\n\n/**\n * Transform a Cube.js query from external format to UI internal format\n * This handles format differences between server/API queries and QueryBuilder state\n */\nexport function transformQueryForUI(query: any): CubeQuery {\n if (!query || typeof query !== 'object') {\n return {}\n }\n\n const transformed: CubeQuery = {}\n\n // Copy simple fields if they exist\n if (query.measures) transformed.measures = Array.isArray(query.measures) ? query.measures : []\n if (query.dimensions) transformed.dimensions = Array.isArray(query.dimensions) ? query.dimensions : []\n if (query.timeDimensions) transformed.timeDimensions = Array.isArray(query.timeDimensions) ? query.timeDimensions : []\n if (query.order) transformed.order = query.order\n if (query.limit) transformed.limit = query.limit\n if (query.offset) transformed.offset = query.offset\n if (query.segments) transformed.segments = Array.isArray(query.segments) ? query.segments : []\n\n // Transform filters from server format to UI format\n if (query.filters && Array.isArray(query.filters)) {\n transformed.filters = transformFiltersFromServer(query.filters)\n }\n\n return cleanQuery(transformed)\n}\n\n// ============================================================================\n// Schema utility functions\n// ============================================================================\n\n/**\n * Get cube name from field name (e.g., \"Employees.count\" -> \"Employees\")\n */\nexport function getCubeNameFromField(fieldName: string): string {\n return fieldName.split('.')[0]\n}\n\n/**\n * Get field type from schema\n */\nexport function getFieldType(fieldName: string, schema: MetaResponse): string {\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.type\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.type\n }\n\n return 'string' // Default fallback\n}\n\n/**\n * Get field title from schema metadata, falling back to field name\n */\nexport function getFieldTitle(fieldName: string, schema: MetaResponse | null): string {\n if (!schema) return fieldName\n\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find(m => m.name === fieldName)\n if (measure) return measure.title || measure.shortTitle || fieldName\n\n // Check dimensions\n const dimension = cube.dimensions.find(d => d.name === fieldName)\n if (dimension) return dimension.title || dimension.shortTitle || fieldName\n }\n\n return fieldName // Fallback to field name if not found\n}\n\n/**\n * Get available operators for a field type\n */\nexport function getAvailableOperators(fieldType: string): Array<{operator: string, label: string}> {\n const operators: Array<{operator: string, label: string}> = []\n\n for (const [operator, meta] of Object.entries(FILTER_OPERATORS)) {\n if (meta.fieldTypes.includes(fieldType)) {\n operators.push({\n operator,\n label: meta.label\n })\n }\n }\n\n return operators\n}\n\n/**\n * Get ALL filterable fields from schema\n */\nexport function getAllFilterableFields(schema: MetaResponse): MetaField[] {\n const allFields: MetaField[] = []\n\n schema.cubes.forEach(cube => {\n allFields.push(...cube.measures)\n allFields.push(...cube.dimensions)\n })\n\n return allFields.sort((a, b) => a.name.localeCompare(b.name))\n}\n\n// ============================================================================\n// Date range utility functions\n// ============================================================================\n\n/**\n * Convert DateRangeType to Cube.js compatible date range format\n */\nexport function convertDateRangeTypeToValue(rangeType: string, number?: number): string {\n const typeMap: Record<string, string> = {\n 'today': 'today',\n 'yesterday': 'yesterday',\n 'this_week': 'this week',\n 'this_month': 'this month',\n 'this_quarter': 'this quarter',\n 'this_year': 'this year',\n 'last_7_days': 'last 7 days',\n 'last_30_days': 'last 30 days',\n 'last_week': 'last week',\n 'last_month': 'last month',\n 'last_quarter': 'last quarter',\n 'last_year': 'last year',\n 'last_12_months': 'last 12 months'\n }\n\n // Handle dynamic ranges with number input\n if (rangeType.startsWith('last_n_') && number !== undefined && number > 0) {\n const unit = rangeType.replace('last_n_', '')\n const unitSingular = unit.slice(0, -1) // Remove 's' for singular form\n return number === 1 ? `last ${unitSingular}` : `last ${number} ${unit}`\n }\n\n return typeMap[rangeType] || rangeType\n}\n\n/**\n * Check if a date range type requires a number input\n */\nexport function requiresNumberInput(rangeType: string): boolean {\n return rangeType.startsWith('last_n_')\n}\n\n/**\n * Format date for Cube.js (YYYY-MM-DD)\n */\nexport function formatDateForCube(date: Date): string {\n return date.toISOString().split('T')[0]\n}\n","export function stableStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n\n const stringify = (input: unknown): string => {\n if (input === null || typeof input !== 'object') {\n return JSON.stringify(input)\n }\n\n if (seen.has(input as object)) {\n return '\"[Circular]\"'\n }\n seen.add(input as object)\n\n if (Array.isArray(input)) {\n return `[${input.map((item) => stringify(item)).join(',')}]`\n }\n\n const record = input as Record<string, unknown>\n const keys = Object.keys(record).sort()\n const props = keys.map((key) => `${JSON.stringify(key)}:${stringify(record[key])}`)\n return `{${props.join(',')}}`\n }\n\n return stringify(value)\n}\n","/**\n * useDebounceQuery - Shared debounce logic for query hooks\n *\n * This hook encapsulates the common debouncing pattern used by\n * useCubeLoadQuery and useMultiCubeLoadQuery to prevent excessive API calls\n * when users are actively editing queries.\n *\n * Features:\n * - Debounces value changes with configurable delay\n * - Handles skip-to-unskip transitions (e.g., portlet becoming visible)\n * - Clears debounced value when invalid or skipped\n * - Provides isDebouncing state for UI feedback\n */\n\nimport { useState, useEffect, useRef, useMemo } from 'react'\nimport { stableStringify } from '../shared/queryKey'\n\nexport interface UseDebounceQueryOptions {\n /**\n * Whether the value is valid (has required fields)\n */\n isValid: boolean\n /**\n * Whether to skip the debounced value\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n}\n\nexport interface UseDebounceQueryResult<T> {\n /** The debounced value (null if skipped or invalid) */\n debouncedValue: T | null\n /** Whether the hook is currently debouncing (waiting for timer) */\n isDebouncing: boolean\n}\n\n/**\n * Hook for debouncing query values with skip and validity support\n *\n * Usage:\n * ```tsx\n * const { debouncedValue, isDebouncing } = useDebounceQuery(query, {\n * isValid: isValidCubeQuery(query),\n * skip: !isReady,\n * debounceMs: 300\n * })\n * ```\n */\nexport function useDebounceQuery<T>(\n value: T | null,\n options: UseDebounceQueryOptions\n): UseDebounceQueryResult<T> {\n const { isValid, skip = false, debounceMs = 300 } = options\n\n // Debounced state\n const [debouncedValue, setDebouncedValue] = useState<T | null>(null)\n const [isDebouncing, setIsDebouncing] = useState(false)\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const lastValueStringRef = useRef<string>('')\n const wasSkippedRef = useRef<boolean>(skip)\n\n // Serialize value for comparison\n const valueString = useMemo(() => {\n if (!value) return ''\n return stableStringify(value)\n }, [value])\n\n // Debounce the value changes\n useEffect(() => {\n // Detect skip-to-unskip transition (e.g., portlet becoming visible)\n const wasSkipped = wasSkippedRef.current\n const justBecameUnskipped = wasSkipped && !skip\n wasSkippedRef.current = skip\n\n // Skip if value hasn't actually changed AND we haven't just become unskipped\n // The justBecameUnskipped check ensures we re-trigger when visibility changes\n if (valueString === lastValueStringRef.current && !justBecameUnskipped) {\n return\n }\n\n // Clear existing timer\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n\n // If value is valid, set debouncing state and schedule update\n if (isValid && !skip) {\n setIsDebouncing(true)\n debounceTimerRef.current = setTimeout(() => {\n lastValueStringRef.current = valueString\n setDebouncedValue(value)\n setIsDebouncing(false)\n }, debounceMs)\n } else {\n // Clear debounced value if invalid or skipped\n lastValueStringRef.current = valueString\n setDebouncedValue(null)\n setIsDebouncing(false)\n }\n\n return () => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n }\n }, [valueString, isValid, skip, debounceMs, value])\n\n return {\n debouncedValue,\n isDebouncing,\n }\n}\n","/**\n * useCubeLoadQuery - TanStack Query hook for cube data loading\n *\n * Features:\n * - Built-in debouncing to prevent excessive API calls\n * - Automatic query deduplication\n * - Background refetch support\n * - Proper loading/error states\n * - Query key based on query content for caching\n *\n * This hook replaces the manual debouncing and query execution\n * in useQueryExecution.\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo, useState, useCallback, useEffect, useRef } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport type { CubeQuery, CubeResultSet } from '../../types'\nimport type { QueryWarning } from '../../shared/types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key from a CubeQuery\n * The key includes all query parameters to ensure proper caching\n */\nexport function createQueryKey(query: CubeQuery | null): readonly unknown[] {\n if (!query) return ['cube', 'load', null] as const\n // Use JSON.stringify for deep equality comparison\n return ['cube', 'load', stableStringify(query)] as const\n}\n\nexport interface UseCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\n/** Options for the refetch function */\nexport interface RefetchOptions {\n /** If true, bypasses both client and server caches */\n bustCache?: boolean\n}\n\nexport interface UseCubeLoadQueryResult {\n /** The result set from the query */\n resultSet: CubeResultSet | null\n /** Raw data from the result set */\n rawData: unknown[] | null\n /** Whether the query is loading (initial load) */\n isLoading: boolean\n /** Whether the query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** Error if the query failed */\n error: Error | null\n /** The debounced query that was executed */\n debouncedQuery: CubeQuery | null\n /** Whether the current query is valid */\n isValidQuery: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass caches. */\n refetch: (options?: RefetchOptions) => void\n /** Clear the query cache */\n clearCache: () => void\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n /**\n * Execute the current query (manual refresh mode only).\n * In auto-refresh mode, this is the same as refetch().\n */\n executeQuery: (options?: RefetchOptions) => void\n /** Warnings from query planning (e.g., fan-out without dimensions) */\n warnings: QueryWarning[] | undefined\n}\n\n/**\n * Check if a query is valid (has at least one measure or dimension)\n */\nfunction isValidCubeQuery(query: CubeQuery | null): boolean {\n if (!query) return false\n const hasMeasures = Boolean(query.measures && query.measures.length > 0)\n const hasDimensions = Boolean(query.dimensions && query.dimensions.length > 0)\n const hasTimeDimensions = Boolean(query.timeDimensions && query.timeDimensions.length > 0)\n return hasMeasures || hasDimensions || hasTimeDimensions\n}\n\n/**\n * TanStack Query hook for loading cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { resultSet, rawData, isLoading, error } = useCubeLoadQuery(query, {\n * debounceMs: 300,\n * skip: !isReady\n * })\n * ```\n */\nexport function useCubeLoadQuery(\n query: CubeQuery | null,\n options: UseCubeLoadQueryOptions = {}\n): UseCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n // This is the query that was last sent to the server\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidCubeQuery(query)\n\n // Silence unused variable warning - used for future functionality\n void resetResultSetOnChange\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(query, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Transform query for server (converts filter groups)\n const serverQuery = useMemo(() => {\n if (!debouncedQuery) return null\n return cleanQueryForServer(debouncedQuery)\n }, [debouncedQuery])\n\n // Calculate if the current query differs from the last executed query\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // Ref to track when the next fetch should bust the cache\n // This is used instead of replacing the queryFn to avoid the queryFn getting \"stuck\" with bustCache=true\n const bustCacheRef = useRef(false)\n\n // Execute query with TanStack Query\n const queryResult = useQuery({\n queryKey: createQueryKey(serverQuery),\n queryFn: async () => {\n if (!serverQuery) throw new Error('No query provided')\n\n // Check if this fetch should bust the cache\n const shouldBustCache = bustCacheRef.current\n // Reset the flag immediately so subsequent fetches don't bust cache\n bustCacheRef.current = false\n\n // When busting cache, bypass batch coordinator and make direct API call\n if (shouldBustCache) {\n return cubeApi.load(serverQuery, { bustCache: true })\n }\n\n // Use batch coordinator if enabled (collects queries for 100ms window)\n if (enableBatching && batchCoordinator) {\n return batchCoordinator.register(serverQuery)\n }\n\n // Fall back to direct load when batching disabled\n return cubeApi.load(serverQuery)\n },\n enabled: shouldExecute,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Extract raw data from result set\n const rawData = useMemo(() => {\n if (!queryResult.data) return null\n try {\n return queryResult.data.rawData()\n } catch {\n return null\n }\n }, [queryResult.data])\n\n // Extract warnings from result set\n const warnings = useMemo((): QueryWarning[] | undefined => {\n if (!queryResult.data?.loadResponse) return undefined\n const lr = queryResult.data.loadResponse\n // Handle nested structure: loadResponse.results[0].warnings\n if (lr.results && lr.results[0]?.warnings) {\n return lr.results[0].warnings\n }\n // Handle flat structure: loadResponse.warnings\n return lr.warnings\n }, [queryResult.data])\n\n // Execute query function - for manual refresh mode, triggers execution\n // Also serves as refetch in auto mode\n const executeQuery = useCallback((options?: RefetchOptions) => {\n if (!serverQuery) return\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Set the ref flag so the queryFn knows to bypass cache\n // The flag is reset inside queryFn after reading it\n bustCacheRef.current = true\n }\n\n // Invalidate and refetch - invalidateQueries marks as stale AND triggers refetch\n // when the query is being observed (which it is, via useQuery)\n queryClient.invalidateQueries({ queryKey: createQueryKey(serverQuery) })\n }, [serverQuery, currentQueryKey, queryClient])\n\n // Refetch is an alias for executeQuery for backward compatibility\n const refetch = executeQuery\n\n // Clear cache function\n const clearCache = () => {\n queryClient.removeQueries({ queryKey: ['cube', 'load'] })\n }\n\n // Handle resetResultSetOnChange\n const resultSet = useMemo(() => {\n if (resetResultSetOnChange && isDebouncing) {\n // Keep showing old data while debouncing\n return queryResult.data ?? null\n }\n return queryResult.data ?? null\n }, [queryResult.data, isDebouncing, resetResultSetOnChange])\n\n return {\n resultSet,\n rawData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error,\n debouncedQuery,\n isValidQuery,\n refetch,\n clearCache,\n needsRefresh,\n executeQuery,\n warnings,\n }\n}\n","/**\n * Multi-Query Data Utilities\n * Handles merging results from multiple CubeQuery executions\n *\n * Pattern follows comparisonUtils.ts for metadata injection:\n * - __queryIndex: numeric index of the source query (0-based)\n * - __queryLabel: user-defined or auto-generated label for the query\n */\n\nimport type { CubeResultSet, CubeQuery, QueryMergeStrategy } from '../types'\n\n/**\n * Metadata fields injected into multi-query data\n */\nexport interface MultiQueryMetadata {\n __queryIndex: number\n __queryLabel: string\n}\n\n/**\n * Check if data contains multi-query metadata\n */\nexport function isMultiQueryData(data: unknown[]): boolean {\n return data.length > 0 && typeof data[0] === 'object' && data[0] !== null && '__queryIndex' in data[0]\n}\n\n/**\n * Get unique query labels from multi-query data\n */\nexport function getQueryLabels(data: unknown[]): string[] {\n if (!isMultiQueryData(data)) return []\n\n const labels = new Set<string>()\n for (const row of data) {\n const label = (row as Record<string, unknown>).__queryLabel\n if (typeof label === 'string') {\n labels.add(label)\n }\n }\n return Array.from(labels)\n}\n\n/**\n * Get query indices from multi-query data\n */\nexport function getQueryIndices(data: unknown[]): number[] {\n if (!isMultiQueryData(data)) return []\n\n const indices = new Set<number>()\n for (const row of data) {\n const index = (row as Record<string, unknown>).__queryIndex\n if (typeof index === 'number') {\n indices.add(index)\n }\n }\n return Array.from(indices).sort((a, b) => a - b)\n}\n\n/**\n * Merge results using 'concat' strategy\n * Appends all rows with __queryIndex and __queryLabel metadata\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array with query metadata\n */\nexport function mergeResultsConcat(\n resultSets: CubeResultSet[],\n _queries: CubeQuery[],\n labels?: string[]\n): unknown[] {\n const merged: unknown[] = []\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const label = labels?.[queryIndex] || `Query ${queryIndex + 1}`\n\n data.forEach(row => {\n merged.push({\n ...row,\n __queryIndex: queryIndex,\n __queryLabel: label\n })\n })\n })\n\n return merged\n}\n\n/**\n * Merge results using 'merge' strategy\n * Aligns data by common dimensions (composite key), combining measures from all queries\n *\n * Example:\n * Query 1: [{ date: '2024-01', revenue: 100 }]\n * Query 2: [{ date: '2024-01', cost: 50 }]\n * Result: [{ date: '2024-01', revenue: 100, cost: 50 }]\n *\n * If multiple queries have the same measure, the first query's value is used.\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param mergeKeys - Dimension fields to align data on (composite key)\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Merged data array with combined measures\n */\nexport function mergeResultsByKey(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n mergeKeys: string[],\n _labels?: string[]\n): unknown[] {\n const mergedMap = new Map<string, Record<string, unknown>>()\n\n resultSets.forEach((resultSet, queryIndex) => {\n const data = resultSet.rawData()\n const measures = queries[queryIndex].measures || []\n\n data.forEach(row => {\n // Create composite key from all merge dimensions\n const keyValue = mergeKeys.map(k => String(row[k] ?? '')).join('|')\n\n if (!mergedMap.has(keyValue)) {\n // Initialize with all dimension values\n const baseRow: Record<string, unknown> = {}\n mergeKeys.forEach(k => { baseRow[k] = row[k] })\n mergedMap.set(keyValue, baseRow)\n }\n\n const mergedRow = mergedMap.get(keyValue)!\n\n // Add measures using raw field names (no prefix)\n // If same measure exists in multiple queries, first one wins\n measures.forEach(measure => {\n if (!(measure in mergedRow)) {\n mergedRow[measure] = row[measure]\n }\n })\n\n // Copy other dimensions (non-measure, non-merge-key fields) from first query\n if (queryIndex === 0) {\n Object.keys(row).forEach(field => {\n if (!mergeKeys.includes(field) && !measures.includes(field)) {\n if (!(field in mergedRow)) {\n mergedRow[field] = row[field]\n }\n }\n })\n }\n })\n })\n\n // Sort by first merge key for consistent ordering\n return Array.from(mergedMap.values()).sort((a, b) => {\n const aKey = String(a[mergeKeys[0]] ?? '')\n const bKey = String(b[mergeKeys[0]] ?? '')\n return aKey.localeCompare(bKey)\n })\n}\n\n/**\n * Main entry point for merging query results\n * Delegates to appropriate strategy implementation\n *\n * @param resultSets - Array of CubeResultSet from each query\n * @param queries - Original CubeQuery objects\n * @param strategy - Merge strategy ('concat' or 'merge')\n * @param mergeKeys - Dimension fields to align on (required for 'merge' strategy)\n * @param labels - Optional user-defined labels per query\n * @returns Merged data array\n */\nexport function mergeQueryResults(\n resultSets: CubeResultSet[],\n queries: CubeQuery[],\n strategy: QueryMergeStrategy,\n mergeKeys?: string[],\n labels?: string[]\n): unknown[] {\n // Handle edge cases\n if (resultSets.length === 0) return []\n if (resultSets.length === 1) return resultSets[0].rawData()\n\n // Use merge strategy if we have merge keys\n if (strategy === 'merge' && mergeKeys && mergeKeys.length > 0) {\n return mergeResultsByKey(resultSets, queries, mergeKeys, labels)\n }\n\n // Fall back to concat strategy\n return mergeResultsConcat(resultSets, queries, labels)\n}\n\n/**\n * Get combined fields from all queries\n * Used for chart configuration to show all available measures/dimensions\n *\n * @param queries - Array of CubeQuery objects\n * @param _labels - Optional user-defined labels per query (unused, kept for API compatibility)\n * @returns Object containing combined measures, dimensions, and time dimensions\n */\nexport function getCombinedFields(\n queries: CubeQuery[],\n _labels?: string[]\n): {\n measures: string[]\n dimensions: string[]\n timeDimensions: string[]\n} {\n const measures = new Set<string>()\n const dimensions = new Set<string>()\n const timeDimensions = new Set<string>()\n\n queries.forEach((query) => {\n // Measures use raw field names (no prefix), de-duplicated\n query.measures?.forEach(m => measures.add(m))\n\n // Dimensions are shared across queries (de-duplicated)\n query.dimensions?.forEach(d => dimensions.add(d))\n\n // Time dimensions are also shared\n query.timeDimensions?.forEach(td => timeDimensions.add(td.dimension))\n })\n\n return {\n measures: Array.from(measures),\n dimensions: Array.from(dimensions),\n timeDimensions: Array.from(timeDimensions)\n }\n}\n\n/**\n * Generate a default label for a query based on its measures\n * Used when user doesn't provide custom labels\n */\nexport function generateQueryLabel(query: CubeQuery, index: number): string {\n // Try to use first measure name without cube prefix\n if (query.measures && query.measures.length > 0) {\n const firstMeasure = query.measures[0]\n const parts = firstMeasure.split('.')\n if (parts.length > 1) {\n return parts[parts.length - 1] // Use measure name without cube prefix\n }\n return firstMeasure\n }\n\n // Fall back to indexed label\n return `Query ${index + 1}`\n}\n\n/**\n * Validate merge key exists in all queries\n * Returns validation result with details\n */\nexport function validateMergeKey(\n queries: CubeQuery[],\n mergeKey: string\n): {\n isValid: boolean\n missingInQueries: number[]\n} {\n const missingInQueries: number[] = []\n\n queries.forEach((query, index) => {\n const allDimensions = [\n ...(query.dimensions || []),\n ...(query.timeDimensions?.map(td => td.dimension) || [])\n ]\n\n if (!allDimensions.includes(mergeKey)) {\n missingInQueries.push(index)\n }\n })\n\n return {\n isValid: missingInQueries.length === 0,\n missingInQueries\n }\n}\n","/**\n * useMultiCubeLoadQuery - TanStack Query hook for multi-cube data loading\n *\n * Features:\n * - Execute multiple cube queries in parallel\n * - Merge results using configurable strategies\n * - Built-in debouncing to prevent excessive API calls\n * - Per-query error tracking\n * - BatchCoordinator integration for dashboard-level batching\n */\n\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport type { MultiQueryConfig, CubeResultSet } from '../../types'\nimport { cleanQueryForServer } from '../../shared/utils'\nimport { mergeQueryResults } from '../../utils/multiQueryUtils'\nimport { stableStringify } from '../../shared/queryKey'\nimport { useDebounceQuery } from '../useDebounceQuery'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Create a stable query key for multi-query\n */\nexport function createMultiQueryKey(\n config: MultiQueryConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'multiLoad', null] as const\n return ['cube', 'multiLoad', stableStringify(config)] as const\n}\n\nexport interface UseMultiCubeLoadQueryOptions {\n /**\n * Whether to skip the query\n * @default false\n */\n skip?: boolean\n /**\n * Debounce delay in milliseconds\n * @default 300\n */\n debounceMs?: number\n /**\n * Whether to reset result set when query changes\n * @default true\n */\n resetResultSetOnChange?: boolean\n /**\n * Stale time in milliseconds\n * @default 60 * 1000 (1 minute)\n */\n staleTime?: number\n /**\n * Whether to keep previous data while loading new data\n * @default true\n */\n keepPreviousData?: boolean\n}\n\nexport interface UseMultiCubeLoadQueryResult {\n /** Merged data from all queries */\n data: unknown[] | null\n /** Individual result sets from each query */\n resultSets: (CubeResultSet | null)[] | null\n /** Per-query raw data */\n perQueryData: (unknown[] | null)[] | null\n /** Whether any query is still loading (initial load) */\n isLoading: boolean\n /** Whether any query is fetching (includes refetch) */\n isFetching: boolean\n /** Whether query is debouncing (waiting for user to stop typing) */\n isDebouncing: boolean\n /** First error encountered */\n error: Error | null\n /** Per-query errors */\n errors: (Error | null)[]\n /** The debounced config that was executed */\n debouncedConfig: MultiQueryConfig | null\n /** Whether the current config is valid */\n isValidConfig: boolean\n /** Manually refetch the data. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n}\n\n/**\n * Check if a MultiQueryConfig is valid (has at least 2 valid queries)\n */\nfunction isValidMultiQueryConfig(config: MultiQueryConfig | null): boolean {\n if (!config || !config.queries || config.queries.length < 2) return false\n\n const validQueries = config.queries.filter(\n (q) =>\n (q.measures && q.measures.length > 0) ||\n (q.dimensions && q.dimensions.length > 0) ||\n (q.timeDimensions && q.timeDimensions.length > 0)\n )\n\n return validQueries.length >= 2\n}\n\n/**\n * TanStack Query hook for loading multi-cube data with debouncing\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useMultiCubeLoadQuery(config, {\n * debounceMs: 300,\n * skip: !isMultiQueryMode\n * })\n * ```\n */\nexport function useMultiCubeLoadQuery(\n config: MultiQueryConfig | null,\n options: UseMultiCubeLoadQueryOptions = {}\n): UseMultiCubeLoadQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n resetResultSetOnChange: _resetResultSetOnChange = true,\n staleTime = 60 * 1000,\n keepPreviousData = true,\n } = options\n\n // Silence unused variable warning - used for future functionality\n void _resetResultSetOnChange\n\n const { cubeApi, batchCoordinator, enableBatching } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Validate config\n const isValidConfig = isValidMultiQueryConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Transform queries for server\n const serverConfig = useMemo(() => {\n if (!debouncedConfig) return null\n return {\n ...debouncedConfig,\n queries: debouncedConfig.queries.map((q) => cleanQueryForServer(q)),\n }\n }, [debouncedConfig])\n\n // Execute multi-query with TanStack Query\n const queryResult = useQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n if (!serverConfig) throw new Error('No config provided')\n\n let resultSets: CubeResultSet[]\n\n // Use BatchCoordinator if enabled\n if (enableBatching && batchCoordinator) {\n resultSets = await Promise.all(\n serverConfig.queries.map((query) => batchCoordinator.register(query))\n )\n } else {\n // Direct batch call\n resultSets = await cubeApi.batchLoad(serverConfig.queries)\n }\n\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n\n // Get per-query raw data\n const perQueryData: (unknown[] | null)[] = resultSets.map((rs, i) => {\n if (errors[i]) return null\n try {\n return rs.rawData()\n } catch {\n return null\n }\n })\n\n // Filter successful results for merging\n const successfulResults = resultSets.filter((_, i) => !errors[i])\n const successfulQueries = serverConfig.queries.filter((_, i) => !errors[i])\n\n // Merge results using configured strategy\n const data =\n successfulResults.length > 0\n ? mergeQueryResults(\n successfulResults,\n successfulQueries,\n serverConfig.mergeStrategy,\n serverConfig.mergeKeys,\n serverConfig.queryLabels\n )\n : []\n\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n enabled: !!serverConfig && !skip,\n staleTime,\n placeholderData: keepPreviousData ? (prevData) => prevData : undefined,\n })\n\n // Refetch function - forces immediate refetch\n // Pass { bustCache: true } to bypass both client and server caches\n const refetch = (options?: { bustCache?: boolean }) => {\n if (serverConfig) {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey: createMultiQueryKey(serverConfig),\n queryFn: async () => {\n // Direct batch call with bustCache\n const resultSets = await cubeApi.batchLoad(\n serverConfig.queries,\n { bustCache: true }\n )\n // Track per-query errors\n const errors: (Error | null)[] = resultSets.map((rs) => {\n if (rs && 'error' in rs && (rs as { error?: string }).error) {\n return new Error((rs as { error: string }).error)\n }\n return null\n })\n // Merge results based on strategy\n const data = serverConfig.mergeStrategy === 'concat'\n ? resultSets.flatMap((rs) => rs?.rawData() || [])\n : resultSets[0]?.rawData() || []\n // Keep per-query data for table views\n const perQueryData = serverConfig.mergeStrategy === 'concat'\n ? resultSets.map((rs) => rs?.rawData() || [])\n : []\n return {\n data,\n resultSets,\n perQueryData,\n errors,\n firstError: errors.find((e) => e !== null) || null,\n }\n },\n })\n } else {\n queryClient.refetchQueries({\n queryKey: createMultiQueryKey(serverConfig),\n })\n }\n }\n }\n\n // Extract data from query result\n const data = queryResult.data?.data ?? null\n const resultSets = queryResult.data?.resultSets ?? null\n const perQueryData = queryResult.data?.perQueryData ?? null\n const errors = queryResult.data?.errors ?? []\n const error = queryResult.data?.firstError ?? queryResult.error\n\n return {\n data,\n resultSets,\n perQueryData,\n isLoading: queryResult.isLoading || isDebouncing,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error,\n errors,\n debouncedConfig,\n isValidConfig,\n refetch,\n }\n}\n","/**\n * useFunnelQuery - Hook for server-side funnel query execution\n *\n * Executes funnel queries on the server using a single SQL query with\n * CTE-based generation. This provides:\n * - True temporal ordering (step N must occur AFTER step N-1)\n * - Time window enforcement (timeToConvert constraints)\n * - No binding key value limits\n * - Time-to-convert metrics (avg, median, P90)\n *\n * Previously this hook used client-side sequential execution. The server-side\n * approach is strictly better and the data shapes are compatible.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n FunnelConfig,\n FunnelChartData,\n UseFunnelQueryOptions,\n UseFunnelQueryResult,\n FunnelStepResult,\n FunnelExecutionResult,\n} from '../../types/funnel'\nimport {\n buildServerFunnelQuery,\n transformServerFunnelResult,\n} from '../../utils/funnelExecution'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Check if a FunnelConfig is valid for execution\n */\nfunction isValidFunnelConfig(config: FunnelConfig | null): boolean {\n if (!config) return false\n if (!config.bindingKey) return false\n if (!config.steps || config.steps.length < 2) return false\n\n // Check that binding key dimension is defined\n if (typeof config.bindingKey.dimension === 'string') {\n if (!config.bindingKey.dimension) return false\n } else if (Array.isArray(config.bindingKey.dimension)) {\n if (config.bindingKey.dimension.length === 0) return false\n }\n\n // Check that each step has a valid query\n // For funnels, a step can have:\n // - measures/dimensions/timeDimensions (standard fields), OR\n // - filters only (the binding key dimension is auto-added by buildStepQuery)\n for (const step of config.steps) {\n const query = step.query\n const hasFields =\n (query.measures && query.measures.length > 0) ||\n (query.dimensions && query.dimensions.length > 0) ||\n (query.timeDimensions && query.timeDimensions.length > 0) ||\n (query.filters && query.filters.length > 0)\n if (!hasFields) return false\n }\n\n return true\n}\n\n/**\n * Hook for server-side funnel query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isExecuting, error } = useFunnelQuery(config, {\n * debounceMs: 300,\n * skip: !hasBindingKey\n * })\n *\n * // Results available after single server request\n * <FunnelChart data={chartData} />\n * ```\n */\nexport function useFunnelQuery(\n config: FunnelConfig | null,\n options: UseFunnelQueryOptions = {}\n): UseFunnelQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n prebuiltServerQuery,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate config\n const isValidConfig = isValidFunnelConfig(config)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedConfig, isDebouncing } = useDebounceQuery(config, {\n isValid: isValidConfig,\n skip,\n debounceMs,\n })\n\n // Build server query from config (or use prebuilt if provided)\n const serverQuery = useMemo(() => {\n // If prebuiltServerQuery is provided, use it directly\n if (prebuiltServerQuery) {\n return prebuiltServerQuery\n }\n\n // Otherwise build from config (legacy mode)\n if (!debouncedConfig || !isValidConfig) {\n return null\n }\n\n try {\n const result = buildServerFunnelQuery(\n debouncedConfig.steps.map(s => s.query),\n debouncedConfig.bindingKey,\n debouncedConfig.steps.map(s => s.name),\n debouncedConfig.steps.map(s => s.timeToConvert || null),\n true // includeTimeMetrics\n )\n return result\n } catch (error) {\n console.error('Failed to build server funnel query:', error)\n return null\n }\n }, [prebuiltServerQuery, debouncedConfig, isValidConfig])\n\n // Create stable query key\n // Include step count explicitly to ensure cache invalidation when steps change\n const queryKey = useMemo(() => {\n if (!serverQuery) return ['cube', 'funnel', null] as const\n const stepCount = serverQuery.funnel?.steps?.length || 0\n return ['cube', 'funnel', stepCount, JSON.stringify(serverQuery)] as const\n }, [serverQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = serverQuery ? stableStringify(serverQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever serverQuery is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!serverQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [serverQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && serverQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, serverQuery, skip, currentQueryKey])\n\n // Execute funnel query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!serverQuery) {\n throw new Error('No server query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send funnel query to server (single request)\n const resultSet = await cubeApi.load(serverQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err, 0)\n throw err\n }\n },\n // Enable when we have a server query (either prebuilt or built from config)\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && serverQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, serverQuery, currentQueryKey])\n\n // Get step names from either config or prebuilt server query\n const stepNames = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.map(s => s.name)\n }\n return debouncedConfig?.steps?.map(s => s.name)\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Get expected step count from either config or prebuilt server query\n const expectedStepCount = useMemo(() => {\n if (prebuiltServerQuery?.funnel?.steps) {\n return prebuiltServerQuery.funnel.steps.length\n }\n return debouncedConfig?.steps?.length || 0\n }, [prebuiltServerQuery, debouncedConfig])\n\n // Transform server result to chart data\n // Validate step count matches to prevent showing stale data during transitions\n const chartData = useMemo<FunnelChartData[]>(() => {\n if (!queryResult.data?.rawData) return []\n\n // Check if data step count matches expected step count\n const dataStepCount = queryResult.data.rawData.length\n\n if (dataStepCount !== expectedStepCount) {\n // Data is stale (from a different query) - don't return it\n // This prevents showing mismatched step counts while a new query loads\n return []\n }\n\n return transformServerFunnelResult(\n queryResult.data.rawData,\n stepNames\n )\n }, [queryResult.data, expectedStepCount, stepNames])\n\n // Build step results from chart data (for backward compatibility)\n const stepResults = useMemo<FunnelStepResult[]>(() => {\n if (!chartData.length) return []\n\n const firstCount = chartData[0]?.value || 0\n\n return chartData.map((data, index) => ({\n stepIndex: index,\n stepName: data.name,\n // Get step ID from config, or generate one for prebuilt queries\n stepId: debouncedConfig?.steps?.[index]?.id || `step-${index}`,\n data: [], // Raw data not available from server funnel\n bindingKeyValues: [], // Not available from server funnel\n bindingKeyTotalCount: 0,\n count: data.value,\n conversionRate: data.conversionRate !== null ? data.conversionRate / 100 : null,\n cumulativeConversionRate: firstCount > 0 ? data.value / firstCount : 0,\n executionTime: queryResult.data?.executionTime || 0,\n error: null,\n }))\n }, [chartData, debouncedConfig, queryResult.data?.executionTime])\n\n // Build full result for compatibility\n const result = useMemo<FunnelExecutionResult | null>(() => {\n // Need either config or prebuilt query for results\n if (!chartData.length) return null\n if (!debouncedConfig && !prebuiltServerQuery) return null\n\n const firstCount = chartData[0]?.value || 0\n const lastCount = chartData[chartData.length - 1]?.value || 0\n\n // Create a config object (use debouncedConfig if available, else synthesize from prebuilt)\n const effectiveConfig: FunnelConfig = debouncedConfig || {\n id: 'prebuilt-funnel',\n name: 'Funnel Analysis',\n bindingKey: {\n dimension: typeof prebuiltServerQuery?.funnel?.bindingKey === 'string'\n ? prebuiltServerQuery.funnel.bindingKey\n : prebuiltServerQuery?.funnel?.bindingKey?.[0]?.dimension || ''\n },\n steps: (prebuiltServerQuery?.funnel?.steps || []).map((s, i) => ({\n id: `step-${i}`,\n name: s.name,\n query: { filters: s.filter ? [s.filter as unknown as import('../../types').Filter] : [] },\n timeToConvert: s.timeToConvert || undefined,\n })),\n }\n\n const fullResult: FunnelExecutionResult = {\n config: effectiveConfig,\n steps: stepResults,\n summary: {\n totalEntries: firstCount,\n totalCompletions: lastCount,\n overallConversionRate: firstCount > 0 ? lastCount / firstCount : 0,\n totalExecutionTime: queryResult.data?.executionTime || 0,\n },\n chartData,\n status: queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle',\n error: queryResult.error as Error | null,\n currentStepIndex: null,\n }\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(fullResult)\n }\n\n return fullResult\n }, [debouncedConfig, prebuiltServerQuery, chartData, stepResults, queryResult, onComplete])\n\n // Determine current status\n const status: FunnelExecutionResult['status'] = queryResult.isError\n ? 'error'\n : queryResult.isLoading\n ? 'executing'\n : queryResult.isSuccess\n ? 'success'\n : 'idle'\n\n /**\n * Manually execute/refetch the funnel query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const execute = useCallback(async (options?: { bustCache?: boolean }): Promise<FunnelExecutionResult | null> => {\n // Allow execution if we have a serverQuery (either from prebuiltServerQuery or built from config)\n if (!serverQuery) return null\n\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n try {\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n serverQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n await queryResult.refetch()\n }\n return result\n } catch {\n return result\n }\n }, [serverQuery, queryResult, result, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Cancel is a no-op for TanStack Query (handled automatically)\n */\n const cancel = useCallback(() => {\n // TanStack Query handles cancellation automatically\n }, [])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n result,\n status,\n isExecuting: queryResult.isLoading || queryResult.isFetching,\n isDebouncing,\n currentStepIndex: null, // Not applicable for server-side execution\n stepLoadingStates: [], // Not applicable for server-side execution\n stepResults,\n chartData,\n error: queryResult.error as Error | null,\n execute,\n cancel,\n reset,\n // Not exposing executedQueries - server builds the query internally\n executedQueries: [],\n // Expose the server query for debug panel display\n // This is the actual { funnel: {...} } query sent to the server\n serverQuery,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for funnel queries\n */\nexport function createFunnelQueryKey(\n config: FunnelConfig | null\n): readonly unknown[] {\n if (!config) return ['cube', 'funnel', null] as const\n // Create a stable key based on config\n return ['cube', 'funnel', JSON.stringify(config)] as const\n}\n","/**\n * useFlowQuery - Hook for server-side flow query execution\n *\n * Executes flow queries on the server for bidirectional Sankey chart data.\n * Flow queries explore paths BEFORE and AFTER a defined starting step.\n *\n * The server returns { nodes: [], links: [] } structure ready for Sankey visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerFlowQuery,\n FlowChartData,\n} from '../../types/flow'\nimport { isSankeyData } from '../../types/flow'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for useFlowQuery hook\n */\nexport interface UseFlowQueryOptions {\n /** Skip query execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes successfully */\n onComplete?: (data: FlowChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n}\n\n/**\n * Result from useFlowQuery hook\n */\nexport interface UseFlowQueryResult {\n /** Transformed flow chart data (nodes and links) */\n data: FlowChartData | null\n /** Raw data from server */\n rawData: unknown[] | null\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n /** Is initial load in progress */\n isLoading: boolean\n /** Is refetch in progress */\n isFetching: boolean\n /** Is waiting for debounce */\n isDebouncing: boolean\n /** Is executing (loading or fetching) */\n isExecuting: boolean\n /** Error if query failed */\n error: Error | null\n /** Refetch the query. Pass { bustCache: true } to bypass client and server caches. */\n refetch: (options?: { bustCache?: boolean }) => void\n /** Reset the query cache */\n reset: () => void\n /** The server query being executed */\n serverQuery: ServerFlowQuery | null\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current flow config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerFlowQuery is valid for execution\n */\nfunction isValidFlowQuery(query: ServerFlowQuery | null): boolean {\n if (!query?.flow) return false\n\n const { flow } = query\n\n // Must have binding key\n if (!flow.bindingKey) return false\n\n // Must have time dimension\n if (!flow.timeDimension) return false\n\n // Must have event dimension\n if (!flow.eventDimension) return false\n\n // Must have starting step with filter\n if (!flow.startingStep?.filter) return false\n\n // Must have valid depth\n if (flow.stepsBefore < 0 || flow.stepsBefore > 5) return false\n if (flow.stepsAfter < 0 || flow.stepsAfter > 5) return false\n\n return true\n}\n\n/**\n * Transform raw server result to FlowChartData\n */\nfunction transformFlowResult(rawData: unknown[]): FlowChartData | null {\n // Server returns a single row with { nodes: [], links: [] } structure\n if (rawData.length === 1) {\n const row = rawData[0]\n if (row && typeof row === 'object' && 'nodes' in row && 'links' in row) {\n return row as FlowChartData\n }\n }\n\n // Alternative: Server might return nodes and links as separate items\n // or the entire array might be the flow result\n if (rawData.length > 0) {\n const firstItem = rawData[0]\n // Check if it looks like Sankey data using type guard\n if (firstItem && typeof firstItem === 'object' && isSankeyData(firstItem)) {\n return firstItem as FlowChartData\n }\n }\n\n return null\n}\n\n/**\n * Hook for server-side flow query execution\n *\n * Usage:\n * ```tsx\n * const { data, isLoading, error } = useFlowQuery(serverFlowQuery, {\n * debounceMs: 300,\n * skip: !isConfigured\n * })\n *\n * // Results available after single server request\n * <SankeyChart data={data} />\n * ```\n */\nexport function useFlowQuery(\n query: ServerFlowQuery | null,\n options: UseFlowQueryOptions = {}\n): UseFlowQueryResult {\n const {\n skip = false,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n onComplete,\n onError,\n } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValid = isValidFlowQuery(query)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(\n query,\n {\n isValid,\n skip,\n debounceMs,\n }\n )\n\n // Create stable query key string for the debounced query (used for TanStack Query cache key)\n const queryKeyString = useMemo(() => {\n if (!debouncedQuery) return null\n return JSON.stringify(debouncedQuery)\n }, [debouncedQuery])\n\n // Create stable query key string for the RAW input query (used for staleness detection)\n // This detects when the input query has changed but debounce hasn't completed yet\n const rawQueryKeyString = useMemo(() => {\n if (!query) return null\n return JSON.stringify(query)\n }, [query])\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'flow', null] as const\n return ['cube', 'flow', queryKeyString] as const\n }, [debouncedQuery, queryKeyString])\n\n // Calculate current query key for manual refresh tracking (uses raw input query)\n const currentQueryKey = query ? stableStringify(query) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n // On first load (executedQueryKey is null), don't show \"needs refresh\" - we'll auto-execute\n if (executedQueryKey === null) return false\n // After initial execution, show \"needs refresh\" when query has changed\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n // In auto mode, execute whenever query is valid and not skipped\n const shouldExecute = useMemo(() => {\n if (!isValid || !debouncedQuery || skip) return false\n if (!manualRefresh) return true // Auto mode: always execute\n // Manual mode: auto-execute on first load (executedQueryKey is null),\n // then require explicit trigger for subsequent changes\n if (executedQueryKey === null) return true // First load: auto-execute\n return executedQueryKey === currentQueryKey\n }, [isValid, debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n // This ensures needsRefresh stays false when query auto-executes\n useEffect(() => {\n if (!manualRefresh && query && !skip && isValid) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, query, skip, isValid, currentQueryKey])\n\n // Execute flow query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No flow query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send flow query to server (single request)\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n // Include query key in result so we can detect stale data\n // (data from a different query key, e.g., sankey vs sunburst mode)\n // We store the RAW query key so we can compare against the current raw query\n queryKeyString: rawQueryKeyString,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n // In manual refresh mode, only execute when explicitly triggered\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n // This ensures executedQueryKey is set after the first auto-execution,\n // preventing subsequent auto-executions until user clicks refresh\n useEffect(() => {\n // Only relevant in manual refresh mode\n if (!manualRefresh) return\n\n // When query successfully completes (and we were executing)\n // update the executed query key\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Check if data is stale (from a different query key)\n // This happens when switching between sankey/sunburst modes\n // We compare the current RAW query key with the one stored in the data\n // This catches staleness even during debounce (when debouncedQuery hasn't updated yet)\n const isDataStale = rawQueryKeyString !== null &&\n queryResult.data?.queryKeyString !== undefined &&\n queryResult.data.queryKeyString !== rawQueryKeyString\n\n // Transform server result to chart data\n // Return null if data is stale (from a different query) to show loading instead\n const chartData = useMemo<FlowChartData | null>(() => {\n // If data is stale (from different query), return null to show loading\n if (isDataStale) return null\n\n if (!queryResult.data?.rawData) return null\n\n const transformed = transformFlowResult(queryResult.data.rawData)\n\n // Call completion callback\n if (transformed && queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(transformed)\n }\n\n return transformed\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, isDataStale])\n\n /**\n * Refetch the flow query\n * Pass { bustCache: true } to bypass both client and server caches\n */\n const refetch = useCallback((options?: { bustCache?: boolean }) => {\n if (debouncedQuery && isValid) {\n // Mark this query as executed (for manual refresh mode)\n setExecutedQueryKey(currentQueryKey)\n\n if (options?.bustCache) {\n // Remove from TanStack Query cache first\n queryClient.removeQueries({ queryKey })\n // Fetch with cache bust header\n queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const startTime = performance.now()\n const resultSet = await cubeApi.load(\n debouncedQuery as unknown as CubeQuery,\n { bustCache: true }\n )\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime, cacheInfo }\n },\n })\n } else {\n queryResult.refetch()\n }\n }\n }, [debouncedQuery, isValid, queryResult, queryClient, queryKey, cubeApi, currentQueryKey])\n\n /**\n * Reset clears the query cache\n */\n const reset = useCallback(() => {\n queryClient.removeQueries({ queryKey })\n }, [queryClient, queryKey])\n\n return {\n data: chartData,\n rawData: isDataStale ? null : (queryResult.data?.rawData ?? null),\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n isLoading: queryResult.isLoading || isDataStale,\n isFetching: queryResult.isFetching,\n isDebouncing,\n isExecuting: queryResult.isLoading || queryResult.isFetching || isDataStale,\n error: queryResult.error as Error | null,\n refetch,\n reset,\n serverQuery: debouncedQuery,\n // Manual refresh mode support\n needsRefresh,\n }\n}\n\n/**\n * Create a stable query key for flow queries\n */\nexport function createFlowQueryKey(\n query: ServerFlowQuery | null\n): readonly unknown[] {\n if (!query) return ['cube', 'flow', null] as const\n return ['cube', 'flow', JSON.stringify(query)] as const\n}\n","/**\n * useRetentionQuery - Hook for server-side retention query execution\n *\n * Executes retention queries on the server using SQL generation.\n * Returns cohort-based retention data for heatmap visualization.\n */\n\nimport { useMemo, useCallback, useState, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useCubeApi } from '../../providers/CubeApiProvider'\nimport { useCubeFeatures } from '../../providers/CubeFeaturesProvider'\nimport { useDebounceQuery } from '../useDebounceQuery'\nimport { stableStringify } from '../../shared/queryKey'\nimport type { CubeQuery } from '../../types'\nimport type {\n ServerRetentionQuery,\n RetentionChartData,\n RetentionResultRow,\n RetentionSummary,\n RetentionGranularity,\n} from '../../types/retention'\n\n// Default debounce delay in milliseconds\nconst DEFAULT_DEBOUNCE_MS = 300\n\n/**\n * Options for retention query hook\n */\nexport interface UseRetentionQueryOptions {\n /** Skip execution */\n skip?: boolean\n /** Debounce delay in milliseconds */\n debounceMs?: number\n /** Callback when query completes */\n onComplete?: (result: RetentionChartData) => void\n /** Callback when query fails */\n onError?: (error: Error) => void\n /** Function to resolve field names to human-readable display labels */\n getFieldLabel?: (fieldName: string) => string\n}\n\n/**\n * Result from retention query hook\n */\nexport interface UseRetentionQueryResult {\n /** Retention chart data */\n chartData: RetentionChartData | null\n\n /** Raw data rows from server */\n rawData: RetentionResultRow[] | null\n\n /** Current execution status */\n status: 'idle' | 'loading' | 'success' | 'error'\n\n /** Whether currently loading */\n isLoading: boolean\n\n /** Whether fetching (includes refetch) */\n isFetching: boolean\n\n /** Whether waiting for debounce */\n isDebouncing: boolean\n\n /** Error if execution failed */\n error: Error | null\n\n /** Cache metadata when served from cache */\n cacheInfo?: { hit: true; cachedAt: string; ttlMs: number; ttlRemainingMs: number } | null\n\n /** Execute the query (for manual refresh mode) */\n execute: (options?: { bustCache?: boolean }) => Promise<RetentionChartData | null>\n\n /** Refetch the query */\n refetch: () => void\n\n /**\n * Whether the query needs to be refreshed (manual refresh mode only).\n * True when the current query config differs from the last executed query.\n */\n needsRefresh: boolean\n}\n\n/**\n * Check if a ServerRetentionQuery is valid for execution\n * Uses new simplified Mixpanel-style format with single timeDimension\n */\nfunction isValidRetentionQuery(query: ServerRetentionQuery | null): boolean {\n if (!query) return false\n if (!query.retention) return false\n if (!query.retention.timeDimension) return false\n if (!query.retention.bindingKey) return false\n if (!query.retention.periods || query.retention.periods < 1) return false\n return true\n}\n\n/**\n * Extract the human-readable label from the binding key\n * e.g., \"Users.userId\" → \"userId\", \"Events.customerId\" → \"customerId\"\n */\nfunction extractBindingKeyLabel(\n bindingKey: ServerRetentionQuery['retention']['bindingKey'] | undefined\n): string | undefined {\n if (!bindingKey) return undefined\n\n // String format: \"Cube.dimensionName\" → \"dimensionName\"\n if (typeof bindingKey === 'string') {\n return bindingKey.split('.').pop()\n }\n\n // Array format: [{ cube, dimension }] → extract first dimension's name\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n const firstMapping = bindingKey[0]\n if (firstMapping?.dimension) {\n return firstMapping.dimension.split('.').pop()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract breakdown value from server response\n * Server returns breakdownValues as an object like {\"PREvents.eventType\": \"approved\"}\n * We extract the first (and typically only) value from this object\n */\nfunction extractBreakdownValue(\n breakdownValues: unknown\n): string | null {\n // If it's already a string, return it\n if (typeof breakdownValues === 'string') {\n return breakdownValues\n }\n\n // If it's an object, extract the first value\n if (breakdownValues && typeof breakdownValues === 'object' && !Array.isArray(breakdownValues)) {\n const values = Object.values(breakdownValues as Record<string, unknown>)\n if (values.length > 0 && values[0] != null) {\n return String(values[0])\n }\n }\n\n return null\n}\n\n/**\n * Transform raw server data to RetentionChartData format\n * New simplified format: rows with period, cohortSize, retainedUsers, retentionRate, breakdownValue\n */\nfunction transformRetentionResult(\n rawData: unknown[],\n granularity?: RetentionGranularity,\n bindingKeyLabel?: string\n): RetentionChartData {\n if (!rawData || !Array.isArray(rawData) || rawData.length === 0) {\n return { rows: [], periods: [], granularity, bindingKeyLabel }\n }\n\n const rows: RetentionResultRow[] = rawData.map((row: unknown) => {\n const r = row as Record<string, unknown>\n return {\n period: Number(r.period ?? r.period_number ?? 0),\n cohortSize: Number(r.cohortSize ?? r.cohort_size ?? 0),\n retainedUsers: Number(r.retainedUsers ?? r.retained_users ?? 0),\n retentionRate: Number(r.retentionRate ?? r.retention_rate ?? 0),\n // Server returns breakdownValues (object) or breakdownValue (string) or breakdown_value (snake_case)\n breakdownValue: extractBreakdownValue(r.breakdownValues) ?? r.breakdownValue ?? r.breakdown_value ?? null,\n } as RetentionResultRow\n })\n\n // Extract unique periods and breakdown values\n const periodsSet = new Set<number>()\n const breakdownSet = new Set<string>()\n\n rows.forEach((row) => {\n periodsSet.add(row.period)\n if (row.breakdownValue) {\n breakdownSet.add(row.breakdownValue)\n }\n })\n\n const periods = Array.from(periodsSet).sort((a, b) => a - b)\n const breakdownValues = breakdownSet.size > 0 ? Array.from(breakdownSet).sort() : undefined\n\n // Calculate summary statistics\n const summary = calculateSummary(rows, breakdownValues)\n\n return { rows, periods, breakdownValues, summary, granularity, bindingKeyLabel }\n}\n\n/**\n * Calculate summary statistics from retention data\n */\nfunction calculateSummary(\n rows: RetentionResultRow[],\n breakdownValues?: string[]\n): RetentionSummary {\n const period1Rows = rows.filter((r) => r.period === 1)\n const period1Rates = period1Rows.map((r) => r.retentionRate)\n\n const totalUsers = rows\n .filter((r) => r.period === 0)\n .reduce((sum, r) => sum + r.cohortSize, 0)\n\n return {\n totalUsers,\n avgPeriod1Retention:\n period1Rates.length > 0\n ? period1Rates.reduce((a, b) => a + b, 0) / period1Rates.length\n : 0,\n maxPeriod1Retention: period1Rates.length > 0 ? Math.max(...period1Rates) : 0,\n minPeriod1Retention: period1Rates.length > 0 ? Math.min(...period1Rates) : 0,\n segmentCount: breakdownValues?.length || 1,\n }\n}\n\n/**\n * Hook for server-side retention query execution\n *\n * Usage:\n * ```tsx\n * const { chartData, isLoading, error } = useRetentionQuery(serverQuery, {\n * debounceMs: 300,\n * skip: !hasRequiredFields\n * })\n *\n * <RetentionHeatmap data={chartData} />\n * ```\n */\nexport function useRetentionQuery(\n serverQuery: ServerRetentionQuery | null,\n options: UseRetentionQueryOptions = {}\n): UseRetentionQueryResult {\n const { skip = false, debounceMs = DEFAULT_DEBOUNCE_MS, onComplete, onError, getFieldLabel } = options\n\n const { cubeApi } = useCubeApi()\n const queryClient = useQueryClient()\n\n // Get manual refresh mode from features\n const { features } = useCubeFeatures()\n const manualRefresh = features.manualRefresh ?? false\n\n // Track the last executed query (for manual refresh mode)\n const [executedQueryKey, setExecutedQueryKey] = useState<string | null>(null)\n\n // Validate query\n const isValidQuery = isValidRetentionQuery(serverQuery)\n\n // Use shared debounce hook\n const { debouncedValue: debouncedQuery, isDebouncing } = useDebounceQuery(serverQuery, {\n isValid: isValidQuery,\n skip,\n debounceMs,\n })\n\n // Create stable query key\n const queryKey = useMemo(() => {\n if (!debouncedQuery) return ['cube', 'retention', null] as const\n return ['cube', 'retention', JSON.stringify(debouncedQuery)] as const\n }, [debouncedQuery])\n\n // Calculate current query key for manual refresh tracking\n const currentQueryKey = debouncedQuery ? stableStringify(debouncedQuery) : null\n\n // Calculate if the current query differs from the last executed query\n const needsRefresh = useMemo(() => {\n if (!manualRefresh) return false\n if (!currentQueryKey) return false\n if (executedQueryKey === null) return false\n return currentQueryKey !== executedQueryKey\n }, [manualRefresh, currentQueryKey, executedQueryKey])\n\n // In manual refresh mode, only execute when explicitly triggered\n const shouldExecute = useMemo(() => {\n if (!debouncedQuery || skip) return false\n if (!manualRefresh) return true\n if (executedQueryKey === null) return true\n return executedQueryKey === currentQueryKey\n }, [debouncedQuery, skip, manualRefresh, executedQueryKey, currentQueryKey])\n\n // In auto mode, track executed query for consistency\n useEffect(() => {\n if (!manualRefresh && debouncedQuery && !skip) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, debouncedQuery, skip, currentQueryKey])\n\n // Execute retention query via TanStack Query\n const queryResult = useQuery({\n queryKey,\n queryFn: async () => {\n if (!debouncedQuery) {\n throw new Error('No retention query available')\n }\n\n const startTime = performance.now()\n\n try {\n // Send retention query to server\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const executionTime = performance.now() - startTime\n const cacheInfo = resultSet.cacheInfo?.()\n\n return {\n rawData,\n executionTime,\n cacheInfo,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n onError?.(err)\n throw err\n }\n },\n enabled: shouldExecute,\n staleTime: 60000, // 1 minute cache\n gcTime: 5 * 60 * 1000, // 5 minute garbage collection\n })\n\n // Track when query successfully executes in manual refresh mode\n useEffect(() => {\n if (!manualRefresh) return\n if (shouldExecute && queryResult.isSuccess && !queryResult.isFetching && debouncedQuery) {\n setExecutedQueryKey(currentQueryKey)\n }\n }, [manualRefresh, shouldExecute, queryResult.isSuccess, queryResult.isFetching, debouncedQuery, currentQueryKey])\n\n // Extract granularity and binding key label from server query\n const granularity = serverQuery?.retention?.granularity\n\n // Get the raw binding key field name for label lookup\n const rawBindingKeyField = useMemo(() => {\n const bindingKey = serverQuery?.retention?.bindingKey\n if (!bindingKey) return undefined\n if (typeof bindingKey === 'string') return bindingKey\n if (Array.isArray(bindingKey) && bindingKey.length > 0) {\n return bindingKey[0]?.dimension\n }\n return undefined\n }, [serverQuery?.retention?.bindingKey])\n\n // Use getFieldLabel if provided, fallback to extractBindingKeyLabel\n const bindingKeyLabel = useMemo(() => {\n if (rawBindingKeyField && getFieldLabel) {\n const label = getFieldLabel(rawBindingKeyField)\n // If getFieldLabel returns the same string, it didn't find a better label\n if (label && label !== rawBindingKeyField) {\n return label\n }\n }\n // Fallback to extracting from the field name\n return extractBindingKeyLabel(serverQuery?.retention?.bindingKey)\n }, [rawBindingKeyField, getFieldLabel, serverQuery?.retention?.bindingKey])\n\n // Transform server result to chart data\n const chartData = useMemo<RetentionChartData | null>(() => {\n if (!queryResult.data?.rawData) return null\n const result = transformRetentionResult(\n queryResult.data.rawData,\n granularity,\n bindingKeyLabel\n )\n\n // Call completion callback\n if (queryResult.isSuccess && !queryResult.isFetching) {\n onComplete?.(result)\n }\n\n return result\n }, [queryResult.data, queryResult.isSuccess, queryResult.isFetching, onComplete, granularity, bindingKeyLabel])\n\n // Execute function for manual refresh mode\n const execute = useCallback(\n async (executeOptions?: { bustCache?: boolean }) => {\n if (!debouncedQuery) return null\n\n if (executeOptions?.bustCache) {\n queryClient.removeQueries({ queryKey })\n }\n\n // Update executed query key to trigger execution\n setExecutedQueryKey(currentQueryKey)\n\n // Wait for the query to complete\n const result = await queryClient.fetchQuery({\n queryKey,\n queryFn: async () => {\n const resultSet = await cubeApi.load(debouncedQuery as unknown as CubeQuery)\n const rawData = resultSet.rawData()\n const cacheInfo = resultSet.cacheInfo?.()\n return { rawData, executionTime: 0, cacheInfo }\n },\n })\n\n return transformRetentionResult(result.rawData, granularity, bindingKeyLabel)\n },\n [debouncedQuery, queryClient, queryKey, cubeApi, currentQueryKey, granularity, bindingKeyLabel]\n )\n\n // Refetch function\n const refetch = useCallback(() => {\n queryResult.refetch()\n }, [queryResult])\n\n // Determine status\n const status = useMemo(() => {\n if (queryResult.isError) return 'error' as const\n if (queryResult.isLoading) return 'loading' as const\n if (queryResult.isSuccess) return 'success' as const\n return 'idle' as const\n }, [queryResult.isError, queryResult.isLoading, queryResult.isSuccess])\n\n return {\n chartData,\n rawData: queryResult.data?.rawData as RetentionResultRow[] | null ?? null,\n status,\n isLoading: queryResult.isLoading,\n isFetching: queryResult.isFetching,\n isDebouncing,\n error: queryResult.error as Error | null,\n cacheInfo: queryResult.data?.cacheInfo ?? null,\n execute,\n refetch,\n needsRefresh,\n }\n}\n","/**\n * Hook for fetching distinct field values for filter dropdowns\n * Uses TanStack Query via useCubeLoadQuery for data fetching\n */\n\nimport { useState, useCallback, useRef, useMemo, useEffect } from 'react'\nimport { useCubeLoadQuery } from './queries/useCubeLoadQuery'\nimport type { CubeQuery } from '../types'\n\ninterface UseFilterValuesResult {\n values: any[]\n loading: boolean\n error: string | null\n refetch: () => void\n searchValues: (searchTerm: string, force?: boolean) => void\n}\n\n/**\n * Custom hook to fetch distinct values for a field\n *\n * Uses TanStack Query for server state (data fetching, caching, loading).\n * Values are derived via useMemo from query results - NOT stored in useState.\n */\nexport function useFilterValues(\n fieldName: string | null,\n enabled: boolean = true\n): UseFilterValuesResult {\n const [currentQuery, setCurrentQuery] = useState<CubeQuery | null>(null)\n const lastSearchTerm = useRef<string>('')\n\n // Use TanStack Query hook for data fetching\n const {\n resultSet,\n isLoading,\n error: queryError,\n } = useCubeLoadQuery(currentQuery, {\n skip: !currentQuery || !enabled || !fieldName,\n debounceMs: 150, // Quick debounce for filter searches\n keepPreviousData: true,\n })\n\n // Derive values from resultSet using useMemo (NOT useState)\n // This is the correct pattern - server state stays in TanStack Query\n const values = useMemo(() => {\n // Return empty if no result set, loading, or error\n if (!resultSet || isLoading || queryError || !fieldName) {\n return []\n }\n\n try {\n const data = resultSet.tablePivot()\n const uniqueValues = new Set<any>()\n\n data.forEach((row: any) => {\n const value = row[fieldName]\n if (value !== null && value !== undefined && value !== '') {\n uniqueValues.add(value)\n }\n })\n\n // Convert to array - already sorted by query\n return Array.from(uniqueValues)\n } catch (err) {\n console.error('Error extracting values from result set:', err)\n return []\n }\n }, [resultSet, isLoading, queryError, fieldName])\n\n // Reset query when fieldName becomes null or enabled changes\n useEffect(() => {\n if (!fieldName || !enabled) {\n setCurrentQuery(null)\n lastSearchTerm.current = ''\n }\n }, [fieldName, enabled])\n\n // Refetch function\n const refetch = useCallback(() => {\n if (!fieldName) return\n\n lastSearchTerm.current = ''\n\n try {\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating query:', err)\n }\n }, [fieldName])\n\n // Search function for server-side filtering\n const searchValues = useCallback((searchTerm: string, force: boolean = false) => {\n if (!fieldName) {\n return\n }\n\n // Don't create a new query if the search term hasn't changed (unless forced)\n if (!force && searchTerm === lastSearchTerm.current) {\n return\n }\n\n lastSearchTerm.current = searchTerm\n\n try {\n // Create query inline to avoid dependency issues\n const query: CubeQuery = {\n dimensions: [fieldName],\n limit: 25,\n order: { [fieldName]: 'asc' }\n }\n\n if (searchTerm && searchTerm.trim()) {\n query.filters = [{\n member: fieldName,\n operator: 'contains',\n values: [searchTerm.trim()]\n }]\n }\n\n setCurrentQuery(query)\n } catch (err) {\n console.error('Error creating search query:', err)\n }\n }, [fieldName])\n\n return {\n values,\n loading: isLoading,\n error: queryError ? (queryError instanceof Error ? queryError.message : String(queryError)) : null,\n refetch,\n searchValues\n }\n}\n","/**\n * Custom hook for debouncing values\n * Delays updating the value until after the specified delay has passed\n * since the last change\n */\n\nimport { useState, useEffect } from 'react'\n\n/**\n * Debounces a value by the specified delay\n * @param value The value to debounce\n * @param delay The delay in milliseconds\n * @returns The debounced value\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value)\n\n useEffect(() => {\n // Set up a timer to update the debounced value after the delay\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n // Clean up the timer if the value changes before the delay\n return () => {\n clearTimeout(handler)\n }\n }, [value, delay])\n\n return debouncedValue\n}","/**\n * Custom hook for responsive dashboard layout management\n * Implements a three-tier responsive strategy:\n * - Desktop (1200px+): Normal grid layout with full editing\n * - Scaled (768-1199px): CSS transform scaling, read-only\n * - Mobile (<768px): Single-column stacked layout, read-only\n */\n\nimport { useState, useEffect, useRef, useMemo, useCallback, RefCallback } from 'react'\n\nexport type DashboardDisplayMode = 'desktop' | 'scaled' | 'mobile'\n\nconst DESIGN_WIDTH = 1200\nconst MOBILE_THRESHOLD = 768\n\nexport interface UseResponsiveDashboardResult {\n containerRef: RefCallback<HTMLDivElement>\n containerWidth: number\n displayMode: DashboardDisplayMode\n scaleFactor: number\n isEditable: boolean\n designWidth: number\n}\n\n/**\n * Hook for managing responsive dashboard layouts\n * Uses ResizeObserver for accurate width detection and calculates\n * the appropriate display mode and scale factor\n */\nexport function useResponsiveDashboard(): UseResponsiveDashboardResult {\n // Start with window width as initial estimate\n const [containerWidth, setContainerWidth] = useState(() =>\n typeof window !== 'undefined' ? window.innerWidth : DESIGN_WIDTH\n )\n const observerRef = useRef<ResizeObserver | null>(null)\n const elementRef = useRef<HTMLDivElement | null>(null)\n\n // Ref callback - called when element is attached/detached\n // This is key: unlike useEffect, this fires immediately when the DOM element exists\n const containerRef = useCallback((node: HTMLDivElement | null) => {\n // Cleanup previous observer\n if (observerRef.current) {\n observerRef.current.disconnect()\n observerRef.current = null\n }\n\n elementRef.current = node\n\n if (node) {\n // Get initial width immediately (synchronously when element attaches)\n const initialWidth = node.offsetWidth\n if (initialWidth > 0) {\n setContainerWidth(initialWidth)\n }\n\n // Set up ResizeObserver for ongoing changes\n observerRef.current = new ResizeObserver((entries) => {\n const width = entries[0]?.contentRect.width\n if (width && width > 0) {\n setContainerWidth(width)\n }\n })\n observerRef.current.observe(node)\n }\n }, [])\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect()\n }\n }\n }, [])\n\n // Fallback: window resize listener to catch resize events that ResizeObserver might miss\n // This is particularly important for containers in flex/grid layouts or deeply nested elements\n useEffect(() => {\n const handleWindowResize = () => {\n if (elementRef.current) {\n const width = elementRef.current.offsetWidth\n if (width > 0) {\n setContainerWidth(width)\n }\n }\n }\n\n window.addEventListener('resize', handleWindowResize)\n\n // Also measure after a short delay to catch late layout calculations\n const timeoutId = setTimeout(handleWindowResize, 100)\n\n return () => {\n window.removeEventListener('resize', handleWindowResize)\n clearTimeout(timeoutId)\n }\n }, [])\n\n const displayMode = useMemo<DashboardDisplayMode>(() => {\n if (containerWidth >= DESIGN_WIDTH) return 'desktop'\n if (containerWidth >= MOBILE_THRESHOLD) return 'scaled'\n return 'mobile'\n }, [containerWidth])\n\n const scaleFactor = useMemo(() => {\n if (displayMode !== 'scaled') return 1\n return containerWidth / DESIGN_WIDTH\n }, [containerWidth, displayMode])\n\n const isEditable = displayMode === 'desktop'\n\n return {\n containerRef,\n containerWidth,\n displayMode,\n scaleFactor,\n isEditable,\n designWidth: DESIGN_WIDTH\n }\n}\n","/**\n * useDirtyStateTracking - Track configuration changes and dirty state\n *\n * Extracts dirty state tracking logic from AnalyticsDashboard:\n * - Tracks initial config to prevent saves during initial load\n * - Detects meaningful changes from initial state\n * - Manages dirty state through onDirtyStateChange callback\n *\n * @example\n * const { handleConfigChange, handleSave } = useDirtyStateTracking({\n * initialConfig: config,\n * onConfigChange,\n * onSave,\n * onDirtyStateChange,\n * })\n */\n\nimport { useCallback, useRef } from 'react'\n\nexport interface UseDirtyStateTrackingOptions<T> {\n /** Initial configuration to compare against */\n initialConfig: T\n /** Original config change handler */\n onConfigChange?: (config: T) => void\n /** Original save handler */\n onSave?: (config: T) => Promise<void> | void\n /** Dirty state change callback */\n onDirtyStateChange?: (isDirty: boolean) => void\n}\n\nexport interface UseDirtyStateTrackingResult<T> {\n /** Wrapped config change handler that tracks dirty state */\n handleConfigChange: (config: T) => void\n /** Wrapped save handler that tracks dirty state */\n handleSave: (config: T) => Promise<void>\n /** Whether config has changed from initial */\n hasChanged: () => boolean\n /** Reset the initial config reference (e.g., after external config update) */\n resetInitialConfig: (config: T) => void\n}\n\nexport function useDirtyStateTracking<T>({\n initialConfig,\n onConfigChange,\n onSave,\n onDirtyStateChange,\n}: UseDirtyStateTrackingOptions<T>): UseDirtyStateTrackingResult<T> {\n // Track initial config to prevent saves during initial load\n const initialConfigRef = useRef(initialConfig)\n const hasConfigChangedFromInitial = useRef(false)\n\n // Enhanced save handler that tracks dirty state and prevents saves during initial load\n const handleSave = useCallback(\n async (config: T) => {\n // Don't save if this config hasn't actually changed from the initial load\n if (!hasConfigChangedFromInitial.current) {\n return // Prevent saves during initial load/responsive changes\n }\n\n if (onDirtyStateChange) {\n onDirtyStateChange(true) // Mark as dirty when save starts\n }\n\n try {\n if (onSave) {\n await onSave(config)\n }\n\n // Update our reference point after successful save\n initialConfigRef.current = config\n\n // Mark as clean after successful save\n if (onDirtyStateChange) {\n onDirtyStateChange(false)\n }\n } catch (error) {\n // Keep dirty state if save failed\n console.error('Save failed:', error)\n throw error\n }\n },\n [onSave, onDirtyStateChange]\n )\n\n // Enhanced config change handler that marks as dirty (only after initial load)\n const handleConfigChange = useCallback(\n (config: T) => {\n if (onConfigChange) {\n onConfigChange(config)\n }\n\n // Check if this is a meaningful change from the initial config\n const configString = JSON.stringify(config)\n const initialConfigString = JSON.stringify(initialConfigRef.current)\n\n if (configString !== initialConfigString) {\n hasConfigChangedFromInitial.current = true\n\n if (onDirtyStateChange) {\n onDirtyStateChange(true)\n }\n }\n },\n [onConfigChange, onDirtyStateChange]\n )\n\n // Check if config has changed from initial\n const hasChanged = useCallback(() => {\n return hasConfigChangedFromInitial.current\n }, [])\n\n // Reset initial config reference (useful when config is updated externally)\n const resetInitialConfig = useCallback((config: T) => {\n initialConfigRef.current = config\n hasConfigChangedFromInitial.current = false\n }, [])\n\n return {\n handleConfigChange,\n handleSave,\n hasChanged,\n resetInitialConfig,\n }\n}\n"],"names":["FILTER_OPERATORS","DATE_RANGE_OPTIONS","isSimpleFilter","filter","isGroupFilter","transformFiltersForServer","filters","transformFilter","transformedSubFilters","cleanQuery","query","cleanedQuery","cleanQueryForServer","getAvailableOperators","fieldType","operators","operator","meta","convertDateRangeTypeToValue","rangeType","number","typeMap","unit","unitSingular","requiresNumberInput","formatDateForCube","date","stableStringify","value","seen","stringify","input","item","record","key","useDebounceQuery","options","isValid","skip","debounceMs","debouncedValue","setDebouncedValue","useState","isDebouncing","setIsDebouncing","debounceTimerRef","useRef","lastValueStringRef","wasSkippedRef","valueString","useMemo","useEffect","justBecameUnskipped","DEFAULT_DEBOUNCE_MS","createQueryKey","isValidCubeQuery","hasMeasures","hasDimensions","hasTimeDimensions","useCubeLoadQuery","resetResultSetOnChange","staleTime","keepPreviousData","cubeApi","batchCoordinator","enableBatching","useCubeApi","queryClient","useQueryClient","features","useCubeFeatures","manualRefresh","executedQueryKey","setExecutedQueryKey","isValidQuery","debouncedQuery","serverQuery","currentQueryKey","needsRefresh","shouldExecute","bustCacheRef","queryResult","useQuery","shouldBustCache","prevData","rawData","warnings","lr","executeQuery","useCallback","refetch","clearCache","isMultiQueryData","data","getQueryLabels","labels","row","label","getQueryIndices","indices","index","a","b","mergeResultsConcat","resultSets","_queries","merged","resultSet","queryIndex","mergeResultsByKey","queries","mergeKeys","_labels","mergedMap","measures","keyValue","k","baseRow","mergedRow","measure","field","aKey","bKey","mergeQueryResults","strategy","getCombinedFields","dimensions","timeDimensions","m","d","td","generateQueryLabel","firstMeasure","parts","validateMergeKey","mergeKey","missingInQueries","createMultiQueryKey","config","isValidMultiQueryConfig","q","useMultiCubeLoadQuery","isValidConfig","debouncedConfig","serverConfig","errors","rs","perQueryData","i","successfulResults","_","successfulQueries","e","error","isValidFunnelConfig","step","useFunnelQuery","onComplete","onError","prebuiltServerQuery","buildServerFunnelQuery","s","queryKey","startTime","executionTime","cacheInfo","err","stepNames","expectedStepCount","chartData","transformServerFunnelResult","stepResults","firstCount","result","lastCount","fullResult","status","execute","cancel","reset","createFunnelQueryKey","isValidFlowQuery","flow","transformFlowResult","firstItem","isSankeyData","useFlowQuery","queryKeyString","rawQueryKeyString","isDataStale","transformed","createFlowQueryKey","isValidRetentionQuery","extractBindingKeyLabel","bindingKey","firstMapping","extractBreakdownValue","breakdownValues","values","transformRetentionResult","granularity","bindingKeyLabel","rows","r","periodsSet","breakdownSet","periods","summary","calculateSummary","period1Rates","sum","useRetentionQuery","getFieldLabel","rawBindingKeyField","executeOptions","useFilterValues","fieldName","enabled","currentQuery","setCurrentQuery","lastSearchTerm","isLoading","queryError","uniqueValues","searchValues","searchTerm","force","useDebounce","delay","handler","DESIGN_WIDTH","MOBILE_THRESHOLD","useResponsiveDashboard","containerWidth","setContainerWidth","observerRef","elementRef","containerRef","node","initialWidth","entries","width","handleWindowResize","timeoutId","displayMode","scaleFactor","useDirtyStateTracking","initialConfig","onConfigChange","onSave","onDirtyStateChange","initialConfigRef","hasConfigChangedFromInitial","handleSave","handleConfigChange","configString","initialConfigString","hasChanged","resetInitialConfig"],"mappings":"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"}
|