mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
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:
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
|
||||
}
|
||||
-118
@@ -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>
|
||||
)
|
||||
}
|
||||
-56
@@ -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,
|
||||
|
||||
-183
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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<
|
||||
|
||||
Reference in new issue
Block a user