storage.tsx203 lines · main
1import dayjs from 'dayjs'
2import { ArrowRight, ExternalLinkIcon, RefreshCw } from 'lucide-react'
3import Link from 'next/link'
4
5import {
6 NetworkTrafficRenderer,
7 ResponseSpeedChartRenderer,
8 TopApiRoutesRenderer,
9 TotalRequestsChartRenderer,
10} from '@/components/interfaces/Reports/renderers/ApiRenderers'
11import {
12 CacheHitRateChartRenderer,
13 TopCacheMissesRenderer,
14} from '@/components/interfaces/Reports/renderers/StorageRenderers'
15import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
16import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
17import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
18import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
19import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
20import ReportWidget from '@/components/interfaces/Reports/ReportWidget'
21import {
22 DatePickerValue,
23 LogsDatePicker,
24} from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
25import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
26import DefaultLayout from '@/components/layouts/DefaultLayout'
27import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
28import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
29import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
30import { useStorageReport } from '@/data/reports/storage-report-query'
31import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
32import { DOCS_URL } from '@/lib/constants'
33import type { NextPageWithLayout } from '@/types'
34
35export const StorageReport: NextPageWithLayout = () => {
36 const report = useStorageReport()
37
38 const {
39 data,
40 error,
41 filters,
42 isLoading,
43 params,
44 mergeParams,
45 removeFilters,
46 addFilter,
47 refresh,
48 } = report
49
50 const {
51 datePickerHelpers,
52 datePickerValue,
53 handleDatePickerChange: handleDatePickerChangeFromHook,
54 showUpgradePrompt,
55 setShowUpgradePrompt,
56 selectedDateRange,
57 } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
58
59 const handleDatepickerChange = (vals: DatePickerValue) => {
60 const promptShown = handleDatePickerChangeFromHook(vals)
61 if (!promptShown) {
62 mergeParams({
63 iso_timestamp_start: vals.from ?? '',
64 iso_timestamp_end: vals.to ?? '',
65 })
66 }
67 }
68
69 const onRefreshReport = useRefreshHandler(
70 datePickerValue,
71 datePickerHelpers,
72 handleDatepickerChange,
73 refresh
74 )
75
76 return (
77 <ReportPadding>
78 <ReportHeader title="Storage" showDatabaseSelector={false} />
79 <ReportStickyNav
80 content={
81 <div className="flex flex-col gap-2">
82 <div className="flex items-center gap-2">
83 <ButtonTooltip
84 type="default"
85 disabled={report.isLoading}
86 icon={<RefreshCw className={report.isLoading ? 'animate-spin' : ''} />}
87 className="w-7"
88 tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
89 onClick={onRefreshReport}
90 />
91 <LogsDatePicker
92 onSubmit={handleDatepickerChange}
93 value={datePickerValue}
94 helpers={datePickerHelpers}
95 />
96 {selectedDateRange && (
97 <div className="flex items-center gap-x-2 text-xs">
98 <p className="text-foreground-light">
99 {dayjs(selectedDateRange.period_start.date).format('MMM D, h:mma')}
100 </p>
101 <p className="text-foreground-light">
102 <ArrowRight size={12} />
103 </p>
104 <p className="text-foreground-light">
105 {dayjs(selectedDateRange.period_end.date).format('MMM D, h:mma')}
106 </p>
107 </div>
108 )}
109 <UpgradePrompt
110 show={showUpgradePrompt}
111 setShowUpgradePrompt={setShowUpgradePrompt}
112 title="Report date range"
113 description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
114 source="storageReportDateRange"
115 />
116 </div>
117 <ReportFilterBar
118 onRemoveFilters={removeFilters}
119 onDatepickerChange={handleDatepickerChange}
120 datepickerFrom={params.totalRequests.iso_timestamp_start}
121 datepickerTo={params.totalRequests.iso_timestamp_end}
122 onAddFilter={addFilter}
123 isLoading={isLoading}
124 filters={filters}
125 selectedProduct="storage"
126 hideDatepicker={true}
127 datepickerHelpers={datePickerHelpers}
128 initialDatePickerValue={datePickerValue}
129 className="w-full"
130 showDatabaseSelector={false}
131 />
132 </div>
133 }
134 >
135 <div className="mt-8 flex flex-col gap-4">
136 <ReportWidget
137 isLoading={isLoading}
138 params={params.totalRequests}
139 title="Total Requests"
140 data={data.totalRequests || []}
141 error={error.totalRequest}
142 renderer={TotalRequestsChartRenderer}
143 append={TopApiRoutesRenderer}
144 appendProps={{ data: data.topRoutes || [], params: params.topRoutes }}
145 />
146 <ReportWidget
147 isLoading={isLoading}
148 params={params.responseSpeed}
149 title="Response Speed"
150 tooltip="Average response speed of a request (in ms)"
151 data={data.responseSpeed || []}
152 error={error.responseSpeed}
153 renderer={ResponseSpeedChartRenderer}
154 appendProps={{ data: data.topSlowRoutes || [], params: params.topSlowRoutes }}
155 append={TopApiRoutesRenderer}
156 />
157 <ReportWidget
158 isLoading={isLoading}
159 params={params.networkTraffic}
160 error={error.networkTraffic}
161 title="Network Traffic"
162 tooltip="Ingress and egress of requests and responses respectively"
163 data={data.networkTraffic || []}
164 renderer={NetworkTrafficRenderer}
165 />
166
167 <ReportWidget
168 isLoading={isLoading}
169 params={params.cacheHitRate}
170 title="Request Caching"
171 tooltip={
172 <div>
173 The number of storage requests that are cached at the edge level. A higher number of
174 hits is better.{' '}
175 <span className="flex items-center gap-1 text-foreground-lighter">
176 <Link href={`${DOCS_URL}/guides/storage/cdn/fundamentals`} target="_blank">
177 Read More
178 </Link>
179 <ExternalLinkIcon className="w-3 h-3" />
180 </span>
181 </div>
182 }
183 data={data.cacheHitRate || []}
184 renderer={CacheHitRateChartRenderer}
185 append={TopCacheMissesRenderer}
186 appendProps={{ data: data.topCacheMisses || [] }}
187 />
188 </div>
189 </ReportStickyNav>
190 <div className="py-8">
191 <ObservabilityLink />
192 </div>
193 </ReportPadding>
194 )
195}
196
197StorageReport.getLayout = (page) => (
198 <DefaultLayout>
199 <ObservabilityLayout title="Storage">{page}</ObservabilityLayout>
200 </DefaultLayout>
201)
202
203export default StorageReport