1- /**
2- * ComposedChartHandler
3- *
4- * A presentational component for rendering charts.
5- * It is responsible only for rendering the chart UI based on the data and loading state passed to it as props.
6- * All the complex data fetching logic has been moved to the useChartData hook.
7- */
8- import React , { PropsWithChildren , useState , useEffect , useRef } from 'react'
1+ import React , { PropsWithChildren , useState , useMemo , useEffect , useRef } from 'react'
2+ import { useRouter } from 'next/router'
93import { Loader2 } from 'lucide-react'
104import { cn , WarningIcon } from 'ui'
115
@@ -17,14 +11,14 @@ import { InfraMonitoringAttribute } from 'data/analytics/infra-monitoring-query'
1711import { useInfraMonitoringQueries } from 'data/analytics/infra-monitoring-queries'
1812import { ProjectDailyStatsAttribute } from 'data/analytics/project-daily-stats-query'
1913import { useProjectDailyStatsQueries } from 'data/analytics/project-daily-stats-queries'
14+ import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
2015import { useChartHighlight } from './useChartHighlight'
21- import { getMockDataForAttribute } from 'data/reports/auth-charts'
2216
2317import type { ChartData } from './Charts.types'
2418import type { UpdateDateRange } from 'pages/project/[ref]/reports/database'
25- import type { MultiAttribute } from './ComposedChart.utils'
19+ import { MultiAttribute } from './ComposedChart.utils'
2620
27- interface ComposedChartHandlerProps {
21+ export interface ComposedChartHandlerProps {
2822 id ?: string
2923 label : string
3024 attributes : MultiAttribute [ ]
@@ -46,7 +40,6 @@ interface ComposedChartHandlerProps {
4640 updateDateRange : UpdateDateRange
4741 valuePrecision ?: number
4842 isVisible ?: boolean
49- titleTooltip ?: string
5043 docsUrl ?: string
5144}
5245
@@ -98,6 +91,9 @@ const LazyChartWrapper = ({ children }: PropsWithChildren) => {
9891const ComposedChartHandler = ( {
9992 label,
10093 attributes,
94+ startDate,
95+ endDate,
96+ interval,
10197 customDateFormat,
10298 children = null ,
10399 defaultChartStyle = 'bar' ,
@@ -113,14 +109,131 @@ const ComposedChartHandler = ({
113109 showTotal,
114110 updateDateRange,
115111 valuePrecision,
116- titleTooltip ,
112+ isVisible = true ,
117113 id,
118114 ...otherProps
119115} : PropsWithChildren < ComposedChartHandlerProps > ) => {
116+ const router = useRouter ( )
117+ const { ref } = router . query
118+
119+ const state = useDatabaseSelectorStateSnapshot ( )
120120 const [ chartStyle , setChartStyle ] = useState < string > ( defaultChartStyle )
121121 const chartHighlight = useChartHighlight ( )
122122
123- if ( isLoading ) {
123+ const databaseIdentifier = state . selectedDatabaseId
124+
125+ // Use the custom hook at the top level of the component
126+ const attributeQueries = useAttributeQueries (
127+ attributes ,
128+ ref ,
129+ startDate ,
130+ endDate ,
131+ interval as AnalyticsInterval ,
132+ databaseIdentifier ,
133+ data ,
134+ isVisible
135+ )
136+
137+ // Combine all the data into a single dataset
138+ const combinedData = useMemo ( ( ) => {
139+ if ( data ) return data
140+
141+ const isLoading = attributeQueries . some ( ( query : any ) => query . isLoading )
142+ if ( isLoading ) return undefined
143+
144+ const hasError = attributeQueries . some ( ( query : any ) => ! query . data )
145+ if ( hasError ) return undefined
146+
147+ // Get all unique timestamps from all datasets
148+ const timestamps = new Set < string > ( )
149+ attributeQueries . forEach ( ( query : any ) => {
150+ query . data ?. data ?. forEach ( ( point : any ) => {
151+ if ( point ?. period_start ) {
152+ timestamps . add ( point . period_start )
153+ }
154+ } )
155+ } )
156+
157+ const referenceLineQueries = attributeQueries . filter (
158+ ( _ , index ) => attributes [ index ] . provider === 'reference-line'
159+ )
160+
161+ // Combine data points for each timestamp
162+ const combined = Array . from ( timestamps )
163+ . sort ( )
164+ . map ( ( timestamp ) => {
165+ const point : any = { timestamp }
166+
167+ // Add regular attributes
168+ attributes . forEach ( ( attr , index ) => {
169+ if ( ! attr ) return
170+
171+ // Handle custom value attributes (like disk size)
172+ if ( attr . customValue !== undefined ) {
173+ point [ attr . attribute ] = attr . customValue
174+ return
175+ }
176+
177+ // Skip reference line attributes here, we'll add them below
178+ if ( attr . provider === 'reference-line' ) return
179+
180+ const queryData = attributeQueries [ index ] ?. data ?. data
181+ const matchingPoint = queryData ?. find ( ( p : any ) => p . period_start === timestamp )
182+ let value = matchingPoint ?. [ attr . attribute ] ?? 0
183+
184+ // Apply value manipulation if provided
185+ if ( attr . manipulateValue && typeof attr . manipulateValue === 'function' ) {
186+ // Ensure value is a number before manipulation
187+ const numericValue = typeof value === 'number' ? value : Number ( value ) || 0
188+ value = attr . manipulateValue ( numericValue )
189+ }
190+
191+ point [ attr . attribute ] = value
192+ } )
193+
194+ // Add reference line values for each timestamp
195+ referenceLineQueries . forEach ( ( query : any ) => {
196+ const attr = query . data . attribute
197+ const value = query . data . total
198+ point [ attr ] = value
199+ } )
200+
201+ return point as DataPoint
202+ } )
203+
204+ return combined as DataPoint [ ]
205+ } , [ data , attributeQueries , attributes ] )
206+
207+ const loading = isLoading || attributeQueries . some ( ( query : any ) => query . isLoading )
208+
209+ // Calculate highlighted value based on the first attribute's data
210+ const _highlightedValue = useMemo ( ( ) => {
211+ if ( highlightedValue !== undefined ) return highlightedValue
212+
213+ const firstAttr = attributes [ 0 ]
214+ const firstQuery = attributeQueries [ 0 ]
215+ const firstData = firstQuery ?. data
216+
217+ if ( ! firstData ) return undefined
218+
219+ const shouldHighlightMaxValue =
220+ firstAttr . provider === 'daily-stats' &&
221+ ! firstAttr . attribute . includes ( 'ingress' ) &&
222+ ! firstAttr . attribute . includes ( 'egress' ) &&
223+ 'maximum' in firstData
224+
225+ const shouldHighlightTotalGroupedValue = 'totalGrouped' in firstData
226+
227+ return shouldHighlightMaxValue
228+ ? firstData . maximum
229+ : firstAttr . provider === 'daily-stats'
230+ ? firstData . total
231+ : shouldHighlightTotalGroupedValue
232+ ? firstData . totalGrouped ?. [ firstAttr . attribute as keyof typeof firstData . totalGrouped ]
233+ : ( firstData . data [ firstData . data . length - 1 ] as any ) ?. [ firstAttr . attribute ]
234+ } , [ highlightedValue , attributes , attributeQueries ] )
235+
236+ if ( loading ) {
124237 return (
125238 < Panel
126239 className = { cn (
@@ -137,7 +250,7 @@ const ComposedChartHandler = ({
137250 )
138251 }
139252
140- if ( ! data ) {
253+ if ( ! combinedData ) {
141254 return (
142255 < div className = "flex h-52 w-full flex-col items-center justify-center gap-y-2" >
143256 < WarningIcon />
@@ -159,11 +272,11 @@ const ComposedChartHandler = ({
159272 < div className = "absolute right-6 z-50 flex justify-between scroll-mt-16" > { children } </ div >
160273 < ComposedChart
161274 attributes = { attributes }
162- data = { data as any }
275+ data = { combinedData as DataPoint [ ] }
163276 format = { format }
164277 xAxisKey = "period_start"
165278 yAxisKey = { attributes [ 0 ] . attribute }
166- highlightedValue = { highlightedValue }
279+ highlightedValue = { _highlightedValue }
167280 title = { label }
168281 customDateFormat = { customDateFormat }
169282 chartHighlight = { chartHighlight }
@@ -176,15 +289,14 @@ const ComposedChartHandler = ({
176289 updateDateRange = { updateDateRange }
177290 valuePrecision = { valuePrecision }
178291 hideChartType = { hideChartType }
179- titleTooltip = { titleTooltip }
180292 { ...otherProps }
181293 />
182294 </ Panel . Content >
183295 </ Panel >
184296 )
185297}
186298
187- export const useAttributeQueries = (
299+ const useAttributeQueries = (
188300 attributes : MultiAttribute [ ] ,
189301 ref : string | string [ ] | undefined ,
190302 startDate : string ,
@@ -194,15 +306,16 @@ export const useAttributeQueries = (
194306 data : ChartData | undefined ,
195307 isVisible : boolean
196308) => {
197- const projectRef = typeof ref === 'string' ? ref : Array . isArray ( ref ) ? ref [ 0 ] : ''
198-
199- const infraAttributes = attributes . filter ( ( attr ) => attr . provider === 'infra-monitoring' )
200- const dailyStatsAttributes = attributes . filter ( ( attr ) => attr . provider === 'daily-stats' )
201- const mockAttributes = attributes . filter ( ( attr ) => attr . provider === 'mock' )
202- const referenceLineAttributes = attributes . filter ( ( attr ) => attr . provider === 'reference-line' )
309+ const infraAttributes = attributes
310+ . filter ( ( attr ) => attr ?. provider === 'infra-monitoring' )
311+ . map ( ( attr ) => attr . attribute as InfraMonitoringAttribute )
312+ const dailyStatsAttributes = attributes
313+ . filter ( ( attr ) => attr ?. provider === 'daily-stats' )
314+ . map ( ( attr ) => attr . attribute as ProjectDailyStatsAttribute )
315+ const referenceLines = attributes . filter ( ( attr ) => attr ?. provider === 'reference-line' )
203316
204317 const infraQueries = useInfraMonitoringQueries (
205- infraAttributes . map ( ( attr ) => attr . attribute as InfraMonitoringAttribute ) ,
318+ infraAttributes ,
206319 ref ,
207320 startDate ,
208321 endDate ,
@@ -212,7 +325,7 @@ export const useAttributeQueries = (
212325 isVisible
213326 )
214327 const dailyStatsQueries = useProjectDailyStatsQueries (
215- dailyStatsAttributes . map ( ( attr ) => attr . attribute as ProjectDailyStatsAttribute ) ,
328+ dailyStatsAttributes ,
216329 ref ,
217330 startDate ,
218331 endDate ,
@@ -222,48 +335,23 @@ export const useAttributeQueries = (
222335 isVisible
223336 )
224337
225- let infraIdx = 0
226- let dailyStatsIdx = 0
227- return attributes
228- . filter ( ( attr ) => attr . provider !== 'logs' )
229- . map ( ( attr ) => {
230- if ( attr . provider === 'infra-monitoring' ) {
231- return {
232- ...infraQueries [ infraIdx ++ ] ,
233- data : { ...infraQueries [ infraIdx - 1 ] ?. data , provider : 'infra-monitoring' } ,
234- }
235- } else if ( attr . provider === 'daily-stats' ) {
236- return {
237- ...dailyStatsQueries [ dailyStatsIdx ++ ] ,
238- data : { ...dailyStatsQueries [ dailyStatsIdx - 1 ] ?. data , provider : 'daily-stats' } ,
239- }
240- } else if ( attr . provider === 'mock' ) {
241- const mockData = getMockDataForAttribute ( attr . attribute )
242- return {
243- isLoading : false ,
244- data : { ...mockData , provider : 'mock' , attribute : attr . attribute } ,
245- }
246- } else if ( attr . provider === 'reference-line' ) {
247- let value = attr . value || 0
248- return {
249- data : {
250- data : [ ] ,
251- attribute : attr . attribute ,
252- total : value ,
253- maximum : value ,
254- totalGrouped : { [ attr . attribute ] : value } ,
255- provider : 'reference-line' ,
256- } ,
257- isLoading : false ,
258- isError : false ,
259- }
260- } else {
261- return {
262- isLoading : false ,
263- data : undefined ,
264- }
265- }
266- } )
338+ const referenceLineQueries = referenceLines . map ( ( line ) => {
339+ let value = line . value || 0
340+
341+ return {
342+ data : {
343+ data : [ ] , // Will be populated in combinedData
344+ attribute : line . attribute ,
345+ total : value ,
346+ maximum : value ,
347+ totalGrouped : { [ line . attribute ] : value } ,
348+ } ,
349+ isLoading : false ,
350+ isError : false ,
351+ }
352+ } )
353+
354+ return [ ...infraQueries , ...dailyStatsQueries , ...referenceLineQueries ]
267355}
268356
269357export default function LazyComposedChartHandler ( props : ComposedChartHandlerProps ) {
0 commit comments