Files
supabase/apps/studio/data/database/activity-query.ts
Joshen Lim c7803b8b9b Chore/add sessions database connections (#48094)
## Context

Initial work for Top for Postgres - adds a "Sessions" section under a
new Observability segment "Database Connections"
NOTE: All the copywriting and naming might change - not sure what's an
ideal title for this
We'll also be iteratively building on top of this UI, adding more
actionable signals instead of just information
Changes are featured flagged, off for public

- This would essentially replace the "View ongoing queries" in the SQL
Editor by providing a dedicated UI
  - It checks against `pg_stat_activity` as per the ongoing queries UI
- We'll also subsequently deprecate the "Ongoing queries" UI in the SQL
editor
- Defaults into a "live mode" where the data is refreshed every 3
seconds via long-polling
<img width="983" height="474" alt="image"
src="https://github.com/user-attachments/assets/16402fe4-0b53-4f9e-9342-cdda26e3778a"
/>
- Supports filtering by state  
<img width="374" height="282" alt="image"
src="https://github.com/user-attachments/assets/562f8fbe-2dc6-48e7-8ec0-de7ffb8348d1"
/>
- Users can also terminate queries through here
<img width="247" height="164" alt="image"
src="https://github.com/user-attachments/assets/23a639dc-8f96-473a-a823-605b0bab02ee"
/>





<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

# Release Notes

* **New Features**
* Added an Observability **Database Connections** page with a live
**Sessions** activity table (state/roles filtering, blocked-by details,
session duration, and per-session termination with confirmation).
* Included a **Live/Pause** toggle to control automatic refresh (~3
seconds).

* **Enhancements**
* Improved Reports selection filtering: supports optional option
quantities, better popover styling, sorted apply behavior, and shows
quantity inline.
* Query performance duration formatting now supports configurable
decimal precision.
  * Tooltips can now render richer content (string or React node).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-21 16:52:03 +08:00

120 lines
3.4 KiB
TypeScript

import { getPgStatActivitySql } from '@supabase/pg-meta'
import { useQuery } from '@tanstack/react-query'
import { databaseKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { ResponseError, UseCustomQueryOptions } from '@/types'
type LockWaitEvent =
| 'relation'
| 'extend'
| 'page'
| 'tuple'
| 'transactionid'
| 'virtualxid'
| 'speculative token'
| 'object'
| 'userlock'
| 'advisory'
| 'applytransaction'
type ClientWaitEvent = 'ClientRead' | 'ClientWrite' | 'WalSenderWaitForWAL' | 'WalSenderWriteData'
type TimeoutWaitEvent =
| 'BaseBackupThrottle'
| 'CheckpointWriteDelay'
| 'PgSleep'
| 'RecoveryApplyDelay'
| 'VacuumDelay'
| 'VacuumTruncate'
type ActivityWaitEvent =
| 'ArchiverMain'
| 'AutoVacuumMain'
| 'BgWriterHibernate'
| 'BgWriterMain'
| 'CheckpointerMain'
| 'LogicalApplyMain'
| 'LogicalLauncherMain'
| 'WalReceiverMain'
| 'WalSenderMain'
| 'WalWriterMain'
type BufferPinWaitEvent = 'BufferPin'
type IOWaitEvent = string
type IPCWaitEvent = string
type LWLockWaitEvent = string
/**
* wait_event_type: What the session is blocked on
* wait_event: The specific event within that wait type
* */
type WaitEvent =
| { wait_event_type: 'Lock'; wait_event: LockWaitEvent }
| { wait_event_type: 'Client'; wait_event: ClientWaitEvent }
| { wait_event_type: 'Timeout'; wait_event: TimeoutWaitEvent }
| { wait_event_type: 'Activity'; wait_event: ActivityWaitEvent }
| { wait_event_type: 'BufferPin'; wait_event: BufferPinWaitEvent }
| { wait_event_type: 'Extension'; wait_event: string }
| { wait_event_type: 'IO'; wait_event: IOWaitEvent }
| { wait_event_type: 'IPC'; wait_event: IPCWaitEvent }
| { wait_event_type: 'LWLock'; wait_event: LWLockWaitEvent }
| { wait_event_type: null; wait_event: null }
export type DatabaseActivity = {
pid: number
role_name: string
application_name: string
blocked_by: number[]
query: string | null
query_start: string | null
transaction_start: string | null
state_change: string | null
state:
| 'idle'
| 'active'
| 'idle in transaction'
| 'idle in transaction (aborted)'
| 'fastpath function call'
| 'disabled'
| null
} & WaitEvent
export type DatabaseActivityVariables = {
projectRef?: string
connectionString?: string | null
}
export async function getDatabaseActivity(
{ projectRef, connectionString }: DatabaseActivityVariables,
signal?: AbortSignal
) {
const sql = getPgStatActivitySql()
const { result } = await executeSql(
{ projectRef, connectionString, sql, queryKey: ['activity'] },
signal
)
return (result ?? []).filter(
(x: DatabaseActivity) => !x.query?.startsWith(sql)
) as DatabaseActivity[]
}
export type DatabaseActivityData = Awaited<ReturnType<typeof getDatabaseActivity>>
export type DatabaseActivityError = ResponseError
export const useDatabaseActivityQuery = <TData = DatabaseActivityData>(
{ projectRef, connectionString }: DatabaseActivityVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<DatabaseActivityData, DatabaseActivityError, TData> = {}
) =>
useQuery<DatabaseActivityData, DatabaseActivityError, TData>({
queryKey: databaseKeys.databaseActivity(projectRef),
queryFn: ({ signal }) => getDatabaseActivity({ projectRef, connectionString }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...options,
})