Reports.utils.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import dayjs from 'dayjs'
  2. import {
  3. type BaseQueries,
  4. type PresetConfig,
  5. type ReportFilterItem,
  6. type ReportQuery,
  7. } from './Reports.types'
  8. import {
  9. isUnixMicro,
  10. unixMicroToIsoTimestamp,
  11. } from '@/components/interfaces/Settings/Logs/Logs.utils'
  12. import { REPORT_STATUS_CODE_COLORS } from '@/data/reports/report.utils'
  13. import useDbQuery, { DbQueryHook } from '@/hooks/analytics/useDbQuery'
  14. import useLogsQuery, { LogsQueryHook } from '@/hooks/analytics/useLogsQuery'
  15. import { getHttpStatusCodeInfo } from '@/lib/http-status-codes'
  16. /**
  17. * Converts a query params string to an object
  18. */
  19. export const queryParamsToObject = (params: string) => {
  20. return Object.fromEntries(new URLSearchParams(params))
  21. }
  22. export type PresetHookResult = LogsQueryHook | DbQueryHook
  23. type PresetHooks = Record<keyof PresetConfig['queries'], () => PresetHookResult>
  24. /**
  25. * @deprecated
  26. * Queries are hooks, avoid generating hooks dynamically
  27. * Generate fetch functions instead, and pass it to a hook inside the component
  28. */
  29. export const queriesFactory = <T extends string>(
  30. queries: BaseQueries<T>,
  31. projectRef: string
  32. ): PresetHooks => {
  33. const hooks: PresetHooks = Object.entries<ReportQuery>(queries).reduce((acc, [k, query]) => {
  34. if (query.queryType === 'db') {
  35. return {
  36. ...acc,
  37. [k]: () => useDbQuery({ sql: query.safeSql }),
  38. }
  39. } else {
  40. return {
  41. ...acc,
  42. [k]: () => useLogsQuery(projectRef),
  43. }
  44. }
  45. }, {})
  46. return hooks
  47. }
  48. export function getLogsSql(query: ReportQuery, filters: ReportFilterItem[]): string {
  49. if (query.queryType !== 'logs') {
  50. throw new Error(`Expected logs query, got ${query.queryType}`)
  51. }
  52. return query.sql(filters)
  53. }
  54. /**
  55. * Formats a timestamp to a human readable format in UTC
  56. *
  57. * @param timestamp - The timestamp to format
  58. * @param returnUtc - Whether to return the timestamp in UTC
  59. * @param format - The format to use for the timestamp
  60. * @returns The formatted timestamp string
  61. */
  62. export const formatTimestamp = (
  63. timestamp: number | string,
  64. { returnUtc = false, format = 'MMM D, h:mma' }: { returnUtc?: boolean; format?: string } = {}
  65. ) => {
  66. try {
  67. const isSeconds = String(timestamp).length === 10
  68. const isMicroseconds = String(timestamp).length === 16
  69. const timestampInMs = isSeconds
  70. ? Number(timestamp) * 1000
  71. : isMicroseconds
  72. ? Number(timestamp) / 1000
  73. : Number(timestamp)
  74. if (returnUtc) {
  75. return dayjs.utc(timestampInMs).format(format)
  76. } else {
  77. return dayjs(timestampInMs).format(format)
  78. }
  79. } catch (error) {
  80. console.error(error)
  81. return 'Invalid Date'
  82. }
  83. }
  84. /**
  85. * Extracts distinct status codes from log data rows
  86. */
  87. export function extractStatusCodesFromData(data: any[]): string[] {
  88. const statusCodes = new Set<string>()
  89. data.forEach((item: any) => {
  90. if (item.status_code !== undefined && item.status_code !== null) {
  91. statusCodes.add(String(item.status_code))
  92. }
  93. })
  94. return Array.from(statusCodes).sort()
  95. }
  96. /**
  97. * Generates chart attributes for status codes with labels and colors
  98. */
  99. export function generateStatusCodeAttributes(statusCodes: string[]) {
  100. return statusCodes.map((code) => ({
  101. attribute: code,
  102. label: `${code} ${getHttpStatusCodeInfo(parseInt(code, 10)).label}`,
  103. color: REPORT_STATUS_CODE_COLORS[code] || REPORT_STATUS_CODE_COLORS.default,
  104. }))
  105. }
  106. /**
  107. * Pivots rows of { timestamp, status_code, count } into { timestamp, [status_code]: count }
  108. * and normalizes timestamps to ISO strings (UTC), filling missing codes with 0 per timestamp
  109. */
  110. export function transformStatusCodeData(data: any[], statusCodes: string[]) {
  111. const pivotedData = data.reduce((acc: Record<string, any>, d: any) => {
  112. const timestamp = isUnixMicro(d.timestamp)
  113. ? unixMicroToIsoTimestamp(d.timestamp)
  114. : dayjs.utc(d.timestamp).toISOString()
  115. if (!acc[timestamp]) {
  116. acc[timestamp] = { timestamp }
  117. statusCodes.forEach((code) => {
  118. acc[timestamp][code] = 0
  119. })
  120. }
  121. const codeKey = String(d.status_code)
  122. if (codeKey in acc[timestamp]) {
  123. acc[timestamp][codeKey] = d.count
  124. }
  125. return acc
  126. }, {})
  127. return Object.values(pivotedData)
  128. }
  129. /**
  130. * Extract distinct string values for a given field from data rows
  131. */
  132. export function extractDistinctValuesFromData(data: any[], field: string): string[] {
  133. const values = new Set<string>()
  134. data.forEach((item: any) => {
  135. if (item[field] !== undefined && item[field] !== null) {
  136. values.add(String(item[field]))
  137. }
  138. })
  139. return Array.from(values).sort()
  140. }
  141. /**
  142. * Generates chart attributes from a list of category values
  143. */
  144. export function generateCategoryAttributes(
  145. values: string[],
  146. labelResolver?: (v: string) => string
  147. ) {
  148. return values.map((v) => ({
  149. attribute: v,
  150. label: labelResolver ? labelResolver(v) : v,
  151. }))
  152. }
  153. /**
  154. * Pivot rows of { timestamp, [categoryField], count } into { timestamp, [category]: count }
  155. */
  156. export function transformCategoricalCountData(
  157. data: any[],
  158. categoryField: string,
  159. categories: string[]
  160. ) {
  161. const pivotedData = data.reduce((acc: Record<string, any>, d: any) => {
  162. const timestamp = isUnixMicro(d.timestamp)
  163. ? unixMicroToIsoTimestamp(d.timestamp)
  164. : dayjs.utc(d.timestamp).toISOString()
  165. if (!acc[timestamp]) {
  166. acc[timestamp] = { timestamp }
  167. categories.forEach((c) => {
  168. acc[timestamp][c] = 0
  169. })
  170. }
  171. const key = String(d[categoryField])
  172. if (key in acc[timestamp]) {
  173. acc[timestamp][key] = d.count
  174. }
  175. return acc
  176. }, {})
  177. return Object.values(pivotedData)
  178. }