From d2017189e14a626d67c3ef4400e3c0ce7f5de3f0 Mon Sep 17 00:00:00 2001
From: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Date: Wed, 8 Apr 2026 16:54:31 +0200
Subject: [PATCH 1/2] fix(migrations): parse migration version timestamps as
UTC SU-353303 (#44655)
## Problem
The "INSERTED AT (UTC)" column in the Database Migrations UI showed
local time instead of UTC. For a user in Buenos Aires (UTC-3), a
migration timestamped at UTC 08:33:31 would show 05:33:31 in the table.
The tooltip's relative time also showed "in 3 hours" (future) for a
migration that had already run, because the UTC offset was applied in
the wrong direction.
Root cause: `parseMigrationVersion` parsed the version string (format
`YYYYMMDDHHmmss`, which the Supabase CLI generates in UTC) using
`dayjs()` without the UTC flag, so dayjs interpreted the digits as local
time.
## Fix
- Changed `parseMigrationVersion` to use `dayjs.utc()` so the version
string is correctly interpreted as a UTC timestamp.
- Updated the label formatter in `Migrations.tsx` to use
`.utc().format()`, so the displayed time matches the column header
("INSERTED AT (UTC)").
- Added the dayjs UTC plugin setup to the test file and added a
regression test that asserts `toISOString()` returns the correct UTC
time.
## Before
## After
## How to test
- Set your browser/OS timezone to something other than UTC (e.g.
America/Buenos_Aires, UTC-3).
- Open the Database Migrations page for a project that has migrations.
- The "INSERTED AT (UTC)" column should show the UTC time matching the
version number digits (e.g. version `20260406083331` should show `06 Apr
2026, 08:33:31`).
- Hover over the timestamp. The tooltip should show the same value for
"UTC", a correctly offset value for your local timezone, and a relative
time that reflects the past (e.g. "3 hours ago", not "in 3 hours").
## Summary by CodeRabbit
* **Bug Fixes**
* Migration timestamps now parsed and displayed in UTC for consistent,
accurate labels; unparsable versions show "Unknown".
* **New Features**
* Improved migration version labeling for clearer, uniformly formatted
date/time shown in the UI.
* **Tests**
* Expanded tests for migration parsing and label formatting; test setup
updated for UTC handling.
---------
Co-authored-by: Claude Sonnet 4.6
---
.../Database/Migrations/Migrations.tsx | 6 ++---
apps/studio/lib/migration-utils.test.ts | 25 ++++++++++++++++++-
apps/studio/lib/migration-utils.ts | 15 ++++++++---
3 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx b/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx
index 8cc432d903b..f67be422436 100644
--- a/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx
+++ b/apps/studio/components/interfaces/Database/Migrations/Migrations.tsx
@@ -27,7 +27,7 @@ import { InlineLink } from '@/components/ui/InlineLink'
import { DatabaseMigration, useMigrationsQuery } from '@/data/database/migrations-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
-import { parseMigrationVersion } from '@/lib/migration-utils'
+import { formatMigrationVersionLabel, parseMigrationVersion } from '@/lib/migration-utils'
const Migrations = () => {
const [search, setSearch] = useState('')
@@ -119,9 +119,7 @@ const Migrations = () => {
{migrations.length > 0 ? (
migrations.map((migration) => {
const versionDayjs = parseMigrationVersion(migration.version)
- const label = versionDayjs
- ? versionDayjs.format('DD MMM YYYY, HH:mm:ss')
- : 'Unknown'
+ const label = formatMigrationVersionLabel(migration.version)
const insertedAt = versionDayjs ? versionDayjs.toISOString() : undefined
return (
diff --git a/apps/studio/lib/migration-utils.test.ts b/apps/studio/lib/migration-utils.test.ts
index 77a4dcaa781..a23a71913d2 100644
--- a/apps/studio/lib/migration-utils.test.ts
+++ b/apps/studio/lib/migration-utils.test.ts
@@ -1,7 +1,10 @@
import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
import { describe, expect, it } from 'vitest'
-import { parseMigrationVersion } from './migration-utils'
+import { formatMigrationVersionLabel, parseMigrationVersion } from './migration-utils'
+
+dayjs.extend(utc)
describe('parseMigrationVersion', () => {
it('should parse valid migration version in YYYYMMDDHHmmss format', () => {
@@ -70,4 +73,24 @@ describe('parseMigrationVersion', () => {
expect(result?.toISOString()).toBeDefined()
expect(result?.format('DD MMM YYYY')).toBe('28 Nov 2023')
})
+
+ it('should parse version digits as UTC so the UTC time is preserved exactly', () => {
+ // Migration versions are stored as UTC in the DB. Parsing without UTC mode
+ // would shift the time by the viewer's local offset, showing wrong UTC values.
+ const result = parseMigrationVersion('20231128095400')
+
+ expect(result?.toISOString()).toBe('2023-11-28T09:54:00.000Z')
+ })
+})
+
+describe('formatMigrationVersionLabel', () => {
+ it('should format a valid version as a UTC date string', () => {
+ expect(formatMigrationVersionLabel('20231128095400')).toBe('28 Nov 2023, 09:54:00')
+ })
+
+ it('should return Unknown for an invalid version', () => {
+ expect(formatMigrationVersionLabel('001')).toBe('Unknown')
+ expect(formatMigrationVersionLabel(null)).toBe('Unknown')
+ expect(formatMigrationVersionLabel(undefined)).toBe('Unknown')
+ })
})
diff --git a/apps/studio/lib/migration-utils.ts b/apps/studio/lib/migration-utils.ts
index dceef55ab64..dc829d3be23 100644
--- a/apps/studio/lib/migration-utils.ts
+++ b/apps/studio/lib/migration-utils.ts
@@ -6,7 +6,7 @@ import dayjs, { Dayjs } from 'dayjs'
* However, some projects may have custom version formats (e.g., "001", "002") that cannot be parsed as dates.
*
* @param version - Migration version string
- * @returns Dayjs object if the version is a valid datetime, null otherwise
+ * @returns Dayjs object (UTC mode) if the version is a valid datetime, undefined otherwise
*
* @example
* const parsed = parseMigrationVersion('20231128095400')
@@ -16,7 +16,7 @@ import dayjs, { Dayjs } from 'dayjs'
* }
*
* @example
- * const invalid = parseMigrationVersion('001') // returns null
+ * const invalid = parseMigrationVersion('001') // returns undefined
*/
export function parseMigrationVersion(version: string | null | undefined): Dayjs | undefined {
if (!version) return undefined
@@ -24,6 +24,15 @@ export function parseMigrationVersion(version: string | null | undefined): Dayjs
// Must contain only digits
if (!/^\d{14}$/.test(version)) return undefined
- const parsed = dayjs(version, 'YYYYMMDDHHmmss', true)
+ const parsed = dayjs.utc(version, 'YYYYMMDDHHmmss', true)
return parsed.isValid() ? parsed : undefined
}
+
+/**
+ * Formats a migration version string as a human-readable UTC date label.
+ * Returns 'Unknown' if the version cannot be parsed.
+ */
+export function formatMigrationVersionLabel(version: string | null | undefined): string {
+ const parsed = parseMigrationVersion(version)
+ return parsed ? parsed.format('DD MMM YYYY, HH:mm:ss') : 'Unknown'
+}
From 84a74b721e3c6e818d69a73b8e85847c89339433 Mon Sep 17 00:00:00 2001
From: Jordi Enric <37541088+jordienr@users.noreply.github.com>
Date: Wed, 8 Apr 2026 16:54:57 +0200
Subject: [PATCH 2/2] fix(logs): filter edge function logs by pathname instead
of function_id FUNC-476 (#44656)
## Problem
429 rate-limited responses from edge-functions-ingress do not appear in
the edge function logs dashboard. The cause is that rate-limited
requests are rejected before a function ID is resolved, so their log
events have no `metadata.function_id`. The dashboard filters logs by
`metadata.function_id`, silently dropping these events. Cloudflare
errors (502, 503, 525) are excluded for the same reason.
## Fix
Changed the filter in the edge function logs page from
`metadata.function_id` to `metadata.request.pathname`. The pathname
(`/functions/v1/{slug}`) is present on all requests, including
rate-limited ones, so 429s and Cloudflare errors are now visible
alongside normal invocation logs.
## Before
- Not seeing 429s
## After
- seeing 429s
## How to test
- Navigate to an edge function's Logs tab in the dashboard
- Confirm that normal invocation logs still load and are scoped to the
correct function
- Trigger a rate-limited request (>100 req/s to a function) and confirm
the 429 now appears in the logs tab
- Expected result: 429 events appear in the function logs dashboard
## Summary by CodeRabbit
* **Bug Fixes**
* Improved accuracy of edge function invocation log filtering by
refining the matching criteria used to display relevant logs.
---------
Co-authored-by: Claude Opus 4.6
---
.../project/[ref]/functions/[functionSlug]/invocations.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx
index 8f8ed161d6d..db6a67fb74f 100644
--- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx
+++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx
@@ -21,7 +21,7 @@ export const LogPage: NextPageWithLayout = () => {
condensedLayout
projectRef={ref as string}
queryType="fn_edge"
- filterOverride={{ function_id: selectedFunction.id }}
+ filterOverride={{ 'request.pathname': `/functions/v1/${selectedFunction.slug}` }}
/>
)