import dayjs from 'dayjs' import { type BaseQueries, type PresetConfig, type ReportFilterItem, type ReportQuery, } from './Reports.types' import { isUnixMicro, unixMicroToIsoTimestamp, } from '@/components/interfaces/Settings/Logs/Logs.utils' import { REPORT_STATUS_CODE_COLORS } from '@/data/reports/report.utils' import useDbQuery, { DbQueryHook } from '@/hooks/analytics/useDbQuery' import useLogsQuery, { LogsQueryHook } from '@/hooks/analytics/useLogsQuery' import { getHttpStatusCodeInfo } from '@/lib/http-status-codes' /** * Converts a query params string to an object */ export const queryParamsToObject = (params: string) => { return Object.fromEntries(new URLSearchParams(params)) } export type PresetHookResult = LogsQueryHook | DbQueryHook type PresetHooks = Record PresetHookResult> /** * @deprecated * Queries are hooks, avoid generating hooks dynamically * Generate fetch functions instead, and pass it to a hook inside the component */ export const queriesFactory = ( queries: BaseQueries, projectRef: string ): PresetHooks => { const hooks: PresetHooks = Object.entries(queries).reduce((acc, [k, query]) => { if (query.queryType === 'db') { return { ...acc, [k]: () => useDbQuery({ sql: query.safeSql }), } } else { return { ...acc, [k]: () => useLogsQuery(projectRef), } } }, {}) return hooks } export function getLogsSql(query: ReportQuery, filters: ReportFilterItem[]): string { if (query.queryType !== 'logs') { throw new Error(`Expected logs query, got ${query.queryType}`) } return query.sql(filters) } /** * Formats a timestamp to a human readable format in UTC * * @param timestamp - The timestamp to format * @param returnUtc - Whether to return the timestamp in UTC * @param format - The format to use for the timestamp * @returns The formatted timestamp string */ export const formatTimestamp = ( timestamp: number | string, { returnUtc = false, format = 'MMM D, h:mma' }: { returnUtc?: boolean; format?: string } = {} ) => { try { const isSeconds = String(timestamp).length === 10 const isMicroseconds = String(timestamp).length === 16 const timestampInMs = isSeconds ? Number(timestamp) * 1000 : isMicroseconds ? Number(timestamp) / 1000 : Number(timestamp) if (returnUtc) { return dayjs.utc(timestampInMs).format(format) } else { return dayjs(timestampInMs).format(format) } } catch (error) { console.error(error) return 'Invalid Date' } } /** * Extracts distinct status codes from log data rows */ export function extractStatusCodesFromData(data: any[]): string[] { const statusCodes = new Set() data.forEach((item: any) => { if (item.status_code !== undefined && item.status_code !== null) { statusCodes.add(String(item.status_code)) } }) return Array.from(statusCodes).sort() } /** * Generates chart attributes for status codes with labels and colors */ export function generateStatusCodeAttributes(statusCodes: string[]) { return statusCodes.map((code) => ({ attribute: code, label: `${code} ${getHttpStatusCodeInfo(parseInt(code, 10)).label}`, color: REPORT_STATUS_CODE_COLORS[code] || REPORT_STATUS_CODE_COLORS.default, })) } /** * Pivots rows of { timestamp, status_code, count } into { timestamp, [status_code]: count } * and normalizes timestamps to ISO strings (UTC), filling missing codes with 0 per timestamp */ export function transformStatusCodeData(data: any[], statusCodes: string[]) { const pivotedData = data.reduce((acc: Record, d: any) => { const timestamp = isUnixMicro(d.timestamp) ? unixMicroToIsoTimestamp(d.timestamp) : dayjs.utc(d.timestamp).toISOString() if (!acc[timestamp]) { acc[timestamp] = { timestamp } statusCodes.forEach((code) => { acc[timestamp][code] = 0 }) } const codeKey = String(d.status_code) if (codeKey in acc[timestamp]) { acc[timestamp][codeKey] = d.count } return acc }, {}) return Object.values(pivotedData) } /** * Extract distinct string values for a given field from data rows */ export function extractDistinctValuesFromData(data: any[], field: string): string[] { const values = new Set() data.forEach((item: any) => { if (item[field] !== undefined && item[field] !== null) { values.add(String(item[field])) } }) return Array.from(values).sort() } /** * Generates chart attributes from a list of category values */ export function generateCategoryAttributes( values: string[], labelResolver?: (v: string) => string ) { return values.map((v) => ({ attribute: v, label: labelResolver ? labelResolver(v) : v, })) } /** * Pivot rows of { timestamp, [categoryField], count } into { timestamp, [category]: count } */ export function transformCategoricalCountData( data: any[], categoryField: string, categories: string[] ) { const pivotedData = data.reduce((acc: Record, d: any) => { const timestamp = isUnixMicro(d.timestamp) ? unixMicroToIsoTimestamp(d.timestamp) : dayjs.utc(d.timestamp).toISOString() if (!acc[timestamp]) { acc[timestamp] = { timestamp } categories.forEach((c) => { acc[timestamp][c] = 0 }) } const key = String(d[categoryField]) if (key in acc[timestamp]) { acc[timestamp][key] = d.count } return acc }, {}) return Object.values(pivotedData) }