mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix(studio): support form validation for client library (#42325)
Bug fix ## What is the current behavior? The client library selector validation was failing when the simplified support form was used, because the field was required but hidden. ## What is the new behavior? The client library selector is only required when: client library JSON flag is on + not using simplified support form + client libraries is chosen as the problem category. ## Summary by CodeRabbit * **Bug Fixes** * Improved support form validation logic and category eligibility for support access requests. * Refined conditional rendering of support access toggle based on selected category. * **New Features** * Added feature-flag-based library selection capability for support submissions. * **Refactor** * Streamlined support category options and removed obsolete entries. * Simplified form schema initialization and made library field optional where applicable.
This commit is contained in:
1 parent
29c5211864
commit
d1c6cbacbd
7 files changed
+73
-110
No files matched your search
@@ -1,21 +1,21 @@
|
||||
import type { UseFormReturn } from 'react-hook-form'
|
||||
// End of third-party imports
|
||||
|
||||
import { SupportCategories } from '@supabase/shared-types/out/constants'
|
||||
import { InlineLink } from 'components/ui/InlineLink'
|
||||
import type { UseFormReturn } from 'react-hook-form'
|
||||
import {
|
||||
cn,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectGroup_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
Select_Shadcn_,
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import {
|
||||
CATEGORY_OPTIONS,
|
||||
type ExtendedSupportCategories,
|
||||
@@ -91,7 +91,7 @@ function CategorySelector({ form }: CategorySelectorProps) {
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{CATEGORY_OPTIONS.filter((option) => !option.hidden).map((option) => (
|
||||
{CATEGORY_OPTIONS.map((option) => (
|
||||
<SelectItem_Shadcn_ key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
<span className="block text-xs text-foreground-lighter">
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useLinkSupportTicketMutation } from 'data/feedback/link-support-ticket-mutation'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useEffect } from 'react'
|
||||
import type { SubmitHandler } from 'react-hook-form'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { useLinkSupportTicketMutation } from 'data/feedback/link-support-ticket-mutation'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import {
|
||||
Button,
|
||||
DialogSectionSeparator,
|
||||
Form_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
Form_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
|
||||
import {
|
||||
LinkSupportTicketFormSchema,
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
} from './LinkSupportTicketForm.schema'
|
||||
import { OrganizationSelector } from './OrganizationSelector'
|
||||
import { ProjectAndPlanInfo } from './ProjectAndPlanInfo'
|
||||
import { SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
|
||||
import { getOrgSubscriptionPlan, NO_ORG_MARKER, NO_PROJECT_MARKER } from './SupportForm.utils'
|
||||
import { DISABLE_SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
|
||||
import { NO_ORG_MARKER, NO_PROJECT_MARKER, getOrgSubscriptionPlan } from './SupportForm.utils'
|
||||
|
||||
interface LinkSupportTicketFormProps {
|
||||
conversationId: string
|
||||
@@ -88,9 +88,10 @@ export const LinkSupportTicketForm = ({
|
||||
? values.projectRef
|
||||
: undefined,
|
||||
category: values.category,
|
||||
allow_support_access: SUPPORT_ACCESS_CATEGORIES.includes(values.category)
|
||||
? values.allowSupportAccess
|
||||
: false,
|
||||
allow_support_access:
|
||||
values.category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(values.category)
|
||||
? values.allowSupportAccess
|
||||
: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,7 +154,7 @@ export const LinkSupportTicketForm = ({
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
{SUPPORT_ACCESS_CATEGORIES.includes(category) && (
|
||||
{!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
|
||||
<>
|
||||
<div className="py-4">
|
||||
<SupportAccessToggle form={form as any} />
|
||||
|
||||
@@ -10,7 +10,6 @@ export const CATEGORY_OPTIONS: {
|
||||
label: string
|
||||
description: string
|
||||
query?: string
|
||||
hidden?: boolean
|
||||
}[] = [
|
||||
{
|
||||
value: SupportCategories.PROBLEM,
|
||||
@@ -78,13 +77,6 @@ export const CATEGORY_OPTIONS: {
|
||||
query: undefined,
|
||||
},
|
||||
]),
|
||||
{
|
||||
value: 'Others' as const,
|
||||
label: 'Others',
|
||||
description: 'Issues that are not related to any of the other categories',
|
||||
query: undefined,
|
||||
hidden: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const SEVERITY_OPTIONS = [
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import type { UseFormReturn } from 'react-hook-form'
|
||||
// End of third-party imports
|
||||
|
||||
import { SupportCategories } from '@supabase/shared-types/out/constants'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import type { UseFormReturn } from 'react-hook-form'
|
||||
import {
|
||||
Badge,
|
||||
Collapsible_Shadcn_,
|
||||
CollapsibleContent_Shadcn_,
|
||||
CollapsibleTrigger_Shadcn_,
|
||||
Collapsible_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
Switch,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import type { ExtendedSupportCategories } from './Support.constants'
|
||||
import type { SupportFormValues } from './SupportForm.schema'
|
||||
|
||||
export const SUPPORT_ACCESS_CATEGORIES: ExtendedSupportCategories[] = [
|
||||
SupportCategories.DATABASE_UNRESPONSIVE,
|
||||
SupportCategories.PERFORMANCE_ISSUES,
|
||||
SupportCategories.PROBLEM,
|
||||
SupportCategories.DASHBOARD_BUG,
|
||||
export const DISABLE_SUPPORT_ACCESS_CATEGORIES: ExtendedSupportCategories[] = [
|
||||
SupportCategories.ACCOUNT_DELETION,
|
||||
SupportCategories.SALES_ENQUIRY,
|
||||
SupportCategories.REFUND,
|
||||
]
|
||||
|
||||
interface SupportAccessToggleProps {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { PLAN_REQUEST_EMPTY_PLACEHOLDER } from 'components/ui/UpgradePlanButton'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { isFeatureEnabled } from 'common'
|
||||
import { PLAN_REQUEST_EMPTY_PLACEHOLDER } from 'components/ui/UpgradePlanButton'
|
||||
import { CATEGORY_OPTIONS, type ExtendedSupportCategories } from './Support.constants'
|
||||
|
||||
const createFormSchema = (showClientLibraries: boolean) => {
|
||||
const baseSchema = z.object({
|
||||
export const SupportFormSchema = z
|
||||
.object({
|
||||
organizationSlug: z.string().min(1, 'Please select an organization'),
|
||||
projectRef: z.string().min(1, 'Please select a project'),
|
||||
category: z.enum(
|
||||
@@ -15,7 +14,7 @@ const createFormSchema = (showClientLibraries: boolean) => {
|
||||
]
|
||||
),
|
||||
severity: z.string(),
|
||||
library: z.string(),
|
||||
library: z.string().optional(),
|
||||
subject: z.string().min(1, 'Please add a subject heading'),
|
||||
message: z.string().min(1, "Please add a message about the issue that you're facing"),
|
||||
affectedServices: z.string(),
|
||||
@@ -23,45 +22,14 @@ const createFormSchema = (showClientLibraries: boolean) => {
|
||||
attachDashboardLogs: z.boolean(),
|
||||
dashboardSentryIssueId: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
return !data.message.includes(PLAN_REQUEST_EMPTY_PLACEHOLDER)
|
||||
},
|
||||
{
|
||||
message: `Please let us know which plan you'd like to upgrade to for your organization`,
|
||||
path: ['message'],
|
||||
}
|
||||
)
|
||||
|
||||
if (showClientLibraries) {
|
||||
return baseSchema
|
||||
.refine(
|
||||
(data) => {
|
||||
return !(data.category === 'Problem' && data.library === '')
|
||||
},
|
||||
{
|
||||
message: "Please select the library that you're facing issues with",
|
||||
path: ['library'],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
return !data.message.includes(PLAN_REQUEST_EMPTY_PLACEHOLDER)
|
||||
},
|
||||
{
|
||||
message: `Please let us know which plan you'd like to upgrade to for your organization`,
|
||||
path: ['message'],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// When showClientLibraries is false, make library optional and remove the refine validation
|
||||
return baseSchema
|
||||
.extend({
|
||||
library: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
return !data.message.includes(PLAN_REQUEST_EMPTY_PLACEHOLDER)
|
||||
},
|
||||
{
|
||||
message: `Please let us know which plan you'd like to upgrade to for your organization`,
|
||||
path: ['message'],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const showClientLibraries = isFeatureEnabled('support:show_client_libraries')
|
||||
export const SupportFormSchema = createFormSchema(showClientLibraries)
|
||||
export type SupportFormValues = z.infer<typeof SupportFormSchema>
|
||||
@@ -1,7 +1,4 @@
|
||||
import { useEffect, type Dispatch, type MouseEventHandler } from 'react'
|
||||
import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
|
||||
// End of third-party imports
|
||||
|
||||
import { SupportCategories } from '@supabase/shared-types/out/constants'
|
||||
import { useConstant, useFlag } from 'common'
|
||||
import { CLIENT_LIBRARIES } from 'common/constants'
|
||||
@@ -11,9 +8,13 @@ import { type OrganizationPlanID } from 'data/organizations/organization-query'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useGenerateAttachmentURLsMutation } from 'data/support/generate-attachment-urls-mutation'
|
||||
import { useDeploymentCommitQuery } from 'data/utils/deployment-commit-query'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { detectBrowser } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { type Dispatch, type MouseEventHandler } from 'react'
|
||||
import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
|
||||
import { DialogSectionSeparator, Form_Shadcn_ } from 'ui'
|
||||
|
||||
import {
|
||||
AffectedServicesSelector,
|
||||
CATEGORIES_WITHOUT_AFFECTED_SERVICES,
|
||||
@@ -27,15 +28,15 @@ import { OrganizationSelector } from './OrganizationSelector'
|
||||
import { ProjectAndPlanInfo } from './ProjectAndPlanInfo'
|
||||
import { SubjectAndSuggestionsInfo } from './SubjectAndSuggestionsInfo'
|
||||
import { SubmitButton } from './SubmitButton'
|
||||
import { SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
|
||||
import { DISABLE_SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
|
||||
import type { SupportFormValues } from './SupportForm.schema'
|
||||
import type { SupportFormActions, SupportFormState } from './SupportForm.state'
|
||||
import {
|
||||
NO_ORG_MARKER,
|
||||
NO_PROJECT_MARKER,
|
||||
formatMessage,
|
||||
formatStudioVersion,
|
||||
getOrgSubscriptionPlan,
|
||||
NO_ORG_MARKER,
|
||||
NO_PROJECT_MARKER,
|
||||
} from './SupportForm.utils'
|
||||
import {
|
||||
DASHBOARD_LOG_CATEGORIES,
|
||||
@@ -77,6 +78,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
const { data: organizations } = useOrganizationsQuery()
|
||||
const subscriptionPlanId = getOrgSubscriptionPlan(organizations, selectedOrgSlug)
|
||||
const simplifiedSupportForm = useIsSimplifiedForm(organizationSlug, subscriptionPlanId)
|
||||
const showClientLibraries = useIsFeatureEnabled('support:show_client_libraries')
|
||||
|
||||
const attachmentUpload = useAttachmentUpload()
|
||||
const { mutateAsync: uploadDashboardLogFn } = useGenerateAttachmentURLsMutation()
|
||||
@@ -105,6 +107,21 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
})
|
||||
|
||||
const onSubmit: SubmitHandler<SupportFormValues> = async (formValues) => {
|
||||
// Library is required when selecting "APIs and Client Libraries" category,
|
||||
// but only when the library selector is visible (not in simplified form)
|
||||
if (
|
||||
!simplifiedSupportForm &&
|
||||
showClientLibraries &&
|
||||
formValues.category === SupportCategories.PROBLEM &&
|
||||
!formValues.library
|
||||
) {
|
||||
form.setError('library', {
|
||||
type: 'manual',
|
||||
message: "Please select the library that you're facing issues with",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
dispatch({ type: 'SUBMIT' })
|
||||
|
||||
const { attachDashboardLogs: formAttachDashboardLogs, ...values } = formValues
|
||||
@@ -128,12 +145,12 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
|
||||
const payload = {
|
||||
...values,
|
||||
category,
|
||||
organizationSlug: values.organizationSlug ?? NO_ORG_MARKER,
|
||||
projectRef: values.projectRef ?? NO_PROJECT_MARKER,
|
||||
allowSupportAccess: SUPPORT_ACCESS_CATEGORIES.includes(values.category)
|
||||
? values.allowSupportAccess
|
||||
: false,
|
||||
allowSupportAccess:
|
||||
values.category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(values.category)
|
||||
? values.allowSupportAccess
|
||||
: false,
|
||||
library:
|
||||
values.category === SupportCategories.PROBLEM && selectedLibrary !== undefined
|
||||
? selectedLibrary.key
|
||||
@@ -179,15 +196,6 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
handleFormSubmit(event)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (simplifiedSupportForm) {
|
||||
form.setValue('category', 'Others')
|
||||
} else {
|
||||
form.setValue('category', '' as any)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [simplifiedSupportForm])
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form id="support-form" className="flex flex-col gap-y-6">
|
||||
@@ -202,14 +210,12 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
subscriptionPlanId={subscriptionPlanId}
|
||||
category={category}
|
||||
/>
|
||||
{!simplifiedSupportForm && (
|
||||
<CategoryAndSeverityInfo
|
||||
form={form}
|
||||
category={category}
|
||||
severity={severity}
|
||||
projectRef={projectRef}
|
||||
/>
|
||||
)}
|
||||
<CategoryAndSeverityInfo
|
||||
form={form}
|
||||
category={category}
|
||||
severity={severity}
|
||||
projectRef={projectRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
@@ -235,7 +241,7 @@ export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFo
|
||||
</>
|
||||
)}
|
||||
|
||||
{SUPPORT_ACCESS_CATEGORIES.includes(category) && (
|
||||
{!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
|
||||
<>
|
||||
<SupportAccessToggle form={form} />
|
||||
<DialogSectionSeparator />
|
||||
|
||||
@@ -891,10 +891,6 @@ describe('SupportFormPage', () => {
|
||||
await userEvent.clear(messageField)
|
||||
await userEvent.type(messageField, 'MFA challenge fails with an unknown error code')
|
||||
|
||||
expect(
|
||||
screen.queryByRole('switch', { name: /allow support access to your project/i })
|
||||
).toBeNull()
|
||||
|
||||
await userEvent.click(getSubmitButton(screen))
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -910,7 +906,7 @@ describe('SupportFormPage', () => {
|
||||
organizationSlug: 'org-2',
|
||||
library: '',
|
||||
affectedServices: '',
|
||||
allowSupportAccess: false,
|
||||
allowSupportAccess: true,
|
||||
verified: true,
|
||||
tags: ['dashboard-support-form'],
|
||||
siteUrl: 'https://project-2.supabase.dev',
|
||||
|
||||
Reference in new issue
Block a user