ProjectUsage.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import { useParams } from 'common'
  2. import dayjs from 'dayjs'
  3. import { Auth, Database, Realtime, Storage } from 'icons'
  4. import sumBy from 'lodash/sumBy'
  5. import Link from 'next/link'
  6. import { useRouter } from 'next/router'
  7. import { useEffect, useState } from 'react'
  8. import { Loading } from 'ui'
  9. import BarChart from '@/components/ui/Charts/BarChart'
  10. import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown'
  11. import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils'
  12. import Panel from '@/components/ui/Panel'
  13. import {
  14. ProjectLogStatsVariables,
  15. UsageApiCounts,
  16. useProjectLogStatsQuery,
  17. } from '@/data/analytics/project-log-stats-query'
  18. import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted'
  19. import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
  20. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  21. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  22. type ChartIntervalKey = ProjectLogStatsVariables['interval']
  23. const ProjectUsage = () => {
  24. const router = useRouter()
  25. const { ref: projectRef } = useParams()
  26. const { data: organization } = useSelectedOrganizationQuery()
  27. const { projectAuthAll: authEnabled, projectStorageAll: storageEnabled } = useIsFeatureEnabled([
  28. 'project_auth:all',
  29. 'project_storage:all',
  30. ])
  31. const { getEntitlementMax } = useCheckEntitlements('log.retention_days')
  32. const retentionDays = getEntitlementMax()
  33. const DEFAULT_INTERVAL: ChartIntervalKey =
  34. retentionDays !== undefined && retentionDays < 7 ? '1hr' : '1day'
  35. const [interval, setInterval] = useState<ChartIntervalKey>(DEFAULT_INTERVAL)
  36. useEffect(() => {
  37. setInterval(retentionDays !== undefined && retentionDays < 7 ? '1hr' : '1day')
  38. }, [retentionDays])
  39. const { data, isPending: isLoading } = useProjectLogStatsQuery({ projectRef, interval })
  40. const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
  41. const startDateLocal = dayjs().subtract(
  42. selectedInterval.startValue,
  43. selectedInterval.startUnit as dayjs.ManipulateType
  44. )
  45. const endDateLocal = dayjs()
  46. const { data: charts } = useFillTimeseriesSorted({
  47. data: data?.result ?? [],
  48. timestampKey: 'timestamp',
  49. valueKey: [
  50. 'total_auth_requests',
  51. 'total_rest_requests',
  52. 'total_storage_requests',
  53. 'total_realtime_requests',
  54. ],
  55. defaultValue: 0,
  56. startDate: startDateLocal.toISOString(),
  57. endDate: endDateLocal.toISOString(),
  58. minPointsToFill: 5,
  59. })
  60. const datetimeFormat = selectedInterval.format || 'MMM D, ha'
  61. const handleBarClick = (
  62. value: UsageApiCounts,
  63. _type: 'rest' | 'realtime' | 'storage' | 'auth'
  64. ) => {
  65. const unit = selectedInterval.startUnit
  66. const selectedStart = dayjs(value?.timestamp)
  67. const selectedEnd = selectedStart.add(1, unit)
  68. if (_type === 'rest') {
  69. router.push(
  70. `/project/${projectRef}/logs/edge-logs?its=${selectedStart.toISOString()}&ite=${selectedEnd.toISOString()}`
  71. )
  72. return
  73. }
  74. router.push(
  75. `/project/${projectRef}/logs/edge-logs?its=${selectedStart.toISOString()}&ite=${selectedEnd.toISOString()}&f=${JSON.stringify(
  76. {
  77. product: {
  78. [_type]: true,
  79. },
  80. }
  81. )}`
  82. )
  83. }
  84. return (
  85. <div className="space-y-6">
  86. <div className="flex flex-row items-center gap-x-2">
  87. <ChartIntervalDropdown
  88. value={interval || '1day'}
  89. onChange={(interval) => setInterval(interval as ProjectLogStatsVariables['interval'])}
  90. organizationSlug={organization?.slug}
  91. dropdownAlign="start"
  92. tooltipSide="right"
  93. />
  94. <span className="text-xs text-foreground-light">
  95. Statistics for {selectedInterval.label.toLowerCase()}
  96. </span>
  97. </div>
  98. <div className="grid grid-cols-1 @md:grid-cols-2 gap-4 @2xl:grid-cols-4">
  99. <Panel className="mb-0">
  100. <Panel.Content className="space-y-4">
  101. <PanelHeader
  102. icon={
  103. <div className="rounded-sm bg-surface-300 p-1.5 text-foreground-light shadow-xs">
  104. <Database strokeWidth={1.5} size={16} />
  105. </div>
  106. }
  107. title="Database"
  108. href={`/project/${projectRef}/editor`}
  109. />
  110. <Loading active={isLoading}>
  111. <BarChart
  112. title="REST Requests"
  113. data={charts}
  114. xAxisKey="timestamp"
  115. yAxisKey="total_rest_requests"
  116. onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'rest')}
  117. customDateFormat={datetimeFormat}
  118. highlightedValue={sumBy(charts, 'total_rest_requests')}
  119. />
  120. </Loading>
  121. </Panel.Content>
  122. </Panel>
  123. {authEnabled && (
  124. <Panel className="mb-0 md:mb-0">
  125. <Panel.Content className="space-y-4">
  126. <PanelHeader
  127. icon={
  128. <div className="rounded-sm bg-surface-300 p-1.5 text-foreground-light shadow-xs">
  129. <Auth strokeWidth={1.5} size={16} />
  130. </div>
  131. }
  132. title="Auth"
  133. href={`/project/${projectRef}/auth/users`}
  134. />
  135. <Loading active={isLoading}>
  136. <BarChart
  137. title="Auth Requests"
  138. data={charts}
  139. xAxisKey="timestamp"
  140. yAxisKey="total_auth_requests"
  141. onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'auth')}
  142. customDateFormat={datetimeFormat}
  143. highlightedValue={sumBy(charts || [], 'total_auth_requests')}
  144. />
  145. </Loading>
  146. </Panel.Content>
  147. </Panel>
  148. )}
  149. {storageEnabled && (
  150. <Panel className="mb-0 md:mb-0">
  151. <Panel.Content className="space-y-4">
  152. <PanelHeader
  153. icon={
  154. <div className="rounded-sm bg-surface-300 p-1.5 text-foreground-light shadow-xs">
  155. <Storage strokeWidth={1.5} size={16} />
  156. </div>
  157. }
  158. title="Storage"
  159. href={`/project/${projectRef}/storage/buckets`}
  160. />
  161. <Loading active={isLoading}>
  162. <BarChart
  163. title="Storage Requests"
  164. data={charts}
  165. xAxisKey="timestamp"
  166. yAxisKey="total_storage_requests"
  167. onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'storage')}
  168. customDateFormat={datetimeFormat}
  169. highlightedValue={sumBy(charts, 'total_storage_requests')}
  170. />
  171. </Loading>
  172. </Panel.Content>
  173. </Panel>
  174. )}
  175. <Panel className="mb-0 md:mb-0">
  176. <Panel.Content className="space-y-4">
  177. <PanelHeader
  178. icon={
  179. <div className="rounded-sm bg-surface-300 p-1.5 text-foreground-light shadow-xs">
  180. <Realtime strokeWidth={1.5} size={16} />
  181. </div>
  182. }
  183. title="Realtime"
  184. />
  185. <Loading active={isLoading}>
  186. <BarChart
  187. title="Realtime Requests"
  188. data={charts}
  189. xAxisKey="timestamp"
  190. yAxisKey="total_realtime_requests"
  191. onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'realtime')}
  192. customDateFormat={datetimeFormat}
  193. highlightedValue={sumBy(charts, 'total_realtime_requests')}
  194. />
  195. </Loading>
  196. </Panel.Content>
  197. </Panel>
  198. </div>
  199. </div>
  200. )
  201. }
  202. export default ProjectUsage
  203. const PanelHeader = (props: any) => {
  204. const Tag = props?.href ? Link : 'div'
  205. return (
  206. <Tag href={props.href}>
  207. <div
  208. className={
  209. 'flex items-center space-x-3 opacity-80 transition ' +
  210. (props.href ? 'cursor-pointer hover:text-gray-1200 hover:opacity-100' : '')
  211. }
  212. >
  213. <div>{props.icon}</div>
  214. <span className="flex items-center space-x-1">
  215. <h4 className="mb-0 text-lg">{props.title}</h4>
  216. </span>
  217. </div>
  218. </Tag>
  219. )
  220. }