From 99835713d2a4a5a170b80ff960f0b87914e6d05d Mon Sep 17 00:00:00 2001 From: Sean Oliver <882952+seanoliver@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:30:43 -0800 Subject: [PATCH 1/9] fix: enable App Router page view tracking on navigation (#40225) Fixes bug where App Router pages (docs, www/blog) only tracked the initial page load but not subsequent navigations. The issue was introduced in PR #35384 which inverted the logic for App Router telemetry tracking. The condition used a boolean flag that prevented tracking after the initial page view. The root cause: useEffect with [appPathname] dependency fires both on initial mount AND on pathname changes. The flag-based approach couldn't differentiate between these two cases. Solution: Track the previous pathname to detect actual changes. - Initial mount: previousAppPathnameRef is null, skip (initial effect handles it) - Navigation: previousAppPathnameRef !== appPathname, send telemetry Before: Only first page view tracked After: All page navigations tracked (like Pages Router) Affects: docs app, www app (blog pages) --- packages/common/telemetry.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/common/telemetry.tsx b/packages/common/telemetry.tsx index 64f77f1d503da..c4f8c4fc17cdc 100644 --- a/packages/common/telemetry.tsx +++ b/packages/common/telemetry.tsx @@ -172,6 +172,9 @@ export const PageTelemetry = ({ // Handle initial page telemetry event const hasSentInitialPageTelemetryRef = useRef(false) + // Track previous pathname for App Router to detect actual changes + const previousAppPathnameRef = useRef(null) + // Initialize PostHog client when consent is accepted useEffect(() => { if (hasAcceptedConsent && IS_PLATFORM) { @@ -239,10 +242,13 @@ export const PageTelemetry = ({ // For app router if (router !== null) return - // Wait until we've sent the initial page telemetry event - if (appPathname && !hasSentInitialPageTelemetryRef.current) { + // Only track if pathname actually changed (not initial mount) + if (appPathname && previousAppPathnameRef.current !== null && previousAppPathnameRef.current !== appPathname) { sendPageTelemetry() } + + // Update previous pathname + previousAppPathnameRef.current = appPathname }, [appPathname, router, sendPageTelemetry]) useEffect(() => { From 277abeb1f130a641967a00bbe629b9dc3e4a094e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 7 Nov 2025 12:33:18 +0800 Subject: [PATCH 2/9] Clean up RQ retries (#40229) --- ...ganization-billing-subscription-preview.ts | 17 ----------------- .../project-transfer-preview-query.ts | 19 +------------------ apps/studio/data/storage/buckets-query.ts | 8 ++------ 3 files changed, 3 insertions(+), 41 deletions(-) diff --git a/apps/studio/data/organizations/organization-billing-subscription-preview.ts b/apps/studio/data/organizations/organization-billing-subscription-preview.ts index e8e2e2d4903bf..de845902c8fcd 100644 --- a/apps/studio/data/organizations/organization-billing-subscription-preview.ts +++ b/apps/studio/data/organizations/organization-billing-subscription-preview.ts @@ -88,21 +88,4 @@ export const useOrganizationBillingSubscriptionPreview = < queryFn: () => previewOrganizationBillingSubscription({ organizationSlug, tier }), enabled: enabled && typeof organizationSlug !== 'undefined' && typeof tier !== 'undefined', ...options, - retry: (failureCount, error) => { - // Don't retry on 400s - if ( - typeof error === 'object' && - error !== null && - 'code' in error && - (error as any).code === 400 - ) { - return false - } - - if (failureCount < 3) { - return true - } - - return false - }, }) diff --git a/apps/studio/data/projects/project-transfer-preview-query.ts b/apps/studio/data/projects/project-transfer-preview-query.ts index 3f7d7be8bb7f1..71624745a4da2 100644 --- a/apps/studio/data/projects/project-transfer-preview-query.ts +++ b/apps/studio/data/projects/project-transfer-preview-query.ts @@ -1,8 +1,8 @@ import { useQuery } from '@tanstack/react-query' import { handleError, post } from 'data/fetchers' -import { projectKeys } from './keys' import { UseCustomQueryOptions } from 'types' +import { projectKeys } from './keys' export type ProjectTransferPreviewVariables = { projectRef?: string @@ -44,21 +44,4 @@ export const useProjectTransferPreviewQuery = { - // Don't retry on 400s - if ( - typeof error === 'object' && - error !== null && - 'code' in error && - (error as any).code === 400 - ) { - return false - } - - if (failureCount < 3) { - return true - } - - return false - }, }) diff --git a/apps/studio/data/storage/buckets-query.ts b/apps/studio/data/storage/buckets-query.ts index 0cb30a07c197f..581c4e64d9035 100644 --- a/apps/studio/data/storage/buckets-query.ts +++ b/apps/studio/data/storage/buckets-query.ts @@ -4,7 +4,7 @@ import { components } from 'api-types' import { get, handleError } from 'data/fetchers' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' -import type { ResponseError, UseCustomQueryOptions } from 'types' +import { ResponseError, type UseCustomQueryOptions } from 'types' import { storageKeys } from './keys' export type BucketsVariables = { projectRef?: string } @@ -41,11 +41,7 @@ export const useBucketsQuery = ( enabled: enabled && typeof projectRef !== 'undefined' && isActive, ...options, retry: (failureCount, error) => { - if ( - typeof error === 'object' && - error !== null && - error.message.includes('Missing tenant config') - ) { + if (error instanceof ResponseError && error.message.includes('Missing tenant config')) { return false } From 3b98a9844d2a222eeb883483b112670ec2ae1e90 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:59:48 +1100 Subject: [PATCH 3/9] chore(studio): table presentation improvements (#40224) * fix table head * fix edge functions list * fix secret * access keys * policies * misc table fixes * publication height fix * fix-publications * table documentation * fix colspan * s3 fixes --- .../content/docs/components/table.mdx | 40 ++-------- .../registry/default/example/table-demo.tsx | 76 +++++++++++-------- .../Publications/PublicationsList.tsx | 40 +++++----- .../Publications/PublicationsTableItem.tsx | 4 +- .../Publications/PublicationsTables.tsx | 2 +- .../interfaces/Database/Tables/TableList.tsx | 8 +- .../EdgeFunctionSecret.tsx | 2 +- .../Functions/EdgeFunctionsListItem.tsx | 6 +- .../StoragePoliciesBucketRow.tsx | 12 +-- .../Storage/StorageSettings/S3Connection.tsx | 12 +-- .../StorageSettings/StorageCredItem.tsx | 4 +- .../ui/src/components/shadcn/ui/badge.tsx | 2 +- .../ui/src/components/shadcn/ui/table.tsx | 2 +- 13 files changed, 97 insertions(+), 113 deletions(-) diff --git a/apps/design-system/content/docs/components/table.mdx b/apps/design-system/content/docs/components/table.mdx index ff05bebe235e7..ab2cc6bf5dc55 100644 --- a/apps/design-system/content/docs/components/table.mdx +++ b/apps/design-system/content/docs/components/table.mdx @@ -1,44 +1,16 @@ --- title: Table -description: A responsive table component. +description: A responsive table component for basic data. component: true -source: - shadcn: true --- - - -## Installation - - - - - CLI - Manual - - - -```bash -npx shadcn-ui@latest add table -``` - - +Table presents basic tabular data presentation within a [Card](/docs/components/card). - - - - -Copy and paste the following code into your project. - - - -Update the import paths to match your project setup. - - + - +Columns will naturally take the width of their content. Width specification is therefore not required unless you require columns to span a specific width. An example use-case may be when placing multiple Table instances on the one page, where columns on each table should optically align. - +`TableHead` includes a `whitespace-nowrap` utility class to prevent its text from wrapping. `TableCell` must have any custom width specified, as there are legitimate use cases for both wrapping (or truncated) text. ## Usage @@ -59,7 +31,7 @@ import { A list of your recent invoices. - Invoice + Invoice Status Method Amount diff --git a/apps/design-system/registry/default/example/table-demo.tsx b/apps/design-system/registry/default/example/table-demo.tsx index d3893e1864982..6ee6dad665a74 100644 --- a/apps/design-system/registry/default/example/table-demo.tsx +++ b/apps/design-system/registry/default/example/table-demo.tsx @@ -1,4 +1,5 @@ import { + Card, Table, TableBody, TableCaption, @@ -14,74 +15,87 @@ const invoices = [ invoice: 'INV001', paymentStatus: 'Paid', totalAmount: '$250.00', - paymentMethod: 'Credit Card', + paymentMethod: 'Credit card', + description: 'Website design services', }, { invoice: 'INV002', paymentStatus: 'Pending', totalAmount: '$150.00', paymentMethod: 'PayPal', + description: 'Monthly subscription fee', }, { invoice: 'INV003', paymentStatus: 'Unpaid', totalAmount: '$350.00', - paymentMethod: 'Bank Transfer', + paymentMethod: 'Bank transfer', + description: 'Consulting hours', }, { invoice: 'INV004', paymentStatus: 'Paid', totalAmount: '$450.00', - paymentMethod: 'Credit Card', + paymentMethod: 'Credit card', + description: 'Software license renewal', }, { invoice: 'INV005', paymentStatus: 'Paid', totalAmount: '$550.00', paymentMethod: 'PayPal', + description: 'Custom development work', }, { invoice: 'INV006', paymentStatus: 'Pending', totalAmount: '$200.00', - paymentMethod: 'Bank Transfer', + paymentMethod: 'Bank transfer', + description: 'Hosting and maintenance', }, { invoice: 'INV007', paymentStatus: 'Unpaid', totalAmount: '$300.00', - paymentMethod: 'Credit Card', + paymentMethod: 'Credit card', + description: 'Training session package', }, ] export default function TableDemo() { return ( - - A list of your recent invoices. - - - Invoice - Status - Method - Amount - - - - {invoices.map((invoice) => ( - - {invoice.invoice} - {invoice.paymentStatus} - {invoice.paymentMethod} - {invoice.totalAmount} + +
+ A list of your recent invoices + + + Invoice + Status + Method + Description + Amount - ))} - - - - Total - $2,500.00 - - -
+
+ + {invoices.map((invoice) => ( + + {invoice.invoice} + {invoice.paymentStatus} + {invoice.paymentMethod} + + {invoice.description} + + {invoice.totalAmount} + + ))} + + + + Total + $2,250.00 + + + + ) } diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx index 6bb04e304d198..27955f326a1f0 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx @@ -147,25 +147,27 @@ export const PublicationsList = () => { {isSuccess && publications.map((x) => ( - - {x.name} - {/* [Joshen] Making this tooltip very specific for these 2 publications */} - {['supabase_realtime', 'supabase_realtime_messages_publication'].includes( - x.name - ) && ( - - - - - - {x.name === 'supabase_realtime' - ? 'This publication is managed by Supabase and handles Postgres changes' - : x.name === 'supabase_realtime_messages_publication' - ? 'This publication is managed by Supabase and handles broadcasts from the database' - : undefined} - - - )} + +
+ {x.name} + {/* [Joshen] Making this tooltip very specific for these 2 publications */} + {['supabase_realtime', 'supabase_realtime_messages_publication'].includes( + x.name + ) && ( + + + + + + {x.name === 'supabase_realtime' + ? 'Managed by Supabase and handles Postgres changes' + : x.name === 'supabase_realtime_messages_publication' + ? 'Managed by Supabase and handles broadcasts from the database' + : undefined} + + + )} +
{x.id} {publicationEvents.map((event) => ( diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx index c5aec787a996d..62b689f984384 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx @@ -78,8 +78,8 @@ export const PublicationsTableItem = ({ return ( {table.name} - {table.schema} - + {table.schema} + {table.comment} diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx index d3e77df1c6329..f543e5469dc26 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx @@ -102,7 +102,7 @@ export const PublicationsTables = () => { Name Schema - Description + Description {/* We've disabled All tables toggle for publications. See https://github.com/supabase/supabase/pull/7233. diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index f88f13ebe16b6..cfaa15e552c09 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -20,6 +20,7 @@ import { useRouter } from 'next/router' import { useState } from 'react' import { useParams } from 'common' +import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' @@ -63,7 +64,6 @@ import { } from 'ui' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import { formatAllEntities } from './Tables.utils' -import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils' interface TableListProps { onAddTable: () => void @@ -325,7 +325,7 @@ export const TableList = ({ Size (Estimated) - + Realtime Enabled @@ -457,11 +457,11 @@ export const TableList = ({ {(realtimePublication?.tables ?? []).find( (table) => table.id === x.id ) ? ( -
+
) : ( -
+
)} diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx index 9e9aaf20ecc28..235f0284c584a 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx @@ -37,7 +37,7 @@ const EdgeFunctionSecret = ({ secret, onSelectDelete }: EdgeFunctionSecretProps) displayAs="utc" utcTimestamp={secret.updated_at} labelFormat="DD MMM YYYY HH:mm:ss (ZZ)" - className="!text-sm text-foreground-light" + className="!text-sm text-foreground-light whitespace-nowrap" /> ) : ( '-' diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx index 9056d0f55ccb5..358c8fc2ad6b3 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -37,9 +37,7 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP className="cursor-pointer" > -
-

{item.name}

-
+

{item.name}

@@ -80,7 +78,7 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP - +

{dayjs(item.updated_at).fromNow()}

diff --git a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx index a80d9d4762396..95ebf325cd8e0 100644 --- a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx +++ b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx @@ -1,8 +1,8 @@ import { PostgresPolicy } from '@supabase/postgres-meta' import { noop } from 'lodash' -import { Bucket } from 'data/storage/buckets-query' import { PolicyRow } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow' +import { Bucket } from 'data/storage/buckets-query' import { Bucket as BucketIcon } from 'icons' import { @@ -40,11 +40,13 @@ export const StoragePoliciesBucketRow = ({ }: StoragePoliciesBucketRowProps) => { return ( - -
+ +
- {label} - {bucket?.public && Public} +
+ {label} + {bucket?.public && Public} +