realtime.tsx221 lines · main
1import { useQueryClient } from '@tanstack/react-query'
2import { useParams } from 'common'
3import dayjs from 'dayjs'
4import { ArrowRight, RefreshCw } from 'lucide-react'
5import { useEffect, useMemo, useRef, useState } from 'react'
6
7import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
8import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
9import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
10import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
11import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
12import { SharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
13import { useSharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport.constants'
14import { ReportChartV2 } from '@/components/interfaces/Reports/v2/ReportChartV2'
15import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
16import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
17import DefaultLayout from '@/components/layouts/DefaultLayout'
18import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
19import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
20import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
21import { realtimeReports } from '@/data/reports/v2/realtime.config'
22import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
23import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
24import type { NextPageWithLayout } from '@/types'
25
26const RealtimeReport: NextPageWithLayout = () => {
27 return (
28 <ReportPadding>
29 <RealtimeUsage />
30 </ReportPadding>
31 )
32}
33
34RealtimeReport.getLayout = (page) => (
35 <DefaultLayout>
36 <ObservabilityLayout title="Realtime">{page}</ObservabilityLayout>
37 </DefaultLayout>
38)
39
40export type UpdateDateRange = (from: string, to: string) => void
41export default RealtimeReport
42
43const RealtimeUsage = () => {
44 const [isRefreshing, setIsRefreshing] = useState(false)
45 const { db, chart, ref } = useParams()
46
47 const {
48 selectedDateRange,
49 updateDateRange: updateDateRangeFromHook,
50 datePickerValue,
51 datePickerHelpers,
52 showUpgradePrompt,
53 setShowUpgradePrompt,
54 handleDatePickerChange,
55 } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
56 const queryClient = useQueryClient()
57 const {
58 data,
59 error,
60 isLoading,
61 refetch,
62 isRefetching,
63 filters,
64 addFilter,
65 removeFilters,
66 isLoadingData,
67 sql,
68 } = useSharedAPIReport({
69 filterBy: 'realtime',
70 start: selectedDateRange?.period_start?.date,
71 end: selectedDateRange?.period_end?.date,
72 })
73
74 const chartSyncId = `realtime-${ref}`
75
76 const state = useDatabaseSelectorStateSnapshot()
77
78 const reportConfig = useMemo(() => {
79 return realtimeReports({
80 projectRef: ref!,
81 startDate: selectedDateRange?.period_start?.date ?? '',
82 endDate: selectedDateRange?.period_end?.date ?? '',
83 interval: selectedDateRange?.interval ?? 'minute',
84 databaseIdentifier: state.selectedDatabaseId,
85 })
86 }, [ref, selectedDateRange, state.selectedDatabaseId])
87
88 const onRefreshReport = useRefreshHandler(
89 datePickerValue,
90 datePickerHelpers,
91 handleDatePickerChange,
92 async () => {
93 if (!selectedDateRange) return
94
95 setIsRefreshing(true)
96
97 queryClient.invalidateQueries({
98 queryKey: ['projects', ref, 'report-v2', { queryGroup: 'realtime' }],
99 })
100 refetch()
101 setTimeout(() => setIsRefreshing(false), 1000)
102 }
103 )
104
105 const urlStateHasSyncedRef = useRef(false)
106 useEffect(() => {
107 if (urlStateHasSyncedRef.current) return
108 urlStateHasSyncedRef.current = true
109
110 if (db !== undefined) {
111 setTimeout(() => {
112 // [Joshen] Adding a timeout here to support navigation from settings to reports
113 // Both are rendering different instances of ProjectLayout which is where the
114 // DatabaseSelectorContextProvider lies in (unless we reckon shifting the provider up one more level is better)
115 state.setSelectedDatabaseId(db)
116 }, 100)
117 }
118 if (chart !== undefined) {
119 setTimeout(() => {
120 const el = document.getElementById(chart)
121 if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' })
122 }, 200)
123 }
124 }, [db, chart, state])
125
126 const updateDateRange: UpdateDateRange = (from: string, to: string) => {
127 updateDateRangeFromHook(from, to)
128 }
129
130 return (
131 <>
132 <ReportHeader showDatabaseSelector={false} title="Realtime" />
133 <ReportStickyNav
134 content={
135 <div className="flex flex-col gap-3">
136 <div className="flex gap-2">
137 <ButtonTooltip
138 type="default"
139 disabled={isRefreshing}
140 icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
141 className="w-7"
142 tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
143 onClick={onRefreshReport}
144 />
145 <LogsDatePicker
146 onSubmit={handleDatePickerChange}
147 value={datePickerValue}
148 helpers={datePickerHelpers}
149 />
150 <UpgradePrompt
151 show={showUpgradePrompt}
152 setShowUpgradePrompt={setShowUpgradePrompt}
153 title="Report date range"
154 description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
155 source="realtimeReportDateRange"
156 />
157 {selectedDateRange && (
158 <div className="flex items-center gap-x-2 text-xs">
159 <p className="text-foreground-light">
160 {dayjs(selectedDateRange.period_start.date).format('MMM D, h:mma')}
161 </p>
162 <p className="text-foreground-light">
163 <ArrowRight size={12} />
164 </p>
165 <p className="text-foreground-light">
166 {dayjs(selectedDateRange.period_end.date).format('MMM D, h:mma')}
167 </p>
168 </div>
169 )}
170 </div>
171 </div>
172 }
173 >
174 <div className="mt-8 grid md:grid-cols-2 gap-4">
175 {selectedDateRange &&
176 reportConfig
177 .filter((report) => !report.hide)
178 .map((report) => (
179 <ReportChartV2
180 key={`${report.id}`}
181 queryGroup="realtime"
182 report={report}
183 projectRef={ref!}
184 interval={selectedDateRange.interval}
185 startDate={selectedDateRange?.period_start?.date}
186 endDate={selectedDateRange?.period_end?.date}
187 updateDateRange={updateDateRange}
188 syncId={chartSyncId}
189 />
190 ))}
191 </div>
192 <div className="">
193 <div className="mb-4">
194 <h5 className="text-foreground mb-2">Realtime API Gateway</h5>
195 <ReportFilterBar
196 filters={filters}
197 onAddFilter={addFilter}
198 onRemoveFilters={removeFilters}
199 isLoading={isLoadingData || isRefetching}
200 hideDatepicker={true}
201 datepickerHelpers={datePickerHelpers}
202 selectedProduct={'realtime'}
203 showDatabaseSelector={false}
204 />
205 </div>
206 <SharedAPIReport
207 data={data}
208 error={error}
209 isLoading={isLoading}
210 isRefetching={isRefetching}
211 hiddenReports={['networkTraffic']}
212 sql={sql}
213 />
214 </div>
215 </ReportStickyNav>
216 <div className="py-8">
217 <ObservabilityLink />
218 </div>
219 </>
220 )
221}