Files
supabase/apps/studio/components/interfaces/ProjectCreation/SecurityOptions.tsx
Sean Oliver fdceb29260 fix(telemetry): exposure event captures dataApiDefaultPrivileges + drop race-fix hook (#46085)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix.

## What is the current behavior?

This PR addresses three issues with the implementation of the
`dataApiRevokeOnCreateDefault` experiment
([GROWTH-858](https://linear.app/supabase/issue/GROWTH-858)) on the
frontend side:

1. The `project_creation_default_privileges_exposed` event payload
captures `dataApiEnabled`, which is the parent "Enable Data API" toggle.
That value defaults to `true` for everyone in both arms, which doesn't
help understand how people are interacting with the form.

2. The hook itself was gating on PostHog JS SDK values rather than our
backend server values being sent from `/telemetry/feature-flags`.

3. The form on `/new/[slug]` captures `dataApiDefaultPrivileges`
defaults once at mount via react-hook-form's `defaultValues`. If the
flag is still loading when the page mounts,
`useDataApiRevokeOnCreateDefaultEnabled()` returns `false` (coerced from
undefined), and the form locks the field to the legacy default of
`true`. The flag later resolving has no effect, and treatment users get
the legacy default visually and in the exposure event.

## What is the new behavior?

1. Main-surface payload now sends `dataApiDefaultPrivileges` — the form
field the experiment actually controls (`true` = legacy grants kept,
`false` = revoked on create). Post-fix data will let us read out whether
treatment users actually got the new default.

2. Hook is simplified: drop `orgCountReady`, drop the `onFeatureFlags`
subscription, drop the `posthogClient` import. It now fires once when
the flag resolves, period. Vercel surface is unchanged (still no
`dataApiDefaultPrivileges` since there's no user-facing toggle there).
Tests updated.

3. New useEffect in `/new/[slug]` watches the raw flag value and syncs
`dataApiDefaultPrivileges` to the correct experiment-driven default when
the flag resolves, gated on `getFieldState(...).isDirty` so we don't
clobber intentional user input.

## Additional context

Backend half of this fix is at supabase/platform#32933 (passes
`org_count` and `signup_timestamp` in `personProperties` so the audience
filter actually evaluates correctly). Both PRs are needed for the
experiment to bucket at 5% and be measurable.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Changes**
* Telemetry now records the selected default-privileges setting
(dataApiDefaultPrivileges) in project-creation events; the previous
dataApiEnabled field was removed.
* Project-creation flows apply the experiment-driven default for that
setting once the experiment resolves, but they do not overwrite
user-edited choices. Vercel new-project flow syncs with the experiment
until the user changes the checkbox.

* **Tests**
* Updated tests to validate tracking, deduplication, and sync/timing
behaviors for dataApiDefaultPrivileges.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46085?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-19 12:54:36 -07:00

158 lines
5.7 KiB
TypeScript

import { UseFormReturn } from 'react-hook-form'
import {
Checkbox,
cn,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
Tooltip,
TooltipContent,
TooltipTrigger,
useWatch,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import { InlineLink } from '@/components/ui/InlineLink'
import Panel from '@/components/ui/Panel'
import { useTrackDefaultPrivilegesExposure } from '@/hooks/misc/useDataApiRevokeOnCreateDefault'
import { DOCS_URL } from '@/lib/constants'
interface SecurityOptionsProps {
form: UseFormReturn<CreateProjectForm>
layout?: 'vertical' | 'horizontal'
}
export const SecurityOptions = ({ form, layout = 'horizontal' }: SecurityOptionsProps) => {
const dataApi = useWatch({ control: form.control, name: 'dataApi' })
const dataApiDefaultPrivileges = useWatch({
control: form.control,
name: 'dataApiDefaultPrivileges',
})
const hasUserModified = form.getFieldState('dataApiDefaultPrivileges', form.formState).isDirty
useTrackDefaultPrivilegesExposure({
surface: 'main',
dataApiDefaultPrivileges: dataApiDefaultPrivileges ?? true,
hasUserModified,
})
return (
<Panel.Content className="pb-8">
<FormItemLayout layout={layout} label="Security" isReactForm={false}>
<div className="flex flex-col gap-4">
<FormField
name="dataApi"
control={form.control}
render={({ field }) => (
<FormItem className="flex items-start gap-3">
<FormControl>
<Checkbox
checked={field.value}
disabled={field.disabled}
onCheckedChange={(value) => field.onChange(value === true)}
/>
</FormControl>
<div className="space-y-1">
<FormLabel className="text-sm text-foreground">Enable Data API</FormLabel>
<FormDescription className="text-foreground-lighter">
Autogenerate a RESTful API for your public schema. Recommended if using a client
library like{' '}
<InlineLink href={`${DOCS_URL}/reference/javascript/introduction`}>
supabase-js
</InlineLink>
.
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
name="dataApiDefaultPrivileges"
control={form.control}
render={({ field }) => (
<FormItem
className={cn(
'flex items-start gap-3',
!dataApi && 'opacity-50 cursor-not-allowed'
)}
>
<FormControl>
{dataApi ? (
<Checkbox
checked={field.value}
disabled={field.disabled}
onCheckedChange={(value) => field.onChange(value === true)}
/>
) : (
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-not-allowed">
<Checkbox checked={field.value} disabled />
</span>
</TooltipTrigger>
<TooltipContent side="top">
Enable the Data API to configure default privileges.
</TooltipContent>
</Tooltip>
)}
</FormControl>
<div className="space-y-1">
<FormLabel
className={cn('text-sm text-foreground', !dataApi && 'text-foreground-muted')}
>
Automatically expose new tables
</FormLabel>
<FormDescription className="text-foreground-lighter">
Grants privileges to Data API roles by default, exposing new tables.
<br />
<strong className="font-medium text-foreground-light">
We recommend disabling this to control access manually.
</strong>
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
name="enableRlsEventTrigger"
control={form.control}
render={({ field }) => (
<FormItem className="flex items-start gap-3">
<FormControl>
<Checkbox
checked={field.value}
disabled={field.disabled}
onCheckedChange={(value) => field.onChange(value === true)}
/>
</FormControl>
<div className="space-y-1">
<FormLabel className="text-sm text-foreground">Enable automatic RLS</FormLabel>
<FormDescription className="text-foreground-lighter">
Create an event trigger that automatically enables Row Level Security on all new
tables in the public schema.
</FormDescription>
</div>
</FormItem>
)}
/>
{!dataApi && (
<Admonition
type="warning"
title="Client libraries need Data API to query your database"
>
Disabling it means supabase-js and similar libraries can't query or mutate data.
</Admonition>
)}
</div>
</FormItemLayout>
</Panel.Content>
)
}