From 9caa0d548a9df81c9d00aae3d9e35a45f6c99231 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:50:52 -0500 Subject: [PATCH] feat(studio): clean up support form message (#42174) * feat(studio): clean up support form message Support form message currently has the studio version and dashboard logs link appended to the end. On request from the Support team, we're moving this to metadata fields so it won't be as distracting. * Hide IncidentAdmonition if event is maintenance --------- Co-authored-by: Joshen Lim --- .../interfaces/Support/IncidentAdmonition.tsx | 3 +- .../interfaces/Support/SupportForm.utils.tsx | 21 ++++++----- .../interfaces/Support/SupportFormV2.tsx | 5 +-- .../__tests__/SupportFormPage.test.tsx | 35 ++++++++----------- .../data/feedback/support-ticket-send.ts | 6 ++++ 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/apps/studio/components/interfaces/Support/IncidentAdmonition.tsx b/apps/studio/components/interfaces/Support/IncidentAdmonition.tsx index 129570067f1..e7caa76a708 100644 --- a/apps/studio/components/interfaces/Support/IncidentAdmonition.tsx +++ b/apps/studio/components/interfaces/Support/IncidentAdmonition.tsx @@ -54,7 +54,8 @@ const getStatusDescription = ( } export function IncidentAdmonition({ isActive }: IncidentAdmonitionProps) { - const { data: incidents, isLoading, isError } = useIncidentStatusQuery() + const { data: allStatusPageEvents = [], isLoading, isError } = useIncidentStatusQuery() + const incidents = allStatusPageEvents.filter((x) => x.impact !== 'maintenance') // Don't render anything while loading, on error, or if no incidents if (isLoading || isError || !incidents || incidents.length === 0) { diff --git a/apps/studio/components/interfaces/Support/SupportForm.utils.tsx b/apps/studio/components/interfaces/Support/SupportForm.utils.tsx index 853600d4d76..9889aad22ea 100644 --- a/apps/studio/components/interfaces/Support/SupportForm.utils.tsx +++ b/apps/studio/components/interfaces/Support/SupportForm.utils.tsx @@ -27,24 +27,23 @@ export const formatMessage = ({ message, attachments = [], error, - commit, - dashboardLogUrl, }: { message: string - attachments?: string[] + attachments?: Array error: string | null | undefined - commit: { commitSha: string; commitTime: string } | undefined - dashboardLogUrl?: string }) => { const errorString = error != null ? `\n\nError: ${error}` : '' const attachmentsString = attachments.length > 0 ? `\n\nAttachments:\n${attachments.join('\n')}` : '' - const commitString = - commit != undefined - ? `\n\n---\nSupabase Studio version: SHA ${commit.commitSha} deployed at ${commit.commitTime === 'unknown' ? 'unknown time' : dayjs(commit.commitTime).format('YYYY-MM-DD HH:mm:ss Z')}` - : '' - const logString = dashboardLogUrl ? `\nDashboard logs: ${dashboardLogUrl}` : '' - return `${message}${errorString}${attachmentsString}${commitString}${logString}` + return `${message}${errorString}${attachmentsString}` +} + +export const formatStudioVersion = (commit: { commitSha: string; commitTime: string }): string => { + const formattedTime = + commit.commitTime === 'unknown' + ? 'unknown time' + : dayjs(commit.commitTime).format('YYYY-MM-DD HH:mm:ss Z') + return `SHA ${commit.commitSha} deployed at ${formattedTime}` } export function getPageIcon(page: Page) { diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 91959f7be6e..38e928934a7 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -32,6 +32,7 @@ import type { SupportFormValues } from './SupportForm.schema' import type { SupportFormActions, SupportFormState } from './SupportForm.state' import { formatMessage, + formatStudioVersion, getOrgSubscriptionPlan, NO_ORG_MARKER, NO_PROJECT_MARKER, @@ -141,8 +142,6 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo message: values.message, attachments, error: initialError, - commit, - dashboardLogUrl: dashboardLogUrl?.[0], }), verified: true, tags: ['dashboard-support-form'], @@ -155,6 +154,8 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo .map((x) => x.trim().replace(/ /g, '_').toLowerCase()) .join(';'), browserInformation: detectBrowser(), + dashboardLogs: dashboardLogUrl?.[0], + dashboardStudioVersion: commit ? formatStudioVersion(commit) : undefined, } if (values.projectRef !== NO_PROJECT_MARKER) { diff --git a/apps/studio/components/interfaces/Support/__tests__/SupportFormPage.test.tsx b/apps/studio/components/interfaces/Support/__tests__/SupportFormPage.test.tsx index 95aafd1b5b4..c64257e678d 100644 --- a/apps/studio/components/interfaces/Support/__tests__/SupportFormPage.test.tsx +++ b/apps/studio/components/interfaces/Support/__tests__/SupportFormPage.test.tsx @@ -86,9 +86,7 @@ const { mockCommitSha, mockCommitTime, mockUseDeploymentCommitQuery } = vi.hoist } }) -const supportVersionInfo = `\n\n---\nSupabase Studio version: SHA ${mockCommitSha} deployed at ${dayjs( - mockCommitTime -).format('YYYY-MM-DD HH:mm:ss Z')}` +const mockStudioVersion = `SHA ${mockCommitSha} deployed at ${dayjs(mockCommitTime).format('YYYY-MM-DD HH:mm:ss Z')}` vi.mock('react-inlinesvg', () => ({ __esModule: true, @@ -810,10 +808,9 @@ describe('SupportFormPage', () => { siteUrl: 'https://project-1.example.com', additionalRedirectUrls: 'https://project-1.example.com/callbacks', browserInformation: 'Chrome', + dashboardStudioVersion: mockStudioVersion, }) - const expectedMessage = - 'Requests return status 500 when calling the RPC endpoint' + supportVersionInfo - expect(payload.message).toBe(expectedMessage) + expect(payload.message).toBe('Requests return status 500 when calling the RPC endpoint') await waitFor(() => { expect(screen.getByRole('heading', { name: /support request sent/i })).toBeInTheDocument() @@ -901,9 +898,9 @@ describe('SupportFormPage', () => { siteUrl: 'https://project-2.supabase.dev', additionalRedirectUrls: 'https://project-2.supabase.dev/redirect', browserInformation: 'Chrome', + dashboardStudioVersion: mockStudioVersion, }) - const expectedMessage = 'MFA challenge fails with an unknown error code' + supportVersionInfo - expect(payload.message).toBe(expectedMessage) + expect(payload.message).toBe('MFA challenge fails with an unknown error code') await waitFor(() => { expect(screen.getByRole('heading', { name: /support request sent/i })).toBeInTheDocument() @@ -1002,10 +999,10 @@ describe('SupportFormPage', () => { siteUrl: 'https://project-3.apps.supabase.co', additionalRedirectUrls: 'https://project-3.apps.supabase.co/auth', browserInformation: 'Chrome', + dashboardStudioVersion: mockStudioVersion, }) expect(payload.message).toBe( - 'Connections time out after 30 seconds\n\nError: Connection timeout detected' + - supportVersionInfo + 'Connections time out after 30 seconds\n\nError: Connection timeout detected' ) await waitFor(() => { @@ -1381,10 +1378,9 @@ describe('SupportFormPage', () => { }) const payload = submitSpy.mock.calls[0]?.[0] - expect(payload.message).toContain('Navigation menu does not respond after latest deploy') - expect(payload.message).toMatch( - /Dashboard logs: https:\/\/storage\.example\.com\/signed\/.+\.json/ - ) + expect(payload.message).toBe('Navigation menu does not respond after latest deploy') + expect(payload.dashboardLogs).toMatch(/^https:\/\/storage\.example\.com\/signed\/.+\.json$/) + expect(payload.dashboardStudioVersion).toBe(mockStudioVersion) }) test('shows toast on submission error and allows form re-editing and resubmission', async () => { @@ -1455,10 +1451,9 @@ describe('SupportFormPage', () => { const payload = submitSpy.mock.calls[0]?.[0] expect(payload.subject).toBe('Cannot access settings') - expect(payload.message).toMatch( - 'Settings page shows 500 error - updated description' + supportVersionInfo - ) - expect(payload.message).toMatch(/Dashboard logs: https:\/\/storage\.example\.com\/.+\.json/) + expect(payload.message).toBe('Settings page shows 500 error - updated description') + expect(payload.dashboardLogs).toMatch(/^https:\/\/storage\.example\.com\/signed\/.+\.json$/) + expect(payload.dashboardStudioVersion).toBe(mockStudioVersion) await waitFor(() => { expect(toastSuccessSpy).toHaveBeenCalledWith('Support request sent. Thank you!') @@ -1684,9 +1679,9 @@ describe('SupportFormPage', () => { verified: true, tags: ['dashboard-support-form'], browserInformation: 'Chrome', + dashboardStudioVersion: mockStudioVersion, }) - const expectedMessage = 'I need help accessing my Supabase account' + supportVersionInfo - expect(payload.message).toBe(expectedMessage) + expect(payload.message).toBe('I need help accessing my Supabase account') await waitFor(() => { expect(screen.getByRole('heading', { name: /support request sent/i })).toBeInTheDocument() diff --git a/apps/studio/data/feedback/support-ticket-send.ts b/apps/studio/data/feedback/support-ticket-send.ts index 91ce4b2f932..bebf3168f4b 100644 --- a/apps/studio/data/feedback/support-ticket-send.ts +++ b/apps/studio/data/feedback/support-ticket-send.ts @@ -20,6 +20,8 @@ export type sendSupportTicketVariables = { siteUrl?: string additionalRedirectUrls?: string dashboardSentryIssueId?: string + dashboardLogs?: string + dashboardStudioVersion?: string } export async function sendSupportTicket({ @@ -36,6 +38,8 @@ export async function sendSupportTicket({ siteUrl, additionalRedirectUrls, dashboardSentryIssueId, + dashboardLogs, + dashboardStudioVersion, }: sendSupportTicketVariables) { const { data, error } = await post('/platform/feedback/send', { body: { @@ -54,6 +58,8 @@ export async function sendSupportTicket({ browserInformation, allowSupportAccess, dashboardSentryIssueId, + dashboardLogs, + dashboardStudioVersion, }, })