mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
## Context Just extracting the fixes which I think are applicable from this [PR](https://github.com/supabase/supabase/pull/47695) Main files are - `apps/studio/hooks/analytics/useLogsQuery.tsx` - `packages/common/auth.tsx` - `packages/common/feature-flags.tsx` ## Changes involved - Adjust `useLogsQuery` to accept an object as prop, rather than 4 individual params - This one doesn't address any Sentry issues, but is just a improvement to the function's API imo, more readable - Adjust how user email is retrieved in `feature-flags` - Related Sentry issue [here](https://supabase.sentry.io/issues/7592718607/?project=5459134) - The error is a bit vague, but Claude's attempt to fix looks alright in general IMO - Minimally verified that feature flags are loading as expected still <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved log-related screens and queries for more reliable loading and filtering across the app. * Fixed profile and account data handling so identity details are retrieved more consistently. * Improved authentication handling to better recognize missing user data and keep the app stable. * Updated feature flag personalization to use more accurate account information. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
212 lines
6.0 KiB
TypeScript
212 lines
6.0 KiB
TypeScript
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 type { SafeLogSqlFragment } from '@/data/logs/safe-analytics-sql'
|
|
import { REPORT_STATUS_CODE_COLORS } from '@/data/reports/report.utils'
|
|
import useDbQuery, { DbQueryHook } from '@/hooks/analytics/useDbQuery'
|
|
import { useLogsQuery, type 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))
|
|
}
|
|
|
|
/**
|
|
* Decodes a URI component, returning the original string if it is malformed.
|
|
*
|
|
* `decodeURIComponent` throws a `URIError` on invalid percent-encoding (e.g. a
|
|
* literal `%` such as `?discount=100%`). Request paths are user-controlled, so
|
|
* decoding inline during render can crash the page.
|
|
*/
|
|
export const safeDecodeURIComponent = (value: string) => {
|
|
try {
|
|
return decodeURIComponent(value)
|
|
} catch {
|
|
return value
|
|
}
|
|
}
|
|
|
|
export type PresetHookResult = LogsQueryHook | DbQueryHook
|
|
type PresetHooks = Record<keyof PresetConfig['queries'], () => 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 = <T extends string>(
|
|
queries: BaseQueries<T>,
|
|
projectRef: string
|
|
): PresetHooks => {
|
|
const hooks: PresetHooks = Object.entries<ReportQuery>(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[]): SafeLogSqlFragment {
|
|
if (query.queryType !== 'logs') {
|
|
throw new Error(`Expected logs query, got ${query.queryType}`)
|
|
}
|
|
return query.safeSql(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<string>()
|
|
|
|
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<string, any>, 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<string>()
|
|
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<string, any>, 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)
|
|
}
|