| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import { useQuery } from '@tanstack/react-query'
- import { Loader2 } from 'lucide-react'
- import { useState } from 'react'
- import { Card, CardContent, cn } from 'ui'
- import { ReportChartUpsell } from './ReportChartUpsell'
- import type { ChartHighlightAction } from '@/components/ui/Charts/ChartHighlightActions'
- import { ComposedChart } from '@/components/ui/Charts/ComposedChart'
- import type { MultiAttribute } from '@/components/ui/Charts/ComposedChart.utils'
- import { useChartHighlight } from '@/components/ui/Charts/useChartHighlight'
- import type { AnalyticsInterval } from '@/data/analytics/constants'
- import type { ReportConfig } from '@/data/reports/v2/reports.types'
- import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
- import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- export interface ReportChartV2Props {
- report: ReportConfig
- projectRef: string
- startDate: string
- endDate: string
- interval: AnalyticsInterval
- updateDateRange: (from: string, to: string) => void
- /**
- * Group ID used to invalidate React Query caches
- */
- queryGroup?: string
- className?: string
- syncId?: string
- filters?: any
- highlightActions?: ChartHighlightAction[]
- }
- // Compute total across entire period over unique attribute keys.
- // Excludes attributes that are disabled, reference lines, max values, or marked omitFromTotal.
- export function computePeriodTotal(
- chartData: Record<string, unknown>[],
- dynamicAttributes: MultiAttribute[]
- ): number {
- const attributeKeys = Array.from(
- new Set(
- dynamicAttributes
- .filter(
- (a) =>
- a?.enabled !== false &&
- a?.provider !== 'reference-line' &&
- !a?.isMaxValue &&
- !a?.omitFromTotal
- )
- .map((a) => a.attribute)
- )
- )
- return chartData.reduce((sum: number, row: Record<string, unknown>) => {
- const rowTotal = attributeKeys.reduce((acc: number, key: string) => {
- const value = row?.[key]
- return acc + (typeof value === 'number' ? value : 0)
- }, 0)
- return sum + rowTotal
- }, 0)
- }
- export const ReportChartV2 = ({
- report,
- projectRef,
- startDate,
- endDate,
- interval,
- updateDateRange,
- className,
- syncId,
- filters,
- highlightActions,
- queryGroup,
- }: ReportChartV2Props) => {
- const { data: org } = useSelectedOrganizationQuery()
- const { getEntitlementSetValues, isLoading: isEntitlementLoading } = useCheckEntitlements(
- 'observability.dashboard_advanced_metrics',
- undefined,
- { enabled: !!report.entitlement }
- )
- const entitledFeatures = getEntitlementSetValues()
- const isAvailable = !report.entitlement || entitledFeatures.includes(report.entitlement)
- const canFetch = isAvailable
- const {
- data: queryResult,
- isLoading: isLoadingChart,
- error,
- isFetching,
- } = useQuery({
- queryKey: [
- 'projects',
- projectRef,
- 'report-v2',
- { reportId: report.id, queryGroup, startDate, endDate, interval, filters },
- ],
- queryFn: async () => {
- return await report.dataProvider(projectRef, startDate, endDate, interval, filters)
- },
- enabled: Boolean(projectRef && canFetch && isAvailable && !report.hide),
- refetchOnWindowFocus: false,
- staleTime: 0,
- })
- const chartData = queryResult?.data || []
- const dynamicAttributes = queryResult?.attributes || []
- const showSumAsDefaultHighlight = report.showSumAsDefaultHighlight ?? true
- const headerTotal = showSumAsDefaultHighlight
- ? computePeriodTotal(chartData, dynamicAttributes)
- : undefined
- /**
- * Depending on the source the timestamp key could be 'timestamp' or 'period_start'
- */
- const firstItem = chartData[0]
- const timestampKey = firstItem?.hasOwnProperty('timestamp') ? 'timestamp' : 'period_start'
- const { data: filledChartData } = useFillTimeseriesSorted({
- data: chartData,
- timestampKey,
- valueKey: dynamicAttributes.map((attr) => attr.attribute),
- defaultValue: 0,
- startDate,
- endDate,
- minPointsToFill: undefined,
- interval,
- })
- const [chartStyle, setChartStyle] = useState<string>(report.defaultChartStyle)
- const chartHighlight = useChartHighlight()
- if (!isAvailable && !isEntitlementLoading) {
- return <ReportChartUpsell report={report} orgSlug={org?.slug ?? ''} />
- }
- const isErrorState = error && !isLoadingChart
- if (report.hide) return null
- return (
- <Card id={report.id} className={cn('relative w-full overflow-hidden scroll-mt-16', className)}>
- <CardContent
- className={cn(
- 'flex flex-col gap-4 min-h-[280px] items-center justify-center',
- isFetching && 'opacity-50'
- )}
- >
- {isLoadingChart ? (
- <Loader2 className="size-5 animate-spin text-foreground-light" />
- ) : isErrorState ? (
- <p className="text-sm text-foreground-light text-center h-full flex items-center justify-center">
- Error loading chart data
- </p>
- ) : (
- <div className="w-full relative">
- <ComposedChart
- chartId={report.id}
- attributes={dynamicAttributes}
- data={filledChartData}
- format={report.format ?? undefined}
- xAxisKey={report.xAxisKey ?? 'timestamp'}
- yAxisKey={report.yAxisKey ?? dynamicAttributes[0]?.attribute}
- hideHighlightedValue={report.hideHighlightedValue}
- highlightedValue={headerTotal}
- title={report.label}
- customDateFormat={undefined}
- chartStyle={chartStyle}
- chartHighlight={chartHighlight}
- showTooltip={report.showTooltip}
- showLegend={report.showLegend}
- showTotal={false}
- showMaxValue={report.showMaxValue}
- onChartStyleChange={setChartStyle}
- updateDateRange={updateDateRange}
- valuePrecision={report.valuePrecision}
- hideChartType={report.hideChartType}
- titleTooltip={report.titleTooltip}
- syncId={syncId}
- sql={queryResult?.query}
- highlightActions={highlightActions}
- showNewBadge={report.showNewBadge}
- />
- </div>
- )}
- </CardContent>
- </Card>
- )
- }
|