chore(studio): remove dead code found by knip (#49719)

Removes Studio code that nothing imports, as reported by knip. First PR
in a stack of three: this one is pure deletions, #49720 removes the
unused dependencies, #49721 upgrades knip and adds the CI gate so this
doesn't accumulate again.

Every file was verified with a repo-wide grep for its basename, exported
symbols, and string/dynamic imports before deletion — none are reachable
via `next/dynamic`, a barrel file, or a config.

**Removed:**
-
`Billing/Usage/UsageWarningAlerts/{CPU,RAM,DiskIOBandwidth}Warnings.tsx`
(whole directory)
- `DataWarehouse/FormFooterChangeBadge.tsx` (whole directory)
- `Database/Replication/ReplicationDiagram/EmptyReplicationDiagram.tsx`
- `Integrations/Vercel/OrganizationPicker.tsx`
- `QueryInsights/QueryInsightsTable/QueryInsightsTableRow.tsx`
- `hooks/misc/useTrackExperimentExposure.ts`
- `data/ai/{parse-client-code,sql-policy}-mutation.ts`,
`data/misc/parse-query-mutation.ts`,
`data/database/table-check-rls-mutation.ts`
-
`data/notifications/notifications-v2-{archive-all-mutation,summary-query}.ts`
+ their two now-unused keys in `notifications/keys.ts` (`listV2` kept)
-
`data/platform-apps/platform-app-{update,signing-key-delete}-mutation.ts`
- `DateTimeFormats.DATE_ONLY` and the unused
`Notebooks.{MarkdownCell,LogCell,ChartConfig}` types

**Changed:**
- `ReportPadding` no longer has a duplicate default export; its 9
default importers (observability pages) now use the named export

Not removed: `CONSTRAINT_TYPE`'s unused members mirror the closed set of
`pg_constraint.contype` values, so they're documentation rather than
dead code — suppressed narrowly in #49721's knip config instead.

## To test

- `pnpm --filter studio run typecheck` and `lint:ratchet` pass
- Observability pages (`/project/[ref]/observability/*`) still render
with padding — they're the only code touched, via the `ReportPadding`
import change
- Notifications popover still loads and marks-as-read (the removed keys
weren't used for invalidation)


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

## Summary by CodeRabbit

- **Removed Features**
  - Removed CPU, memory, and disk usage warning alerts.
- Removed the Vercel organization picker and empty replication diagram.
  - Removed query insights row actions and several SQL assistance tools.
  - Removed notification summary and archive-all capabilities.
  - Removed platform app update and signing-key deletion actions.
- Removed the form change-count badge and experiment exposure tracking.

- **Refactor**
- Updated observability reports to use the revised report layout export.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-08-31 10:55:23 +08:00
1 parent 8439b0c77e
commit b0e31be89a
29 files changed
+9 -1169

No files matched your search

@@ -1,68 +0,0 @@
import { AlertTitle } from '@ui/components/shadcn/ui/alert'
import { AlertCircle } from 'lucide-react'
import Link from 'next/link'
import { Alert, AlertDescription, Button } from 'ui'
import { DOCS_URL } from '@/lib/constants'
interface CPUWarningsProps {
hasAccessToComputeSizes: boolean
upgradeUrl: string
severity?: 'warning' | 'critical' | null
}
export const CPUWarnings = ({
hasAccessToComputeSizes,
upgradeUrl,
severity,
}: CPUWarningsProps) => {
if (severity === 'warning') {
return (
<Alert variant="warning">
<AlertCircle />
<AlertTitle>Your max CPU usage has exceeded 80%</AlertTitle>
<AlertDescription>
High CPU usage could result in slower queries, disruption of daily back up routines, and
in rare cases, your instance may become unresponsive. If you need more resources, consider
upgrading to a larger compute add-on.
</AlertDescription>
<div className="mt-3 flex items-center space-x-2">
<Button asChild variant="default">
<Link href={`${DOCS_URL}/guides/troubleshooting/high-cpu-usage`}>Learn more</Link>
</Button>
<Button asChild variant="warning">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</div>
</Alert>
)
}
if (severity === 'critical') {
return (
<Alert variant="destructive">
<AlertCircle />
<AlertTitle>Your max CPU usage has reached 100%</AlertTitle>
<AlertDescription>
High CPU usage could result in slower queries, disruption of daily back up routines, and
in rare cases, your instance may become unresponsive. If you need more resources, consider
upgrading to a larger compute add-on.
</AlertDescription>
<div className="mt-3 flex items-center space-x-2">
<Button asChild variant="default">
<Link href={`${DOCS_URL}/guides/troubleshooting/high-cpu-usage`}>Learn more</Link>
</Button>
<Button asChild variant="danger">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</div>
</Alert>
)
}
return null
}
@@ -1,118 +0,0 @@
import Link from 'next/link'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
// [Joshen] In the future, conditionals should be from resource exhaustion endpoint as single source of truth
interface DiskIOBandwidthWarningsProps {
hasAccessToComputeSizes: boolean
hasLatest: boolean
upgradeUrl: string
currentBillingCycleSelected: boolean
latestIoBudgetConsumption: number
highestIoBudgetConsumption: number
}
export const DiskIOBandwidthWarnings = ({
hasAccessToComputeSizes,
hasLatest,
currentBillingCycleSelected,
upgradeUrl,
latestIoBudgetConsumption,
highestIoBudgetConsumption,
}: DiskIOBandwidthWarningsProps) => {
if (hasLatest && latestIoBudgetConsumption >= 100) {
return (
<Admonition
type="destructive"
title="Your Disk IO Budget has been used up"
description={
<>
<p className="mb-4">
Your workload has used up all your Disk IO Budget and is now running at the baseline
performance. If you need consistent disk performance, consider upgrading to a larger
compute add-on.
</p>
<Button asChild variant="danger">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</>
}
/>
)
}
if (hasLatest && latestIoBudgetConsumption >= 80) {
return (
<Admonition
type="destructive"
title="You are close to running out of Disk IO Budget"
description={
<>
<p className="mb-4">
Your workload has consumed {latestIoBudgetConsumption}% of your Disk IO Budget. If you
use up all your Disk IO Budget, your instance will reverted to baseline performance.
If you need consistent disk performance, consider upgrading to a larger compute
add-on.
</p>
<Button asChild variant="danger">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</>
}
/>
)
}
if (currentBillingCycleSelected && highestIoBudgetConsumption >= 100) {
return (
<Admonition
type="warning"
title="You ran out of IO Budget at least once"
description={
<>
<p className="mb-4">
Your workload has used up all your Disk IO Budget and reverted to baseline performance
at least once during this billing cycle. If you need consistent disk performance,
consider upgrading to a larger compute add-on.
</p>
<Button asChild variant="warning">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</>
}
/>
)
}
if (currentBillingCycleSelected && highestIoBudgetConsumption >= 80) {
return (
<Admonition
type="warning"
title="You were close to using all your IO Budget at least once"
description={
<>
<p className="mb-4">
Your workload has consumed {highestIoBudgetConsumption}% of your Disk IO budget during
this billing cycle. If you use up all your Disk IO Budget, your instance will reverted
to baseline performance. If you need consistent disk performance, consider upgrading
to a larger compute add-on.
</p>
<Button asChild variant="warning">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</>
}
/>
)
}
return null
}
@@ -1,68 +0,0 @@
import { AlertTitle } from '@ui/components/shadcn/ui/alert'
import { AlertCircle } from 'lucide-react'
import Link from 'next/link'
import { Alert, AlertDescription, Button } from 'ui'
import { DOCS_URL } from '@/lib/constants'
interface RAMWarningsProps {
hasAccessToComputeSizes: boolean
upgradeUrl: string
severity?: 'warning' | 'critical' | null
}
export const RAMWarnings = ({
hasAccessToComputeSizes,
upgradeUrl,
severity,
}: RAMWarningsProps) => {
if (severity === 'warning') {
return (
<Alert variant="warning">
<AlertCircle />
<AlertTitle>Your memory usage has exceeded 80%</AlertTitle>
<AlertDescription>
High memory usage could result in overall degraded performance, and in rare cases, your
instance may become unresponsive. If you need more resources, consider upgrading to a
larger compute add-on.
</AlertDescription>
<div className="mt-3 flex items-center space-x-2">
<Button asChild variant="default">
<Link href={`${DOCS_URL}/guides/troubleshooting/exhaust-ram`}>Learn more</Link>
</Button>
<Button asChild variant="warning">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</div>
</Alert>
)
}
if (severity === 'critical') {
return (
<Alert variant="destructive">
<AlertCircle />
<AlertTitle>Your memory usage has reached 100%</AlertTitle>
<AlertDescription>
High memory usage could result in overall degraded performance, and in rare cases, your
instance may become unresponsive. If you need more resources, consider upgrading to a
larger compute add-on.
</AlertDescription>
<div className="mt-3 flex items-center space-x-2">
<Button asChild variant="default">
<Link href={`${DOCS_URL}/guides/troubleshooting/high-cpu-usage`}>Learn more</Link>
</Button>
<Button asChild variant="danger">
<Link href={upgradeUrl}>
{hasAccessToComputeSizes ? 'Change compute add-on' : 'Upgrade project'}
</Link>
</Button>
</div>
</Alert>
)
}
return null
}
@@ -1,42 +0,0 @@
import { AnimatePresence, motion } from 'framer-motion'
interface FormFooterChangeBadgeProps {
formState: {
dirtyFields: Record<string, boolean>
}
}
export const FormFooterChangeBadge = ({ formState }: FormFooterChangeBadgeProps) => {
return (
<AnimatePresence mode="wait">
{Object.keys(formState.dirtyFields).length > 0 && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.2 }}
>
<motion.div
animate={{
scale: [1, 1.05, 1],
transition: { duration: 0.3 },
}}
>
<p className="text-sm text-foreground-lighter">
<motion.span
key={Object.keys(formState.dirtyFields).length}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.1 }}
>
{Object.keys(formState.dirtyFields).length === 1
? '1 change to review'
: `${Object.keys(formState.dirtyFields).length} changes to review`}
</motion.span>
</p>
</motion.div>
</motion.div>
)}
</AnimatePresence>
)
}
@@ -1,56 +0,0 @@
import { Background, ColorMode, ReactFlow, ReactFlowProvider } from '@xyflow/react'
import { useTheme } from 'next-themes'
import { PrimaryDatabaseNode, ReplicationNode } from './Nodes'
import '@xyflow/react/dist/style.css'
import { SmoothstepEdge } from './Edges'
export const EmptyReplicationDiagram = () => {
return (
<ReactFlowProvider>
<ReplicationDiagramContent />
</ReactFlowProvider>
)
}
const nodeTypes = {
primary: PrimaryDatabaseNode,
replication: ReplicationNode,
}
const edgeTypes = { smoothstep: SmoothstepEdge }
const ReplicationDiagramContent = () => {
const { resolvedTheme } = useTheme()
const backgroundPatternColor =
resolvedTheme === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.4)'
return (
<div className="nowheel relative h-[350px] w-full border border-muted rounded-md overflow-hidden">
<ReactFlow
// FIXME: https://github.com/xyflow/xyflow/issues/4876
colorMode={'' as unknown as ColorMode}
fitView
fitViewOptions={{ minZoom: 0.8, maxZoom: 0.9 }}
className="bg"
zoomOnPinch={false}
zoomOnScroll={false}
nodesDraggable={false}
nodesConnectable={false}
zoomOnDoubleClick={false}
edgesFocusable={false}
edgesReconnectable={false}
defaultNodes={[]}
defaultEdges={[]}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
proOptions={{ hideAttribution: true }}
>
<Background color={backgroundPatternColor} />
</ReactFlow>
</div>
)
}
@@ -1,127 +0,0 @@
import { ChevronDown } from 'lucide-react'
import { useMemo, useRef, useState } from 'react'
import {
Badge,
Button,
cn,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { getHasInstalledObject } from '@/components/layouts/IntegrationsLayout/Integrations.utils'
import PartnerIcon from '@/components/ui/PartnerIcon'
import { useIntegrationsQuery } from '@/data/integrations/integrations-query'
import type { IntegrationName } from '@/data/integrations/integrations.types'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import type { Organization } from '@/types'
export interface OrganizationPickerProps {
integrationName: IntegrationName
configurationId?: string
selectedOrg: Organization | null
onSelectedOrgChange: (organization: Organization) => void
disabled?: boolean
}
const OrganizationPicker = ({
integrationName,
configurationId,
selectedOrg,
onSelectedOrgChange,
disabled,
}: OrganizationPickerProps) => {
const [open, setOpen] = useState(false)
const ref = useRef<HTMLButtonElement>(null)
const { data: integrationData } = useIntegrationsQuery()
const { data: organizationsData, isPending: isLoadingOrganization } = useOrganizationsQuery()
const installed = useMemo(
() =>
integrationData && organizationsData
? getHasInstalledObject({
integrationName,
integrationData,
organizationsData,
installationId: configurationId,
})
: {},
[configurationId, integrationData, integrationName, organizationsData]
)
return (
<>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
ref={ref}
variant="default"
size="medium"
block
className="justify-start"
loading={isLoadingOrganization}
disabled={disabled}
iconRight={
<span className="grow flex justify-end">
<ChevronDown />
</span>
}
>
<div className="flex gap-2">
<span className={cn('truncate', !selectedOrg && 'text-foreground-light')}>
{selectedOrg?.name ? selectedOrg?.name : 'Choose an organization'}
</span>
{selectedOrg && configurationId && installed[selectedOrg.slug] && (
<Badge>Integration Installed</Badge>
)}
</div>
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-full" side="bottom" align="center" sameWidthAsTrigger>
<Command>
<CommandInput placeholder="Search organizations..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{organizationsData?.map((org) => {
return (
<CommandItem
value={org.slug}
key={org.slug}
className="flex gap-2 items-center"
onSelect={(slug) => {
const org = organizationsData?.find(
(org) => org.slug.toLowerCase() === slug.toLowerCase()
)
if (org) {
onSelectedOrgChange(org)
}
setOpen(false)
}}
>
<PartnerIcon organization={org} />
<span className="truncate">{org.name}</span>{' '}
{configurationId && installed[org.slug] && (
<Badge className="flex-none!">Integration Installed</Badge>
)}
</CommandItem>
)
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</>
)
}
export default OrganizationPicker
@@ -14,7 +14,7 @@ import { ObservabilityOverviewFooter } from './ObservabilityOverviewFooter'
import { ServiceHealthTable } from './ServiceHealthTable'
import { useSlowQueriesCount } from './useSlowQueriesCount'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import {
buildUnifiedLogsUrl,
type UnifiedLogType,
@@ -1,183 +0,0 @@
import { Loader2 } from 'lucide-react'
import { AiIconAnimation, Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import type { ClassifiedQuery } from '../QueryInsightsHealth/QueryInsightsHealth.types'
import { ISSUE_DOT_COLORS, ISSUE_ICONS } from './QueryInsightsTable.constants'
import { formatDuration, getColumnName, getTableName } from './QueryInsightsTable.utils'
interface QueryInsightsTableRowProps {
item: ClassifiedQuery
onRowClick?: () => void
onGoToLogs?: () => void
onCreateIndex?: () => void
onExplain?: () => void
onAiSuggestedFix?: () => void
isExplainLoading?: boolean
}
export const QueryInsightsTableRow = ({
item,
onRowClick,
onGoToLogs,
onCreateIndex,
onExplain,
onAiSuggestedFix,
isExplainLoading,
}: QueryInsightsTableRowProps) => {
const IssueIcon = item.issueType ? ISSUE_ICONS[item.issueType] : null
return (
<div
className="flex items-center gap-4 px-6 py-4 border-b hover:bg-surface-100 cursor-pointer group"
onClick={onRowClick}
>
{item.issueType && IssueIcon && (
<div
className={cn(
'h-6 w-6 rounded-full shrink-0 border flex items-center justify-center',
ISSUE_DOT_COLORS[item.issueType]?.border,
ISSUE_DOT_COLORS[item.issueType]?.background
)}
>
<IssueIcon size={14} className={ISSUE_DOT_COLORS[item.issueType].color} />
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-xs font-mono text-foreground line-clamp-1">
<span className="text-foreground">{item.queryType ?? '–'}</span>
{getTableName(item.query) && (
<>
{' '}
<span className="text-foreground-lighter">in</span> {getTableName(item.query)}
</>
)}
{getColumnName(item.query) && (
<>
<span className="text-foreground-lighter">,</span> {getColumnName(item.query)}
</>
)}
</p>
<p
className={cn(
'text-xs mt-0.5 font-mono line-clamp-1',
item.issueType === 'error' && 'text-destructive-600',
item.issueType === 'index' && 'text-warning-600',
item.issueType === 'slow' && 'text-foreground-lighter'
)}
>
{item.hint}
</p>
</div>
<div className="flex items-stretch divide-x divide-border shrink-0 tabular-nums">
<Tooltip>
<TooltipTrigger asChild>
<div className="flex flex-col items-end pr-4 cursor-default">
<span
className={cn(
'text-sm font-mono leading-snug',
item.mean_time >= 1000 && 'text-destructive-600'
)}
>
{formatDuration(item.mean_time)}
</span>
<span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
avg
</span>
</div>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-[220px] text-center">
Average execution time per call. High mean time means individual runs are slow —
directly felt by users.
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex flex-col items-end px-4 cursor-default">
<span className="text-sm font-mono leading-snug">
{item.prop_total_time.toFixed(1)}%
</span>
<span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
of db
</span>
</div>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-[220px] text-center">
Percentage of total database execution time. Fixing high-impact queries has the biggest
overall effect on your database.
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex flex-col items-end pl-4 cursor-default">
<span className="text-sm font-mono leading-snug">{item.calls.toLocaleString()}</span>
<span className="text-[10px] text-foreground-muted uppercase tracking-wide leading-snug">
calls
</span>
</div>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-[220px] text-center">
Number of times this query ran in the selected time window.
</TooltipContent>
</Tooltip>
</div>
<div className="flex items-center gap-2 shrink-0 justify-end w-[260px]">
<Button
variant="default"
size="tiny"
onClick={(e) => {
e.stopPropagation()
onGoToLogs?.()
}}
>
Go to Logs
</Button>
{(item.issueType === 'index' || item.issueType === 'slow') && (
<Button
variant="default"
size="tiny"
icon={isExplainLoading ? <Loader2 size={12} className="animate-spin" /> : undefined}
onClick={(e) => {
e.stopPropagation()
onExplain?.()
}}
>
Explain
</Button>
)}
{item.issueType === 'index' && (
<Button
variant="primary"
size="tiny"
onClick={(e) => {
e.stopPropagation()
onCreateIndex?.()
}}
>
Create Index
</Button>
)}
{(item.issueType === 'error' || item.issueType === 'slow') && (
<Button
variant="default"
size="tiny"
icon={<AiIconAnimation size={14} />}
onClick={(e) => {
e.stopPropagation()
onAiSuggestedFix?.()
}}
>
Fix with AI
</Button>
)}
</div>
</div>
)
}
@@ -19,4 +19,3 @@ export const ReportPadding = ({
</div>
)
}
export default ReportPadding
@@ -102,5 +102,4 @@ export const DEFAULT_STACK_COLORS: ValidStackColor[] = [
export enum DateTimeFormats {
FULL = 'MMM D, YYYY, hh:mma',
FULL_SECONDS = 'MMM D, hh:mm:ssa',
DATE_ONLY = 'MMM D, YYYY',
}
@@ -1,65 +0,0 @@
import { untrustedSql, type UntrustedSqlFragment } from '@supabase/pg-meta'
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
import { constructHeaders, fetchHandler } from '@/data/fetchers'
import { BASE_PATH } from '@/lib/constants'
import { ResponseError, UseCustomMutationOptions } from '@/types'
export type ParseClientCodeResponse = {
// Named unchecked_sql to highlight that this SQL must never be run
// automatically without user confirmation — it is AI-generated and may not
// be correct.
unchecked_sql: UntrustedSqlFragment | undefined
valid: boolean
}
export type ParseClientCodeVariables = {
code: string
}
export async function generateSqlTitle({ code }: ParseClientCodeVariables) {
try {
const url = `${BASE_PATH}/api/ai/sql/parse-client-code`
const headers = await constructHeaders({ 'Content-Type': 'application/json' })
const response = await fetchHandler(url, {
headers,
method: 'POST',
body: JSON.stringify({ code }),
}).then((res) => res.json())
if (response.error) throw new Error(response.error)
return {
valid: response.valid as boolean,
unchecked_sql: response.sql != null ? untrustedSql(response.sql as string) : undefined,
} satisfies ParseClientCodeResponse
} catch (error) {
throw error
}
}
type ParseClientCodeData = Awaited<ReturnType<typeof generateSqlTitle>>
export const useParseClientCodeMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<ParseClientCodeData, ResponseError, ParseClientCodeVariables>,
'mutationFn'
> = {}) => {
return useMutation<ParseClientCodeData, ResponseError, ParseClientCodeVariables>({
mutationFn: (vars) => generateSqlTitle(vars),
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to parse client code: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,94 +0,0 @@
import { untrustedSql, type UntrustedSqlFragment } from '@supabase/pg-meta'
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
import { fetchPost } from '@/data/fetchers'
import { BASE_PATH } from '@/lib/constants'
import { ResponseError, UseCustomMutationOptions } from '@/types'
export type SqlPolicyGenerateVariables = {
tableName: string
schema?: string
columns?: string[]
projectRef: string
connectionString: string
orgSlug?: string
message?: string
}
type RawSqlPolicy = {
name: string
table: string
schema: string
action: 'PERMISSIVE' | 'RESTRICTIVE'
roles: string[]
command?: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'ALL'
definition?: string
check?: string
sql: string
}
/**
* AI-generated policy. `definition` and `check` are branded `UntrustedSqlFragment` at the
* boundary — they originate from third-party AI output and must be promoted via
* `acceptUntrustedSql` at a user gesture before executing.
*/
export type SqlPolicyGenerateResponse = Array<
Omit<RawSqlPolicy, 'definition' | 'check'> & {
definition?: UntrustedSqlFragment
check?: UntrustedSqlFragment
}
>
export async function generateSqlPolicy({
tableName,
schema,
columns,
projectRef,
connectionString,
orgSlug,
message,
}: SqlPolicyGenerateVariables): Promise<SqlPolicyGenerateResponse> {
const result = await fetchPost<Array<RawSqlPolicy>>(`${BASE_PATH}/api/ai/sql/policy`, {
tableName,
schema,
columns,
projectRef,
connectionString,
orgSlug,
message,
})
if ('error' in result) throw new ResponseError((result.error as any).message, 400)
return (result as Array<RawSqlPolicy>).map((policy) => ({
...policy,
definition: policy.definition === undefined ? undefined : untrustedSql(policy.definition),
check: policy.check === undefined ? undefined : untrustedSql(policy.check),
}))
}
type SqlPolicyGenerateData = Awaited<ReturnType<typeof generateSqlPolicy>>
export const useSqlPolicyGenerateMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<SqlPolicyGenerateData, ResponseError, SqlPolicyGenerateVariables>,
'mutationFn'
> = {}) => {
return useMutation<SqlPolicyGenerateResponse, ResponseError, SqlPolicyGenerateVariables>({
mutationFn: (vars) => generateSqlPolicy(vars),
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to generate policy: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,59 +0,0 @@
import { getTablesRlsEnabledStatusSQL } from '@supabase/pg-meta'
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
import { executeSql } from '../sql/execute-sql-mutation'
import { ResponseError, UseCustomMutationOptions } from '@/types'
type CheckTableRLSStatusVariables = {
projectRef?: string
connectionString?: string | null
tables: { schema: string; table: string }[]
}
export type CheckTableRLSStatusResponse = {
schema: string
table: string
rls_enabled: boolean
}
export async function checkTableRLSStatus({
projectRef,
connectionString,
tables,
}: CheckTableRLSStatusVariables) {
const sql = getTablesRlsEnabledStatusSQL({ tables })
const { result } = await executeSql({
projectRef,
connectionString,
sql,
queryKey: ['table-rls-status'],
})
return result as CheckTableRLSStatusResponse[]
}
type CheckTableRLSStatusData = Awaited<ReturnType<typeof checkTableRLSStatus>>
export const useCheckTableRLSStatusMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<CheckTableRLSStatusData, ResponseError, CheckTableRLSStatusVariables>,
'mutationFn'
> = {}) => {
return useMutation<CheckTableRLSStatusData, ResponseError, CheckTableRLSStatusVariables>({
mutationFn: (vars) => checkTableRLSStatus(vars),
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to retrieve table RLS statuses: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,59 +0,0 @@
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
import { constructHeaders, fetchHandler } from '@/data/fetchers'
import { BASE_PATH } from '@/lib/constants'
import { ResponseError, UseCustomMutationOptions } from '@/types'
type ParseSQLQueryVariables = { sql: string }
export type ParseSQLQueryOperations = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | undefined
export type ParseSQLQueryResponse = {
tables: string[]
operation: ParseSQLQueryOperations
whereClause: string | null
statementCount: number
}
export async function parseSQLQuery({ sql }: ParseSQLQueryVariables) {
try {
const url = `${BASE_PATH}/api/parse-query`
const headers = await constructHeaders({ 'Content-Type': 'application/json' })
const response = await fetchHandler(url, {
headers,
method: 'POST',
body: JSON.stringify({ sql }),
}).then((res) => res.json())
if (response.error) throw new Error(response.error)
return response as ParseSQLQueryResponse
} catch (error) {
throw error
}
}
type ParseSQLQueryData = Awaited<ReturnType<typeof parseSQLQuery>>
export const useParseSQLQueryMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<ParseSQLQueryData, ResponseError, ParseSQLQueryVariables>,
'mutationFn'
> = {}) => {
return useMutation<ParseSQLQueryData, ResponseError, ParseSQLQueryVariables>({
mutationFn: (vars) => parseSQLQuery(vars),
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to parse query: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
-2
View File
@@ -1,6 +1,4 @@
export const notificationKeys = {
list: () => ['notifications'] as const,
listV2: (options?: { status?: string; limit: number; filters: any }) =>
['notifications', options] as const,
summary: () => ['notifications', 'summary'] as const,
}
@@ -1,43 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { notificationKeys } from './keys'
import { handleError, patch } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export async function archiveAllNotifications() {
const { data, error } = await patch('/platform/notifications/archive-all', {
headers: { Version: '2' },
})
if (error) handleError(error)
return data
}
type NotificationsArchiveAllData = Awaited<ReturnType<typeof archiveAllNotifications>>
export const useNotificationsArchiveAllMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<NotificationsArchiveAllData, ResponseError>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<NotificationsArchiveAllData, ResponseError>({
mutationFn: () => archiveAllNotifications(),
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries({ queryKey: notificationKeys.list() })
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to archive all notifications: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,28 +0,0 @@
import type { Notification } from '@supabase/shared-types/out/notifications'
import { useQuery } from '@tanstack/react-query'
import { notificationKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type NotificationsResponse = Notification[]
export async function getNotificationsSummary(signal?: AbortSignal) {
const { data, error } = await get('/platform/notifications/summary', {
signal,
})
if (error) handleError(error)
return data
}
export type NotificationsData = Awaited<ReturnType<typeof getNotificationsSummary>>
export type NotificationsError = ResponseError
export const useNotificationsSummaryQuery = <TData = NotificationsData>(
options: UseCustomQueryOptions<NotificationsData, NotificationsError, TData> = {}
) =>
useQuery<NotificationsData, NotificationsError, TData>({
queryKey: notificationKeys.summary(),
queryFn: ({ signal }) => getNotificationsSummary(signal),
...options,
})
@@ -1,69 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { platformAppKeys } from './keys'
import { del, handleError } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export type PlatformAppSigningKeyDeleteVariables = {
slug: string
appId: string
keyId: string
}
export async function deletePlatformAppSigningKey({
slug,
appId,
keyId,
}: PlatformAppSigningKeyDeleteVariables) {
const { data, error } = await del(
'/platform/organizations/{slug}/apps/{app_id}/signing-keys/{key_id}',
{
params: { path: { slug, app_id: appId, key_id: keyId } },
}
)
if (error) handleError(error)
return data
}
export type PlatformAppSigningKeyDeleteData = Awaited<
ReturnType<typeof deletePlatformAppSigningKey>
>
export const usePlatformAppSigningKeyDeleteMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<
PlatformAppSigningKeyDeleteData,
ResponseError,
PlatformAppSigningKeyDeleteVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
PlatformAppSigningKeyDeleteData,
ResponseError,
PlatformAppSigningKeyDeleteVariables
>({
mutationFn: (vars) => deletePlatformAppSigningKey(vars),
async onSuccess(data, variables, context) {
await queryClient.invalidateQueries({
queryKey: platformAppKeys.signingKeys(variables.slug, variables.appId),
})
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to delete signing key: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,56 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import type { components } from 'api-types'
import { toast } from 'sonner'
import { platformAppKeys } from './keys'
import { handleError, patch } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export type PlatformAppUpdateVariables = {
slug: string
appId: string
} & components['schemas']['UpdatePlatformAppBody']
export async function updatePlatformApp({ slug, appId, ...body }: PlatformAppUpdateVariables) {
const { data, error } = await patch('/platform/organizations/{slug}/apps/{app_id}', {
params: { path: { slug, app_id: appId } },
body,
})
if (error) handleError(error)
return data
}
export type PlatformAppUpdateData = Awaited<ReturnType<typeof updatePlatformApp>>
export const usePlatformAppUpdateMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<PlatformAppUpdateData, ResponseError, PlatformAppUpdateVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<PlatformAppUpdateData, ResponseError, PlatformAppUpdateVariables>({
mutationFn: (vars) => updatePlatformApp(vars),
async onSuccess(data, variables, context) {
await Promise.all([
queryClient.invalidateQueries({ queryKey: platformAppKeys.list(variables.slug) }),
queryClient.invalidateQueries({
queryKey: platformAppKeys.detail(variables.slug, variables.appId),
}),
])
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to update app: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,18 +0,0 @@
import { hasConsented, posthogClient } from 'common'
import { useEffect } from 'react'
export function useTrackExperimentExposure(
experimentId: string,
variant: string | undefined,
extraProperties?: Record<string, any>
) {
useEffect(() => {
if (!variant) return
posthogClient.captureExperimentExposure(
experimentId,
{ variant, ...extraProperties },
hasConsented()
)
}, [experimentId, variant])
}
@@ -1,4 +1,4 @@
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import Reports from '@/components/interfaces/Reports/Reports'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
@@ -11,7 +11,7 @@ import {
} from '@/components/interfaces/Reports/renderers/ApiRenderers'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import ReportWidget from '@/components/interfaces/Reports/ReportWidget'
@@ -9,7 +9,7 @@ import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import { SharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
@@ -10,7 +10,7 @@ import { Alert, AlertDescription, Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import ReportWidget from '@/components/interfaces/Reports/ReportWidget'
@@ -8,7 +8,7 @@ import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import {
EDGE_FUNCTION_REGIONS,
REPORT_DATERANGE_HELPER_LABELS,
@@ -7,7 +7,7 @@ import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import { SharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
@@ -8,7 +8,7 @@ import { Button } from 'ui'
import { OBSERVABILITY_DOCS_HREFS } from '@/components/interfaces/Observability/Observability.constants'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import { SharedAPIReport } from '@/components/interfaces/Reports/SharedAPIReport/SharedAPIReport'
@@ -17,7 +17,7 @@ import {
} from '@/components/interfaces/Reports/renderers/StorageRenderers'
import ReportFilterBar from '@/components/interfaces/Reports/ReportFilterBar'
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
import ReportWidget from '@/components/interfaces/Reports/ReportWidget'
-3
View File
@@ -7,11 +7,8 @@ import type { UntrustedLogSqlFragment } from '@/data/logs/safe-analytics-sql'
export namespace Notebooks {
export type Content = NotebookSchema.NotebookContent
export type Cell = NotebookSchema.Cell
export type MarkdownCell = NotebookSchema.MarkdownCell
export type DatabaseCell = NotebookSchema.DatabaseCell
export type LogCell = NotebookSchema.LogCell
export type TimeRange = NotebookSchema.TimeRange
export type ChartConfig = NotebookSchema.ChartConfig
}
export interface UserContent<