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 CleanShot 2026-04-08 at 12 41 18@2x ## After CleanShot 2026-04-08 at 12 42 03@2x ## 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 CleanShot 2026-04-08 at 13 13
24@2x ## After - seeing 429s CleanShot 2026-04-08 at 13 12
27@2x ## 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}` }} /> )