-
-
-
+
+
+
+
)
}
-
-export default EmptyBucketModal
diff --git a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx
index 6f234884903ba..550cf5f90b771 100644
--- a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx
+++ b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx
@@ -18,7 +18,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Loader } from 'lucide-react'
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
import { formatPoliciesForStorage } from '../Storage.utils'
-import StoragePoliciesBucketRow from './StoragePoliciesBucketRow'
+import { StoragePoliciesBucketRow } from './StoragePoliciesBucketRow'
import StoragePoliciesEditPolicyModal from './StoragePoliciesEditPolicyModal'
import StoragePoliciesPlaceholder from './StoragePoliciesPlaceholder'
diff --git a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx
index 26eb07822ced7..1dffed3424640 100644
--- a/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx
+++ b/apps/studio/components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketRow.tsx
@@ -1,6 +1,9 @@
-import Panel from 'components/ui/Panel'
-import { isEmpty } from 'lodash'
-import { Archive, Edit, MoreVertical, Trash } from 'lucide-react'
+import { PostgresPolicy } from '@supabase/postgres-meta'
+import { noop } from 'lodash'
+import { Archive } from 'lucide-react'
+
+import { PolicyRow } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow'
+import { Bucket } from 'data/storage/buckets-query'
import {
Badge,
Button,
@@ -8,101 +11,76 @@ import {
CardContent,
CardHeader,
CardTitle,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
+ Table,
+ TableBody,
+ TableHead,
+ TableHeader,
+ TableRow,
} from 'ui'
-interface PolicyRowProps {
- policy: any
- table: any
- bucketName: string
- onSelectPolicyEdit: (p: any, s: string, t: any) => void
- onSelectPolicyDelete: (s: string) => void
+interface StoragePoliciesBucketRowProps {
+ table: string
+ label: string
+ bucket?: Bucket
+ policies: PostgresPolicy[]
+ onSelectPolicyAdd: (bucketName: string, table: string) => void
+ onSelectPolicyEdit: (policy: PostgresPolicy, bucketName: string, table: string) => void
+ onSelectPolicyDelete: (policy: PostgresPolicy) => void
}
-const PolicyRow = ({
- policy,
- table,
- bucketName,
- onSelectPolicyEdit = () => {},
- onSelectPolicyDelete = () => {},
-}: PolicyRowProps) => {
- const { name, command } = policy
- return (
-
-
-
{command}
-
- {name}
-
-
-
-
- } />
-
-
- onSelectPolicyEdit(policy, bucketName, table)}
- >
-
- Edit
-
-
- onSelectPolicyDelete(policy)}>
-
- Delete
-
-
-
-
- )
-}
-
-const StoragePoliciesBucketRow = ({
+export const StoragePoliciesBucketRow = ({
table = '',
label = '',
- bucket = {},
+ bucket,
policies = [],
- onSelectPolicyAdd = () => {},
- onSelectPolicyEdit = () => {},
- onSelectPolicyDelete = () => {},
-}: any) => {
+ onSelectPolicyAdd = noop,
+ onSelectPolicyEdit = noop,
+ onSelectPolicyDelete = noop,
+}: StoragePoliciesBucketRowProps) => {
return (
{label}
- {bucket.public &&
Public}
+ {bucket?.public &&
Public}
-
+ {!!bucket && (
+
+ )}
{policies.length === 0 ? (
No policies created yet
) : (
-
- {policies.map((policy: any) => (
-
- ))}
-
+
+
+
+
+ Name
+ Command
+ Applied to
+
+ Actions
+
+
+
+
+ {policies.map((policy) => (
+ onSelectPolicyEdit(p, bucket?.name ?? '', table)}
+ onSelectDeletePolicy={onSelectPolicyDelete}
+ />
+ ))}
+
+
+
)}
)
}
-
-export default StoragePoliciesBucketRow
diff --git a/apps/studio/components/interfaces/Storage/__tests__/DeleteBucketModal.test.tsx b/apps/studio/components/interfaces/Storage/__tests__/DeleteBucketModal.test.tsx
index ea63c932272ad..4f173c4a52014 100644
--- a/apps/studio/components/interfaces/Storage/__tests__/DeleteBucketModal.test.tsx
+++ b/apps/studio/components/interfaces/Storage/__tests__/DeleteBucketModal.test.tsx
@@ -1,15 +1,15 @@
-import { describe, expect, it, beforeEach, vi } from 'vitest'
-import { screen, waitFor, fireEvent } from '@testing-library/dom'
+import { faker } from '@faker-js/faker'
+import { fireEvent, screen, waitFor } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
-import { faker } from '@faker-js/faker'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { addAPIMock } from 'tests/lib/msw'
import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext'
import { Bucket } from 'data/storage/buckets-query'
-import DeleteBucketModal from '../DeleteBucketModal'
import { render } from 'tests/helpers'
+import { addAPIMock } from 'tests/lib/msw'
import { routerMock } from 'tests/lib/route-mock'
+import { DeleteBucketModal } from '../DeleteBucketModal'
const bucket: Bucket = {
id: faker.string.uuid(),
@@ -113,7 +113,7 @@ describe(`DeleteBucketModal`, () => {
const input = screen.getByLabelText(/Type/)
await userEvent.type(input, `test`)
- const confirmButton = screen.getByRole(`button`, { name: `Delete Bucket` })
+ const confirmButton = screen.getByRole(`button`, { name: `Delete bucket` })
fireEvent.click(confirmButton)
await waitFor(() => expect(onClose).toHaveBeenCalledOnce())
@@ -131,7 +131,7 @@ describe(`DeleteBucketModal`, () => {
const input = screen.getByLabelText(/Type/)
await userEvent.type(input, `invalid`)
- const confirmButton = screen.getByRole(`button`, { name: `Delete Bucket` })
+ const confirmButton = screen.getByRole(`button`, { name: `Delete bucket` })
fireEvent.click(confirmButton)
await waitFor(() => {
diff --git a/apps/studio/components/interfaces/Storage/__tests__/EditBucketModal.test.tsx b/apps/studio/components/interfaces/Storage/__tests__/EditBucketModal.test.tsx
index 0ae722625860b..3dd0c9bb8feb1 100644
--- a/apps/studio/components/interfaces/Storage/__tests__/EditBucketModal.test.tsx
+++ b/apps/studio/components/interfaces/Storage/__tests__/EditBucketModal.test.tsx
@@ -1,14 +1,14 @@
-import { describe, expect, it, beforeEach, vi } from 'vitest'
-import { screen, waitFor, fireEvent } from '@testing-library/dom'
+import { faker } from '@faker-js/faker'
+import { fireEvent, screen, waitFor } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
-import { faker } from '@faker-js/faker'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { addAPIMock } from 'tests/lib/msw'
import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext'
import { Bucket } from 'data/storage/buckets-query'
import { render } from 'tests/helpers'
-import EditBucketModal from '../EditBucketModal'
+import { addAPIMock } from 'tests/lib/msw'
+import { EditBucketModal } from '../EditBucketModal'
const bucket: Bucket = {
id: faker.string.uuid(),
diff --git a/apps/studio/components/interfaces/Storage/__tests__/EmptyBucketModal.test.tsx b/apps/studio/components/interfaces/Storage/__tests__/EmptyBucketModal.test.tsx
index 8576210874584..a9b79f4daa36b 100644
--- a/apps/studio/components/interfaces/Storage/__tests__/EmptyBucketModal.test.tsx
+++ b/apps/studio/components/interfaces/Storage/__tests__/EmptyBucketModal.test.tsx
@@ -1,15 +1,15 @@
-import { describe, expect, it, beforeEach, vi } from 'vitest'
-import { screen, waitFor, fireEvent } from '@testing-library/dom'
+import { faker } from '@faker-js/faker'
+import { fireEvent, screen, waitFor } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
-import { faker } from '@faker-js/faker'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { addAPIMock } from 'tests/lib/msw'
import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext'
import { Bucket } from 'data/storage/buckets-query'
-import EmptyBucketModal from '../EmptyBucketModal'
import { render } from 'tests/helpers'
+import { addAPIMock } from 'tests/lib/msw'
import { routerMock } from 'tests/lib/route-mock'
+import { EmptyBucketModal } from '../EmptyBucketModal'
const bucket: Bucket = {
id: faker.string.uuid(),
@@ -87,7 +87,7 @@ describe(`EmptyBucketModal`, () => {
await userEvent.click(openButton)
await screen.findByRole(`dialog`)
- const confirmButton = screen.getByRole(`button`, { name: `Empty Bucket` })
+ const confirmButton = screen.getByRole(`button`, { name: `Empty bucket` })
fireEvent.click(confirmButton)
diff --git a/apps/studio/components/interfaces/Storage/index.ts b/apps/studio/components/interfaces/Storage/index.ts
index efecd4c782bcf..2cc7a39e25a75 100644
--- a/apps/studio/components/interfaces/Storage/index.ts
+++ b/apps/studio/components/interfaces/Storage/index.ts
@@ -2,4 +2,4 @@ export { default as StorageExplorer } from './StorageExplorer/StorageExplorer'
export { default as StoragePolicies } from './StoragePolicies/StoragePolicies'
export { default as StorageSettings } from './StorageSettings/StorageSettings'
-export { default as DeleteBucketModal } from './DeleteBucketModal'
+export { DeleteBucketModal } from './DeleteBucketModal'
diff --git a/apps/studio/components/interfaces/Support/AIAssistantOption.tsx b/apps/studio/components/interfaces/Support/AIAssistantOption.tsx
index debff627760f3..055ee41a9c6ff 100644
--- a/apps/studio/components/interfaces/Support/AIAssistantOption.tsx
+++ b/apps/studio/components/interfaces/Support/AIAssistantOption.tsx
@@ -1,7 +1,7 @@
import { useProjectsQuery } from 'data/projects/projects-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { AnimatePresence, motion } from 'framer-motion'
-import { MessageSquare, X } from 'lucide-react'
+import { MessageSquare } from 'lucide-react'
import Link from 'next/link'
import { useCallback, useEffect, useState } from 'react'
import { Button } from 'ui'
@@ -17,9 +17,11 @@ export const AIAssistantOption = ({
organizationSlug,
isCondensed = false,
}: AIAssistantOptionProps) => {
- const [isVisible, setIsVisible] = useState(isCondensed ? true : false)
- const { data: projects } = useProjectsQuery()
+ const { data } = useProjectsQuery()
+ const projects = data?.projects ?? []
+
const { mutate: sendEvent } = useSendEventMutation()
+ const [isVisible, setIsVisible] = useState(isCondensed ? true : false)
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 800)
diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
index a194baa25e60d..7246e96887c9b 100644
--- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx
+++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
@@ -140,11 +140,9 @@ export const SupportFormV2 = ({
() => organizations?.find((org) => org.slug === organizationSlug),
[organizationSlug, organizations]
)
- const {
- data: allProjects,
- isLoading: isLoadingProjects,
- isSuccess: isSuccessProjects,
- } = useProjectsQuery()
+ const { data, isLoading: isLoadingProjects, isSuccess: isSuccessProjects } = useProjectsQuery()
+ const allProjects = data?.projects ?? []
+
const { mutate: sendEvent } = useSendEventMutation()
const { mutate: submitSupportTicket } = useSendSupportTicketMutation({
diff --git a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
index bebe326aab6d3..afabafbead6ec 100644
--- a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
+++ b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
@@ -91,15 +91,15 @@ export const ProjectDropdown = () => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
- const { data: allProjects, isLoading: isLoadingProjects } = useProjectsQuery()
+ const { data, isLoading: isLoadingProjects } = useProjectsQuery()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
const isBranch = project?.parentRef !== project?.ref
- const projects = allProjects
- ?.filter((x) => x.organization_id === selectedOrganization?.id)
+ const projects = (data?.projects ?? [])
+ .filter((x) => x.organization_id === selectedOrganization?.id)
.sort((a, b) => a.name.localeCompare(b.name))
const selectedProject = isBranch
? projects?.find((p) => p.ref === project?.parentRef)
diff --git a/apps/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/apps/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
index 0d71b585f89e1..4919aac04a7c7 100644
--- a/apps/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
+++ b/apps/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
@@ -1,6 +1,7 @@
import { useRouter } from 'next/router'
import { PropsWithChildren } from 'react'
+import { useFlag } from 'common'
import { useIsColumnLevelPrivilegesEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { ProductMenu } from 'components/ui/ProductMenu'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
@@ -8,7 +9,6 @@ import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { withAuth } from 'hooks/misc/withAuth'
-import { useFlag } from 'hooks/ui/useFlag'
import ProjectLayout from '../ProjectLayout/ProjectLayout'
import { generateDatabaseMenu } from './DatabaseMenu.utils'
diff --git a/apps/studio/components/layouts/Integrations/layout.tsx b/apps/studio/components/layouts/Integrations/layout.tsx
index 10ed88f9571d9..8a0bd18923cc4 100644
--- a/apps/studio/components/layouts/Integrations/layout.tsx
+++ b/apps/studio/components/layouts/Integrations/layout.tsx
@@ -1,6 +1,7 @@
import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
+import { useFlag } from 'common'
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
import { Header } from 'components/layouts/Integrations/header'
import ProjectLayout from 'components/layouts/ProjectLayout/ProjectLayout'
@@ -11,7 +12,6 @@ import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem'
import { useScroll } from 'framer-motion'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { withAuth } from 'hooks/misc/withAuth'
-import { useFlag } from 'hooks/ui/useFlag'
import { Menu, Separator } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns'
import { IntegrationTabs } from './tabs'
diff --git a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
index 35a7b60cede58..c5f086fc59d65 100644
--- a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
+++ b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
-import { IS_PLATFORM, useParams } from 'common'
+import { IS_PLATFORM, useFlag, useParams } from 'common'
import {
useFeaturePreviewModal,
useUnifiedLogsPreview,
@@ -15,7 +15,6 @@ import { useContentQuery } from 'data/content/content-query'
import { useReplicationSourcesQuery } from 'data/replication/sources-query'
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
-import { useFlag } from 'hooks/ui/useFlag'
import {
Badge,
Button,
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsFilter.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsFilter.tsx
index c8c9a2c78134f..ba8ee16dc2678 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsFilter.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsFilter.tsx
@@ -19,17 +19,18 @@ import {
} from 'ui'
import { CommandGroup } from '@ui/components/shadcn/ui/command'
-import { CriticalIcon, WarningIcon } from 'ui'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useNotificationsStateSnapshot } from 'state/notifications'
+import { CriticalIcon, WarningIcon } from 'ui'
export const NotificationsFilter = ({ activeTab }: { activeTab: 'inbox' | 'archived' }) => {
const [open, setOpen] = useState(false)
const snap = useNotificationsStateSnapshot()
const { data: organizations } = useOrganizationsQuery()
- const { data: projects } = useProjectsQuery()
+ const { data } = useProjectsQuery()
+ const projects = data?.projects ?? []
return (
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsPopover.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsPopover.tsx
index e610bb3a4e170..1e30b9525a155 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsPopover.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/NotificationsPopoverV2/NotificationsPopover.tsx
@@ -3,6 +3,7 @@ import { useMemo, useRef, useState } from 'react'
import { toast } from 'sonner'
import AlertError from 'components/ui/AlertError'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import InfiniteList from 'components/ui/InfiniteList'
import ShimmeringLoader, { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useNotificationsArchiveAllMutation } from 'data/notifications/notifications-v2-archive-all-mutation'
@@ -24,7 +25,6 @@ import {
} from 'ui'
import NotificationRow from './NotificationRow'
import { NotificationsFilter } from './NotificationsFilter'
-import { ButtonTooltip } from 'components/ui/ButtonTooltip'
export const NotificationsPopoverV2 = () => {
const [open, setOpen] = useState(false)
@@ -40,7 +40,9 @@ export const NotificationsPopoverV2 = () => {
// so opting to simplify and implement it here for now
const rowHeights = useRef<{ [key: number]: number }>({})
- const { data: projects } = useProjectsQuery({ enabled: open })
+ const { data: projectsData } = useProjectsQuery({ enabled: open })
+ const projects = projectsData?.projects ?? []
+
const { data: organizations } = useOrganizationsQuery({ enabled: open })
const {
data,
diff --git a/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx b/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx
index 25f46fb6bdb37..36da56931d606 100644
--- a/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx
@@ -2,9 +2,10 @@ import { useParams } from 'common'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useProjectsQuery } from 'data/projects/projects-query'
-const LoadingState = () => {
+export const LoadingState = () => {
const { ref } = useParams()
- const { data: allProjects, isLoading } = useProjectsQuery()
+ const { data, isLoading } = useProjectsQuery()
+ const allProjects = data?.projects ?? []
const projectName =
ref !== 'default'
@@ -37,8 +38,6 @@ const LoadingState = () => {
)
}
-export default LoadingState
-
export const ProjectUsageLoadingState = () => {
return (
diff --git a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx
index dcfc4bb4c9b1e..a369a28f2600e 100644
--- a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx
@@ -9,7 +9,7 @@ import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'
-import { useParams } from 'common'
+import { useFlag, useParams } from 'common'
import { PostgresVersionSelector } from 'components/interfaces/ProjectCreation/PostgresVersionSelector'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -21,7 +21,7 @@ import { setProjectStatus } from 'data/projects/projects-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { useFlag, usePHFlag } from 'hooks/ui/useFlag'
+import { usePHFlag } from 'hooks/ui/useFlag'
import { PROJECT_STATUS } from 'lib/constants'
import { AWS_REGIONS, CloudProvider } from 'shared-data'
import {
diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
index fb9c5bcf9fa2a..47e0c76f2b90b 100644
--- a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
@@ -22,7 +22,7 @@ import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav'
import { useEditorType } from '../editors/EditorsLayout.hooks'
import BuildingState from './BuildingState'
import ConnectingState from './ConnectingState'
-import LoadingState from './LoadingState'
+import { LoadingState } from './LoadingState'
import { ProjectPausedState } from './PausedState/ProjectPausedState'
import PauseFailedState from './PauseFailedState'
import PausingState from './PausingState'
diff --git a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
index d3d034eef97a5..b5a548c201609 100644
--- a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
+++ b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
@@ -2,10 +2,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Plus } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
-import { useState, useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { toast } from 'sonner'
-import { useParams } from 'common'
+import { useFlag, useParams } from 'common'
import { CreateReportModal } from 'components/interfaces/Reports/CreateReportModal'
import { UpdateCustomReportModal } from 'components/interfaces/Reports/UpdateModal'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -18,7 +18,6 @@ import { useProfile } from 'lib/profile'
import { Menu, cn } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { ReportMenuItem } from './ReportMenuItem'
-import { useFlag } from 'hooks/ui/useFlag'
const ReportsMenu = () => {
const router = useRouter()
diff --git a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx
index 6243f55675b4d..5a97ecb7b262c 100644
--- a/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx
+++ b/apps/studio/components/layouts/SignInLayout/SignInLayout.tsx
@@ -1,13 +1,14 @@
import { useQueryClient } from '@tanstack/react-query'
-import { DocsButton } from 'components/ui/DocsButton'
-import { useFlag } from 'hooks/ui/useFlag'
-import { BASE_PATH } from 'lib/constants'
-import { auth, buildPathWithParams, getAccessToken, getReturnToPath } from 'lib/gotrue'
import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect, useState } from 'react'
+
+import { useFlag } from 'common'
+import { DocsButton } from 'components/ui/DocsButton'
+import { BASE_PATH } from 'lib/constants'
+import { auth, buildPathWithParams, getAccessToken, getReturnToPath } from 'lib/gotrue'
import { tweets } from 'shared-data'
type SignInLayoutProps = {
diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx
index b189896edd748..da0603309cafd 100644
--- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx
+++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx
@@ -6,7 +6,7 @@ import { ArrowDown, Eraser, Info, Pencil, Settings, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { LOCAL_STORAGE_KEYS } from 'common'
+import { LOCAL_STORAGE_KEYS, useFlag } from 'common'
import { useParams, useSearchParamsShallow } from 'common/hooks'
import { Markdown } from 'components/interfaces/Markdown'
import { useCheckOpenAIKeyQuery } from 'data/ai/check-api-key-query'
@@ -17,7 +17,6 @@ import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useOrgAiOptInLevel } from 'hooks/misc/useOrgOptedIntoAi'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { useFlag } from 'hooks/ui/useFlag'
import { useHotKey } from 'hooks/ui/useHotKey'
import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
import uuidv4 from 'lib/uuid'
diff --git a/apps/studio/components/ui/AIAssistantPanel/Message.tsx b/apps/studio/components/ui/AIAssistantPanel/Message.tsx
index c30bb9776a6a5..4306c4078a1dd 100644
--- a/apps/studio/components/ui/AIAssistantPanel/Message.tsx
+++ b/apps/studio/components/ui/AIAssistantPanel/Message.tsx
@@ -129,7 +129,7 @@ export const Message = function Message({
/>
)}
-
+
{shouldUsePartsRendering ? (
(() => {
const shownLoadingTools = new Set
()
diff --git a/apps/studio/components/ui/DataTable/FilterSideBar.tsx b/apps/studio/components/ui/DataTable/FilterSideBar.tsx
index c81d299fe34a6..d8cc32766ffbb 100644
--- a/apps/studio/components/ui/DataTable/FilterSideBar.tsx
+++ b/apps/studio/components/ui/DataTable/FilterSideBar.tsx
@@ -1,8 +1,7 @@
import { useRouter } from 'next/router'
-import { useParams } from 'common'
+import { useFlag, useParams } from 'common'
import { useUnifiedLogsPreview } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import { useFlag } from 'hooks/ui/useFlag'
import { Button, cn, ResizablePanel } from 'ui'
import { FeaturePreviewSidebarPanel } from '../FeaturePreviewSidebarPanel'
import { DateRangeDisabled } from './DataTable.types'
diff --git a/apps/studio/components/ui/UpgradeToPro.tsx b/apps/studio/components/ui/UpgradeToPro.tsx
index e3576c2722625..fe15d76aebf23 100644
--- a/apps/studio/components/ui/UpgradeToPro.tsx
+++ b/apps/studio/components/ui/UpgradeToPro.tsx
@@ -2,10 +2,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import Link from 'next/link'
import { ReactNode } from 'react'
+import { useFlag } from 'common'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { useFlag } from 'hooks/ui/useFlag'
import { Button, cn } from 'ui'
import { ButtonTooltip } from './ButtonTooltip'
diff --git a/apps/studio/data/misc/get-default-region-query.ts b/apps/studio/data/misc/get-default-region-query.ts
index 3a304a0250f5e..9273e585773a3 100644
--- a/apps/studio/data/misc/get-default-region-query.ts
+++ b/apps/studio/data/misc/get-default-region-query.ts
@@ -1,12 +1,12 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { useFlag } from 'common'
import { COUNTRY_LAT_LON } from 'components/interfaces/ProjectCreation/ProjectCreation.constants'
import {
AWS_REGIONS_COORDINATES,
FLY_REGIONS_COORDINATES,
} from 'components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
import { fetchHandler } from 'data/fetchers'
-import { useFlag } from 'hooks/ui/useFlag'
import { getDistanceLatLonKM, tryParseJson } from 'lib/helpers'
import type { CloudProvider } from 'shared-data'
import { AWS_REGIONS, FLY_REGIONS } from 'shared-data'
diff --git a/apps/studio/data/permissions/permissions-query.ts b/apps/studio/data/permissions/permissions-query.ts
index 975786dd73bca..bad66b84e7278 100644
--- a/apps/studio/data/permissions/permissions-query.ts
+++ b/apps/studio/data/permissions/permissions-query.ts
@@ -1,4 +1,5 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import * as Sentry from '@sentry/nextjs'
import { useIsLoggedIn } from 'common'
import { get, handleError } from 'data/fetchers'
@@ -10,7 +11,13 @@ export type PermissionsResponse = Permission[]
export async function getPermissions(signal?: AbortSignal) {
const { data, error } = await get('/platform/profile/permissions', { signal })
- if (error) handleError(error)
+ if (error) {
+ handleError(error)
+ Sentry.withScope(function (scope) {
+ scope.setTag('permissions-query-error', true)
+ Sentry.captureException(error)
+ })
+ }
// [Joshen] TODO: Type this properly from the API
return data as unknown as PermissionsResponse
diff --git a/apps/studio/data/projects/projects-query.ts b/apps/studio/data/projects/projects-query.ts
index 5059f538adf24..6b37ad3ab94be 100644
--- a/apps/studio/data/projects/projects-query.ts
+++ b/apps/studio/data/projects/projects-query.ts
@@ -12,7 +12,8 @@ export type ProjectsVariables = {
ref?: string
}
-export type ProjectInfo = components['schemas']['ProjectInfo']
+type PaginatedProjectsResponse = components['schemas']['ListProjectsPaginatedResponse']
+export type ProjectInfo = PaginatedProjectsResponse['projects'][number]
export async function getProjects({
signal,
@@ -21,10 +22,14 @@ export async function getProjects({
signal?: AbortSignal
headers?: Record
}) {
- const { data, error } = await get('/platform/projects', { signal, headers })
+ const { data, error } = await get('/platform/projects', {
+ signal,
+ headers: { ...headers, Version: '2' },
+ })
if (error) handleError(error)
- return data as ProjectInfo[]
+ // [Joshen] API TS issue
+ return data as unknown as PaginatedProjectsResponse
}
export type ProjectsData = Awaited>
diff --git a/apps/studio/hooks/misc/useSelectedProject.ts b/apps/studio/hooks/misc/useSelectedProject.ts
index 9159cccb8f069..88a4d3c2eb3e8 100644
--- a/apps/studio/hooks/misc/useSelectedProject.ts
+++ b/apps/studio/hooks/misc/useSelectedProject.ts
@@ -28,7 +28,7 @@ export function useProjectByRefQuery(ref?: string) {
const projectsQuery = useProjectsQuery({
enabled: isLoggedIn,
select: (data) => {
- return data.find((project) => project.ref === ref)
+ return data.projects.find((project) => project.ref === ref)
},
})
diff --git a/apps/studio/hooks/ui/useFlag.ts b/apps/studio/hooks/ui/useFlag.ts
index 8dd5262287ecf..635c54343a482 100644
--- a/apps/studio/hooks/ui/useFlag.ts
+++ b/apps/studio/hooks/ui/useFlag.ts
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/nextjs'
-import { useFeatureFlags } from 'common'
+import { useFeatureFlags, useFlag } from 'common'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { trackFeatureFlag } from 'lib/posthog'
@@ -9,18 +9,6 @@ const isObjectEmpty = (obj: Object) => {
return Object.keys(obj).length === 0
}
-export function useFlag(name: string) {
- const flagStore = useFeatureFlags()
-
- const store = flagStore.configcat
-
- if (!isObjectEmpty(store) && store[name] === undefined) {
- console.error(`Flag key "${name}" does not exist in ConfigCat flag store`)
- return false
- }
- return store[name] as T
-}
-
// TODO(Alaister): move this to packages/common/feature-flags.tsx and rename to useFlag
export function usePHFlag(name: string) {
const flagStore = useFeatureFlags()
diff --git a/apps/studio/hooks/use-check-latest-deploy.tsx b/apps/studio/hooks/use-check-latest-deploy.tsx
index eb09ccaeda3c0..f174e03f47d7e 100644
--- a/apps/studio/hooks/use-check-latest-deploy.tsx
+++ b/apps/studio/hooks/use-check-latest-deploy.tsx
@@ -3,10 +3,9 @@ import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
-import { IS_PLATFORM } from 'common'
+import { IS_PLATFORM, useFlag } from 'common'
import { useDeploymentCommitQuery } from 'data/utils/deployment-commit-query'
import { Button, StatusIcon } from 'ui'
-import { useFlag } from './ui/useFlag'
const DeployCheckToast = ({ id }: { id: string | number }) => {
const router = useRouter()
diff --git a/apps/studio/lib/ai/bedrock.ts b/apps/studio/lib/ai/bedrock.ts
index b32e15dedbf9a..857808726e920 100644
--- a/apps/studio/lib/ai/bedrock.ts
+++ b/apps/studio/lib/ai/bedrock.ts
@@ -1,4 +1,5 @@
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'
+import { LanguageModel } from 'ai'
import { createCredentialChain, fromNodeProviderChain } from '@aws-sdk/credential-providers'
import { CredentialsProviderError } from '@smithy/property-provider'
import { awsCredentialsProvider } from '@vercel/functions/oidc'
@@ -60,9 +61,7 @@ export const regionPrefixMap: Record = {
euc1: 'eu',
}
-export type BedrockModel =
- | 'anthropic.claude-3-7-sonnet-20250219-v1:0'
- | 'anthropic.claude-3-5-haiku-20241022-v1:0'
+export type BedrockModel = 'anthropic.claude-3-7-sonnet-20250219-v1:0' | 'openai.gpt-oss-120b-1:0'
export type RegionWeights = Record
@@ -77,23 +76,25 @@ const modelRegionWeights: Record = {
usw2: 10,
euc1: 10,
},
- ['anthropic.claude-3-5-haiku-20241022-v1:0']: {
- use1: 40,
+ ['openai.gpt-oss-120b-1:0']: {
+ use1: 0,
use2: 0,
- usw2: 40,
+ usw2: 30,
euc1: 0,
},
}
/**
* Creates a Bedrock client that routes requests to different regions
- * based on a routing key.
+ * based on a routing key, with optional OpenAI support.
*
* Used to load balance requests across multiple regions depending on
* their capacities.
*/
-export function createRoutedBedrock(routingKey?: string) {
- return async (modelId: BedrockModel) => {
+export function createRoutedBedrock(routingKey?: string, useOpenAI = false) {
+ return async (
+ modelId: BedrockModel
+ ): Promise<{ model: LanguageModel; supportsCachePoint: boolean }> => {
const regionWeights = modelRegionWeights[modelId]
// Select the Bedrock region based on the routing key and the model
@@ -101,7 +102,9 @@ export function createRoutedBedrock(routingKey?: string) {
? await selectWeightedKey(routingKey, regionWeights)
: // There's a few places where getModel is called without a routing key
// Will cause disproportionate load on use1 region
- 'use1'
+ regionWeights['use1'] > 0
+ ? 'use1'
+ : 'usw2'
const bedrock = createAmazonBedrock({
credentialProvider,
@@ -109,8 +112,11 @@ export function createRoutedBedrock(routingKey?: string) {
})
// Cross-region models require the region prefix
- const modelName = `${regionPrefixMap[bedrockRegion]}.${modelId}`
+ const activeRegions = Object.values(regionWeights).filter((weight) => weight > 0).length
+ const modelName = activeRegions > 1 ? `${regionPrefixMap[bedrockRegion]}.${modelId}` : modelId
- return bedrock(modelName)
+ const model = bedrock(modelName)
+ const supportsCachePoint = modelId === 'anthropic.claude-3-7-sonnet-20250219-v1:0'
+ return { model, supportsCachePoint }
}
}
diff --git a/apps/studio/lib/ai/model.test.ts b/apps/studio/lib/ai/model.test.ts
index 8418ad36893b9..59986528989e1 100644
--- a/apps/studio/lib/ai/model.test.ts
+++ b/apps/studio/lib/ai/model.test.ts
@@ -9,7 +9,10 @@ vi.mock('@ai-sdk/openai', () => ({
vi.mock('./bedrock', async () => ({
...(await vi.importActual('./bedrock')),
- createRoutedBedrock: vi.fn(() => () => 'bedrock-model'),
+ createRoutedBedrock: vi.fn(() => async (modelId: string) => ({
+ model: 'bedrock-model',
+ supportsCachePoint: modelId === 'anthropic.claude-3-7-sonnet-20250219-v1:0',
+ })),
checkAwsCredentials: vi.fn(),
}))
@@ -25,12 +28,25 @@ describe('getModel', () => {
process.env = { ...originalEnv }
})
- it('should return bedrock model when AWS credentials are available', async () => {
+ it('should return bedrock model when AWS credentials are available and not throttled', async () => {
vi.mocked(bedrockModule.checkAwsCredentials).mockResolvedValue(true)
+ vi.stubEnv('IS_THROTTLED', 'false')
- const { model, error } = await getModel()
+ const { model, error, supportsCachePoint } = await getModel()
expect(model).toEqual('bedrock-model')
+ expect(supportsCachePoint).toBe(true)
+ expect(error).toBeUndefined()
+ })
+
+ it('should return bedrock model when AWS credentials are available and throttled', async () => {
+ vi.mocked(bedrockModule.checkAwsCredentials).mockResolvedValue(true)
+ vi.stubEnv('IS_THROTTLED', 'true')
+
+ const { model, error, supportsCachePoint } = await getModel()
+
+ expect(model).toEqual('bedrock-model')
+ expect(supportsCachePoint).toBe(false)
expect(error).toBeUndefined()
})
@@ -38,10 +54,11 @@ describe('getModel', () => {
vi.mocked(bedrockModule.checkAwsCredentials).mockResolvedValue(false)
process.env.OPENAI_API_KEY = 'test-key'
- const { model } = await getModel()
+ const { model, supportsCachePoint } = await getModel()
expect(model).toEqual('openai-model')
expect(openai).toHaveBeenCalledWith('gpt-4.1-2025-04-14')
+ expect(supportsCachePoint).toBe(false)
})
it('should return error when neither AWS credentials nor OPENAI_API_KEY is available', async () => {
diff --git a/apps/studio/lib/ai/model.ts b/apps/studio/lib/ai/model.ts
index 79e52cd4e2015..1cf7a56fa7fbd 100644
--- a/apps/studio/lib/ai/model.ts
+++ b/apps/studio/lib/ai/model.ts
@@ -2,20 +2,19 @@ import { openai } from '@ai-sdk/openai'
import { LanguageModel } from 'ai'
import { checkAwsCredentials, createRoutedBedrock } from './bedrock'
-// Default behaviour here is to be throttled (e.g if this env var is not available, IS_THROTTLED should be true, unless specified 'false')
-const IS_THROTTLED = process.env.IS_THROTTLED !== 'false'
-
const BEDROCK_PRO_MODEL = 'anthropic.claude-3-7-sonnet-20250219-v1:0'
-const BEDROCK_NORMAL_MODEL = 'anthropic.claude-3-5-haiku-20241022-v1:0'
+const BEDROCK_NORMAL_MODEL = 'openai.gpt-oss-120b-1:0'
const OPENAI_MODEL = 'gpt-4.1-2025-04-14'
export type ModelSuccess = {
model: LanguageModel
+ supportsCachePoint: boolean
error?: never
}
export type ModelError = {
model?: never
+ supportsCachePoint?: never
error: Error
}
@@ -31,28 +30,26 @@ export const ModelErrorMessage =
* different Bedrock regions.
*/
export async function getModel(routingKey?: string, isLimited?: boolean): Promise {
+ // Default behaviour here is to be throttled (e.g if this env var is not available, isThrottled should be true, unless specified 'false')
+ const isThrottled = process.env.IS_THROTTLED !== 'false'
+
const hasAwsCredentials = await checkAwsCredentials()
const hasAwsBedrockRoleArn = !!process.env.AWS_BEDROCK_ROLE_ARN
const hasOpenAIKey = !!process.env.OPENAI_API_KEY
if (hasAwsBedrockRoleArn && hasAwsCredentials) {
- const bedrockModel = IS_THROTTLED || isLimited ? BEDROCK_NORMAL_MODEL : BEDROCK_PRO_MODEL
+ const bedrockModel = isThrottled || isLimited ? BEDROCK_NORMAL_MODEL : BEDROCK_PRO_MODEL
const bedrock = createRoutedBedrock(routingKey)
+ const { model, supportsCachePoint } = await bedrock(bedrockModel)
- return {
- model: await bedrock(bedrockModel),
- }
+ return { model, supportsCachePoint }
}
// [Joshen] Only for local/self-hosted, hosted should always only use bedrock
if (hasOpenAIKey) {
- return {
- model: openai(OPENAI_MODEL),
- }
+ return { model: openai(OPENAI_MODEL), supportsCachePoint: false }
}
- return {
- error: new Error(ModelErrorMessage),
- }
+ return { error: new Error(ModelErrorMessage) }
}
diff --git a/apps/studio/lib/ai/org-ai-details.ts b/apps/studio/lib/ai/org-ai-details.ts
index b2e394dde4755..4c04eaa2fe25f 100644
--- a/apps/studio/lib/ai/org-ai-details.ts
+++ b/apps/studio/lib/ai/org-ai-details.ts
@@ -27,7 +27,7 @@ export const getOrgAIDetails = async ({
])
const selectedOrg = organizations.find((org) => org.slug === orgSlug)
- const selectedProject = projects.find(
+ const selectedProject = projects.projects.find(
(project) => project.ref === projectRef || project.preview_branch_refs.includes(projectRef)
)
diff --git a/apps/studio/lib/ai/prompts.ts b/apps/studio/lib/ai/prompts.ts
index 28072a1f986bb..83b4aa10235fc 100644
--- a/apps/studio/lib/ai/prompts.ts
+++ b/apps/studio/lib/ai/prompts.ts
@@ -791,6 +791,7 @@ export const PG_BEST_PRACTICES = `
- Prefer \`text\` over \`varchar\`.
- Prefer \`timestamp with time zone\` over \`date\`.
- Feel free to suggest corrections for suspected typos in user input.
+ - We do not need pgcrypto extension for generating UUIDs
## Object Generation:
- **Auth Schema**: The \`auth.users\` table stores user authentication data. Create a \`public.profiles\` table linked to \`auth.users\` (via user_id referencing auth.users.id) for user-specific public data. Do not create a new 'users' table. Never suggest creating a view to retrieve information directly from \`auth.users\`.
@@ -825,21 +826,38 @@ export const PG_BEST_PRACTICES = `
`
export const GENERAL_PROMPT = `
-You are a Supabase Postgres expert. Your goal is to generate SQL or Edge Function code based on user requests.
-
-Always attempt to use tools like \`list_tables\` and \`list_extensions\` and \`list_edge_functions\` to gather contextual information if available that will help inform your response.
+# Goals
+You are a Supabase Postgres expert. Your goals are to help people manage their Supabase project via:
+ - Writing SQL queries
+ - Writing Edge Functions
+ - Debugging issues
+ - Checking the status of the project
+
+# Tools
+ - Always attempt to use tools like \`list_tables\` and \`list_extensions\` and \`list_edge_functions\` before answering to gather contextual information if available that will help inform your response.
+ - Tools are only available to you, the user cannot use them, so do not suggest they use them
+ - The user may not have access to these tools based on their organization settings
`
export const CHAT_PROMPT = `
# Response Style:
-- Be **direct and concise**. Focus on delivering the essential information.
-- Instead of explaining results, offer: "Would you like me to explain this in more detail?"
-- Only provide detailed explanations when explicitly requested.
+ - Be **direct and concise**. Focus on delivering the essential information.
+
+# Response Format
+## CommonMark Markdown - mandatory
+
+Always format your entire response in CommonMark. Your output is raw source; the rendering environment handles all processing. Details:
+ - Output must be valid CommonMark, supporting UTF-8. Use rich Markdown naturally and fluently: headings, lists (hyphen bullets), blockquotes, *italics*, **bold**
+ - Structure:
+ - Use a clear heading hierarchy (H1–H4) without skipping levels when useful.
+ - Do not use tables to display information
+ - Use bold text only to highlight important information.
# Rename Chat**:
- **Always call \`rename_chat\` before you respond at the start of the conversation** with a 2-4 word descriptive name. Examples: "User Authentication Setup", "Sales Data Analysis", "Product Table Creation"**.
# Query rendering**:
+ - **Always call the \`display_query\` tool to render sql queries. You do not need to write the query yourself. ie Do not use markdown code blocks.**
- READ ONLY: Use \`display_query\` with \`sql\` and \`label\`. If results may be visualized, also provide \`view\` ('table' or 'chart'), \`xAxis\`, and \`yAxis\`.
- The user can run the query from the UI when you use display_query.
- Use \`display_query\` in the natural flow of the conversation. **Do not output the query in markdown**
@@ -847,8 +865,13 @@ export const CHAT_PROMPT = `
- If multiple, separate queries are needed, call \`display_query\` once per distinct query.
# Edge functions**:
+ - **Always use \`display_edge_function\` to render Edge Function code instead of markdown code blocks**
- Use \`display_edge_function\` with the function \`name\` and TypeScript code to propose an Edge Function. Only use this to display Edge Function code (not logs or other content). The user can deploy the function from the UI when you use display_edge_function.
+# Checking health
+ - Use \`get_advisors\` to check for any issues with the project.
+ - If the user does not have access to the \`get_advisors\` tool, they will have to use the Supabase dashboard to check for issues
+
# Safety**:
- For destructive queries (e.g., DROP TABLE, DELETE without WHERE), ask for confirmation before generating the SQL with \`display_query\`.
`
@@ -856,7 +879,7 @@ export const CHAT_PROMPT = `
export const OUTPUT_ONLY_PROMPT = `
# Output-Only Mode
-- **Final message must be only raw code needed to fulfill the request.**
+- **CRITICAL: Final message must be only raw code needed to fulfill the request.**
- **If you lack privelages to use a tool, do your best to generate the code without it. No need to explain why you couldn't use the tool.**
- **No explanations, no commentary, no markdown**. Do not wrap output in backticks.
- **Do not call UI display tools** (no \`display_query\`, no \`display_edge_function\").
diff --git a/apps/studio/package.json b/apps/studio/package.json
index 5951a073fd825..76f867c8c4b24 100644
--- a/apps/studio/package.json
+++ b/apps/studio/package.json
@@ -76,7 +76,6 @@
"common": "workspace:*",
"common-tags": "^1.8.2",
"config": "workspace:*",
- "configcat-js": "^9.5.1",
"cron-parser": "^4.9.0",
"cronstrue": "^2.50.0",
"dayjs": "^1.11.10",
diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx
index edeb124fc6f6a..19e0af384908b 100644
--- a/apps/studio/pages/_app.tsx
+++ b/apps/studio/pages/_app.tsx
@@ -33,7 +33,13 @@ import { NuqsAdapter } from 'nuqs/adapters/next/pages'
import { ErrorInfo } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
-import { FeatureFlagProvider, TelemetryTagManager, ThemeProvider, useThemeSandbox } from 'common'
+import {
+ FeatureFlagProvider,
+ getFlags as getConfigCatFlags,
+ TelemetryTagManager,
+ ThemeProvider,
+ useThemeSandbox,
+} from 'common'
import MetaFaviconsPagesRouter from 'common/MetaFavicons/pages-router'
import { RouteValidationWrapper } from 'components/interfaces/App'
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
@@ -45,7 +51,6 @@ import { GlobalErrorBoundaryState } from 'components/ui/GlobalErrorBoundaryState
import { useRootQueryClient } from 'data/query-client'
import { customFont, sourceCodePro } from 'fonts'
import { AuthProvider } from 'lib/auth'
-import { getFlags as getConfigCatFlags } from 'lib/configcat'
import { API_URL, BASE_PATH, IS_PLATFORM } from 'lib/constants'
import { ProfileProvider } from 'lib/profile'
import { Telemetry } from 'lib/telemetry'
diff --git a/apps/studio/pages/api/ai/code/complete.ts b/apps/studio/pages/api/ai/code/complete.ts
index 13957ee1511b0..43cf4d8fdc54f 100644
--- a/apps/studio/pages/api/ai/code/complete.ts
+++ b/apps/studio/pages/api/ai/code/complete.ts
@@ -47,6 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const accessToken = authorization?.replace('Bearer ', '')
let aiOptInLevel: AiOptInLevel = 'disabled'
+ let isLimited = false
if (!IS_PLATFORM) {
aiOptInLevel = 'schema'
@@ -54,16 +55,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (IS_PLATFORM && orgSlug && authorization && projectRef) {
// Get organizations and compute opt in level server-side
- const { aiOptInLevel: orgAIOptInLevel } = await getOrgAIDetails({
+ const { aiOptInLevel: orgAIOptInLevel, isLimited: orgAILimited } = await getOrgAIDetails({
orgSlug,
authorization,
projectRef,
})
aiOptInLevel = orgAIOptInLevel
+ isLimited = orgAILimited
}
- const { model, error: modelError } = await getModel(projectRef)
+ const { model, error: modelError, supportsCachePoint } = await getModel(projectRef, isLimited)
if (modelError) {
return res.status(500).json({ error: modelError.message })
@@ -109,12 +111,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
{
role: 'system',
content: system,
- providerOptions: {
- bedrock: {
- // Always cache the system prompt (must not contain dynamic content)
- cachePoint: { type: 'default' },
+ ...(supportsCachePoint && {
+ providerOptions: {
+ bedrock: {
+ // Always cache the system prompt (must not contain dynamic content)
+ cachePoint: { type: 'default' },
+ },
},
- },
+ }),
},
{
role: 'assistant',
@@ -149,19 +153,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
accessToken,
})
- const { experimental_output } = await generateText({
+ const { text } = await generateText({
model,
stopWhen: stepCountIs(5),
- experimental_output: Output.object({
- schema: z.object({
- code: z.string().describe('The modified code'),
- }),
- }),
messages: coreMessages,
tools,
})
- return res.status(200).json(experimental_output?.code)
+ return res.status(200).json(text)
} catch (error) {
console.error('Completion error:', error)
return res.status(500).json({
diff --git a/apps/studio/pages/api/ai/sql/generate-v4.ts b/apps/studio/pages/api/ai/sql/generate-v4.ts
index 4603683725d82..1bf5038bef38b 100644
--- a/apps/studio/pages/api/ai/sql/generate-v4.ts
+++ b/apps/studio/pages/api/ai/sql/generate-v4.ts
@@ -117,7 +117,7 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
}
}
- const { model, error: modelError } = await getModel(projectRef, isLimited) // use project ref as routing key
+ const { model, error: modelError, supportsCachePoint } = await getModel(projectRef, isLimited) // use project ref as routing key
if (modelError) {
return res.status(500).json({ error: modelError.message })
@@ -165,12 +165,14 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
{
role: 'system',
content: system,
- providerOptions: {
- bedrock: {
- // Always cache the system prompt (must not contain dynamic content)
- cachePoint: { type: 'default' },
+ ...(supportsCachePoint && {
+ providerOptions: {
+ bedrock: {
+ // Always cache the system prompt (must not contain dynamic content)
+ cachePoint: { type: 'default' },
+ },
},
- },
+ }),
},
{
role: 'assistant',
diff --git a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
index 51a722e19345e..c283adf4a15cd 100644
--- a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
+++ b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
@@ -59,8 +59,8 @@ const VercelIntegration: NextPageWithLayout = () => {
const supabaseProjects = useMemo(
() =>
- supabaseProjectsData
- ?.filter(
+ (supabaseProjectsData?.projects ?? [])
+ .filter(
(project) =>
project.organization_id === organization?.id &&
(project.status === PROJECT_STATUS['ACTIVE_HEALTHY'] ||
diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx
index 16e115c33933e..1612259122d49 100644
--- a/apps/studio/pages/new/[slug].tsx
+++ b/apps/studio/pages/new/[slug].tsx
@@ -11,7 +11,7 @@ import { z } from 'zod'
import { PopoverSeparator } from '@ui/components/shadcn/ui/popover'
import { components } from 'api-types'
-import { LOCAL_STORAGE_KEYS, useParams } from 'common'
+import { LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common'
import {
FreeProjectLimitWarning,
NotOrganizationOwnerWarning,
@@ -53,7 +53,6 @@ import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { withAuth } from 'hooks/misc/withAuth'
-import { useFlag } from 'hooks/ui/useFlag'
import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils'
import {
AWS_REGIONS_DEFAULT,
@@ -430,7 +429,7 @@ const Wizard: NextPageWithLayout = () => {
useEffect(() => {
// Only set once to ensure compute credits dont change while project is being created
if (allProjectsFromApi && !allProjects) {
- setAllProjects(allProjectsFromApi)
+ setAllProjects(allProjectsFromApi.projects)
}
}, [allProjectsFromApi, allProjects, setAllProjects])
diff --git a/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx b/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx
index 2e45d0d18789b..916e5085309c3 100644
--- a/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx
+++ b/apps/studio/pages/project/[ref]/database/replication/[pipelineId].tsx
@@ -1,13 +1,12 @@
import { useRouter } from 'next/router'
import { useContext, useEffect } from 'react'
-import { FeatureFlagContext, useParams } from 'common'
+import { FeatureFlagContext, useFlag, useParams } from 'common'
import { ReplicationPipelineStatus } from 'components/interfaces/Database/Replication/ReplicationPipelineStatus'
import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import { FormHeader } from 'components/ui/Forms/FormHeader'
-import { useFlag } from 'hooks/ui/useFlag'
import { PipelineRequestStatusProvider } from 'state/replication-pipeline-request-status'
import type { NextPageWithLayout } from 'types'
diff --git a/apps/studio/pages/project/[ref]/database/replication/index.tsx b/apps/studio/pages/project/[ref]/database/replication/index.tsx
index b89a4e10a1602..ee3b304caa7aa 100644
--- a/apps/studio/pages/project/[ref]/database/replication/index.tsx
+++ b/apps/studio/pages/project/[ref]/database/replication/index.tsx
@@ -1,4 +1,4 @@
-import { useParams } from 'common'
+import { useFlag, useParams } from 'common'
import { ReplicationComingSoon } from 'components/interfaces/Database/Replication/ComingSoon'
import { Destinations } from 'components/interfaces/Database/Replication/Destinations'
import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
@@ -7,7 +7,6 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { UnknownInterface } from 'components/ui/UnknownInterface'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
-import { useFlag } from 'hooks/ui/useFlag'
import { PipelineRequestStatusProvider } from 'state/replication-pipeline-request-status'
import type { NextPageWithLayout } from 'types'
diff --git a/apps/studio/pages/project/[ref]/realtime/policies.tsx b/apps/studio/pages/project/[ref]/realtime/policies.tsx
index 028d3990d2e4b..54dea9feff308 100644
--- a/apps/studio/pages/project/[ref]/realtime/policies.tsx
+++ b/apps/studio/pages/project/[ref]/realtime/policies.tsx
@@ -2,15 +2,35 @@ import { RealtimePolicies } from 'components/interfaces/Realtime/Policies'
import type { NextPageWithLayout } from 'types'
import DefaultLayout from 'components/layouts/DefaultLayout'
+import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
import RealtimeLayout from 'components/layouts/RealtimeLayout/RealtimeLayout'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
+import { DocsButton } from 'components/ui/DocsButton'
const RealtimePoliciesPage: NextPageWithLayout = () => {
- return
+ return (
+
+
+
+
+
+ )
}
RealtimePoliciesPage.getLayout = (page) => (
- {page}
+
+
+ }
+ size="large"
+ >
+ {page}
+
+
)
diff --git a/apps/studio/pages/project/[ref]/reports/database.tsx b/apps/studio/pages/project/[ref]/reports/database.tsx
index ace9855c9a8fa..48ab74b916f04 100644
--- a/apps/studio/pages/project/[ref]/reports/database.tsx
+++ b/apps/studio/pages/project/[ref]/reports/database.tsx
@@ -6,7 +6,7 @@ import Link from 'next/link'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
-import { useParams } from 'common'
+import { useFlag, useParams } from 'common'
import ReportChart from 'components/interfaces/Reports/ReportChart'
import ReportHeader from 'components/interfaces/Reports/ReportHeader'
import ReportPadding from 'components/interfaces/Reports/ReportPadding'
@@ -27,8 +27,8 @@ import { ReportSettings } from 'components/ui/Charts/ReportSettings'
import GrafanaPromoBanner from 'components/ui/GrafanaPromoBanner'
import Panel from 'components/ui/Panel'
import { analyticsKeys } from 'data/analytics/keys'
-import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
import { useDiskAttributesQuery } from 'data/config/disk-attributes-query'
+import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { useMaxConnectionsQuery } from 'data/database/max-connections-query'
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
@@ -38,7 +38,6 @@ import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useReportDateRange } from 'hooks/misc/useReportDateRange'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { useFlag } from 'hooks/ui/useFlag'
import { formatBytes } from 'lib/helpers'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import type { NextPageWithLayout } from 'types'
diff --git a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx b/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx
index 72f40dd8e683d..99b866a2e9498 100644
--- a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx
+++ b/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx
@@ -1,6 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import JWTSecretKeysTable from 'components/interfaces/JwtSecrets/jwt-secret-keys-table'
+import { JWTSecretKeysTable } from 'components/interfaces/JwtSecrets/jwt-secret-keys-table'
import DefaultLayout from 'components/layouts/DefaultLayout'
import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout'
import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
diff --git a/apps/studio/pages/support/new.tsx b/apps/studio/pages/support/new.tsx
index 0c1f57637cd60..c3e402d615575 100644
--- a/apps/studio/pages/support/new.tsx
+++ b/apps/studio/pages/support/new.tsx
@@ -91,7 +91,7 @@ const SupportPage: NextPageWithLayout = () => {
) : (
{
Project name
ID
- {projectsData?.map((project) => (
+ {(projectsData?.projects ?? []).map((project) => (