drizzle-cube 0.3.21 → 0.3.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.
Files changed (120) hide show
  1. package/dist/adapters/express/index.cjs +1 -1
  2. package/dist/adapters/express/index.js +1 -1
  3. package/dist/adapters/fastify/index.cjs +1 -1
  4. package/dist/adapters/fastify/index.js +1 -1
  5. package/dist/adapters/hono/index.cjs +1 -1
  6. package/dist/adapters/hono/index.js +1 -1
  7. package/dist/adapters/{mcp-transport-B37JTeww.cjs → mcp-transport-49vIZe0U.cjs} +91 -91
  8. package/dist/adapters/{mcp-transport-DHBUNdYg.js → mcp-transport-CxemGJYK.js} +2379 -2344
  9. package/dist/adapters/nextjs/index.cjs +1 -1
  10. package/dist/adapters/nextjs/index.js +1 -1
  11. package/dist/client/charts/chartConfigs.d.ts +21 -0
  12. package/dist/client/charts.js +12 -12
  13. package/dist/client/chunks/{RetentionCombinedChart-DsWhPI0q.js → RetentionCombinedChart-DpEKFYsd.js} +3 -3
  14. package/dist/client/chunks/RetentionCombinedChart-DpEKFYsd.js.map +1 -0
  15. package/dist/client/chunks/{analysis-builder-DcZgXtPK.js → analysis-builder-BCAr4mEO.js} +9 -9
  16. package/dist/client/chunks/{analysis-builder-DcZgXtPK.js.map → analysis-builder-BCAr4mEO.js.map} +1 -1
  17. package/dist/client/chunks/{analysis-builder-shared-ZnrPzt_d.js → analysis-builder-shared-D-5OhHaS.js} +5 -5
  18. package/dist/client/chunks/{analysis-builder-shared-ZnrPzt_d.js.map → analysis-builder-shared-D-5OhHaS.js.map} +1 -1
  19. package/dist/client/chunks/{chart-activity-grid-BUc21L0U.js → chart-activity-grid-CCGyWo1c.js} +116 -106
  20. package/dist/client/chunks/chart-activity-grid-CCGyWo1c.js.map +1 -0
  21. package/dist/client/chunks/chart-area-INsj4GFi.js +233 -0
  22. package/dist/client/chunks/chart-area-INsj4GFi.js.map +1 -0
  23. package/dist/client/chunks/chart-bar-BKRPTqiM.js +228 -0
  24. package/dist/client/chunks/chart-bar-BKRPTqiM.js.map +1 -0
  25. package/dist/client/chunks/{chart-bubble-CO7qvWR9.js → chart-bubble-BGGAQQUQ.js} +2 -2
  26. package/dist/client/chunks/{chart-bubble-CO7qvWR9.js.map → chart-bubble-BGGAQQUQ.js.map} +1 -1
  27. package/dist/client/chunks/{chart-config-activity-grid-BBSNCbkb.js → chart-config-activity-grid-CAlo1cHA.js} +3 -2
  28. package/dist/client/chunks/{chart-config-activity-grid-BBSNCbkb.js.map → chart-config-activity-grid-CAlo1cHA.js.map} +1 -1
  29. package/dist/client/chunks/{chart-config-bar-BJKGnfLt.js → chart-config-bar-soxw6m2o.js} +2 -1
  30. package/dist/client/chunks/chart-config-bar-soxw6m2o.js.map +1 -0
  31. package/dist/client/chunks/{chart-config-line-DR0ThxZy.js → chart-config-line-D5ME6w0v.js} +2 -1
  32. package/dist/client/chunks/chart-config-line-D5ME6w0v.js.map +1 -0
  33. package/dist/client/chunks/{chart-config-pie-BM5lgH-w.js → chart-config-pie-DlHa2jTy.js} +2 -1
  34. package/dist/client/chunks/chart-config-pie-DlHa2jTy.js.map +1 -0
  35. package/dist/client/chunks/{chart-config-tree-map-CLmRvvMR.js → chart-config-tree-map-IRAYf9YM.js} +3 -2
  36. package/dist/client/chunks/{chart-config-tree-map-CLmRvvMR.js.map → chart-config-tree-map-IRAYf9YM.js.map} +1 -1
  37. package/dist/client/chunks/{chart-data-table-bclSKgkZ.js → chart-data-table-BcH_h6kZ.js} +3 -3
  38. package/dist/client/chunks/{chart-data-table-bclSKgkZ.js.map → chart-data-table-BcH_h6kZ.js.map} +1 -1
  39. package/dist/client/chunks/{chart-funnel-FvDvq015.js → chart-funnel-DI8RMacf.js} +3 -3
  40. package/dist/client/chunks/chart-funnel-DI8RMacf.js.map +1 -0
  41. package/dist/client/chunks/{chart-heat-map-GpFE-PFB.js → chart-heat-map-D3xNV9ep.js} +2 -2
  42. package/dist/client/chunks/{chart-heat-map-GpFE-PFB.js.map → chart-heat-map-D3xNV9ep.js.map} +1 -1
  43. package/dist/client/chunks/{chart-kpi-delta-jmz-CK8X.js → chart-kpi-delta-BJMQKPor.js} +3 -3
  44. package/dist/client/chunks/{chart-kpi-delta-jmz-CK8X.js.map → chart-kpi-delta-BJMQKPor.js.map} +1 -1
  45. package/dist/client/chunks/{chart-kpi-number-DbSmomE8.js → chart-kpi-number-B8u4tWmu.js} +2 -2
  46. package/dist/client/chunks/{chart-kpi-number-DbSmomE8.js.map → chart-kpi-number-B8u4tWmu.js.map} +1 -1
  47. package/dist/client/chunks/{chart-kpi-text-erI9U7PZ.js → chart-kpi-text--r1d4zAz.js} +3 -3
  48. package/dist/client/chunks/{chart-kpi-text-erI9U7PZ.js.map → chart-kpi-text--r1d4zAz.js.map} +1 -1
  49. package/dist/client/chunks/chart-line-DgFmGdWW.js +414 -0
  50. package/dist/client/chunks/chart-line-DgFmGdWW.js.map +1 -0
  51. package/dist/client/chunks/chart-pie-DC7axSwd.js +137 -0
  52. package/dist/client/chunks/chart-pie-DC7axSwd.js.map +1 -0
  53. package/dist/client/chunks/{chart-radar-BE6xsFiF.js → chart-radar-BDKgpLw5.js} +33 -33
  54. package/dist/client/chunks/chart-radar-BDKgpLw5.js.map +1 -0
  55. package/dist/client/chunks/{chart-radial-bar-BEEwtFDc.js → chart-radial-bar-BYNng7Nz.js} +7 -6
  56. package/dist/client/chunks/chart-radial-bar-BYNng7Nz.js.map +1 -0
  57. package/dist/client/chunks/{chart-sankey-Dt3KaYrH.js → chart-sankey-CpsKerey.js} +2 -2
  58. package/dist/client/chunks/{chart-sankey-Dt3KaYrH.js.map → chart-sankey-CpsKerey.js.map} +1 -1
  59. package/dist/client/chunks/{chart-scatter-gAlYkQcW.js → chart-scatter-CXqFltJg.js} +11 -11
  60. package/dist/client/chunks/chart-scatter-CXqFltJg.js.map +1 -0
  61. package/dist/client/chunks/{chart-sunburst-D0Lvdjwu.js → chart-sunburst-DSsO2CzY.js} +2 -2
  62. package/dist/client/chunks/{chart-sunburst-D0Lvdjwu.js.map → chart-sunburst-DSsO2CzY.js.map} +1 -1
  63. package/dist/client/chunks/{chart-tree-map-Bv_PYe0c.js → chart-tree-map-D_SeBBD-.js} +77 -57
  64. package/dist/client/chunks/chart-tree-map-D_SeBBD-.js.map +1 -0
  65. package/dist/client/chunks/{chartConfigRegistry-BumUIPw4.js → chartConfigRegistry-DNEbwgTc.js} +6 -6
  66. package/dist/client/chunks/{chartConfigRegistry-BumUIPw4.js.map → chartConfigRegistry-DNEbwgTc.js.map} +1 -1
  67. package/dist/client/chunks/{charts-DqWRT0TE.js → charts-CsEtJy5f.js} +17 -17
  68. package/dist/client/chunks/charts-CsEtJy5f.js.map +1 -0
  69. package/dist/client/chunks/{charts-core-BfxnhMfd.js → charts-core-8jDh3mKC.js} +64 -63
  70. package/dist/client/chunks/charts-core-8jDh3mKC.js.map +1 -0
  71. package/dist/client/chunks/{charts-loader-DCGbL50r.js → charts-loader-sNk3q8UX.js} +20 -20
  72. package/dist/client/chunks/{charts-loader-DCGbL50r.js.map → charts-loader-sNk3q8UX.js.map} +1 -1
  73. package/dist/client/chunks/{components-NmBmOEqV.js → components-BsV0_0jR.js} +3579 -2880
  74. package/dist/client/chunks/components-BsV0_0jR.js.map +1 -0
  75. package/dist/client/chunks/{hooks-D7APQ8uS.js → hooks-CKYzVf_7.js} +3 -3
  76. package/dist/client/chunks/{hooks-D7APQ8uS.js.map → hooks-CKYzVf_7.js.map} +1 -1
  77. package/dist/client/chunks/{index-CBvXpG92.js → index-_2PSgbkC.js} +270 -261
  78. package/dist/client/chunks/index-_2PSgbkC.js.map +1 -0
  79. package/dist/client/chunks/{providers-Cj7PQfXn.js → providers-BBrUJB2U.js} +2 -2
  80. package/dist/client/chunks/{providers-Cj7PQfXn.js.map → providers-BBrUJB2U.js.map} +1 -1
  81. package/dist/client/chunks/{useDirtyStateTracking-ZSi3voVl.js → useDirtyStateTracking-DDQ_Lbki.js} +2 -2
  82. package/dist/client/chunks/{useDirtyStateTracking-ZSi3voVl.js.map → useDirtyStateTracking-DDQ_Lbki.js.map} +1 -1
  83. package/dist/client/components/DrillBreadcrumb.d.ts +6 -0
  84. package/dist/client/components/DrillMenu.d.ts +8 -0
  85. package/dist/client/components.js +2 -2
  86. package/dist/client/hooks/useDrillInteraction.d.ts +9 -0
  87. package/dist/client/hooks.js +3 -3
  88. package/dist/client/index.js +9 -9
  89. package/dist/client/providers.js +1 -1
  90. package/dist/client/styles.css +1 -1
  91. package/dist/client/types/drill.d.ts +240 -0
  92. package/dist/client/types.d.ts +53 -2
  93. package/dist/client/utils/drillQueryBuilder.d.ts +41 -0
  94. package/dist/client-bundle-stats.html +1 -1
  95. package/dist/server/index.cjs +25 -25
  96. package/dist/server/index.d.ts +78 -0
  97. package/dist/server/index.js +2117 -2082
  98. package/package.json +1 -1
  99. package/dist/client/chunks/RetentionCombinedChart-DsWhPI0q.js.map +0 -1
  100. package/dist/client/chunks/chart-activity-grid-BUc21L0U.js.map +0 -1
  101. package/dist/client/chunks/chart-area-B_64FScj.js +0 -190
  102. package/dist/client/chunks/chart-area-B_64FScj.js.map +0 -1
  103. package/dist/client/chunks/chart-bar-Ctiy2tpQ.js +0 -216
  104. package/dist/client/chunks/chart-bar-Ctiy2tpQ.js.map +0 -1
  105. package/dist/client/chunks/chart-config-bar-BJKGnfLt.js.map +0 -1
  106. package/dist/client/chunks/chart-config-line-DR0ThxZy.js.map +0 -1
  107. package/dist/client/chunks/chart-config-pie-BM5lgH-w.js.map +0 -1
  108. package/dist/client/chunks/chart-funnel-FvDvq015.js.map +0 -1
  109. package/dist/client/chunks/chart-line-B0YOZ88n.js +0 -364
  110. package/dist/client/chunks/chart-line-B0YOZ88n.js.map +0 -1
  111. package/dist/client/chunks/chart-pie-CImB6r4F.js +0 -125
  112. package/dist/client/chunks/chart-pie-CImB6r4F.js.map +0 -1
  113. package/dist/client/chunks/chart-radar-BE6xsFiF.js.map +0 -1
  114. package/dist/client/chunks/chart-radial-bar-BEEwtFDc.js.map +0 -1
  115. package/dist/client/chunks/chart-scatter-gAlYkQcW.js.map +0 -1
  116. package/dist/client/chunks/chart-tree-map-Bv_PYe0c.js.map +0 -1
  117. package/dist/client/chunks/charts-DqWRT0TE.js.map +0 -1
  118. package/dist/client/chunks/charts-core-BfxnhMfd.js.map +0 -1
  119. package/dist/client/chunks/components-NmBmOEqV.js.map +0 -1
  120. package/dist/client/chunks/index-CBvXpG92.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useDirtyStateTracking-ZSi3voVl.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/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 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}\n\nexport interface PreAggregationAnalysis {\n cubeName: string\n cteAlias: string\n reason: string\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 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}\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 { 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}\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 // 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 }\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 * 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","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","useFilterValues","fieldName","enabled","currentQuery","setCurrentQuery","lastSearchTerm","isLoading","queryError","values","uniqueValues","err","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-DDQ_Lbki.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/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 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}\n\nexport interface PreAggregationAnalysis {\n cubeName: string\n cteAlias: string\n reason: string\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 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}\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 { 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}\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 // 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 }\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 * 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","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","useFilterValues","fieldName","enabled","currentQuery","setCurrentQuery","lastSearchTerm","isLoading","queryError","values","uniqueValues","err","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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"}
