-
-
+
+
+
+
+
+
The project "{project?.name}" is currently paused
+
+ {isLoading &&
}
-
-
-
- The project "{project?.name ?? ''}" is currently paused.
-
-
+ {isPauseStatusSuccess && !isRestoreDisabled ? (
+ isFreePlan ? (
+ <>
+
+ All data, including backups and storage objects, remains safe. You can
+ resume this project from the dashboard within{' '}
+
+
+
+ {finalDaysRemainingBeforeRestoreDisabled} day
+ {finalDaysRemainingBeforeRestoreDisabled > 1 ? 's' : ''}
+ {' '}
+
+
+ Free projects cannot be restored through the dashboard if they are
+ paused for more than {pauseStatus.max_days_till_restore_disabled} days
+
+ {' '}
+ (until{' '}
+
+ ). After that, this project will not be resumable, but data will still be
+ available for download.
+
+
+ {enableProBenefitWording === 'variant-a'
+ ? 'Upgrade to Pro to prevent pauses and unlock features like branching, compute upgrades, and daily backups.'
+ : 'To prevent future pauses, consider upgrading to Pro.'}
+
+ >
+ ) : (
+
+ Your project data is safe but inaccessible while paused. Once resumed, usage
+ will be billed by compute size and hours active.
+
+ )
+ ) : !isLoading ? (
+
All of your project's data is still intact, but your project is inaccessible
while paused.{' '}
{product !== undefined ? (
<>
- Restore this project to access the{' '}
- {product} page
+ Resume this project to access the{' '}
+ {product} page.
>
- ) : (
- 'Restore this project and get back to building!'
- )}
+ ) : !isRestoreDisabled ? (
+ 'Resume this project and get back to building!'
+ ) : null}
-
-
- {isLoading &&
}
- {isError && (
-
- )}
- {isSuccess && (
- <>
- {isRestoreDisabled ? (
-
- ) : isFreePlan ? (
- <>
-
- {enableProBenefitWording === 'variant-a'
- ? 'Upgrade to Pro plan to prevent future pauses and use Pro features like branching, compute upgrades, and daily backups.'
- : 'To prevent future pauses, consider upgrading to Pro.'}
-
-
-
- Project can be restored through the dashboard within the next{' '}
- {finalDaysRemainingBeforeRestoreDisabled} day
- {finalDaysRemainingBeforeRestoreDisabled > 1 ? 's' : ''}
-
-
- Free projects cannot be restored through the dashboard if they are
- paused for more than{' '}
-
- {pauseStatus?.max_days_till_restore_disabled} days
-
- . The latest that your project can be restored is by{' '}
-
- {dayjs()
- .utc()
- .add(pauseStatus.remaining_days_till_restore_disabled ?? 0, 'day')
- .format('DD MMM YYYY')}
-
- . However, your database backup and Storage objects will still be
- available for download thereafter.
-
-
- }>
-
- More information
-
-
-
-
- >
- ) : (
-
- )}
- >
- )}
+ ) : null}
-
- {isSuccess && !isRestoreDisabled && (
-
-
- Restore project
-
- {isFreePlan ? (
-
- ) : (
-
- )}
-
- )}
-
-
+
+
+ {isError && (
+
+ )}
+
+ {isPauseStatusSuccess && !isRestoreDisabled && (
+
+
+ Resume project
+
+
+ {isFreePlan ? (
+
+ ) : (
+
+ )}
+
+ )}
-
}
+
+
+
setShowConfirmRestore(false)}
- header={'Restore this project'}
+ onConfirm={() => form.handleSubmit(onConfirmRestore)()}
+ loading={isRestoring}
+ confirmLabel="Confirm resume"
+ cancelLabel="Cancel"
>
-
+
-
setShowFreeProjectLimitWarning(false)}
+
+
+
+
+ Your organization has members who have exceeded their free project limits
+
+
+
+
+ The following members have reached their maximum limits for the number of active free
+ plan projects within organizations where they are an administrator or owner:
+
+
+ {(membersExceededLimit || []).map((member, idx: number) => (
+ -
+ {member.username || member.primary_email} (Limit: {member.free_project_limit} free
+ projects)
+
+ ))}
+
+
+ These members will need to either delete, pause, or upgrade one or more of these
+ projects before you're able to resume this project.
+
+
+
+
+
+
+
>
)
}
diff --git a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
index 68d6e88b3597b..507121413df39 100644
--- a/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/ProjectLayout.tsx
@@ -1,12 +1,13 @@
-import { AnimatePresence, motion } from 'framer-motion'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { forwardRef, Fragment, PropsWithChildren, ReactNode, useEffect, useState } from 'react'
-import { useParams, useFlag } from 'common'
+
+import { useFlag, useParams } from 'common'
import { CreateBranchModal } from 'components/interfaces/BranchManagement/CreateBranchModal'
-import ProjectAPIDocs from 'components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
+import { ProjectAPIDocs } from 'components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
import { Loading } from 'components/ui/Loading'
import { ResourceExhaustionWarningBanner } from 'components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner'
+import { AnimatePresence, motion } from 'framer-motion'
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
@@ -19,6 +20,8 @@ import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav'
import { useEditorType } from '../editors/EditorsLayout.hooks'
import BuildingState from './BuildingState'
import ConnectingState from './ConnectingState'
+import { LayoutSidebar } from './LayoutSidebar'
+import { LayoutSidebarProvider } from './LayoutSidebar/LayoutSidebarProvider'
import { LoadingState } from './LoadingState'
import { ProjectPausedState } from './PausedState/ProjectPausedState'
import { PauseFailedState } from './PauseFailedState'
@@ -29,8 +32,6 @@ import RestartingState from './RestartingState'
import { RestoreFailedState } from './RestoreFailedState'
import RestoringState from './RestoringState'
import { UpgradingState } from './UpgradingState'
-import { LayoutSidebar } from './LayoutSidebar'
-import { LayoutSidebarProvider } from './LayoutSidebar/LayoutSidebarProvider'
// [Joshen] This is temporary while we unblock users from managing their project
// if their project is not responding well for any reason. Eventually needs a bit of an overhaul
diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx
index 7cade33d6da85..28dda2cedd8b6 100644
--- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx
+++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx
@@ -19,8 +19,8 @@ import { Snippet, SnippetFolder, useSQLSnippetFoldersQuery } from 'data/content/
import { useSqlSnippetsQuery } from 'data/content/sql-snippets-query'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
-import uuidv4 from 'lib/uuid'
import { useSnippetFolders, useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
import { TreeView } from 'ui'
diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx
index e22c2d522138b..89e82df6d9269 100644
--- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx
+++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx
@@ -24,8 +24,8 @@ import { Snippet } from 'data/content/sql-folders-query'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import useLatest from 'hooks/misc/useLatest'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
-import uuidv4 from 'lib/uuid'
import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import {
Button,
diff --git a/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx b/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
index 10057b9076094..81be39cbbada9 100644
--- a/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
+++ b/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
@@ -25,6 +25,8 @@ const StorageLayout = ({ title, children }: StorageLayoutProps) => {
const { pathname } = router
const suffix = !!featurePreviewModal ? `?featurePreviewModal=${featurePreviewModal}` : ''
+ if (!ref) return
+
if (isStorageV2) {
// From old UI to new UI
if (pathname.endsWith('/storage/settings')) {
@@ -58,7 +60,7 @@ const StorageLayout = ({ title, children }: StorageLayoutProps) => {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isStorageV2])
+ }, [ref, isStorageV2])
return (
>
+
+export const useDeletePublicationMutation = ({
+ onSuccess,
+ onError,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (vars) => deletePublication(vars),
+ async onSuccess(data, variables, context) {
+ const { projectRef, sourceId } = variables
+ await queryClient.invalidateQueries(replicationKeys.publications(projectRef, sourceId))
+ await onSuccess?.(data, variables, context)
+ },
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ toast.error(`Failed to delete publication: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ },
+ ...options,
+ })
+}
diff --git a/apps/studio/data/replication/publications-query.ts b/apps/studio/data/replication/publications-query.ts
index 576424cd09aee..8a10561946baf 100644
--- a/apps/studio/data/replication/publications-query.ts
+++ b/apps/studio/data/replication/publications-query.ts
@@ -1,11 +1,15 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
+import { components } from 'api-types'
import { get, handleError } from 'data/fetchers'
import { ResponseError } from 'types'
import { replicationKeys } from './keys'
type ReplicationPublicationsParams = { projectRef?: string; sourceId?: number }
+export type ReplicationPublication =
+ components['schemas']['ReplicationPublicationsResponse']['publications'][number]
+
async function fetchReplicationPublications(
{ projectRef, sourceId }: ReplicationPublicationsParams,
signal?: AbortSignal
diff --git a/apps/studio/data/storage/analytics-bucket-create-mutation.ts b/apps/studio/data/storage/analytics-bucket-create-mutation.ts
new file mode 100644
index 0000000000000..55d9a081ee3ac
--- /dev/null
+++ b/apps/studio/data/storage/analytics-bucket-create-mutation.ts
@@ -0,0 +1,58 @@
+// @ts-nocheck
+// [Joshen] To remove after infra changes for analytics bucket is in
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { toast } from 'sonner'
+
+import { components } from 'api-types'
+import { handleError, post } from 'data/fetchers'
+import type { ResponseError } from 'types'
+import { storageKeys } from './keys'
+
+type AnalyticsBucketCreateVariables = CreateAnalyticsBucketBody & {
+ projectRef: string
+}
+
+type CreateAnalyticsBucketBody = components['schemas']['CreateStorageAnalyticsBucketBody']
+
+async function createAnalyticsBucket({ projectRef, bucketName }: AnalyticsBucketCreateVariables) {
+ if (!projectRef) throw new Error('projectRef is required')
+ if (!bucketName) throw new Error('Bucket name is required')
+
+ const { data, error } = await post('/platform/storage/{ref}/analytics-buckets', {
+ params: { path: { ref: projectRef } },
+ body: { bucketName },
+ })
+
+ if (error) handleError(error)
+ return data
+}
+
+type AnalyticsBucketCreateData = Awaited>
+
+export const useAnalyticsBucketCreateMutation = ({
+ onSuccess,
+ onError,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (vars) => createAnalyticsBucket(vars),
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+ await queryClient.invalidateQueries(storageKeys.analyticsBuckets(projectRef))
+ await onSuccess?.(data, variables, context)
+ },
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ toast.error(`Failed to create analytics bucket: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ },
+ ...options,
+ })
+}
diff --git a/apps/studio/data/storage/analytics-bucket-delete-mutation.ts b/apps/studio/data/storage/analytics-bucket-delete-mutation.ts
new file mode 100644
index 0000000000000..d71105d2b78a3
--- /dev/null
+++ b/apps/studio/data/storage/analytics-bucket-delete-mutation.ts
@@ -0,0 +1,61 @@
+// @ts-nocheck
+// [Joshen] To remove after infra changes for analytics bucket is in
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { toast } from 'sonner'
+
+import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { del, handleError } from 'data/fetchers'
+import type { ResponseError } from 'types'
+import { storageKeys } from './keys'
+
+type AnalyticsBucketDeleteVariables = {
+ projectRef: string
+ id: string
+}
+
+async function deleteAnalyticsBucket({ projectRef, id }: AnalyticsBucketDeleteVariables) {
+ if (!projectRef) throw new Error('projectRef is required')
+ if (!id) throw new Error('Bucket name is requried')
+
+ const { data, error } = await del('/platform/storage/{ref}/analytics-buckets/{id}', {
+ params: { path: { ref: projectRef, id } },
+ } as any)
+
+ if (error) handleError(error)
+ return data
+}
+
+type AnalyticsBucketDeleteData = Awaited>
+
+export const useAnalyticsBucketDeleteMutation = ({
+ onSuccess,
+ onError,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+ const isStorageV2 = useIsNewStorageUIEnabled()
+
+ return useMutation({
+ mutationFn: (vars) => deleteAnalyticsBucket(vars),
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+ if (isStorageV2) {
+ await queryClient.invalidateQueries(storageKeys.analyticsBuckets(projectRef))
+ } else {
+ await queryClient.invalidateQueries(storageKeys.buckets(projectRef))
+ }
+ await onSuccess?.(data, variables, context)
+ },
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ toast.error(`Failed to delete analytics bucket: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ },
+ ...options,
+ })
+}
diff --git a/apps/studio/data/storage/analytics-buckets-query.ts b/apps/studio/data/storage/analytics-buckets-query.ts
new file mode 100644
index 0000000000000..03fd2c7e65b6b
--- /dev/null
+++ b/apps/studio/data/storage/analytics-buckets-query.ts
@@ -0,0 +1,66 @@
+// @ts-nocheck
+// [Joshen] To remove after infra changes for analytics bucket is in
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+
+import { components } from 'api-types'
+import { get, handleError } from 'data/fetchers'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { PROJECT_STATUS } from 'lib/constants'
+import type { ResponseError } from 'types'
+import { storageKeys } from './keys'
+
+export type AnalyticsBucketsVariables = { projectRef?: string }
+export type AnalyticsBucket = components['schemas']['StorageAnalyticsBucketResponse']
+export type AnalyticsBuckets = components['schemas'][]
+
+export async function getAnalyticsBuckets(
+ { projectRef }: AnalyticsBucketsVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) throw new Error('projectRef is required')
+
+ const { data, error } = await get('/platform/storage/{ref}/analytics-buckets', {
+ params: { path: { ref: projectRef } },
+ signal,
+ })
+
+ if (error) handleError(error)
+ return data.data
+}
+
+export type AnalyticsBucketsData = Awaited>
+export type AnalyticsBucketsError = ResponseError
+
+export const useAnalyticsBucketsQuery = (
+ { projectRef }: AnalyticsBucketsVariables,
+ {
+ enabled = true,
+ ...options
+ }: UseQueryOptions = {}
+) => {
+ const { data: project } = useSelectedProjectQuery()
+ const isActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
+
+ return useQuery({
+ queryKey: storageKeys.analyticsBuckets(projectRef),
+ queryFn: ({ signal }) => getAnalyticsBuckets({ projectRef }, signal),
+ enabled: enabled && typeof projectRef !== 'undefined' && isActive,
+ ...options,
+ retry: (failureCount, error) => {
+ if (
+ typeof error === 'object' &&
+ error !== null &&
+ error.message.startsWith('Tenant config') &&
+ error.message.endsWith('not found')
+ ) {
+ return false
+ }
+
+ if (failureCount < 3) {
+ return true
+ }
+
+ return false
+ },
+ })
+}
diff --git a/apps/studio/data/storage/bucket-delete-mutation.ts b/apps/studio/data/storage/bucket-delete-mutation.ts
index 77f2db799110e..7586d1524530b 100644
--- a/apps/studio/data/storage/bucket-delete-mutation.ts
+++ b/apps/studio/data/storage/bucket-delete-mutation.ts
@@ -3,29 +3,26 @@ import { toast } from 'sonner'
import { del, handleError, post } from 'data/fetchers'
import type { ResponseError } from 'types'
-import { BucketType } from './buckets-query'
import { storageKeys } from './keys'
type BucketDeleteVariables = {
projectRef: string
id: string
- type: BucketType
}
-async function deleteBucket({ projectRef, id, type }: BucketDeleteVariables) {
+async function deleteBucket({ projectRef, id }: BucketDeleteVariables) {
if (!projectRef) throw new Error('projectRef is required')
if (!id) throw new Error('Bucket name is requried')
- if (type !== 'ANALYTICS') {
- const { error: emptyBucketError } = await post('/platform/storage/{ref}/buckets/{id}/empty', {
- params: { path: { ref: projectRef, id } },
- })
- if (emptyBucketError) handleError(emptyBucketError)
- }
+ const { error: emptyBucketError } = await post('/platform/storage/{ref}/buckets/{id}/empty', {
+ params: { path: { ref: projectRef, id } },
+ })
+ if (emptyBucketError) handleError(emptyBucketError)
const { data, error: deleteBucketError } = await del('/platform/storage/{ref}/buckets/{id}', {
- params: { path: { ref: projectRef, id }, query: { type } },
+ params: { path: { ref: projectRef, id } },
} as any)
+
if (deleteBucketError) handleError(deleteBucketError)
return data
}
diff --git a/apps/studio/data/storage/buckets-query.ts b/apps/studio/data/storage/buckets-query.ts
index 0779e0d9f6369..621e069d9d405 100644
--- a/apps/studio/data/storage/buckets-query.ts
+++ b/apps/studio/data/storage/buckets-query.ts
@@ -44,8 +44,7 @@ export const useBucketsQuery = (
if (
typeof error === 'object' &&
error !== null &&
- error.message.startsWith('Tenant config') &&
- error.message.endsWith('not found')
+ error.message.includes('Missing tenant config')
) {
return false
}
diff --git a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
index 56752fd634651..0b2c08edf9763 100644
--- a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
+++ b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts
@@ -1,7 +1,10 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { snakeCase } from 'lodash'
import { WRAPPERS } from 'components/interfaces/Integrations/Wrappers/Wrappers.constants'
+import {
+ getAnalyticsBucketFDWName,
+ getAnalyticsBucketS3KeyName,
+} from 'components/interfaces/Storage/AnalyticsBucketDetails/AnalyticsBucketDetails.utils'
import {
getCatalogURI,
getConnectionURL,
@@ -40,10 +43,10 @@ export const useIcebergWrapperCreateMutation = () => {
const mutateAsync = async ({ bucketName }: { bucketName: string }) => {
const createS3KeyData = await createS3AccessKey({
projectRef: project?.ref,
- description: `${snakeCase(bucketName)}_keys`,
+ description: getAnalyticsBucketS3KeyName(bucketName),
})
- const wrapperName = `${snakeCase(bucketName)}_fdw`
+ const wrapperName = getAnalyticsBucketFDWName(bucketName)
const params: FDWCreateVariables = {
projectRef: project?.ref,
diff --git a/apps/studio/data/storage/keys.ts b/apps/studio/data/storage/keys.ts
index e68911d547c71..5d796b07b0dcc 100644
--- a/apps/studio/data/storage/keys.ts
+++ b/apps/studio/data/storage/keys.ts
@@ -1,5 +1,7 @@
export const storageKeys = {
buckets: (projectRef: string | undefined) => ['projects', projectRef, 'buckets'] as const,
+ analyticsBuckets: (projectRef: string | undefined) =>
+ ['projects', projectRef, 'analytics-buckets'] as const,
archive: (projectRef: string | undefined) => ['projects', projectRef, 'archive'] as const,
icebergNamespaces: (catalog: string, warehouse: string) =>
['catalog', catalog, 'warehouse', warehouse, 'namespaces'] as const,
diff --git a/apps/studio/data/storage/s3-access-key-query.ts b/apps/studio/data/storage/s3-access-key-query.ts
index ffdc0243653d9..61adee253acf4 100644
--- a/apps/studio/data/storage/s3-access-key-query.ts
+++ b/apps/studio/data/storage/s3-access-key-query.ts
@@ -1,12 +1,17 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
+import { components } from 'api-types'
import { get, handleError } from 'data/fetchers'
+import { IS_PLATFORM } from 'lib/constants'
import { ResponseError } from 'types'
import { storageCredentialsKeys } from './s3-access-key-keys'
-import { IS_PLATFORM } from 'lib/constants'
type StorageCredentialsVariables = { projectRef?: string }
+export type S3AccessKey = components['schemas']['GetStorageCredentialsResponse']['data'][number] & {
+ access_key: string
+}
+
async function fetchStorageCredentials(
{ projectRef }: StorageCredentialsVariables,
signal?: AbortSignal
@@ -19,16 +24,7 @@ async function fetchStorageCredentials(
})
if (error) handleError(error)
-
- // Generated types by openapi are wrong so we need to cast it.
- return data as unknown as {
- data: {
- id: string
- created_at: string
- access_key: string
- description: string
- }[]
- }
+ return data as { data: S3AccessKey[] }
}
export type StorageCredentialsData = Awaited>
diff --git a/apps/studio/lib/helpers.test.ts b/apps/studio/lib/helpers.test.ts
index 1c53bda36cdac..7699fdd23108b 100644
--- a/apps/studio/lib/helpers.test.ts
+++ b/apps/studio/lib/helpers.test.ts
@@ -1,4 +1,6 @@
+import { v4 as _uuidV4 } from 'uuid'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
import {
detectBrowser,
detectOS,
@@ -22,10 +24,23 @@ import {
timeout,
tryParseInt,
tryParseJson,
+ uuidv4,
} from './helpers'
import { copyToClipboard } from 'ui'
+vi.mock('uuid', () => ({
+ v4: vi.fn(() => 'mocked-uuid'),
+}))
+
+describe('uuidv4', () => {
+ it('calls uuid.v4 and returns the result', () => {
+ const result = uuidv4()
+ expect(_uuidV4).toHaveBeenCalled()
+ expect(result).toBe('mocked-uuid')
+ })
+})
+
describe('tryParseJson', () => {
it('should return the parsed JSON', () => {
const result = tryParseJson('{"test": "test"}')
diff --git a/apps/studio/lib/helpers.ts b/apps/studio/lib/helpers.ts
index 7c4d38c1b0b50..a0a21bd26e80b 100644
--- a/apps/studio/lib/helpers.ts
+++ b/apps/studio/lib/helpers.ts
@@ -1,7 +1,12 @@
-export { default as uuidv4 } from './uuid'
import { UIEvent } from 'react'
+import { v4 as _uuidV4 } from 'uuid'
+
import type { TablesData } from '../data/tables/tables-query'
+export const uuidv4 = () => {
+ return _uuidV4()
+}
+
export const isAtBottom = ({ currentTarget }: UIEvent): boolean => {
return currentTarget.scrollTop + 10 >= currentTarget.scrollHeight - currentTarget.clientHeight
}
diff --git a/apps/studio/lib/uuid.test.ts b/apps/studio/lib/uuid.test.ts
deleted file mode 100644
index 6ceeb8bf49aa7..0000000000000
--- a/apps/studio/lib/uuid.test.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { describe, it, expect, vi } from 'vitest'
-import { v4 as _uuidV4 } from 'uuid'
-import uuidv4 from './uuid'
-
-vi.mock('uuid', () => ({
- v4: vi.fn(() => 'mocked-uuid'),
-}))
-
-describe('uuidv4', () => {
- it('calls uuid.v4 and returns the result', () => {
- const result = uuidv4()
- expect(_uuidV4).toHaveBeenCalled()
- expect(result).toBe('mocked-uuid')
- })
-})
diff --git a/apps/studio/lib/uuid.ts b/apps/studio/lib/uuid.ts
deleted file mode 100644
index c33432b302deb..0000000000000
--- a/apps/studio/lib/uuid.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { v4 as _uuidV4 } from 'uuid'
-
-const uuidv4 = () => {
- return _uuidV4()
-}
-
-export default uuidv4
diff --git a/apps/studio/pages/project/[ref]/settings/general.tsx b/apps/studio/pages/project/[ref]/settings/general.tsx
index 11943bbd52f38..618016b289f99 100644
--- a/apps/studio/pages/project/[ref]/settings/general.tsx
+++ b/apps/studio/pages/project/[ref]/settings/general.tsx
@@ -1,11 +1,9 @@
import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils'
-import {
- ComplianceConfig,
- CustomDomainConfig,
- General,
- TransferProjectPanel,
-} from 'components/interfaces/Settings/General'
+import { ComplianceConfig } from 'components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode'
+import { CustomDomainConfig } from 'components/interfaces/Settings/General/CustomDomainConfig/CustomDomainConfig'
import { DeleteProjectPanel } from 'components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectPanel'
+import { General } from 'components/interfaces/Settings/General/General'
+import { TransferProjectPanel } from 'components/interfaces/Settings/General/TransferProjectPanel/TransferProjectPanel'
import DefaultLayout from 'components/layouts/DefaultLayout'
import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
import { ScaffoldContainer, ScaffoldHeader, ScaffoldTitle } from 'components/layouts/Scaffold'
diff --git a/apps/studio/pages/project/[ref]/storage/analytics/buckets/[bucketId].tsx b/apps/studio/pages/project/[ref]/storage/analytics/buckets/[bucketId].tsx
index 19e8e6768178c..7ea03e11ad711 100644
--- a/apps/studio/pages/project/[ref]/storage/analytics/buckets/[bucketId].tsx
+++ b/apps/studio/pages/project/[ref]/storage/analytics/buckets/[bucketId].tsx
@@ -1,13 +1,17 @@
-import { useParams } from 'common'
+import Link from 'next/link'
-import { AnalyticBucketDetails } from 'components/interfaces/Storage/AnalyticBucketDetails'
+import { useParams } from 'common'
+import { AnalyticBucketDetails } from 'components/interfaces/Storage/AnalyticsBucketDetails'
import StorageBucketsError from 'components/interfaces/Storage/StorageBucketsError'
import { useSelectedBucket } from 'components/interfaces/Storage/StorageExplorer/useSelectedBucket'
import DefaultLayout from 'components/layouts/DefaultLayout'
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
+import { AnalyticsBucket } from 'data/storage/analytics-buckets-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
import type { NextPageWithLayout } from 'types'
+import { Button } from 'ui'
+import { Admonition } from 'ui-patterns'
const AnalyticsBucketPage: NextPageWithLayout = () => {
const { bucketId } = useParams()
@@ -16,23 +20,29 @@ const AnalyticsBucketPage: NextPageWithLayout = () => {
const { bucket, error, isSuccess, isError } = useSelectedBucket()
// [Joshen] Checking against projectRef from storage explorer to check if the store has initialized
+ // We can probably replace this with a better skeleton loader that's more representative of the page layout
if (!project || !projectRef) return null
return (
-
+
{isError &&
}
{isSuccess ? (
!bucket ? (
-
Bucket {bucketId} cannot be found
+
+
+
- ) : bucket.type === 'ANALYTICS' ? (
-
) : (
-
-
This bucket is not an analytics bucket
-
+
)
) : null}
diff --git a/apps/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx b/apps/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx
index 241ecb1f31dc9..8965ff4b64e45 100644
--- a/apps/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx
+++ b/apps/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx
@@ -1,11 +1,12 @@
import { useParams } from 'common'
-import { AnalyticBucketDetails } from 'components/interfaces/Storage/AnalyticBucketDetails'
+import { AnalyticBucketDetails } from 'components/interfaces/Storage/AnalyticsBucketDetails'
import StorageBucketsError from 'components/interfaces/Storage/StorageBucketsError'
import { StorageExplorer } from 'components/interfaces/Storage/StorageExplorer/StorageExplorer'
import { useSelectedBucket } from 'components/interfaces/Storage/StorageExplorer/useSelectedBucket'
import DefaultLayout from 'components/layouts/DefaultLayout'
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
+import { AnalyticsBucket } from 'data/storage/analytics-buckets-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
import type { NextPageWithLayout } from 'types'
@@ -29,7 +30,7 @@ const PageLayout: NextPageWithLayout = () => {
Bucket {bucketId} cannot be found
) : bucket.type === 'ANALYTICS' ? (
-
+
) : (
)
diff --git a/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx b/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx
index 40903b99a29f8..8a8b741620908 100644
--- a/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx
+++ b/apps/studio/pages/project/[ref]/storage/files/buckets/[bucketId].tsx
@@ -10,6 +10,7 @@ import { useSelectedBucket } from 'components/interfaces/Storage/StorageExplorer
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
+import { Bucket } from 'data/storage/buckets-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useStoragePolicyCounts } from 'hooks/storage/useStoragePolicyCounts'
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
@@ -23,8 +24,8 @@ const BucketPage: NextPageWithLayout = () => {
const { bucket, error, isSuccess, isError } = useSelectedBucket()
const [showEditModal, setShowEditModal] = useState(false)
- const { getPolicyCount } = useStoragePolicyCounts(bucket ? [bucket] : [])
- const policyCount = bucket ? getPolicyCount(bucket.name) : 0
+ const { getPolicyCount } = useStoragePolicyCounts(bucket ? [bucket as Bucket] : [])
+ const policyCount = bucket ? getPolicyCount(bucket.id) : 0
// [Joshen] Checking against projectRef from storage explorer to check if the store has initialized
if (!project || !projectRef || !isSuccess) return null
diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts
index 1985673d9ae9f..89941bc713e6c 100644
--- a/packages/api-types/types/api.d.ts
+++ b/packages/api-types/types/api.d.ts
@@ -3086,6 +3086,7 @@ export interface components {
root_key: string
}
PostgresConfigResponse: {
+ checkpoint_timeout?: number
effective_cache_size?: string
hot_standby_feedback?: boolean
logical_decoding_work_mem?: string
@@ -3725,6 +3726,7 @@ export interface components {
root_key: string
}
UpdatePostgresConfigBody: {
+ checkpoint_timeout?: number
effective_cache_size?: string
hot_standby_feedback?: boolean
logical_decoding_work_mem?: string
diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts
index bbc76eb612cf3..3d3762b524626 100644
--- a/packages/api-types/types/platform.d.ts
+++ b/packages/api-types/types/platform.d.ts
@@ -843,11 +843,10 @@ export interface paths {
cookie?: never
}
/** Get notifications */
- get: operations['NotificationsController_getNotificationsV2']
+ get: operations['NotificationsController_getNotifications']
put?: never
post?: never
- /** Delete notifications */
- delete: operations['NotificationsController_deleteNotifications']
+ delete?: never
options?: never
head?: never
/** Update notifications */
@@ -5818,9 +5817,6 @@ export interface components {
GetCloudMarketplaceRedirectUrlResponse: {
url: string
}
- GetContentCountResponse: {
- count: number
- }
GetContentCountV2Response: {
favorites: number
private: number
@@ -6842,6 +6838,7 @@ export interface components {
}
| {
enabled: boolean
+ unit: string
unlimited: boolean
value: number
}
@@ -6855,6 +6852,8 @@ export interface components {
type: 'boolean' | 'numeric' | 'set'
}
hasAccess: boolean
+ /** @enum {string} */
+ type: 'boolean' | 'numeric' | 'set'
}[]
}
ListGitHubConnectionsResponse: {
@@ -7017,18 +7016,7 @@ export interface components {
from: string
to: string
}
- NotificationResponseV1: {
- /** @description Any JSON-serializable value */
- data: unknown
- id: string
- inserted_at: string
- /** @description Any JSON-serializable value */
- meta: unknown
- notification_name: string
- notification_status: string
- project_id: number
- }
- NotificationResponseV2: {
+ NotificationResponse: {
/** @description Any JSON-serializable value */
data: unknown
id: string
@@ -7514,6 +7502,7 @@ export interface components {
relation_schema: string
}
PostgresConfigResponse: {
+ checkpoint_timeout?: number
effective_cache_size?: string
hot_standby_feedback?: boolean
logical_decoding_work_mem?: string
@@ -9701,7 +9690,7 @@ export interface components {
name: string
role_scoped_projects: string[]
}
- UpdateNotificationBodyV2: {
+ UpdateNotificationBody: {
/** Format: uuid */
id: string
/** @enum {string} */
@@ -9718,9 +9707,6 @@ export interface components {
lint_name?: string
note?: string
}
- UpdateNotificationsBodyV1: {
- ids: string[]
- }
UpdateOrganizationBody: {
additional_billing_emails?: string[]
/** Format: email */
@@ -9780,6 +9766,7 @@ export interface components {
server_lifetime?: number
}
UpdatePostgresConfigBody: {
+ checkpoint_timeout?: number
effective_cache_size?: string
hot_standby_feedback?: boolean
logical_decoding_work_mem?: string
@@ -12598,7 +12585,7 @@ export interface operations {
}
}
}
- NotificationsController_getNotificationsV2: {
+ NotificationsController_getNotifications: {
parameters: {
query?: {
limit?: number
@@ -12619,7 +12606,7 @@ export interface operations {
[name: string]: unknown
}
content: {
- 'application/json': components['schemas']['NotificationResponseV2'][]
+ 'application/json': components['schemas']['NotificationResponse'][]
}
}
/** @description Failed to retrieve notifications */
@@ -12631,36 +12618,6 @@ export interface operations {
}
}
}
- NotificationsController_deleteNotifications: {
- parameters: {
- query?: never
- header?: never
- path?: never
- cookie?: never
- }
- requestBody: {
- content: {
- 'application/json': components['schemas']['UpdateNotificationsBodyV1']
- }
- }
- responses: {
- 200: {
- headers: {
- [name: string]: unknown
- }
- content: {
- 'application/json': components['schemas']['NotificationResponseV1'][]
- }
- }
- /** @description Failed to delete notifications */
- 500: {
- headers: {
- [name: string]: unknown
- }
- content?: never
- }
- }
- }
NotificationsController_updateNotificationsV2: {
parameters: {
query?: never
@@ -12670,7 +12627,7 @@ export interface operations {
}
requestBody: {
content: {
- 'application/json': components['schemas']['UpdateNotificationBodyV2']
+ 'application/json': components['schemas']['UpdateNotificationBody']
}
}
responses: {
@@ -12679,7 +12636,7 @@ export interface operations {
[name: string]: unknown
}
content: {
- 'application/json': components['schemas']['NotificationResponseV2'][]
+ 'application/json': components['schemas']['NotificationResponse'][]
}
}
/** @description Failed to update notifications */