From 56a6ec2601f297f9c7b8b4ccbb71cdc7d0b13855 Mon Sep 17 00:00:00 2001 From: Wendie Cheung <49030266+wendeh@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:50:51 +1000 Subject: [PATCH 1/5] Add Wendie Cheung to humans.txt (#48119) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES/NO ## What kind of change does this PR introduce? Bug fix, feature, docs update, ... ## What is the current behavior? Please link any relevant issues here. ## What is the new behavior? Feel free to include screenshots if it includes visual changes. ## Additional context Add any other context or screenshots. ## Summary by CodeRabbit * **Documentation** * Added Wendie Cheung to the team listing on the documentation site. --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 64bf216b7906f..43e4768008df4 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -302,6 +302,7 @@ Utkarash Singh Victor Farazdagi Warwick Mitchell Wen Bo Xie +Wendie Cheung Yorvi Arias Yuliya Marinova Zach Marinov From 751dcecf86563a540257c9ae18b3bd8080a9b845 Mon Sep 17 00:00:00 2001 From: Steven Eubank <47563310+smeubank@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:51:52 +0200 Subject: [PATCH 2/5] =?UTF-8?q?docs(log-drains):=20overhaul=20page=20style?= =?UTF-8?q?=20and=20add=20missing=20Last9=20+=20Syslog=20=E2=80=A6=20(#481?= =?UTF-8?q?40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Brings the Log Drains docs up to the same standard as the Metrics API page. - Replaces the plain destination table with a visual `LogDrainDestinationCards` component — a 3-column card grid that mirrors the product UI destination picker. Cards link to anchors on the same page (no sub-pages needed since setup is simpler than Metrics). - Adds a "What you can do" intro section and a consistent "Required configuration + Steps" structure for every destination. - Adds two destinations that were missing from the docs entirely: **Last9** and **Syslog** (both are live in the product). Config fields sourced from `LogDrainDestinationSheetForm.tsx`. - Cleans up raw `
- {activity.query ?? 'No query'} + {!!activity.query ? activity.query : 'No query'}
{durationSeconds !== null ? ( diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.ai.ts b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.ai.ts new file mode 100644 index 0000000000000..b70b5aa9e6887 --- /dev/null +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.ai.ts @@ -0,0 +1,35 @@ +import { type DatabaseActivity } from '@/data/database/activity-query' + +export const buildDatabaseConnectionsSummaryPrompt = ({ + activities, + timestamp, +}: { + activities: DatabaseActivity[] + timestamp: string +}) => { + const prompt = ` +Data from \`pg_stat_activity\` captured at: ${timestamp} +\`\`\` +${JSON.stringify(activities)} +\`\`\` + +Summarize the sessions in the above data for a developer who isn't a DBA. Cover, in this order, only the categories that have at least one match: +1. Idle-in-transaction sessions — cite the PID and role; note it's almost always a missing commit/rollback in their app. +2. Blocked sessions — cite the blocked PID and the PID/query blocking it. +3. Active queries running longer than 30 seconds — cite the PID and duration. + +Rules: +- Only include a bullet for a category if it has at least one match. Do not mention categories with nothing to report. +- If none of the four apply, respond with a single plain sentence confirming things look healthy — no bullets, no "if you want..." offer, no restating the data. +- Reference every finding by PID so the user can find the row in the table. +- Compute the duration based on the timestamps provided against the captured at timestamp and blocked_by fields from the data + - Rows with status as "active" should be computed using the "query_start" property + - Rows with status as "idle in transaction" or "idle in transaction (aborted) should be computed using the "transaction_start" property + +Format: +- Bold every PID (e.g. **PID 511**). +- Display the data for each category in a table format. +- If anything was flagged, end with one short line inviting the user to ask for more detail on a specific PID. If everything is healthy, stop after the health sentence — do not ask for more data. +` + return prompt +} diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts new file mode 100644 index 0000000000000..be3a075cdc149 --- /dev/null +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts @@ -0,0 +1,18 @@ +// [Joshen] These are opinionated thresholds for when things might need attention +// For queries in "active" state - show warning variant if running longer than 30 seconds +// - Typically Not a problem unless its running longer than expected +// For queries in "idle in transaction" state - show warning variant if running longer than 30 seconds +// - Shorter threshold as it indicates a lock +export const WARN_DURATION_ACTIVE_QUERY = 30 // seconds +export const WARN_DURATION_IDLE_TXN = 10 // seconds + +export const QUERY_STATE_TOOLTIP = { + ['active']: 'Currently executing a query.', + ['idle']: 'Connected, but not currently running a query.', + ['disabled']: 'Activity tracking is disabled for this session.', + ['idle in transaction']: 'Has an open transaction but isn’t currently running a query.', + ['idle in transaction (aborted)']: + 'The last statement in this transaction failed and hasn’t been rolled back yet.', + ['fastpath function call']: + 'Executing a function call via Postgres’s low-level fastpath protocol.', +} diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx new file mode 100644 index 0000000000000..d03ee79e09f8a --- /dev/null +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx @@ -0,0 +1,255 @@ +import dayjs from 'dayjs' +import { parseAsInteger, useQueryState } from 'nuqs' +import { cn } from 'ui' +import { + MetricCard, + MetricCardContent, + MetricCardHeader, + MetricCardLabel, + MetricCardValue, +} from 'ui-patterns/MetricCard' + +import { WARN_DURATION_ACTIVE_QUERY, WARN_DURATION_IDLE_TXN } from './DatabaseConnections.constants' +import { formatDuration } from '@/components/interfaces/QueryPerformance/QueryPerformance.utils' +import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' +import { useDatabaseActivityQuery, type DatabaseActivity } from '@/data/database/activity-query' +import { useMaxConnectionsQuery } from '@/data/database/max-connections-query' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' + +const LONG_RUNNING_STATES: (DatabaseActivity['state'] | undefined)[] = [ + 'active', + 'idle in transaction', + 'idle in transaction (aborted)', +] + +interface OverviewProps { + live?: boolean +} + +/** + * [Joshen] Couple of nuances worth calling out to provide better signals for the user + * - Idle in transaction: + * - Only considers queries in that state, but running for longer than 10 seconds + * - Could otherwise be a query in mid-flight + * - Longest running: + * - Only considers queries that are active or idle in transaction + */ +export const Overview = ({ live }: OverviewProps) => { + const { data: project } = useSelectedProjectQuery() + const [, setSelectedPid] = useQueryState('pid', parseAsInteger) + + const { data, isPending: isLoadingActivity } = useDatabaseActivityQuery( + { + projectRef: project?.ref, + connectionString: project?.connectionString, + }, + { refetchOnWindowFocus: live, refetchInterval: live ? 3000 : false } + ) + const activeQueries = (data ?? []).filter((x) => x.state === 'active') + const blockedQueries = (data ?? []).filter((x) => x.blocked_by.length > 0) + const idleInTransactionQueries = (data ?? []).filter((x) => { + const isIdleInTransaction = + x.state === 'idle in transaction' || x.state === 'idle in transaction (aborted)' + if (!isIdleInTransaction || !x.transaction_start) return false + return dayjs().utc().diff(dayjs(x.transaction_start).utc(), 'second') > WARN_DURATION_IDLE_TXN + }) + + const longestRunningQuery = (data ?? []) + .filter((x) => LONG_RUNNING_STATES.includes(x.state)) + .reduce<{ activity: DatabaseActivity; duration: number } | null>((longest, activity) => { + const start = activity.state === 'active' ? activity.query_start : activity.transaction_start + if (!start) return longest + const duration = Math.max(dayjs().utc().diff(dayjs(start).utc(), 'second'), 0) + return longest === null || duration > longest.duration ? { activity, duration } : longest + }, null) + const warnLongestRunningQuery = + (longestRunningQuery?.activity.state === 'active' && + longestRunningQuery.duration >= WARN_DURATION_ACTIVE_QUERY) || + ((longestRunningQuery?.activity.state === 'idle in transaction' || + longestRunningQuery?.activity.state === 'idle in transaction (aborted)') && + longestRunningQuery.duration >= WARN_DURATION_IDLE_TXN) + + const { data: roles, isPending: isLoadingRoles } = useDatabaseRolesQuery( + { + projectRef: project?.ref, + connectionString: project?.connectionString, + }, + { refetchOnWindowFocus: live, refetchInterval: live ? 3000 : false } + ) + const rolesWithActiveConnections = (roles ?? []).filter((role) => role.activeConnections) + const totalActiveConnections = (roles ?? []) + .map((role) => role.activeConnections) + .reduce((a, b) => a + b, 0) + + const { data: maxConnectionLimit, isPending: isLoadingMaxConnections } = useMaxConnectionsQuery( + { + projectRef: project?.ref, + connectionString: project?.connectionString, + }, + { + select: (data) => data.maxConnections, + refetchInterval: live ? 3000 : false, + } + ) + + return ( +
Connections by roles:
+ {rolesWithActiveConnections.map((role) => ( +{role.name}:
{role.activeConnections} ++ Queries waiting on a lock held by another session - stalls everything queued + behind it. +
++ Typically caused by an uncommitted transaction, a long-running migration, or a + stuck idle-in-transaction session. +
+ > + } + > + Blocked queries ++ Transactions left open without running a query, which can hold locks and block + table cleanup for as long as it stays open +
++ Typically indicates an app issue, such as a forgotten COMMIT or ROLLBACK. +
+ > + } + > + Idle in transaction +{ )}
- PID: {activity.pid} +