@@ -0,0 +1,6 @@
1
+ import { DrillBreadcrumbProps } from '../types/drill';
2
+ /**
3
+ * DrillBreadcrumb component
4
+ */
5
+ export declare function DrillBreadcrumb({ path, onNavigate, onLevelClick }: DrillBreadcrumbProps): import("react/jsx-runtime").JSX.Element | null;
6
+ export default DrillBreadcrumb;
@@ -0,0 +1,8 @@
1
+ import { default as React } from 'react';
2
+ import { DrillMenuProps } from '../types/drill';
3
+ /**
4
+ * DrillMenu component
5
+ * Uses createPortal to render directly to document.body, avoiding stacking context issues
6
+ */
7
+ export declare function DrillMenu({ options, position, onSelect, onClose }: DrillMenuProps): React.ReactPortal | null;
8
+ export default DrillMenu;
@@ -1,5 +1,5 @@
1
- import { a as s, A as t, c as r, D as o, M as d, P as i, Q as l } from "./chunks/components-NmBmOEqV.js";
2
- import { c as b, d as c, b as u, a as y, g as P, v as D } from "./chunks/charts-DqWRT0TE.js";
1
+ import { a as s, A as t, c as r, D as o, M as d, P as i, Q as l } from "./chunks/components-BsV0_0jR.js";
2
+ import { c as b, d as c, b as u, a as y, g as P, v as D } from "./chunks/charts-CsEtJy5f.js";
3
3
  export {
4
4
  s as AnalyticsDashboard,
5
5
  t as AnalyticsPortlet,
@@ -0,0 +1,9 @@
1
+ import { DrillInteraction, UseDrillInteractionOptions } from '../types/drill';
2
+ /**
3
+ * Hook for managing drill-down interaction
4
+ *
5
+ * @param options - Configuration options
6
+ * @returns DrillInteraction object with handlers and state
7
+ */
8
+ export declare function useDrillInteraction(options: UseDrillInteractionOptions): DrillInteraction;
9
+ export default useDrillInteraction;
@@ -1,6 +1,6 @@
1
- import { u as s, u as a, k as r, o, n as t, a as y, p as i } from "./chunks/useDirtyStateTracking-ZSi3voVl.js";
2
- import { g as n } from "./chunks/providers-Cj7PQfXn.js";
3
- import { b as D, u as p, a as l } from "./chunks/hooks-D7APQ8uS.js";
1
+ import { u as s, u as a, k as r, o, n as t, a as y, p as i } from "./chunks/useDirtyStateTracking-DDQ_Lbki.js";
2
+ import { g as n } from "./chunks/providers-BBrUJB2U.js";
3
+ import { b as D, u as p, a as l } from "./chunks/hooks-CKYzVf_7.js";
4
4
  export {
5
5
  s as useCubeLoadQuery,
6
6
  n as useCubeMetaQuery,
@@ -1,17 +1,17 @@
1
- import { d, a as c, A as y, C as m, c as g, D as b, i as h, M as C, b as p, P as f, Q as D, m as x, g as Q, e as S, G as M, E as v, F, y as T, p as A, w as I, r as B, s as L, v as E, o as w, t as K, n as P, q as R, x as N, h as V, j as k, k as j, l as G, B as U, f as q, u as z, z as O } from "./chunks/components-NmBmOEqV.js";
2
- import { L as H, u as $ } from "./chunks/charts-core-BfxnhMfd.js";
3
- import { L as W, g as X, c as Y, b as Z, i as ee, p as ae, a as se } from "./chunks/charts-loader-DCGbL50r.js";
1
+ import { d, a as c, A as y, C as m, c as g, D as b, i as h, M as C, b as p, P as f, Q as D, m as x, g as Q, e as S, G as M, E as v, F, y as T, p as A, w as I, r as B, s as L, v as E, o as w, t as K, n as P, q as R, x as N, h as V, j as k, k as j, l as G, B as U, f as q, u as z, z as O } from "./chunks/components-BsV0_0jR.js";
2
+ import { L as H, u as $ } from "./chunks/charts-core-8jDh3mKC.js";
3
+ import { L as W, g as X, c as Y, b as Z, i as ee, p as ae, a as se } from "./chunks/charts-loader-sNk3q8UX.js";
4
4
  import { jsx as e, jsxs as s } from "react/jsx-runtime";
5
5
  import { b as t } from "./chunks/icons-DRreo6m8.js";
6
6
  import { D as re, a as ie, i as oe, c as le, f as ue, h as ne, g as de, r as ce, e as ye, s as me } from "./chunks/icons-DRreo6m8.js";
7
- import { a as be, S as he, d as Ce, e as pe, b as fe, f as De, u as xe, g as Qe, c as Se } from "./chunks/providers-Cj7PQfXn.js";
8
- import { l as ve, k as Fe, w as Te, x as Ae, n as Ie, t as Be, m as Le, c as Ee, e as we, s as Ke, d as Pe, h as Re, b as Ne, g as Ve, f as ke, u as je, a as Ge, p as Ue, r as qe, o as ze, j as Oe, v as _e, q as He, i as $e } from "./chunks/analysis-builder-shared-ZnrPzt_d.js";
9
- import { u as We } from "./chunks/chart-bubble-CO7qvWR9.js";
10
- import { c as Ye, f as Ze } from "./chunks/charts-DqWRT0TE.js";
11
- import { f as aa, g as sa, e as ta, d as ra, i as ia, m as oa, c as la, b as ua, u as na, u as da, a as ca, v as ya } from "./chunks/useDirtyStateTracking-ZSi3voVl.js";
7
+ import { a as be, S as he, d as Ce, e as pe, b as fe, f as De, u as xe, g as Qe, c as Se } from "./chunks/providers-BBrUJB2U.js";
8
+ import { l as ve, k as Fe, w as Te, x as Ae, n as Ie, t as Be, m as Le, c as Ee, e as we, s as Ke, d as Pe, h as Re, b as Ne, g as Ve, f as ke, u as je, a as Ge, p as Ue, r as qe, o as ze, j as Oe, v as _e, q as He, i as $e } from "./chunks/analysis-builder-shared-D-5OhHaS.js";
9
+ import { u as We } from "./chunks/chart-bubble-BGGAQQUQ.js";
10
+ import { c as Ye, f as Ze } from "./chunks/charts-CsEtJy5f.js";
11
+ import { f as aa, g as sa, e as ta, d as ra, i as ia, m as oa, c as la, b as ua, u as na, u as da, a as ca, v as ya } from "./chunks/useDirtyStateTracking-DDQ_Lbki.js";
12
12
  import { c as ga, b as ba, f as ha, g as Ca, a as pa, i as fa, t as Da } from "./chunks/funnel-utils-CyonoNeC.js";
13
13
  import { T as Qa, c as Sa, g as Ma, a as va, i as Fa, r as Ta, s as Aa, b as Ia, w as Ba } from "./chunks/theme-Dp3hFed1.js";
14
- import { b as Ea, u as wa, a as Ka } from "./chunks/hooks-D7APQ8uS.js";
14
+ import { b as Ea, u as wa, a as Ka } from "./chunks/hooks-CKYzVf_7.js";
15
15
  import { i as Ra, a as Na } from "./chunks/flow-utils-CjQZG5qq.js";
16
16
  const r = t("segment");
17
17
  function l({
@@ -1,4 +1,4 @@
1
- import { a, S as o, d as s, b as t, c as C } from "./chunks/providers-Cj7PQfXn.js";
1
+ import { a, S as o, d as s, b as t, c as C } from "./chunks/providers-BBrUJB2U.js";
2
2
  export {
3
3
  a as CubeProvider,
4
4
  o as ScrollContainerProvider,