From 9eb3c22e99910e99130cda23979245333f716aed Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 8 Jan 2025 18:05:24 +0700 Subject: [PATCH 001/265] Clean up storage s3 protocol settings dynamic rendering (#32601) --- .../StorageSettings/CreateCredentialModal.tsx | 3 +- .../Storage/StorageSettings/S3Connection.tsx | 134 ++++++++---------- .../StorageSettings/StorageSettings.tsx | 5 +- 3 files changed, 61 insertions(+), 81 deletions(-) diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/CreateCredentialModal.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/CreateCredentialModal.tsx index 2df994c711e..db7a0b4d03d 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/CreateCredentialModal.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/CreateCredentialModal.tsx @@ -42,8 +42,7 @@ export const CreateCredentialModal = ({ visible, onOpenChange }: CreateCredentia const canCreateCredentials = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*') const { data: config } = useProjectStorageConfigQuery({ projectRef }) - // [Joshen clean up] Remove ?? true once storage BE changes are deployed - const isS3ConnectionEnabled = config?.features.s3Protocol?.enabled ?? true + const isS3ConnectionEnabled = config?.features.s3Protocol.enabled const disableCreation = !isProjectActive || !canCreateCredentials || !isS3ConnectionEnabled const FormSchema = z.object({ diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx index 21feb7436f7..80616b30845 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx @@ -84,9 +84,6 @@ export const S3Connection = () => { const hasStorageCreds = storageCreds?.data && storageCreds.data.length > 0 const s3connectionUrl = getConnectionURL(projectRef ?? '', protocol, endpoint) - // [Joshen clean up]: Can remove this boolean check once storage changes are deployed - const hasS3ProtocolFeature = config?.features.s3Protocol !== undefined - const onSubmit: SubmitHandler> = async (data) => { if (!projectRef) return console.error('Project ref is required') if (!config) return console.error('Storage config is required') @@ -100,9 +97,8 @@ export const S3Connection = () => { } useEffect(() => { - if (hasS3ProtocolFeature) { - form.reset({ s3ConnectionEnabled: config?.features.s3Protocol.enabled }) - } + form.reset({ s3ConnectionEnabled: config?.features.s3Protocol.enabled }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccessStorageConfig]) @@ -119,43 +115,39 @@ export const S3Connection = () => { ) : isProjectActive ? ( - {hasS3ProtocolFeature && ( - <> - ( - - - - - - )} - /> - - {isErrorStorageConfig && ( -
- ( + + + -
- )} + + + )} + /> - - + {isErrorStorageConfig && ( +
+ +
)} + +
{ )}
- {hasS3ProtocolFeature && ( - <> - + -
- {!canUpdateStorageSettings ? ( -

- You need additional permissions to update storage settings -

- ) : ( -
- )} -
- - -
-
- - )} +
+ {!canUpdateStorageSettings ? ( +

+ You need additional permissions to update storage settings +

+ ) : ( +
+ )} +
+ + +
+
) : ( diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx index b93787e3534..cabedb71ae5 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx @@ -131,12 +131,9 @@ const StorageSettings = () => { updateStorageConfig({ projectRef, fileSizeLimit: convertToBytes(data.fileSizeLimit, data.unit), - // @ts-ignore [Joshen clean up] Remove this once storage changes are deployed' features: { imageTransformation: { enabled: data.imageTransformationEnabled }, - ...(config.features.s3Protocol !== undefined - ? { s3Protocol: config?.features.s3Protocol } - : {}), + s3Protocol: { enabled: config.features.s3Protocol.enabled }, }, }) } From 82bb29a16b6bd1c925590c557b7b0f7951a624e9 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:11:59 +0100 Subject: [PATCH 002/265] fix: branch dropdown sort & default branch shield (#32650) sort and add shield --- .../layouts/AppLayout/BranchDropdown.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx index a90b28548e4..79c3f64d9c9 100644 --- a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx @@ -1,4 +1,4 @@ -import { AlertCircle, Check, ChevronsUpDown, ListTree, MessageCircle } from 'lucide-react' +import { AlertCircle, Check, ChevronsUpDown, ListTree, MessageCircle, Shield } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useState } from 'react' @@ -51,10 +51,11 @@ const BranchLink = ({ setOpen(false) }} > -

+

+ {branch.is_default && } {branch.name}

- {isSelected && } + {isSelected && } ) @@ -77,6 +78,15 @@ const BranchDropdown = ({ isNewNav = false }: BranchDropdownProps) => { const [open, setOpen] = useState(false) const selectedBranch = branches?.find((branch) => branch.project_ref === ref) + const mainBranch = branches?.find((branch) => branch.is_default) + const restOfBranches = branches + ?.filter((branch) => !branch.is_default) + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + + const sortedBranches = mainBranch + ? [mainBranch].concat(restOfBranches ?? []) + : restOfBranches ?? [] + const BRANCHING_GITHUB_DISCUSSION_LINK = 'https://github.com/orgs/supabase/discussions/18937' return ( @@ -112,7 +122,7 @@ const BranchDropdown = ({ isNewNav = false }: BranchDropdownProps) => { No branches found - {branches?.map((branch) => ( + {sortedBranches?.map((branch) => ( Date: Wed, 8 Jan 2025 12:13:42 +0100 Subject: [PATCH 003/265] remove lw13 announcements (#32624) --- apps/docs/features/app.providers.tsx | 3 --- apps/www/components/Hero/Hero.tsx | 7 ------- apps/www/pages/_app.tsx | 6 +----- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/docs/features/app.providers.tsx b/apps/docs/features/app.providers.tsx index 55b37d0ee0b..8513316a789 100644 --- a/apps/docs/features/app.providers.tsx +++ b/apps/docs/features/app.providers.tsx @@ -3,7 +3,6 @@ import { type PropsWithChildren } from 'react' import { ThemeProvider } from 'common' import { SonnerToaster } from 'ui' import { CommandProvider } from 'ui-patterns/CommandMenu' -// import { PromoToast } from 'ui-patterns/PromoToast' import SiteLayout from '~/layouts/SiteLayout' import { AuthContainer } from './auth/auth.client' import { DocsCommandMenu } from './command' @@ -11,7 +10,6 @@ import { QueryClientProvider } from './data/queryClient.client' import { PageTelemetry } from './telemetry/telemetry.client' import { ScrollRestoration } from './ui/helpers.scroll.client' import { ThemeSandbox } from './ui/theme.client' -import { PromoToast } from 'ui-patterns' /** * Global providers that wrap the entire app @@ -26,7 +24,6 @@ function GlobalProviders({ children }: PropsWithChildren) {
- {children} diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index caec1558767..073d584b9a0 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -23,13 +23,6 @@ const Hero = () => {
-
- -

Build in a weekend Scale to millions diff --git a/apps/www/pages/_app.tsx b/apps/www/pages/_app.tsx index a8982819b8d..23ce6964d85 100644 --- a/apps/www/pages/_app.tsx +++ b/apps/www/pages/_app.tsx @@ -15,7 +15,7 @@ import { AppProps } from 'next/app' import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect } from 'react' -import { Announcement, SonnerToaster, themes } from 'ui' +import { SonnerToaster, themes } from 'ui' import { CommandProvider } from 'ui-patterns/CommandMenu' import { useConsent } from 'ui-patterns/ConsentToast' @@ -23,7 +23,6 @@ import MetaFaviconsPagesRouter, { DEFAULT_FAVICON_ROUTE, DEFAULT_FAVICON_THEME_COLOR, } from 'common/MetaFavicons/pages-router' -import LW13CountdownBanner from 'ui/src/layout/banners/LW13CountdownBanner/LW13CountdownBanner' import { WwwCommandMenu } from '~/components/CommandMenu' import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION, IS_PREVIEW } from '~/lib/constants' import { post } from '~/lib/fetchWrapper' @@ -161,9 +160,6 @@ export default function App({ Component, pageProps }: AppProps) { forcedTheme={forceDarkMode ? 'dark' : undefined} > - - - From e326e41cefc3120d0010afbe463ff7d317dc0dfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:41:12 +0100 Subject: [PATCH 004/265] chore(deps): bump next from 14.2.15 to 14.2.21 (#32559) * chore(deps): bump next from 14.2.15 to 15.1.3 Bumps [next](https://github.com/vercel/next.js) from 14.2.15 to 15.1.3. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v14.2.15...v15.1.3) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump next to 14.2.21. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Vasilov --- pnpm-lock.yaml | 152 ++++++++++++++++++++++---------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e13ded4b580..9aa4d09034e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,8 +10,8 @@ catalogs: specifier: ^20.0.0 version: 20.12.11 next: - specifier: 14.2.15 - version: 14.2.15 + specifier: 14.2.21 + version: 14.2.21 importers: @@ -76,10 +76,10 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-contentlayer2: specifier: 0.4.6 - version: 0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) + version: 0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -329,7 +329,7 @@ importers: version: 1.0.1 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) @@ -338,13 +338,13 @@ importers: version: 1.0.1 next-seo: specifier: ^6.5.0 - version: 6.5.0(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) nuqs: specifier: ^1.19.1 - version: 1.19.1(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) + version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -549,7 +549,7 @@ importers: version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@sentry/nextjs': specifier: ^8.34.0 - version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0) + version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0) '@stripe/react-stripe-js': specifier: ^2.5.0 version: 2.5.0(@stripe/stripe-js@3.0.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -588,7 +588,7 @@ importers: version: 10.3.1(react@18.2.0) '@vercel/flags': specifier: ^2.6.0 - version: 2.6.0(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.2.1(supports-color@8.1.1)(vite@5.2.14(@types/node@20.12.11)(sass@1.72.0)(terser@5.32.0)) @@ -681,13 +681,13 @@ importers: version: 0.33.0 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) nuqs: specifier: ^1.17.8 - version: 1.19.1(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) + version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -958,7 +958,7 @@ importers: version: 2.4.11(typescript@5.5.2) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) + version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) postcss: specifier: ^8.4.31 version: 8.4.38 @@ -1072,16 +1072,16 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-contentlayer2: specifier: 0.5.3 - version: 0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) + version: 0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-seo: specifier: ^6.5.0 - version: 6.5.0(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1338,7 +1338,7 @@ importers: version: 4.17.21 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1607,7 +1607,7 @@ importers: version: 0.436.0(react@18.2.0) next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1809,7 +1809,7 @@ importers: version: 0.33.0 next: specifier: 'catalog:' - version: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: '*' version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1903,7 +1903,7 @@ importers: version: link:../api-types next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) + version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) typescript: specifier: ~5.5.0 version: 5.5.2 @@ -3134,8 +3134,8 @@ packages: '@next/bundle-analyzer@14.2.3': resolution: {integrity: sha512-Z88hbbngMs7njZKI8kTJIlpdLKYfMSLwnsqYe54AP4aLmgL70/Ynx/J201DQ+q2Lr6FxFw1uCeLGImDrHOl2ZA==} - '@next/env@14.2.15': - resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} + '@next/env@14.2.21': + resolution: {integrity: sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==} '@next/eslint-plugin-next@14.2.4': resolution: {integrity: sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==} @@ -3151,56 +3151,56 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@14.2.15': - resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} + '@next/swc-darwin-arm64@14.2.21': + resolution: {integrity: sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.15': - resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} + '@next/swc-darwin-x64@14.2.21': + resolution: {integrity: sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.15': - resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} + '@next/swc-linux-arm64-gnu@14.2.21': + resolution: {integrity: sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.15': - resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} + '@next/swc-linux-arm64-musl@14.2.21': + resolution: {integrity: sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.15': - resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} + '@next/swc-linux-x64-gnu@14.2.21': + resolution: {integrity: sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.15': - resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} + '@next/swc-linux-x64-musl@14.2.21': + resolution: {integrity: sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.15': - resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} + '@next/swc-win32-arm64-msvc@14.2.21': + resolution: {integrity: sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.15': - resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} + '@next/swc-win32-ia32-msvc@14.2.21': + resolution: {integrity: sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.15': - resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} + '@next/swc-win32-x64-msvc@14.2.21': + resolution: {integrity: sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -9652,8 +9652,8 @@ packages: react: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18 - next@14.2.15: - resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} + next@14.2.21: + resolution: {integrity: sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -14325,7 +14325,7 @@ snapshots: - bufferutil - utf-8-validate - '@next/env@14.2.15': {} + '@next/env@14.2.21': {} '@next/eslint-plugin-next@14.2.4': dependencies: @@ -14345,31 +14345,31 @@ snapshots: '@mdx-js/loader': 2.3.0(supports-color@8.1.1)(webpack@5.94.0) '@mdx-js/react': 2.3.0(react@18.2.0) - '@next/swc-darwin-arm64@14.2.15': + '@next/swc-darwin-arm64@14.2.21': optional: true - '@next/swc-darwin-x64@14.2.15': + '@next/swc-darwin-x64@14.2.21': optional: true - '@next/swc-linux-arm64-gnu@14.2.15': + '@next/swc-linux-arm64-gnu@14.2.21': optional: true - '@next/swc-linux-arm64-musl@14.2.15': + '@next/swc-linux-arm64-musl@14.2.21': optional: true - '@next/swc-linux-x64-gnu@14.2.15': + '@next/swc-linux-x64-gnu@14.2.21': optional: true - '@next/swc-linux-x64-musl@14.2.15': + '@next/swc-linux-x64-musl@14.2.21': optional: true - '@next/swc-win32-arm64-msvc@14.2.15': + '@next/swc-win32-arm64-msvc@14.2.21': optional: true - '@next/swc-win32-ia32-msvc@14.2.15': + '@next/swc-win32-ia32-msvc@14.2.21': optional: true - '@next/swc-win32-x64-msvc@14.2.15': + '@next/swc-win32-x64-msvc@14.2.21': optional: true '@nodelib/fs.scandir@2.1.5': @@ -16103,7 +16103,7 @@ snapshots: '@sentry/types': 8.34.0 '@sentry/utils': 8.34.0 - '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1) '@opentelemetry/semantic-conventions': 1.27.0 @@ -16118,7 +16118,7 @@ snapshots: '@sentry/vercel-edge': 8.34.0 '@sentry/webpack-plugin': 2.22.3(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) chalk: 3.0.0 - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -17411,11 +17411,11 @@ snapshots: '@use-gesture/core': 10.3.1 react: 18.2.0 - '@vercel/flags@2.6.0(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@vercel/flags@2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: jose: 5.2.1 optionalDependencies: - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -22586,12 +22586,12 @@ snapshots: neo-async@2.6.2: {} - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): + next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.4.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.4.3 contentlayer2: 0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -22600,12 +22600,12 @@ snapshots: - markdown-wasm - supports-color - next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): + next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.5.3 contentlayer2: 0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -22629,14 +22629,14 @@ snapshots: dependencies: js-yaml-loader: 1.2.2 - next-router-mock@0.9.13(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0): + next-router-mock@0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0): dependencies: - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 - next-seo@6.5.0(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next-seo@6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -22645,9 +22645,9 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0): + next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0): dependencies: - '@next/env': 14.2.15 + '@next/env': 14.2.21 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001651 @@ -22657,15 +22657,15 @@ snapshots: react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.24.7(supports-color@8.1.1))(react@18.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.15 - '@next/swc-darwin-x64': 14.2.15 - '@next/swc-linux-arm64-gnu': 14.2.15 - '@next/swc-linux-arm64-musl': 14.2.15 - '@next/swc-linux-x64-gnu': 14.2.15 - '@next/swc-linux-x64-musl': 14.2.15 - '@next/swc-win32-arm64-msvc': 14.2.15 - '@next/swc-win32-ia32-msvc': 14.2.15 - '@next/swc-win32-x64-msvc': 14.2.15 + '@next/swc-darwin-arm64': 14.2.21 + '@next/swc-darwin-x64': 14.2.21 + '@next/swc-linux-arm64-gnu': 14.2.21 + '@next/swc-linux-arm64-musl': 14.2.21 + '@next/swc-linux-x64-gnu': 14.2.21 + '@next/swc-linux-x64-musl': 14.2.21 + '@next/swc-win32-arm64-msvc': 14.2.21 + '@next/swc-win32-ia32-msvc': 14.2.21 + '@next/swc-win32-x64-msvc': 14.2.21 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.41.2 sass: 1.72.0 @@ -22823,10 +22823,10 @@ snapshots: number-flow@0.3.7: {} - nuqs@1.19.1(next@14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)): + nuqs@1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)): dependencies: mitt: 3.0.1 - next: 14.2.15(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) nwsapi@2.2.7: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2aeb07628b3..a99e64db0ae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,4 +6,4 @@ packages: catalog: '@types/node': ^20.0.0 - next: 14.2.15 + next: 14.2.21 From 2d2ab28fc93cbcad52b05b51a607175ad31f3c74 Mon Sep 17 00:00:00 2001 From: stepan <133258815+stpn48@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:42:12 +0100 Subject: [PATCH 005/265] Fix theme toggle bug in footer (#32566) * fix: fix theme button overlap and hydration issues * fix: show sun icon when theme is light and moon when theme is dark * fix: use 'resolvedTheme' isntead of 'theme' to change icon when user has theme 'system' * refactor: use ternary operator --- packages/ui-patterns/ThemeToggle.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ui-patterns/ThemeToggle.tsx b/packages/ui-patterns/ThemeToggle.tsx index 4e41bfddde9..ba64de8a145 100644 --- a/packages/ui-patterns/ThemeToggle.tsx +++ b/packages/ui-patterns/ThemeToggle.tsx @@ -2,7 +2,7 @@ import { Moon, Sun } from 'lucide-react' import { useTheme } from 'next-themes' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { DropdownMenu, DropdownMenuContent, @@ -26,12 +26,19 @@ export const ThemeToggle = ({ triggerClassName, contentClassName, }: ThemeToggleProps) => { - const { theme, setTheme } = useTheme() + const { theme, setTheme, resolvedTheme } = useTheme() const [open, setOpen] = useState(false) + const [isMounted, setIsMounted] = useState(false) + + useEffect(() => { + setIsMounted(true) + }, []) // Conditionally force the theme to 'dark' when disabled is true const currentTheme = forceDark ? 'dark' : theme + if (!isMounted) return null + return ( setOpen(!open)} modal={false}> @@ -42,8 +49,11 @@ export const ThemeToggle = ({ triggerClassName )} > - - + {resolvedTheme === 'dark' ? ( + + ) : ( + + )} Toggle theme From 900b4bbad6cb9e0187985de4148f53e526124ce1 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:08:45 -0500 Subject: [PATCH 006/265] fix: swift ref docs filter params (#32633) Parameters given for `filter` function of Swift SDK don't match actual implementation. Documented as enum, implementation uses string. --- apps/docs/spec/supabase_swift_v2.yml | 102 +++++++++++++-------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index e63548c8f33..252554af2c2 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -32,7 +32,7 @@ functions: code: | ```swift let client = SupabaseClient( - supabaseURL: URL(string: "https://xyzcompany.supabase.co")!, + supabaseURL: URL(string: "https://xyzcompany.supabase.co")!, supabaseKey: "public-anon-key", options: SupabaseClientOptions( db: .init( @@ -62,7 +62,7 @@ functions: schema: "other_schema" ) ) - ) + ) ``` description: | By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard. @@ -136,7 +136,7 @@ functions: code: | ```swift let supabase = SupabaseClient( - supabaseURL: URL(string: "https://xyzcompany.supabase.co")!, + supabaseURL: URL(string: "https://xyzcompany.supabase.co")!, supabaseKey: "public-anon-key", options: .init( auth: .init( @@ -256,7 +256,7 @@ functions: - `INITIAL_SESSION` - Emitted right after the Supabase client is constructed and the initial session from storage is loaded. - `SIGNED_IN` - - Emitted each time a user session is confirmed or re-established, including on user sign in. + - Emitted each time a user session is confirmed or re-established, including on user sign in. - Avoid making assumptions as to when this event is fired, this may occur even when the user is already signed in. Instead, check the user object attached to the event to see if a new user has signed in and update your application's UI. - `SIGNED_OUT` - Emitted when the user signs out. This can be after: @@ -291,7 +291,7 @@ functions: } // Using Closure - let subscription = await supabase.auth.onAuthStateChange { event, session in + let subscription = await supabase.auth.onAuthStateChange { event, session in print(event, session) } @@ -498,7 +498,7 @@ functions: ```swift let session = try await supabase.auth.signInWithOAuth( provider: .github - ) { (session: ASWebAuthenticationSession) in + ) { (session: ASWebAuthenticationSession) in // customize session } ``` @@ -512,7 +512,7 @@ functions: ```swift let session = try await supabase.auth.signInWithOAuth( provider: .github - ) { url in + ) { url in // use url to start OAuth flow // and return a result url that contains the OAuth token. // ... @@ -531,7 +531,7 @@ functions: ```swift let url = try await supabase.auth.getOAuthSignInURL(provider: .github, redirectTo: URL(string: "my-app-scheme://")) - let session = ASWebAuthenticationSession(url: url, callbackURLScheme: "my-app-scheme") { url, error in + let session = ASWebAuthenticationSession(url: url, callbackURLScheme: "my-app-scheme") { url, error in guard let url else { return } Task { @@ -899,7 +899,7 @@ functions: isSpotlight: true code: | ```swift - try await supabase.auth.linkIdentity(provider: provider) { url in + try await supabase.auth.linkIdentity(provider: provider) { url in // custom URL opening logic } ``` @@ -919,7 +919,7 @@ functions: // retrieve all identites linked to a user let identities = try await supabase.auth.userIdentities() - // find the google identity + // find the google identity let googleIdentity = identities.first { $0.provider == .google } @@ -1397,7 +1397,7 @@ functions: let name: String let teams: [Team] } - + struct Team: Decodable { let name: String } @@ -1936,7 +1936,7 @@ functions: .insert(Country(id: 1, name: "Denmark")) .select() // specify you want a single value returned, otherwise it returns a list. - .single() + .single() .execute() .value ``` @@ -2048,7 +2048,7 @@ functions: ```swift struct Country: Decodable { let id: Int - let name: String + let name: String } let country: Country = try await supabase @@ -2825,8 +2825,8 @@ functions: filter() expects you to use the raw PostgREST syntax for the filter values. ```swift - .filter("id", operator: .in, value: "(5,6,7)") // Use `()` for `in` filter - .filter("arraycol", operator: .cs, value: #"{"a","b"}"#) // Use `cs` for `contains()`, `{}` for array values + .filter("id", operator: "in", value: "(5,6,7)") // Use `()` for `in` filter + .filter("arraycol", operator: "cs", value: #"{"a","b"}"#) // Use `cs` for `contains()`, `{}` for array values ``` examples: - id: with-select @@ -2836,7 +2836,7 @@ functions: try await supabase .from("countries") .select() - .filter("name", operator: .in, value: #"("Algeria","Japan")"#) + .filter("name", operator: "in", value: #"("Algeria","Japan")"#) ``` data: sql: | @@ -2880,7 +2880,7 @@ functions: ) """ ) - .filter("cities.name", operator: .eq, value: "Bali") + .filter("cities.name", operator: "eq", value: "Bali") ``` data: sql: | @@ -3435,7 +3435,7 @@ functions: let response: Response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( body: ["foo": "bar"] ) @@ -3448,11 +3448,11 @@ functions: ```swift let response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( body: ["foo": "bar"] ), - decode: { data, response in + decode: { data, response in String(data: data, encoding: .utf8) } ) @@ -3484,7 +3484,7 @@ functions: do { let response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( body: ["foo": "bar"] ) @@ -3506,7 +3506,7 @@ functions: ```swift let response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( headers: [ "my-custom-header": "my-custom-header-value" @@ -3554,7 +3554,7 @@ functions: ```swift let response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( method: .delete, headers: [ @@ -3573,7 +3573,7 @@ functions: ```swift let response = try await supabase.functions .invoke( - "hello", + "hello", options: FunctionInvokeOptions( method: .get, headers: [ @@ -3752,10 +3752,10 @@ functions: name: Listen to a specific table code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let changeStream = channel.postgresChange( - AnyAction.self, + AnyAction.self, schema: "public", table: "users" ) @@ -3817,13 +3817,13 @@ functions: name: Listen to inserts using callback code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let subscription = channel.onPostgresChange( - InsertAction.self, + InsertAction.self, schema: "public", table: "users" - ) { insert in + ) { insert in print("Inserted: \(insert.record)") } @@ -3843,16 +3843,16 @@ functions: ``` code: | ```swift - let channel = supabase.channel("channelId") - + let channel = supabase.channel("channelId") + let updates = channel.postgresChange( - UpdateAction.self, + UpdateAction.self, schema: "public", table: "users" ) await channel.subscribe() - + for await update in updates { print("Updated: \(update.oldRecord) with \(update.record)") } @@ -3868,18 +3868,18 @@ functions: ``` code: | ```swift - let channel = supabase.channel("channelId") - + let channel = supabase.channel("channelId") + let subscription = channel.onPostgresChange( - UpdateAction.self, + UpdateAction.self, schema: "public", table: "users" - ) { update in + ) { update in print("Updated: \(update.oldRecord) with \(update.record)") } await channel.subscribe() - + // remove subscription some time later subscription.cancel() ``` @@ -3894,10 +3894,10 @@ functions: ``` code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let deletions = channel.postgresChange( - DeleteAction.self, + DeleteAction.self, schema: "public", table: "users" ) @@ -3919,10 +3919,10 @@ functions: ``` code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let subscription = channel.onPostgresChange( - DeleteAction.self, + DeleteAction.self, schema: "public", table: "users" ) { deletion in @@ -3938,10 +3938,10 @@ functions: name: Listen to row level changes code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let deletions = channel.postgresChange( - DeleteAction.self, + DeleteAction.self, schema: "public", table: "users", filter: "id=eq.1" @@ -3957,14 +3957,14 @@ functions: name: Listen to row level changes using callback code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") let subscription = channel.onPostgresChange( - DeleteAction.self, + DeleteAction.self, schema: "public", table: "users", filter: "id=eq.1" - ) { deletion in + ) { deletion in print("Deleted: \(deletion.oldRecord)") } @@ -3988,7 +3988,7 @@ functions: isSpotlight: true code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") //... await supabase.removeChannel(channel) @@ -3998,7 +3998,7 @@ functions: isSpotlight: true code: | ```swift - let channel = supabase.channel("channelId") + let channel = supabase.channel("channelId") //... await channel.unsubscribe() @@ -4081,7 +4081,7 @@ functions: ```swift try await supabase.storage .createBucket( - "avatars", + "avatars", options: BucketOptions( public: false, allowedMimeTypes: ["image/png"], @@ -4121,7 +4121,7 @@ functions: ```swift try await supabase.storage .updateBucket( - "avatars", + "avatars", options: BucketOptions( public: false, fileSizeLimit: 1024, From 11caf61f437aed361e202e8d182de14a93263e97 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 8 Jan 2025 21:29:45 +0100 Subject: [PATCH 007/265] chore: Delete the `tests` package (#32527) * Remove the tests package. * Remove all workspace configs for the tests package. --- package.json | 4 - pnpm-lock.yaml | 720 +++++++----------- pnpm-workspace.yaml | 1 - tests/.jest/jest-custom-reporter.ts | 194 ----- tests/.jest/jest-env.ts | 14 - tests/.jest/teardown.ts | 25 - tests/.prettierignore | 4 - tests/.prettierrc | 7 - tests/README.md | 18 - tests/data/func.ts | 12 - tests/features/javascript/authAdmin.spec.ts | 319 -------- .../javascript/authentication.spec.ts | 330 -------- tests/features/javascript/functions.spec.ts | 42 - tests/features/javascript/hooks.ts | 211 ----- tests/features/javascript/postgrest.spec.ts | 86 --- tests/features/javascript/realtime.spec.ts | 316 -------- tests/features/javascript/rpc.spec.ts | 134 ---- tests/features/javascript/storage.spec.ts | 424 ----------- tests/features/python/authentication.py | 44 -- tests/features/templates/authentication.ts | 50 -- tests/features/templates/enums.ts | 11 - tests/jest.config.ts | 24 - tests/package.json | 39 - tests/supabase/.gitignore | 3 - tests/supabase/config.toml | 71 -- tests/supabase/migrations/data.sql | 50 -- tests/tsconfig.json | 29 - 27 files changed, 255 insertions(+), 2927 deletions(-) delete mode 100644 tests/.jest/jest-custom-reporter.ts delete mode 100644 tests/.jest/jest-env.ts delete mode 100644 tests/.jest/teardown.ts delete mode 100644 tests/.prettierignore delete mode 100644 tests/.prettierrc delete mode 100644 tests/README.md delete mode 100644 tests/data/func.ts delete mode 100644 tests/features/javascript/authAdmin.spec.ts delete mode 100644 tests/features/javascript/authentication.spec.ts delete mode 100644 tests/features/javascript/functions.spec.ts delete mode 100644 tests/features/javascript/hooks.ts delete mode 100644 tests/features/javascript/postgrest.spec.ts delete mode 100644 tests/features/javascript/realtime.spec.ts delete mode 100644 tests/features/javascript/rpc.spec.ts delete mode 100644 tests/features/javascript/storage.spec.ts delete mode 100644 tests/features/python/authentication.py delete mode 100644 tests/features/templates/authentication.ts delete mode 100644 tests/features/templates/enums.ts delete mode 100644 tests/jest.config.ts delete mode 100644 tests/package.json delete mode 100644 tests/supabase/.gitignore delete mode 100644 tests/supabase/config.toml delete mode 100644 tests/supabase/migrations/data.sql delete mode 100644 tests/tsconfig.json diff --git a/package.json b/package.json index f92f9268c5d..20584e4137e 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,6 @@ "typecheck": "turbo --continue typecheck", "test:prettier": "prettier -c '{apps,packages}/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'", "format": "prettier --write '{apps,packages}/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'", - "docker:dev": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes", - "docker:up": "cd docker && docker compose up", - "docker:down": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans", - "docker:remove": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs", "test:docs": "turbo run test --filter=docs", "test:ui": "turbo run test --filter=ui", "test:ui-patterns": "turbo run test --filter=ui-patterns", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9aa4d09034e..5def67e2b75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1917,57 +1917,6 @@ importers: specifier: ^1.41.2 version: 1.41.2 - tests: - devDependencies: - '@faker-js/faker': - specifier: ^6.1.2 - version: 6.3.1 - '@supabase/supabase-js': - specifier: ^2.44.3 - version: 2.44.3 - '@testdeck/jest': - specifier: ^0.3.3 - version: 0.3.3 - '@types/jest': - specifier: ^29.5.4 - version: 29.5.5 - '@types/node': - specifier: ^20.0.0 - version: 20.12.11 - '@typescript-eslint/eslint-plugin': - specifier: ^6.19.1 - version: 6.19.1(@typescript-eslint/parser@6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2))(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/parser': - specifier: ^6.19.1 - version: 6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - allure-commandline: - specifier: ^2.17.2 - version: 2.24.0 - allure-js-commons: - specifier: ^2.0.0-beta.14 - version: 2.8.1 - cross-fetch: - specifier: ^3.1.5 - version: 3.1.5(encoding@0.1.13) - jest: - specifier: ^29.7.0 - version: 29.7.0(@types/node@20.12.11)(supports-color@8.1.1) - jest-allure2-adapter: - specifier: ^0.3.12 - version: 0.3.12 - jest-environment-jsdom: - specifier: ^29.7.0 - version: 29.7.0(supports-color@8.1.1) - postgres: - specifier: ^3.0.5 - version: 3.3.5 - ts-jest: - specifier: ^29.1.1 - version: 29.1.2(@babel/core@7.24.7(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7(supports-color@8.1.1))(supports-color@8.1.1))(jest@29.7.0(@types/node@20.12.11)(supports-color@8.1.1))(typescript@5.5.2) - typescript: - specifier: ~5.5.0 - version: 5.5.2 - packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -2798,10 +2747,6 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} - '@faker-js/faker@6.3.1': - resolution: {integrity: sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==} - engines: {node: '>=14.0.0', npm: '>=6.0.0'} - '@fal-works/esbuild-plugin-global-externals@2.1.2': resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} @@ -5082,12 +5027,6 @@ packages: peerDependencies: react: ^18.0.0 - '@testdeck/core@0.3.3': - resolution: {integrity: sha512-yu1yh7yluqnNDLe6Z18/y7kcmxBBEdfZAg3msG8covKkYPRbsCVr1+HmxReqJMKgeoh/UoEW1pi9Sz0fb/GYVQ==} - - '@testdeck/jest@0.3.3': - resolution: {integrity: sha512-BbycIJiFMp12HUQGqi/Fp3NaWDohbG5NLmN0DyV7DVIanfHRpKDjoB/ALVkpaioCSs+VpoNvqxWaC3QnZhcJQQ==} - '@testing-library/dom@10.1.0': resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} engines: {node: '>=18'} @@ -5173,9 +5112,6 @@ packages: '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - '@types/allure-js-commons@0.0.1': - resolution: {integrity: sha512-PuKCD4zeoF7Z6tKP0paOKmiMv3zXP2fKj/NiUylxDwBV1AL0PsD6AsbkieuEuRfbFUbG4C28RnmcSu9rPMWWjQ==} - '@types/animejs@3.1.12': resolution: {integrity: sha512-fpdH+ZtlO0kqjTOqRaBdsEmvpRNOayI8k4EVkEtitL5l6wducDOXk0rgQgfZqWf/ZX9DzXrHf257S5i9xTcISQ==} @@ -5390,9 +5326,6 @@ packages: '@types/js-yaml@4.0.6': resolution: {integrity: sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==} - '@types/jsdom@20.0.1': - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} - '@types/json-logic-js@1.2.1': resolution: {integrity: sha512-g/g+wj/7sgazpiCHiyAtndoNiy/LodLkNG4I9MILAl0UinKKwv3GiPKbtvcE1hIoezQqgDamXfx8Lht62/hHqw==} @@ -5451,9 +5384,6 @@ packages: '@types/node-fetch@2.6.6': resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==} - '@types/node@10.17.60': - resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} - '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} @@ -5556,10 +5486,6 @@ packages: '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} - '@types/strip-ansi@5.2.1': - resolution: {integrity: sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA==} - deprecated: This is a stub types definition. strip-ansi provides its own type definitions, so you do not need this installed. - '@types/stylis@4.2.0': resolution: {integrity: sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==} @@ -5602,27 +5528,6 @@ packages: '@types/zxcvbn@4.4.2': resolution: {integrity: sha512-T7SEL8b/eN7AEhHQ8oFt7c6Y+l3p8OpH7KwJIe+5oBOPLMMioPeMsUTB3huNgEnXhiittV8Ohdw21Jg8E/f70Q==} - '@typescript-eslint/eslint-plugin@6.19.1': - resolution: {integrity: sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@6.19.1': - resolution: {integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/parser@7.2.0': resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5633,41 +5538,14 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@6.19.1': - resolution: {integrity: sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/scope-manager@7.2.0': resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/type-utils@6.19.1': - resolution: {integrity: sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@6.19.1': - resolution: {integrity: sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@7.2.0': resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/typescript-estree@6.19.1': - resolution: {integrity: sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/typescript-estree@7.2.0': resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5677,16 +5555,6 @@ packages: typescript: optional: true - '@typescript-eslint/utils@6.19.1': - resolution: {integrity: sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - - '@typescript-eslint/visitor-keys@6.19.1': - resolution: {integrity: sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@7.2.0': resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5976,13 +5844,6 @@ packages: ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - allure-commandline@2.24.0: - resolution: {integrity: sha512-UuLnq9dOaYAoCCr/hxtxFgDor2/CME3z2Ka2Fj2wN8lRT+v60F+zDN+okrYi77+zFJg4cmCM3jWhEWC9QKja1w==} - hasBin: true - - allure-js-commons@2.8.1: - resolution: {integrity: sha512-Rc6ZQQW3OwX77GrgnQ5vcfDFI6WZteeodfCIgY0fBxUZPQFa14dWbFvLxoMSFV1zUFABtYzkQD7r/2+bfPWb6w==} - animejs@3.2.2: resolution: {integrity: sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==} @@ -6245,10 +6106,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -6642,9 +6499,6 @@ packages: resolution: {integrity: sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==} hasBin: true - cross-fetch@3.1.5: - resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} - cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -8418,9 +8272,6 @@ packages: engines: {node: '>=10'} hasBin: true - jest-allure2-adapter@0.3.12: - resolution: {integrity: sha512-WE4Gk5k7G5kyTr4an0KTaCbHaNGqEo9LatbHL4rRywWH1cWFXlahARpuy73PFxBG8Qt/fPm+7TC/sSBA9S7b+g==} - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8463,15 +8314,6 @@ packages: resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-environment-jsdom@29.7.0: - resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8592,12 +8434,6 @@ packages: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} - js-md5@0.7.3: - resolution: {integrity: sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==} - - js-sha1@0.6.0: - resolution: {integrity: sha512-01gwBFreYydzmU9BmZxpVk6svJJHrVxEN3IOiGl6VO93bVKYETJ0sIth6DASI6mIFdt7NmfX9UiByRzsYHGU9w==} - js-tiktoken@1.0.10: resolution: {integrity: sha512-ZoSxbGjvGyMT13x6ACo9ebhDha/0FHdKA+OsQcMOWcm1Zs7r90Rhk5lhERLzji+3rA7EKpXCgwXcM5fF3DMpdA==} @@ -8852,9 +8688,6 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -8943,9 +8776,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - make-fetch-happen@13.0.1: resolution: {integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -9694,15 +9524,6 @@ packages: resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} engines: {node: 4.x || >=6.0.0} - node-fetch@2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -10257,9 +10078,6 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - postgres@3.3.5: - resolution: {integrity: sha512-+JD93VELV9gHkqpV5gdL5/70HdGtEw4/XE1S4BC8f1mcPmdib3K5XsKVbnR1XcAyC41zOnifJ+9YRKxdIsXiUw==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -10346,10 +10164,6 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - properties@1.2.1: - resolution: {integrity: sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==} - engines: {node: '>=0.10'} - property-expr@2.0.5: resolution: {integrity: sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==} @@ -11784,27 +11598,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-jest@29.1.2: - resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} - engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - ts-morph@22.0.0: resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==} @@ -12159,9 +11952,6 @@ packages: resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} engines: {node: '>= 4'} - uuid-by-string@3.0.7: - resolution: {integrity: sha512-9xf+GAcwzLLGL2Z2Vb7hmi7jWIAKSiuaI5cLFsKw1IIlm7S5VpqvdJ5S7N36hqdy0v7DAwnnENJVAeev57/H1A==} - uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true @@ -13196,71 +12986,85 @@ snapshots: dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.8 + optional: true '@babel/plugin-transform-react-jsx-self@7.24.5(@babel/core@7.24.7(supports-color@8.1.1))': dependencies: @@ -13303,7 +13107,8 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@bcoe/v8-coverage@0.2.3': {} + '@bcoe/v8-coverage@0.2.3': + optional: true '@bundled-es-modules/cookie@2.0.1': dependencies: @@ -13756,8 +13561,6 @@ snapshots: '@exodus/schemasafe@1.3.0': {} - '@faker-js/faker@6.3.1': {} - '@fal-works/esbuild-plugin-global-externals@2.1.2': {} '@fastify/busboy@2.1.0': {} @@ -13928,17 +13731,20 @@ snapshots: get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 + optional: true - '@istanbuljs/schema@0.1.3': {} + '@istanbuljs/schema@0.1.3': + optional: true '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 + optional: true '@jest/core@29.7.0(supports-color@8.1.1)': dependencies: @@ -13947,14 +13753,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.11)(supports-color@8.1.1) + jest-config: 29.7.0(@types/node@22.7.5)(supports-color@8.1.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -13974,17 +13780,20 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 jest-mock: 29.7.0 + optional: true '@jest/expect-utils@29.7.0': dependencies: jest-get-type: 29.6.3 + optional: true '@jest/expect@29.7.0(supports-color@8.1.1)': dependencies: @@ -13992,15 +13801,17 @@ snapshots: jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.11 + '@types/node': 22.7.5 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 + optional: true '@jest/globals@29.7.0(supports-color@8.1.1)': dependencies: @@ -14010,6 +13821,7 @@ snapshots: jest-mock: 29.7.0 transitivePeerDependencies: - supports-color + optional: true '@jest/reporters@29.7.0(supports-color@8.1.1)': dependencies: @@ -14019,7 +13831,7 @@ snapshots: '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -14039,6 +13851,7 @@ snapshots: v8-to-istanbul: 9.1.0 transitivePeerDependencies: - supports-color + optional: true '@jest/schemas@29.6.3': dependencies: @@ -14049,6 +13862,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 + optional: true '@jest/test-result@29.7.0': dependencies: @@ -14056,6 +13870,7 @@ snapshots: '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.2 + optional: true '@jest/test-sequencer@29.7.0': dependencies: @@ -14063,6 +13878,7 @@ snapshots: graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 + optional: true '@jest/transform@29.7.0(supports-color@8.1.1)': dependencies: @@ -14083,15 +13899,17 @@ snapshots: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color + optional: true '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.2 - '@types/node': 20.12.11 + '@types/node': 22.7.5 '@types/yargs': 17.0.26 chalk: 4.1.2 + optional: true '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -16246,10 +16064,12 @@ snapshots: '@sinonjs/commons@3.0.0': dependencies: type-detect: 4.0.8 + optional: true '@sinonjs/fake-timers@10.3.0': dependencies: '@sinonjs/commons': 3.0.0 + optional: true '@smithy/abort-controller@2.0.15': dependencies: @@ -16657,12 +16477,6 @@ snapshots: '@tanstack/query-core': 5.13.4 react: 18.2.0 - '@testdeck/core@0.3.3': {} - - '@testdeck/jest@0.3.3': - dependencies: - '@testdeck/core': 0.3.3 - '@testing-library/dom@10.1.0': dependencies: '@babel/code-frame': 7.24.7 @@ -16760,7 +16574,8 @@ snapshots: '@tootallnate/once@1.1.2': {} - '@tootallnate/once@2.0.0': {} + '@tootallnate/once@2.0.0': + optional: true '@trysound/sax@0.2.0': {} @@ -16777,8 +16592,6 @@ snapshots: dependencies: '@types/estree': 1.0.5 - '@types/allure-js-commons@0.0.1': {} - '@types/animejs@3.1.12': {} '@types/aria-query@5.0.2': {} @@ -16998,7 +16811,8 @@ snapshots: '@types/graceful-fs@4.1.7': dependencies: - '@types/node': 20.12.11 + '@types/node': 22.7.5 + optional: true '@types/hast@2.3.6': dependencies: @@ -17013,31 +16827,29 @@ snapshots: '@types/react': 18.3.3 hoist-non-react-statics: 3.3.2 - '@types/istanbul-lib-coverage@2.0.4': {} + '@types/istanbul-lib-coverage@2.0.4': + optional: true '@types/istanbul-lib-report@3.0.1': dependencies: '@types/istanbul-lib-coverage': 2.0.4 + optional: true '@types/istanbul-reports@3.0.2': dependencies: '@types/istanbul-lib-report': 3.0.1 + optional: true '@types/jest@29.5.5': dependencies: expect: 29.7.0 pretty-format: 29.7.0 + optional: true '@types/js-cookie@2.2.7': {} '@types/js-yaml@4.0.6': {} - '@types/jsdom@20.0.1': - dependencies: - '@types/node': 20.12.11 - '@types/tough-cookie': 4.0.5 - parse5: 7.1.2 - '@types/json-logic-js@1.2.1': {} '@types/json-schema@7.0.13': {} @@ -17095,8 +16907,6 @@ snapshots: '@types/node': 20.12.11 form-data: 4.0.0 - '@types/node@10.17.60': {} - '@types/node@14.18.63': {} '@types/node@18.18.13': @@ -17220,16 +17030,13 @@ snapshots: '@types/sqlstring@2.3.0': {} - '@types/stack-utils@2.0.1': {} + '@types/stack-utils@2.0.1': + optional: true '@types/stats.js@0.17.3': {} '@types/statuses@2.0.5': {} - '@types/strip-ansi@5.2.1': - dependencies: - strip-ansi: 7.1.0 - '@types/stylis@4.2.0': {} '@types/tern@0.23.5': @@ -17263,47 +17070,16 @@ snapshots: dependencies: '@types/node': 20.12.11 - '@types/yargs-parser@21.0.1': {} + '@types/yargs-parser@21.0.1': + optional: true '@types/yargs@17.0.26': dependencies: '@types/yargs-parser': 21.0.1 + optional: true '@types/zxcvbn@4.4.2': {} - '@typescript-eslint/eslint-plugin@6.19.1(@typescript-eslint/parser@6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2))(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2)': - dependencies: - '@eslint-community/regexpp': 4.9.1 - '@typescript-eslint/parser': 6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/scope-manager': 6.19.1 - '@typescript-eslint/type-utils': 6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/utils': 6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.7(supports-color@8.1.1) - eslint: 8.57.0(supports-color@8.1.1) - graphemer: 1.4.0 - ignore: 5.2.4 - natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.0.3(typescript@5.5.2) - optionalDependencies: - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2)': - dependencies: - '@typescript-eslint/scope-manager': 6.19.1 - '@typescript-eslint/types': 6.19.1 - '@typescript-eslint/typescript-estree': 6.19.1(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.7(supports-color@8.1.1) - eslint: 8.57.0(supports-color@8.1.1) - optionalDependencies: - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2)': dependencies: '@typescript-eslint/scope-manager': 7.2.0 @@ -17317,47 +17093,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.19.1': - dependencies: - '@typescript-eslint/types': 6.19.1 - '@typescript-eslint/visitor-keys': 6.19.1 - '@typescript-eslint/scope-manager@7.2.0': dependencies: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - '@typescript-eslint/type-utils@6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2)': - dependencies: - '@typescript-eslint/typescript-estree': 6.19.1(supports-color@8.1.1)(typescript@5.5.2) - '@typescript-eslint/utils': 6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2) - debug: 4.3.7(supports-color@8.1.1) - eslint: 8.57.0(supports-color@8.1.1) - ts-api-utils: 1.0.3(typescript@5.5.2) - optionalDependencies: - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@6.19.1': {} - '@typescript-eslint/types@7.2.0': {} - '@typescript-eslint/typescript-estree@6.19.1(supports-color@8.1.1)(typescript@5.5.2)': - dependencies: - '@typescript-eslint/types': 6.19.1 - '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.7(supports-color@8.1.1) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.0.3(typescript@5.5.2) - optionalDependencies: - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@7.2.0(supports-color@8.1.1)(typescript@5.5.2)': dependencies: '@typescript-eslint/types': 7.2.0 @@ -17373,25 +17115,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.19.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.5.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0(supports-color@8.1.1)) - '@types/json-schema': 7.0.13 - '@types/semver': 7.5.3 - '@typescript-eslint/scope-manager': 6.19.1 - '@typescript-eslint/types': 6.19.1 - '@typescript-eslint/typescript-estree': 6.19.1(supports-color@8.1.1)(typescript@5.5.2) - eslint: 8.57.0(supports-color@8.1.1) - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@6.19.1': - dependencies: - '@typescript-eslint/types': 6.19.1 - eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@7.2.0': dependencies: '@typescript-eslint/types': 7.2.0 @@ -17664,7 +17387,8 @@ snapshots: '@zip.js/zip.js@2.7.30': {} - abab@2.0.6: {} + abab@2.0.6: + optional: true abbrev@1.1.1: {} @@ -17678,6 +17402,7 @@ snapshots: dependencies: acorn: 8.11.3 acorn-walk: 8.3.2 + optional: true acorn-import-attributes@1.9.5(acorn@8.11.3): dependencies: @@ -17763,12 +17488,6 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 - allure-commandline@2.24.0: {} - - allure-js-commons@2.8.1: - dependencies: - properties: 1.2.1 - animejs@3.2.2: {} ansi-colors@4.1.3: {} @@ -17979,6 +17698,7 @@ snapshots: slash: 3.0.0 transitivePeerDependencies: - supports-color + optional: true babel-plugin-istanbul@6.1.1(supports-color@8.1.1): dependencies: @@ -17989,6 +17709,7 @@ snapshots: test-exclude: 6.0.0 transitivePeerDependencies: - supports-color + optional: true babel-plugin-jest-hoist@29.6.3: dependencies: @@ -17996,6 +17717,7 @@ snapshots: '@babel/types': 7.24.7 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.2 + optional: true babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7(supports-color@8.1.1)): dependencies: @@ -18012,12 +17734,14 @@ snapshots: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7(supports-color@8.1.1)) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7(supports-color@8.1.1)) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7(supports-color@8.1.1)) + optional: true babel-preset-jest@29.6.3(@babel/core@7.24.7(supports-color@8.1.1)): dependencies: '@babel/core': 7.24.7(supports-color@8.1.1) babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7(supports-color@8.1.1)) + optional: true bail@2.0.2: {} @@ -18066,13 +17790,10 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - bser@2.1.1: dependencies: node-int64: 0.4.0 + optional: true btoa-lite@1.0.0: {} @@ -18150,9 +17871,11 @@ snapshots: camelcase-css@2.0.1: {} - camelcase@5.3.1: {} + camelcase@5.3.1: + optional: true - camelcase@6.3.0: {} + camelcase@6.3.0: + optional: true camelize@1.0.1: {} @@ -18217,7 +17940,8 @@ snapshots: snake-case: 3.0.4 tslib: 2.6.2 - char-regex@1.0.2: {} + char-regex@1.0.2: + optional: true character-entities-html4@2.1.0: {} @@ -18274,7 +17998,8 @@ snapshots: chrome-trace-event@1.0.3: {} - ci-info@3.8.0: {} + ci-info@3.8.0: + optional: true cjs-module-lexer@1.2.3: {} @@ -18324,7 +18049,8 @@ snapshots: - '@types/react' - '@types/react-dom' - co@4.6.0: {} + co@4.6.0: + optional: true cobe@0.6.3: dependencies: @@ -18354,7 +18080,8 @@ snapshots: collapse-white-space@2.1.0: {} - collect-v8-coverage@1.0.2: {} + collect-v8-coverage@1.0.2: + optional: true color-convert@1.9.3: dependencies: @@ -18479,7 +18206,8 @@ snapshots: - markdown-wasm - supports-color - convert-source-map@1.9.0: {} + convert-source-map@1.9.0: + optional: true convert-source-map@2.0.0: {} @@ -18523,6 +18251,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true cron-parser@4.9.0: dependencies: @@ -18530,12 +18259,6 @@ snapshots: cronstrue@2.50.0: {} - cross-fetch@3.1.5(encoding@0.1.13): - dependencies: - node-fetch: 2.6.7(encoding@0.1.13) - transitivePeerDependencies: - - encoding - cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -18603,13 +18326,16 @@ snapshots: dependencies: css-tree: 2.2.1 - cssom@0.3.8: {} + cssom@0.3.8: + optional: true - cssom@0.5.0: {} + cssom@0.5.0: + optional: true cssstyle@2.3.0: dependencies: cssom: 0.3.8 + optional: true csstype@3.1.2: {} @@ -18694,6 +18420,7 @@ snapshots: abab: 2.0.6 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 + optional: true data-view-buffer@1.0.1: dependencies: @@ -18737,7 +18464,8 @@ snapshots: decimal.js-light@2.5.1: {} - decimal.js@10.4.3: {} + decimal.js@10.4.3: + optional: true decko@1.2.0: {} @@ -18745,7 +18473,8 @@ snapshots: dependencies: character-entities: 2.0.2 - dedent@1.5.1: {} + dedent@1.5.1: + optional: true deep-eql@4.1.3: dependencies: @@ -18811,7 +18540,8 @@ snapshots: detect-libc@2.0.3: {} - detect-newline@3.1.0: {} + detect-newline@3.1.0: + optional: true detect-node-es@1.1.0: {} @@ -18871,6 +18601,7 @@ snapshots: domexception@4.0.0: dependencies: webidl-conversions: 7.0.0 + optional: true domhandler@5.0.3: dependencies: @@ -18911,7 +18642,8 @@ snapshots: electron-to-chromium@1.5.13: {} - emittery@0.13.1: {} + emittery@0.13.1: + optional: true emoji-regex@10.3.0: {} @@ -19115,7 +18847,8 @@ snapshots: escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} + escape-string-regexp@2.0.0: + optional: true escape-string-regexp@4.0.0: {} @@ -19128,6 +18861,7 @@ snapshots: esutils: 2.0.3 optionalDependencies: source-map: 0.6.1 + optional: true eslint-barrel-file-utils-android-arm-eabi@0.0.10: optional: true @@ -19487,6 +19221,7 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + optional: true execa@8.0.1: dependencies: @@ -19500,7 +19235,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - exit@0.1.2: {} + exit@0.1.2: + optional: true expand-range@1.8.2: dependencies: @@ -19515,6 +19251,7 @@ snapshots: jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 + optional: true exponential-backoff@3.1.1: {} @@ -19573,6 +19310,7 @@ snapshots: fb-watchman@2.0.2: dependencies: bser: 2.1.1 + optional: true fetch-blob@3.2.0: dependencies: @@ -19609,6 +19347,7 @@ snapshots: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 + optional: true find-up@5.0.0: dependencies: @@ -19771,13 +19510,15 @@ snapshots: get-nonce@1.0.1: {} - get-package-type@0.1.0: {} + get-package-type@0.1.0: + optional: true get-port-please@3.1.1: {} get-stdin@8.0.0: {} - get-stream@6.0.1: {} + get-stream@6.0.1: + optional: true get-stream@8.0.1: {} @@ -20201,6 +19942,7 @@ snapshots: html-encoding-sniffer@3.0.0: dependencies: whatwg-encoding: 2.0.0 + optional: true html-escaper@2.0.2: {} @@ -20234,6 +19976,7 @@ snapshots: debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true http-proxy-agent@7.0.2(supports-color@8.1.1): dependencies: @@ -20258,7 +20001,8 @@ snapshots: transitivePeerDependencies: - supports-color - human-signals@2.1.0: {} + human-signals@2.1.0: + optional: true human-signals@5.0.0: {} @@ -20273,6 +20017,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true ieee754@1.2.1: {} @@ -20300,6 +20045,7 @@ snapshots: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + optional: true imurmurhash@0.1.4: {} @@ -20430,7 +20176,8 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-fn@2.1.0: {} + is-generator-fn@2.1.0: + optional: true is-generator-function@1.0.10: dependencies: @@ -20478,7 +20225,8 @@ snapshots: is-plain-object@5.0.0: {} - is-potential-custom-element-name@1.0.1: {} + is-potential-custom-element-name@1.0.1: + optional: true is-primitive@3.0.1: {} @@ -20548,7 +20296,8 @@ snapshots: isobject@3.0.1: {} - istanbul-lib-coverage@3.2.0: {} + istanbul-lib-coverage@3.2.0: + optional: true istanbul-lib-instrument@5.2.1(supports-color@8.1.1): dependencies: @@ -20559,6 +20308,7 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + optional: true istanbul-lib-instrument@6.0.0(supports-color@8.1.1): dependencies: @@ -20569,12 +20319,14 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + optional: true istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.0 make-dir: 4.0.0 supports-color: 7.2.0 + optional: true istanbul-lib-source-maps@4.0.1(supports-color@8.1.1): dependencies: @@ -20583,11 +20335,13 @@ snapshots: source-map: 0.6.1 transitivePeerDependencies: - supports-color + optional: true istanbul-reports@3.1.6: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + optional: true iterator.prototype@1.1.2: dependencies: @@ -20620,20 +20374,12 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 - jest-allure2-adapter@0.3.12: - dependencies: - '@types/allure-js-commons': 0.0.1 - '@types/node': 10.17.60 - '@types/strip-ansi': 5.2.1 - allure-js-commons: 2.8.1 - strip-ansi: 6.0.1 - uuid-by-string: 3.0.7 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 + optional: true jest-circus@29.7.0(supports-color@8.1.1): dependencies: @@ -20641,7 +20387,7 @@ snapshots: '@jest/expect': 29.7.0(supports-color@8.1.1) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -20660,6 +20406,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true jest-cli@29.7.0(@types/node@20.12.11)(supports-color@8.1.1): dependencies: @@ -20679,6 +20426,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest-config@29.7.0(@types/node@20.12.11)(supports-color@8.1.1): dependencies: @@ -20709,6 +20457,38 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true + + jest-config@29.7.0(@types/node@22.7.5)(supports-color@8.1.1): + dependencies: + '@babel/core': 7.24.7(supports-color@8.1.1) + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.7(supports-color@8.1.1))(supports-color@8.1.1) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(supports-color@8.1.1) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0(supports-color@8.1.1) + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.7.5 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true jest-diff@29.7.0: dependencies: @@ -20716,10 +20496,12 @@ snapshots: diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 + optional: true jest-docblock@29.7.0: dependencies: detect-newline: 3.1.0 + optional: true jest-each@29.7.0: dependencies: @@ -20728,38 +20510,26 @@ snapshots: jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 - - jest-environment-jsdom@29.7.0(supports-color@8.1.1): - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/jsdom': 20.0.1 - '@types/node': 20.12.11 - jest-mock: 29.7.0 - jest-util: 29.7.0 - jsdom: 20.0.3(supports-color@8.1.1) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + optional: true jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 jest-mock: 29.7.0 jest-util: 29.7.0 + optional: true - jest-get-type@29.6.3: {} + jest-get-type@29.6.3: + optional: true jest-haste-map@29.7.0: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.7 - '@types/node': 20.12.11 + '@types/node': 22.7.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -20770,11 +20540,13 @@ snapshots: walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 + optional: true jest-leak-detector@29.7.0: dependencies: jest-get-type: 29.6.3 pretty-format: 29.7.0 + optional: true jest-matcher-utils@29.7.0: dependencies: @@ -20782,6 +20554,7 @@ snapshots: jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 + optional: true jest-message-util@29.7.0: dependencies: @@ -20794,18 +20567,22 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 + optional: true jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 jest-util: 29.7.0 + optional: true jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): optionalDependencies: jest-resolve: 29.7.0 + optional: true - jest-regex-util@29.6.3: {} + jest-regex-util@29.6.3: + optional: true jest-resolve-dependencies@29.7.0(supports-color@8.1.1): dependencies: @@ -20813,6 +20590,7 @@ snapshots: jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true jest-resolve@29.7.0: dependencies: @@ -20825,6 +20603,7 @@ snapshots: resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 + optional: true jest-runner@29.7.0(supports-color@8.1.1): dependencies: @@ -20833,7 +20612,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -20851,6 +20630,7 @@ snapshots: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color + optional: true jest-runtime@29.7.0(supports-color@8.1.1): dependencies: @@ -20861,7 +20641,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -20878,6 +20658,7 @@ snapshots: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color + optional: true jest-snapshot@29.7.0(supports-color@8.1.1): dependencies: @@ -20903,15 +20684,17 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + optional: true jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 picomatch: 2.3.1 + optional: true jest-validate@29.7.0: dependencies: @@ -20921,30 +20704,33 @@ snapshots: jest-get-type: 29.6.3 leven: 3.1.0 pretty-format: 29.7.0 + optional: true jest-watcher@29.7.0: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.11 + '@types/node': 22.7.5 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 + optional: true jest-worker@27.5.1: dependencies: - '@types/node': 20.12.11 + '@types/node': 22.7.5 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.12.11 + '@types/node': 22.7.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 + optional: true jest@29.7.0(@types/node@20.12.11)(supports-color@8.1.1): dependencies: @@ -20957,6 +20743,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jiti@1.20.0: {} @@ -20973,10 +20760,6 @@ snapshots: js-levenshtein@1.1.6: {} - js-md5@0.7.3: {} - - js-sha1@0.6.0: {} - js-tiktoken@1.0.10: dependencies: base64-js: 1.5.1 @@ -21036,6 +20819,7 @@ snapshots: - bufferutil - supports-color - utf-8-validate + optional: true jsesc@2.5.2: {} @@ -21130,7 +20914,8 @@ snapshots: kind-of@6.0.3: {} - kleur@3.0.3: {} + kleur@3.0.3: + optional: true kleur@4.1.5: {} @@ -21144,7 +20929,8 @@ snapshots: dependencies: set-getter: 0.1.1 - leven@3.1.0: {} + leven@3.1.0: + optional: true levn@0.4.1: dependencies: @@ -21217,6 +21003,7 @@ snapshots: locate-path@5.0.0: dependencies: p-locate: 4.1.0 + optional: true locate-path@6.0.0: dependencies: @@ -21263,8 +21050,6 @@ snapshots: lodash.isstring@4.0.1: {} - lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} lodash.once@4.1.1: {} @@ -21347,8 +21132,7 @@ snapshots: make-dir@4.0.0: dependencies: semver: 7.6.3 - - make-error@1.3.6: {} + optional: true make-fetch-happen@13.0.1(supports-color@8.1.1): dependencies: @@ -21392,6 +21176,7 @@ snapshots: makeerror@1.0.12: dependencies: tmpl: 1.0.5 + optional: true mark.js@8.11.1: {} @@ -22388,7 +22173,8 @@ snapshots: dependencies: mime-db: 1.52.0 - mimic-fn@2.1.0: {} + mimic-fn@2.1.0: + optional: true mimic-fn@4.0.0: {} @@ -22694,12 +22480,6 @@ snapshots: dependencies: http2-client: 1.3.5 - node-fetch@2.6.7(encoding@0.1.13): - dependencies: - whatwg-url: 5.0.0 - optionalDependencies: - encoding: 0.1.13 - node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -22743,7 +22523,8 @@ snapshots: - bluebird - supports-color - node-int64@0.4.0: {} + node-int64@0.4.0: + optional: true node-readfiles@0.2.0: dependencies: @@ -22796,6 +22577,7 @@ snapshots: npm-run-path@4.0.1: dependencies: path-key: 3.1.1 + optional: true npm-run-path@5.3.0: dependencies: @@ -22828,7 +22610,8 @@ snapshots: mitt: 3.0.1 next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) - nwsapi@2.2.7: {} + nwsapi@2.2.7: + optional: true oas-kit-common@1.0.8: dependencies: @@ -22923,6 +22706,7 @@ snapshots: onetime@5.1.2: dependencies: mimic-fn: 2.1.0 + optional: true onetime@6.0.0: dependencies: @@ -22986,6 +22770,7 @@ snapshots: p-limit@2.3.0: dependencies: p-try: 2.2.0 + optional: true p-limit@3.1.0: dependencies: @@ -22998,6 +22783,7 @@ snapshots: p-locate@4.1.0: dependencies: p-limit: 2.3.0 + optional: true p-locate@5.0.0: dependencies: @@ -23007,7 +22793,8 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-try@2.2.0: {} + p-try@2.2.0: + optional: true package-json-from-dist@1.0.1: {} @@ -23062,6 +22849,7 @@ snapshots: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + optional: true parse-numeric-range@1.3.0: {} @@ -23212,6 +23000,7 @@ snapshots: pkg-dir@4.2.0: dependencies: find-up: 4.1.0 + optional: true pkg-types@1.2.0: dependencies: @@ -23308,8 +23097,6 @@ snapshots: dependencies: xtend: 4.0.2 - postgres@3.3.5: {} - prelude-ls@1.2.1: {} prettier-plugin-sql-cst@0.11.1: @@ -23371,6 +23158,7 @@ snapshots: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + optional: true prop-types@15.8.1: dependencies: @@ -23384,8 +23172,6 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 - properties@1.2.1: {} - property-expr@2.0.5: {} property-information@5.6.0: @@ -23419,7 +23205,8 @@ snapshots: punycode@2.3.0: {} - pure-rand@6.0.4: {} + pure-rand@6.0.4: + optional: true querystringify@2.2.0: {} @@ -24214,14 +24001,17 @@ snapshots: resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 + optional: true resolve-from@4.0.0: {} - resolve-from@5.0.0: {} + resolve-from@5.0.0: + optional: true resolve-pkg-maps@1.0.0: {} - resolve.exports@2.0.2: {} + resolve.exports@2.0.2: + optional: true resolve@1.22.8: dependencies: @@ -24311,7 +24101,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 - safer-buffer@2.1.2: {} + safer-buffer@2.1.2: + optional: true sass@1.72.0: dependencies: @@ -24335,6 +24126,7 @@ snapshots: saxes@6.0.0: dependencies: xmlchars: 2.2.0 + optional: true scheduler@0.20.2: dependencies: @@ -24498,7 +24290,8 @@ snapshots: mrmime: 2.0.0 totalist: 3.0.1 - sisteransi@1.0.5: {} + sisteransi@1.0.5: + optional: true slash@3.0.0: {} @@ -24553,6 +24346,7 @@ snapshots: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + optional: true source-map-support@0.5.21: dependencies: @@ -24637,6 +24431,7 @@ snapshots: stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 + optional: true stackback@0.0.2: {} @@ -24677,6 +24472,7 @@ snapshots: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 + optional: true string-width@4.2.3: dependencies: @@ -24759,11 +24555,13 @@ snapshots: strip-bom@3.0.0: {} - strip-bom@4.0.0: {} + strip-bom@4.0.0: + optional: true strip-color@0.1.0: {} - strip-final-newline@2.0.0: {} + strip-final-newline@2.0.0: + optional: true strip-final-newline@3.0.0: {} @@ -24934,7 +24732,8 @@ snapshots: dependencies: vue: 3.3.9(typescript@5.5.2) - symbol-tree@3.2.4: {} + symbol-tree@3.2.4: + optional: true tailwind-merge@1.14.0: {} @@ -25015,6 +24814,7 @@ snapshots: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 + optional: true text-table@0.2.0: {} @@ -25065,7 +24865,8 @@ snapshots: tinyspy@3.0.2: {} - tmpl@1.0.5: {} + tmpl@1.0.5: + optional: true to-fast-properties@2.0.0: {} @@ -25112,6 +24913,7 @@ snapshots: tr46@3.0.0: dependencies: punycode: 2.3.0 + optional: true tree-dump@1.0.1(tslib@2.6.2): dependencies: @@ -25129,23 +24931,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.1.2(@babel/core@7.24.7(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7(supports-color@8.1.1))(supports-color@8.1.1))(jest@29.7.0(@types/node@20.12.11)(supports-color@8.1.1))(typescript@5.5.2): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.12.11)(supports-color@8.1.1) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.6.3 - typescript: 5.5.2 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.24.7(supports-color@8.1.1) - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7(supports-color@8.1.1))(supports-color@8.1.1) - ts-morph@22.0.0: dependencies: '@ts-morph/common': 0.23.0 @@ -25526,11 +25311,6 @@ snapshots: utility-types@3.11.0: {} - uuid-by-string@3.0.7: - dependencies: - js-md5: 0.7.3 - js-sha1: 0.6.0 - uuid@7.0.3: {} uuid@8.3.2: {} @@ -25549,6 +25329,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 + optional: true validate-npm-package-license@3.0.4: dependencies: @@ -25786,10 +25567,12 @@ snapshots: w3c-xmlserializer@4.0.0: dependencies: xml-name-validator: 4.0.0 + optional: true walker@1.0.8: dependencies: makeerror: 1.0.12 + optional: true warning@4.0.3: dependencies: @@ -25808,7 +25591,8 @@ snapshots: webidl-conversions@3.0.1: {} - webidl-conversions@7.0.0: {} + webidl-conversions@7.0.0: + optional: true webpack-bundle-analyzer@4.10.1: dependencies: @@ -25897,13 +25681,16 @@ snapshots: whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 + optional: true - whatwg-mimetype@3.0.0: {} + whatwg-mimetype@3.0.0: + optional: true whatwg-url@11.0.0: dependencies: tr46: 3.0.0 webidl-conversions: 7.0.0 + optional: true whatwg-url@5.0.0: dependencies: @@ -25995,6 +25782,7 @@ snapshots: dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 + optional: true write-file-atomic@5.0.1: dependencies: @@ -26009,14 +25797,16 @@ snapshots: dependencies: eventemitter3: 2.0.3 - xml-name-validator@4.0.0: {} + xml-name-validator@4.0.0: + optional: true xml-reader@2.4.3: dependencies: eventemitter3: 2.0.3 xml-lexer: 0.2.2 - xmlchars@2.2.0: {} + xmlchars@2.2.0: + optional: true xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a99e64db0ae..cb315b73f4c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,6 @@ packages: - "apps/*" - "packages/*" - - "tests" - "playwright-tests" catalog: diff --git a/tests/.jest/jest-custom-reporter.ts b/tests/.jest/jest-custom-reporter.ts deleted file mode 100644 index 28b5a2bf095..00000000000 --- a/tests/.jest/jest-custom-reporter.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import { AllureReporterApi, jasmine_, registerAllureReporter } from 'jest-allure2-adapter' -import { ContentType, Severity } from 'allure-js-commons' - -// eslint-disable-next-line @typescript-eslint/ban-types -type TestDecorator = ( - target: object, - property: string, - descriptor: PropertyDescriptor -) => PropertyDescriptor -export class JasmineAllureReporter implements jasmine_.CustomReporter { - allure: AllureReporterApi - - constructor(allure: AllureReporterApi) { - this.allure = allure - } - - suiteStarted(suite?: jasmine_.CustomReporterResult): void { - this.allure.startGroup(suite.description) - // some actions here on suite started - } - - suiteDone(): void { - // some actions here on suite end - this.allure.endGroup() - } - - specStarted(spec: jasmine_.CustomReporterResult): void { - this.allure.startTest(spec) - // some actions here on test started - } - - specDone(spec: jasmine_.CustomReporterResult): void { - // some actions here on spec end - this.allure.endTest(spec) - } -} - -registerAllureReporter(undefined, (allure) => new JasmineAllureReporter(allure)) - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace NodeJS { - interface Global { - reporter: AllureReporterApi - } - } -} - -function getAllure(): AllureReporterApi { - // @ts-ignore - we are checking if reporter is defined - if (!global.reporter) { - throw new Error('Unable to find Allure implementation') - } - // @ts-ignore - we know that reporter is an AllureReporterApi - return global.reporter -} - -export function step(nameFn: string | ((arg: T) => string)): TestDecorator { - return (target: object, propertyKey: string, descriptor: PropertyDescriptor) => { - const original: object = descriptor.value - let callable: (args: T) => void = () => { - /* */ - } - - if (typeof original === 'function') { - descriptor.value = function (...args: [T]) { - try { - const value: string = typeof nameFn === 'function' ? nameFn.apply(this, args) : nameFn - callable = () => getAllure().step(value, () => original.apply(this, args)) - // tslint:disable-next-line:no-console - console.info(`Step: ${value || nameFn}`) - } catch (e) { - // tslint:disable-next-line:no-console - console.error(`[ERROR] Failed to apply decorator: ${e}`) - } - return callable.apply(this, args) - } - } - return descriptor - } -} - -export function attachment(name: string, type: ContentType) { - return ( - _target: object, - _propertyKey: string, - descriptor: PropertyDescriptor - ): PropertyDescriptor => { - const original: object = descriptor.value - let callable: (args: T) => void = () => { - /* */ - } - - if (typeof original === 'function') { - descriptor.value = async function (...args: [T]) { - try { - const content: Buffer | string = await original.apply(this, args) - callable = () => - getAllure().step(name, () => { - getAllure().attachment(type.toString(), content, type) - }) - } catch (e) { - // tslint:disable-next-line:no-console - console.error(`[ERROR] Failed to apply decorator: ${e}`) - } - return callable.apply(this, args) - } - } - return descriptor - } -} - -export function attach(name: string, content: string | Buffer, type: ContentType): void { - getAllure().step(name, () => { - getAllure().attachment(type.toString(), content, type) - }) -} - -export function log(name: string, description?: string): void { - console.info(description ? `${name}: ${description}` : name) - getAllure().step(name, () => { - if (description) { - getAllure().step(description, () => { - /* */ - }) - } - }) -} - -export function feature(featureFn: string | ((arg: T) => string)): TestDecorator { - return processDecorator(featureFn, (name) => getAllure().feature(name)) -} - -export function story(storyFn: string | ((arg: T) => string)): TestDecorator { - return processDecorator(storyFn, (name) => getAllure().story(name)) -} - -export function severity( - severityFn: Severity | string | ((arg: T) => string | Severity) -): TestDecorator { - return processDecorator(severityFn, (name: Severity) => getAllure().severity(name)) -} - -export function tag(tagFn: string | ((arg: T) => string)): TestDecorator { - return processDecorator(tagFn, (name) => getAllure().tag(name)) -} - -export function owner(ownerFn: string | ((arg: T) => string)): TestDecorator { - return processDecorator(ownerFn, (name) => getAllure().owner(name)) -} - -export function description(descriptionFn: string | ((arg: T) => string)): TestDecorator { - return processDecorator(descriptionFn, (text) => getAllure().description(text)) -} - -function processDecorator( - parameterFn: string | ((arg: T) => string), - reporterFn: (arg: string) => void -): TestDecorator { - return (target: object, property: string, descriptor: PropertyDescriptor) => { - return processDescriptor(parameterFn, reporterFn, descriptor) - } -} - -function processDescriptor( - parameterFn: string | ((arg: T) => string), - reporterFn: (arg: string) => void, - descriptor: PropertyDescriptor -): PropertyDescriptor { - const original: object = descriptor.value - if (typeof original === 'function') { - descriptor.value = function (...args: [T]) { - try { - const value: string = - typeof parameterFn === 'function' ? parameterFn.apply(this, args) : parameterFn - reporterFn(value) - } catch (e) { - // tslint:disable-next-line:no-console - console.error(`[ERROR] Failed to apply decorator: ${e}`) - } - return original.apply(this, args) - } - } - - for (const prop of Object.keys(original)) { - if (original.hasOwnProperty(prop) && prop.startsWith('__testdeck_')) { - // @ts-ignore - we know that prop exists - descriptor.value[prop] = original[prop] - } - } - - return descriptor -} diff --git a/tests/.jest/jest-env.ts b/tests/.jest/jest-env.ts deleted file mode 100644 index 73d770155e6..00000000000 --- a/tests/.jest/jest-env.ts +++ /dev/null @@ -1,14 +0,0 @@ -export default () => { - process.env.SUPABASE_DB_HOST = process.env.SUPABASE_DB_HOST ?? 'localhost' - process.env.SUPABASE_DB_PORT = process.env.SUPABASE_DB_PORT ?? '5432' - process.env.SUPABASE_DB_PASS = - process.env.SUPABASE_DB_PASS ?? 'your-super-secret-and-long-postgres-password' - process.env.SUPABASE_GOTRUE = process.env.SUPABASE_GOTRUE ?? 'http://localhost:8000/auth/v1' - process.env.SUPABASE_URL = process.env.SUPABASE_URL ?? 'http://localhost:8000' - process.env.SUPABASE_KEY_ANON = - process.env.SUPABASE_KEY_ANON ?? - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE' - process.env.SUPABASE_KEY_ADMIN = - process.env.SUPABASE_KEY_ADMIN ?? - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q' -} diff --git a/tests/.jest/teardown.ts b/tests/.jest/teardown.ts deleted file mode 100644 index afb2b1cb2c9..00000000000 --- a/tests/.jest/teardown.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createClient, User, UserResponse } from '@supabase/supabase-js' - -const removeAllUsers = async () => { - const sb = createClient( - process.env.SUPABASE_URL as string, - process.env.SUPABASE_KEY_ADMIN as string - ) - - const { - data: { users }, - } = await sb.auth.admin.listUsers() - - const promises: Promise[] = [] - users.map((u) => { - sb.from('profiles') - .delete() - .match({ id: u.id }) - .then(() => promises.push(sb.auth.admin.deleteUser(u.id))) - }) - await Promise.all(promises) -} - -export default async () => { - await removeAllUsers() -} diff --git a/tests/.prettierignore b/tests/.prettierignore deleted file mode 100644 index 915d0dfbc0f..00000000000 --- a/tests/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -api -node_modules -package-lock.json -docker* diff --git a/tests/.prettierrc b/tests/.prettierrc deleted file mode 100644 index 8df6df7757c..00000000000 --- a/tests/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "trailingComma": "es5", - "tabWidth": 2, - "semi": false, - "singleQuote": true, - "printWidth": 100 -} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 901abed64dd..00000000000 --- a/tests/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Tests - -These tests can be run with Docker. - -We can use either [allure-commandline](https://github.com/allure-framework/allure2) as it is open source and as an github action to host reports with history on GitHub Pages. - -Or use their paid version if we want a bit more groove and possibility to manage test cases not only as code but also manually: . It can be self-hosted or cloud. Not sure that we need it now, but we can always migrate seamlessly to it if we would need it. - -### Steps - -In the parent folder: - -- `npm run docker:dev` -- `npm run test` - -### Clean up - -- `npm run docker:remove` diff --git a/tests/data/func.ts b/tests/data/func.ts deleted file mode 100644 index 37809150683..00000000000 --- a/tests/data/func.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' - -console.log('Hello from Functions!') - -serve(async (req) => { - const { name } = await req.json() - const data = { - message: `Hello ${name}!`, - } - - return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } }) -}) diff --git a/tests/features/javascript/authAdmin.spec.ts b/tests/features/javascript/authAdmin.spec.ts deleted file mode 100644 index 57b55cf495f..00000000000 --- a/tests/features/javascript/authAdmin.spec.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { params, retries, suite, test } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' - -import { AdminUserAttributes, AuthError, SupabaseClient, UserResponse } from '@supabase/supabase-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' - -@suite('auth_admin') -class AuthenticationAPI extends Hooks { - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you create user then it has to be in auth db schema') - @test - async 'create user via admin api'() { - const { user, error } = await this.createUserAsAdmin() - expect(error).toBeNull() - expect(user).not.toBeNull() - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you create user then he can sign in') - @test - async 'user created by admin can login'() { - const { user, error } = await this.createUserAsAdmin() - expect(error).toBeNull() - expect(user).not.toBeNull() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - - const { - data: { user: createdUser }, - error: getErr, - } = await supabase.auth.signInWithPassword({ - email: user.email, - password: user.password, - }) - expect(getErr).toBeNull() - expect(createdUser).not.toBeNull() - expect(createdUser.id).toBe(user.id) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.CRITICAL) - @description('When you try to create user with anon key then you should get error') - @test - async 'admin create user with anon key should fail'() { - const fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - email_confirm: true, - } - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - - const { - error, - data: { user }, - } = await supabase.auth.admin.createUser(fakeUser) - expect(user).toBeNull() - expect(error).not.toBeNull() - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.CRITICAL) - @description('When you try to create user as logged in user then you should get error') - @test - async 'admin create user with logged in user should fail'() { - const { user } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { error: signInError } = await supabase.auth.signInWithPassword({ - email: user.email, - password: user.password, - }) - expect(signInError).toBeNull() - - const fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - } - const { - error, - data: { user: newUser }, - } = await supabase.auth.admin.createUser(fakeUser) - expect(newUser).toBeNull() - expect(error).not.toBeNull() - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you list users then you should get all users') - @test - async 'list users with service key'() { - const { user: user1 } = await this.createUserAsAdmin() - const { user: user2 } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const { - data: { users }, - error, - } = await supabase.auth.admin.listUsers() - - expect(error).toBeNull() - expect(users).not.toBeNull() - expect(users.length).toBeGreaterThanOrEqual(2) - expect(users.map((u) => u.id)).toEqual(expect.arrayContaining([user1.id, user2.id])) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.CRITICAL) - @description('When you try to list user with anon key then you should get error') - @test - async 'list users with anon key should fail'() { - await this.createUserAsAdmin() - await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { - data: { users }, - error, - } = await supabase.auth.admin.listUsers() - - expect(error).not.toBeNull() - expect(users).toHaveLength(0) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.CRITICAL) - @description('When you try to list user as logged in user then you should get error') - @test - async 'list users as logged in user should fail'() { - const { user } = await this.createUserAsAdmin() - await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { error: signInError } = await supabase.auth.signInWithPassword({ - email: user.email, - password: user.password, - }) - expect(signInError).toBeNull() - - const { - data: { users }, - error, - } = await supabase.auth.admin.listUsers() - expect(error).not.toBeNull() - expect(users).toHaveLength(0) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you get provider url then corresponding auth provider url should be returned') - @params.skip({ provider: 'google', options: {}, expectedURL: 'todo' }) - @params.skip({ - provider: 'google', - options: { redirectTo: 'todo', scopes: 'todo' }, - expectedURL: 'todo', - }) - @params.skip({ provider: 'twitter', options: {}, expectedURL: 'todo' }) - // ... - async 'get url for provider'() { - // todo - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you get user by id he has to be returned') - @test - async 'get user should work'() { - const { user } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const { - data: { user: foundUser }, - error, - } = await supabase.auth.admin.getUserById(user.id) - - expect(error).toBeNull() - expect(foundUser).not.toBeNull() - expect(foundUser.id).toBe(user.id) - expect(foundUser.email).toBe(user.email) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you update user then this user has to be updated') - @test - async 'update user should work'() { - const { user } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - - const updatedUser = { - email: faker.internet.exampleEmail(), - phone: faker.phone.phoneNumber('!#!##!######'), - } - let { - data: { user: resultUser }, - error, - } = await this.updateWithRetries(supabase, user.id, updatedUser) - - expect(error).toBeNull() - expect(resultUser).not.toBeNull() - expect(resultUser.id).toBe(user.id) - expect(resultUser.email).toBe(updatedUser.email) - expect(resultUser.phone).toBe(updatedUser.phone) - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.NORMAL) - @description('When you delete user then this user has to be removed') - @test - async 'delete user should work'() { - const { user } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const { - data: { user: deletedUser }, - error, - } = await supabase.auth.admin.deleteUser(user.id) - expect(error).toBeNull() - - const { - data: { user: foundUser }, - error: getError, - } = await supabase.auth.admin.getUserById(user.id) - expect(getError).not.toBeNull() - expect(foundUser).toBeNull() - } - - @feature(FEATURE.AUTH_ADMIN) - @severity(Severity.CRITICAL) - @description('When you delete user with anon key you have to receive an error') - @test - async 'delete user with anon key should fail'() { - const { user } = await this.createUserAsAdmin() - const { user: villain } = await this.createUserAsAdmin() - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - await supabase.auth.signInWithPassword({ - email: villain.email, - password: villain.password, - }) - - const { - data: { user: deletedUser }, - error, - } = await supabase.auth.admin.deleteUser(user.id) - - expect(error).not.toBeNull() - expect(deletedUser).toBeNull() - - const sbAdmin = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const { - data: { user: foundUser }, - error: getError, - } = await sbAdmin.auth.admin.getUserById(user.id) - expect(getError).toBeNull() - expect(foundUser).not.toBeNull() - expect(foundUser.email).toBe(user.email) - } - - @step('Create a user as admin') - async createUserAsAdmin(data: AdminUserAttributes = undefined): Promise<{ - user: { - email: string - password: string - username: string - id: string - } - error: AuthError - }> { - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - - let fakeUser: AdminUserAttributes - if (data) { - fakeUser = data - } else { - fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - email_confirm: true, - } - } - - const { - error, - data: { user }, - } = await supabase.auth.admin.createUser(fakeUser) - - return { - error: error, - user: { - email: user?.email, - password: fakeUser.password, - username: faker.internet.userName(), - id: user?.id, - }, - } - } - - @step('Update user with retries') - async updateWithRetries(supabase: SupabaseClient, uid: string, attributes: AdminUserAttributes) { - let result: UserResponse - for (let i = 1; i < 5; i++) { - result = await supabase.auth.admin.updateUserById(uid, attributes) - - if (result.error && result.error.name === 'AuthRetryableFetchError') { - await new Promise((resolve) => setTimeout(resolve, 1000 * i)) - } else { - break - } - } - return result - } -} diff --git a/tests/features/javascript/authentication.spec.ts b/tests/features/javascript/authentication.spec.ts deleted file mode 100644 index c63af999c2c..00000000000 --- a/tests/features/javascript/authentication.spec.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { suite, test, timeout } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' - -import { AuthChangeEvent, Session } from '@supabase/supabase-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' - -@suite('authentication') -class Authentication extends Hooks { - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.BLOCKER) - @description('When user sign up then corresponding user in auth schema should be created') - @test - async 'signup should create user'() { - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - - const fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - username: faker.internet.userName(), - } - const { - data: { user, session }, - error: signUpError, - } = await this.signUp(supabase, fakeUser) - - expect(signUpError).toBeNull() - expect(user).toBeDefined() - expect(user.email).toEqual(fakeUser.email.toLowerCase()) - expect(session).toBeDefined() - - const [createdUser] = await this.selectUser(user) - expect(createdUser.email).toEqual(fakeUser.email.toLowerCase()) - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.BLOCKER) - @description('When user sign up then he should not be logged in until he confirms his email') - @test - async 'sing up new user and sign in'() { - // sign up user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - - const fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - username: faker.internet.userName(), - } - const { - data: { user, session: emptySession }, - error: signUpError, - } = await this.signUp(supabase, fakeUser) - - expect(signUpError).toBeNull() - expect(user).toBeDefined() - expect(user.email).toEqual(fakeUser.email.toLowerCase()) - expect(emptySession).not.toBeNull() - - const { - data: { session }, - error: signInError, - } = await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - expect(signInError).toBeNull() - expect(session).toBeDefined() - await supabase.auth.setSession(session) - - // check if user is signed in - const { - data: [profile], - error: errorInsert, - } = await this.insertProfile(supabase, user, fakeUser) - expect(errorInsert).toBeNull() - expect(profile.username).toMatch(fakeUser.username) - - const { data: profileGot } = await this.getUserProfile(supabase) - expect(profileGot.username).toMatch(profile.username) - - // check if user is able to sign out - const { error } = await this.signOut(supabase) - expect(error).toBeNull() - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.BLOCKER) - @description('When user sign up with phone then he should be logged in') - @test - async 'create new users by phone auth'() { - // sign up user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - - const fakeUser = { - password: faker.internet.password(), - username: faker.internet.userName(), - phone: faker.phone.phoneNumber('!#!##!######'), - } - const { - data: { user, session }, - error: signUpError, - } = await this.signUpByPhone(supabase, fakeUser) - - expect(signUpError).toBeNull() - expect(user).toBeDefined() - expect(user.phone).toEqual(fakeUser.phone) - expect(session).toBeDefined() - - // check if user is signed in - const { - data: [profile], - error: errorInsert, - } = await this.insertProfile(supabase, user, fakeUser) - expect(errorInsert).toBeNull() - expect(profile.username).toMatch(fakeUser.username) - - const { data: profileGot } = await this.getUserProfile(supabase) - expect(profileGot.username).toMatch(profile.username) - - // check if user is able to sign out - const { error } = await this.signOut(supabase) - expect(error).toBeNull() - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.BLOCKER) - @description('When user is already signed up then he should be able to log in') - @test - async 'existing users should be able to login'() { - // create user - const fakeUser = await this.createUser() - - // sign in as user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { - data: { session, user }, - error: signInError, - } = await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - - expect(signInError).toBeNull() - expect(session).toBeDefined() - expect(user).toBeDefined() - expect(user.email).toEqual(fakeUser.email.toLowerCase()) - - // check if user is signed in correctly and rls is working - const { - data: [profileInserted], - error: errorInsert, - } = await this.insertProfile(supabase, user, fakeUser) - expect(errorInsert).toBeNull() - expect(profileInserted.username).toMatch(fakeUser.username) - - const { data: profileGot } = await this.getUserProfile(supabase) - expect(profileGot.username).toMatch(profileInserted.username) - - // check if user is able to sign out - const { error } = await this.signOut(supabase) - expect(error).toBeNull() - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.NORMAL) - @description('When user is signed in then he should be able to get his info and metadata') - @test - async 'get user should return logged in user'() { - // create user - const username = faker.internet.userName() - const date = faker.date.recent().toUTCString() - const fakeUser = await this.createUser({ - username: username, - date: date, - }) - - // sign in as user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - - // get signed in user data - const { - data: { user }, - error: getUserErr, - } = await this.getUser(supabase) - - log('Check if user is signed in correctly and can get his data') - expect(getUserErr).toBeNull() - expect(user).not.toBeNull() - expect(user.email).toEqual(fakeUser.email.toLowerCase()) - expect(user.role).toEqual('authenticated') - expect(user.aud).toEqual('authenticated') - // verify if metadata is correctly set after sing up - expect(user.user_metadata).toEqual({ - username: username, - date: date, - }) - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.NORMAL) - @description('When user is signed in then he should be able update himself in auth schema') - @test.skip - async 'update user should update logged in user'() { - // create user - const fakeUser = await this.createUser() - - // sign in as user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - - // get signed in user data - const user = await this.getUser(supabase) - - // update user - const updParams = { - email: faker.internet.email(), - password: faker.internet.password(), - phone: faker.phone.phoneNumber('!#!##!######'), - } - const { - data: { user: updUser }, - error: updUserError, - } = await this.updateUser(supabase, updParams) - expect(updUserError).toBeNull() - expect(updUser).not.toBeNull() - expect(updUser.email).toEqual(updParams.email.toLowerCase()) - expect(updUser.phone).toEqual(updParams.phone) - - // get user and check it was updated - const updatedUser = await this.getUser(supabase) - expect(updatedUser.data.user.email).toEqual(updParams.email.toLowerCase()) - expect(updatedUser.data.user.phone).toEqual(updParams.phone) - - // sign in with new credentials - await supabase.auth.signOut() - const signIn = await supabase.auth.signInWithPassword({ - email: updParams.email, - password: updParams.password, - }) - expect(signIn.error).toBeNull() - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.NORMAL) - @description('When user changes session then he still should be correctly logined') - @test - async 'set session should set new auth'() { - // create user - const fakeUser = await this.createUser() - - // sign in as user - const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { - data: { session }, - } = await sb.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { error: sessionErr } = await supabase.auth.setSession(session) - expect(sessionErr).toBeNull() - - // check if user is signed in correctly and rls is working - const { data: profileInserted, error: errorInsert } = await this.insertProfile( - supabase, - fakeUser, - fakeUser - ) - expect(errorInsert).toBeNull() - expect(profileInserted).toHaveLength(1) - expect(profileInserted[0].username).toMatch(fakeUser.username) - } - - @feature(FEATURE.AUTHENTICATION) - @severity(Severity.NORMAL) - @description('When user subscribes on auth changes then user has to receive auth updates') - @test - async 'on auth state changed should return events'() { - // create user - const fakeUser = await this.createUser() - const events: { event: AuthChangeEvent; token: string }[] = [] - const onAuthStateChanged = (event: AuthChangeEvent, session: Session) => { - log('onAuthStateChanged triggered', event) - events.push({ event, token: session?.access_token }) - } - - // create client and subscribe on auth state changes - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(onAuthStateChanged) - - // sign in as user - await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - - // update user - const updParams = { - email: faker.internet.email(), - password: faker.internet.password(), - phone: faker.phone.phoneNumber('!#!##!######'), - } - const { error: updUserError } = await this.updateUser(supabase, updParams) - expect(updUserError).toBeNull() - - // remove subscription and sign out - subscription.unsubscribe() - await supabase.auth.signOut() - - // check if sign in and update events were triggered and sign out event was not triggered - expect(events).toHaveLength(2) - expect(events.map((e) => e.event)).toEqual( - expect.arrayContaining(['SIGNED_IN', 'USER_UPDATED']) - ) - expect(events.map((e) => e.event)).not.toContain('SIGNED_OUT') - } -} diff --git a/tests/features/javascript/functions.spec.ts b/tests/features/javascript/functions.spec.ts deleted file mode 100644 index 0aa8fc33e42..00000000000 --- a/tests/features/javascript/functions.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { params, suite, test } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' -import { exec, ExecException } from 'child_process' -import os from 'os' - -import { Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' -import path from 'path' - -@suite('functions') -class Functions extends Hooks { - @feature(FEATURE.STORAGE) - @severity(Severity.NORMAL) - @description('When you get functions client then you are able to set auth') - @test.skip - async 'set auth'() { - // execute cli command - const deno = path.join(os.homedir(), '.supabase', 'deno') - const funcPath = path.join(process.cwd(), 'data', 'func.ts') - let prom = new Promise<{ error: ExecException; stdout: string; stderr: string }>((resolve) => { - exec(`${deno} bundle --no-check=remote --quiet ${funcPath}`, (error, stdout, stderr) => - resolve({ error, stdout, stderr }) - ) - }) - const { supabase } = await this.createSignedInSupaClient() - const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY) - const { - data: { session }, - } = await supabase.auth.getSession() - - sb.functions.setAuth(session.access_token) - const { error, stdout, stderr } = await prom - expect([null, undefined]).toContain(error) - expect([null, undefined]).toContain(stderr) - expect(stdout).toBeDefined() - sb.functions.invoke('get_user') - } -} diff --git a/tests/features/javascript/hooks.ts b/tests/features/javascript/hooks.ts deleted file mode 100644 index 25993a4ee6e..00000000000 --- a/tests/features/javascript/hooks.ts +++ /dev/null @@ -1,211 +0,0 @@ -import postgres from 'postgres' -import crossFetch from 'cross-fetch' -import { faker } from '@faker-js/faker' -import { - AuthResponse, - createClient, - SupabaseClient, - SupabaseClientOptions, - User, - UserAttributes, - UserResponse, -} from '@supabase/supabase-js' - -import { JasmineAllureReporter, step } from '../../.jest/jest-custom-reporter' - -export abstract class Hooks { - static sql = postgres({ - host: process.env.SUPABASE_DB_HOST, - port: parseInt(process.env.SUPABASE_DB_PORT), - database: 'postgres', - username: 'postgres', - password: process.env.SUPABASE_DB_PASS, - }) - - @step('terminate sql connection') - static async after(): Promise { - try { - Hooks.sql.end({ timeout: 100 }) - return Promise.resolve(null) - } catch (err) { - return Promise.reject(err) - } - } - - @step('Create Supabase client') - createSupaClient( - url: string, - key: string, - options: SupabaseClientOptions<'public'> = {} - ): SupabaseClient { - options.auth = options.auth || {} - options.auth.persistSession = false - - return createClient(url, key, options) - } - - @step('Create a valid user') - async createUser(data: object = {}): Promise<{ - email: string - password: string - username: string - id: string - }> { - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - - const fakeUser = { - email: faker.internet.exampleEmail(), - password: faker.internet.password(), - username: faker.internet.userName(), - id: '', - } - const { - error: signUpError, - data: { user }, - } = await this.signUp(supabase, fakeUser, { - data: data, - }) - expect(signUpError).toBeNull() - expect(user).not.toBeNull() - fakeUser.id = user.id - - return fakeUser - } - - // todo: rework this - @step((token: string) => `verify with token ${token}`) - async verify(token: string, email: string): Promise { - return crossFetch(`${process.env.SUPABASE_GOTRUE}/verify`, { - method: 'POST', - body: JSON.stringify({ - type: 'signup', - token: token, - email: email, - }), - }) - } - - @step((user: User) => `get confirmation token for user ${user.id}`) - async getConfirmationToken(user: User): Promise<[{ confirmation_token: any }]> { - return Hooks.sql` - select confirmation_token - from auth.users - where id = ${user.id} - ` - } - - @step('I sign up with a valid email and password') - async signUp( - supabase: SupabaseClient, - { - email = faker.internet.exampleEmail(), - password = faker.internet.password(), - }: { - email?: string - password?: string - } = {}, - options: { - redirectTo?: string - data?: object - captchaToken?: string - } = {} - ): Promise { - return supabase.auth.signUp({ - email: email, - password: password, - options: options, - }) - } - - @step('Check if I am being able to log out') - async signOut(supabase: SupabaseClient): Promise<{ error: any }> { - return supabase.auth.signOut() - } - - @step('Get user data, if there is a logged in user') - getUser(supabase: SupabaseClient) { - return supabase.auth.getUser() - } - - @step((user: User) => `Get user by ID (${user.id}) from Supabase auth schema`) - async selectUser(user: User): Promise<[{ email: string }]> { - return Hooks.sql` - select - email - from auth.users - where - id = ${user.id} - ` - } - - @step('I sign up with a valid email and password') - async signUpByPhone( - supabase: SupabaseClient, - { - phone = faker.phone.phoneNumber(), - password = faker.internet.password(), - }: { - phone?: string - password?: string - } = {}, - options: { - redirectTo?: string - data?: object - } = {} - ): Promise { - return supabase.auth.signUp({ - phone: phone, - password: password, - options: options, - }) - } - - @step('User inserts profile') - async insertProfile( - supabase: SupabaseClient, - user: { - id: string - }, - fakeUser: { - username: string - } - ): Promise<{ data: any; error: any }> { - return await supabase - .from('profiles') - .insert({ - id: user.id, - username: fakeUser.username, - }) - .select() - } - - @step('I can get my profile via postgREST') - async getUserProfile(supabase: SupabaseClient): Promise<{ data: any; error: any }> { - return supabase.from('profiles').select().maybeSingle() - } - - @step('Update user info') - async updateUser(supabase: SupabaseClient, attr: UserAttributes): Promise { - return supabase.auth.updateUser(attr) - } - - @step('Create signed in supabase client') - async createSignedInSupaClient() { - // create user - const fakeUser = await this.createUser() - - // sign in as user - const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) - const { - data: { user }, - error: signInError, - } = await supabase.auth.signInWithPassword({ - email: fakeUser.email, - password: fakeUser.password, - }) - expect(signInError).toBeNull() - fakeUser.id = user.id - - return { supabase, user: fakeUser } - } -} diff --git a/tests/features/javascript/postgrest.spec.ts b/tests/features/javascript/postgrest.spec.ts deleted file mode 100644 index d0602ce19ce..00000000000 --- a/tests/features/javascript/postgrest.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { suite, test, timeout } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' - -@suite('postgrest') -class PostgREST extends Hooks { - @feature(FEATURE.POSTGREST) - @severity(Severity.BLOCKER) - @description('User can insert profile for himself according to RLS to profiles table') - @test - async 'insert row'() { - const { supabase, user } = await this.createSignedInSupaClient() - - // check if user can insert profile for himself - const { - data: [profileInserted], - error: errorInsert, - } = await this.insertProfile(supabase, user, user) - expect(errorInsert).toBeNull() - expect(profileInserted.username).toMatch(user.username) - } - - @feature(FEATURE.POSTGREST) - @severity(Severity.BLOCKER) - @description('User can select his profile from profiles table') - @test - async 'select row'() { - const { supabase, user } = await this.createSignedInSupaClient() - - // insert profile for user - await this.insertProfile(supabase, user, user) - - const { data: profileGot, error } = await this.getUserProfile(supabase) - expect(error).toBeNull() - expect(profileGot.username).toMatch(user.username) - } - - @feature(FEATURE.POSTGREST) - @severity(Severity.BLOCKER) - @description('User can update his profile in profiles table') - @test - async 'update row'() { - const { supabase, user } = await this.createSignedInSupaClient() - - // insert profile for user - await this.insertProfile(supabase, user, user) - - const newName = faker.internet.userName() - // update profile for user - const { data: updUser, error } = await supabase - .from('profiles') - .update({ - id: user.id, - username: newName, - }) - .select() - expect(error).toBeNull() - expect(updUser.length).toEqual(1) - expect(updUser[0].username).toMatch(newName) - - const { data: profileGot } = await this.getUserProfile(supabase) - expect(profileGot.username).toMatch(newName) - } - - @feature(FEATURE.POSTGREST) - @severity(Severity.BLOCKER) - @description('User can delete his profile in profiles table') - @test - async 'delete row'() { - const { supabase, user } = await this.createSignedInSupaClient() - - // insert profile for user - await this.insertProfile(supabase, user, user) - - // delete profile for user - const { error } = await supabase.from('profiles').delete().eq('id', user.id).select() - expect(error).toBeNull() - - const { data: profileGot } = await this.getUserProfile(supabase) - expect(profileGot).toBeNull() - } -} diff --git a/tests/features/javascript/realtime.spec.ts b/tests/features/javascript/realtime.spec.ts deleted file mode 100644 index 59ba68289bf..00000000000 --- a/tests/features/javascript/realtime.spec.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { params, suite, test, timeout } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' - -import { RealtimeChannel } from '@supabase/supabase-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' - -@suite('realtime') -@timeout(30000) -class Realtime extends Hooks { - @feature(FEATURE.REALTIME) - @severity(Severity.BLOCKER) - @description('When you call "on" table then connected realtime client should be returned') - @test - async '[skip-stage] connect to realtime'() { - const { supabase } = await this.createSignedInSupaClient() - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => - console.log(payload) - ) - channel.subscribe() - - expect(channel).toBeDefined() - const err = await this.waitForChannelJoined(channel) - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(err).toBeNull() - const ok = await supabase.removeChannel(channel) - expect(ok).toBe('ok') - } - - @feature(FEATURE.REALTIME) - @severity(Severity.BLOCKER) - @description('When you subscrive to realtime, you have to receive updates') - @timeout(60000) - @test - async '[skip-stage] receive event when connected to realtime'() { - let res: any - let t: NodeJS.Timeout - const { supabase, user } = await this.createSignedInSupaClient() - - let payloadReceived = (payload: any) => { - if (payload?.eventType !== 'INSERT') { - return - } - clearTimeout(t) - expect(payload.schema).toBe('public') - expect(payload.table).toBe('profiles') - expect(payload.new.id).toBe(user.id) - expect(payload.new.username).toBe(user.username) - expect(payload.old).toEqual({}) - expect(payload.error).toBeUndefined() - res(null) - } - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, payloadReceived) - channel.subscribe() - - expect(channel).toBeDefined() - await this.waitForChannelJoined(channel) - // we should wait some time seconds to connect to database changes - await new Promise((resolve) => setTimeout(resolve, 10000)) - - const eventPromise = new Promise((resolve) => { - res = resolve - new Promise(() => { - t = setTimeout(() => resolve(new Error('timeout')), 30000) - }) - }) - await this.insertProfile(supabase, user, user) - expect(await eventPromise).toBeNull() - - const ok = await supabase.removeChannel(channel) - expect(ok).toBe('ok') - } - - @feature(FEATURE.REALTIME) - @severity(Severity.NORMAL) - @description('When you call "on" table but not subscribe then no events have to be returned') - @test - async 'you should get no events until subscribe'() { - const { supabase, user } = await this.createSignedInSupaClient() - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - - expect(channel).toBeDefined() - await this.insertProfile(supabase, user, user) - - // wait for 1 second to see if we receive any events - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(channel._isClosed).toBeTruthy() - const ok = await supabase.removeChannel(channel) - expect(ok).toBe('ok') - } - - @feature(FEATURE.REALTIME) - @severity(Severity.BLOCKER) - @description( - 'When you create 2 subs (1 subscribed and 1 not yet) then both should be returned on get subs' - ) - @test - async 'get supabase client subscriptions'() { - const { supabase } = await this.createSignedInSupaClient() - - const channel1 = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - const channel2 = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - - const channels = supabase.getChannels() - expect(channels).toEqual(expect.arrayContaining([channel1, channel2])) - supabase.removeAllChannels() - expect(supabase.getChannels().length).toEqual(0) - } - - @feature(FEATURE.REALTIME) - @severity(Severity.NORMAL) - @description('When you subscribe on table then corresponding events have to be returned') - @params.skip({ event: 'INSERT', returnedTypes: ['insert'] }) - @params.skip({ event: 'UPDATE', returnedTypes: ['update'] }) - @params.skip({ event: 'DELETE', returnedTypes: ['delete'] }) - @params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] }) - async 'subscribe on table'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.NORMAL) - @description( - 'When you create multiple subscriptions then corresponding events have to be returned' - ) - @params.skip({ - events: [ - { event: 'INSERT', table: 'rt_table1' }, - { event: '*', table: 'rt_table1' }, - ], - returnedIds: [[], []], - }) - @params.skip({ - events: [ - { event: 'UPDATE', table: 'rt_table1' }, - { event: 'DELETE', table: 'rt_table1' }, - ], - returnedIds: [[], []], - }) - @params.skip({ - events: [ - { event: 'INSERT', table: 'rt_table1' }, - { event: 'INSERT', table: 'rt_table2' }, - ], - returnedIds: [[], []], - }) - async 'multiple subscriptions'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.NORMAL) - @description('When you subscribe on table with RLS then corresponding events have to be returned') - @params.skip({ event: 'INSERT', returnedTypes: ['insert'] }) - @params.skip({ event: 'UPDATE', returnedTypes: ['update'] }) - @params.skip({ event: 'DELETE', returnedTypes: ['delete'] }) - @params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] }) - async 'subscribe on table with RLS'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.CRITICAL) - @description('When you subscribe on table with RLS then only allowed events have to be returned') - @params.skip({ event: 'INSERT', returnedIds: [] }) - @params.skip({ event: 'UPDATE', returnedIds: [] }) - @params.skip({ event: 'DELETE', returnedIds: [] }) - @params.skip({ event: '*', returnedIds: [] }) - async 'subscribe on table with RLS policies'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.CRITICAL) - @description( - 'When you subscribe on table with 2 clients then they have to receive different events' - ) - @test.skip - async 'subscribe from 2 clients'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.NORMAL) - @description( - 'When you subscribe on table with RLS policies then service key subscription has to bypass RLS' - ) - @params.skip({ event: 'INSERT', returnedTypes: ['insert'] }) - @params.skip({ event: 'UPDATE', returnedTypes: ['update'] }) - @params.skip({ event: 'DELETE', returnedTypes: ['delete'] }) - @params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] }) - async 'subscribe on RLS table with service key'() { - // todo - } - - @feature(FEATURE.REALTIME) - @severity(Severity.CRITICAL) - @description('When you unsubscribe from table then no events have to be returned') - @test - async '[skip-stage] unsubscribe from table'() { - const { supabase, user } = await this.createSignedInSupaClient() - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - channel.subscribe() - // wait for subscription to postgres - await new Promise((resolve) => setTimeout(resolve, 8000)) - const ok = await channel.unsubscribe() - expect(ok).toEqual('ok') - - await this.insertProfile(supabase, user, user) - - // wait for 1 second to see if we receive any events - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(channel._isClosed).toBeTruthy() - await supabase.removeChannel(channel) - } - - @feature(FEATURE.REALTIME) - @severity(Severity.CRITICAL) - @description('When you remove one subscription then only events from another have to be returned') - @test - async '[skip-stage] remove one subscription from client'() { - const { supabase, user } = await this.createSignedInSupaClient() - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - channel.subscribe() - // wait for subscription to postgres - await new Promise((resolve) => setTimeout(resolve, 8000)) - const ok = await supabase.removeChannel(channel) - expect(ok).toEqual('ok') - - await this.insertProfile(supabase, user, user) - - // wait for 1 second to see if we receive any events - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(channel._isClosed).toBeTruthy() - } - - @feature(FEATURE.REALTIME) - @severity(Severity.CRITICAL) - @description('When you remove all subscription then no events have to be returned') - @test - async '[skip-stage] remove all subscriptions from client'() { - const { supabase, user } = await this.createSignedInSupaClient() - - const channel = supabase - .channel('profiles') - .on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => { - console.log(payload) - expect('event received').toBe('should not receive event') - }) - channel.subscribe() - // wait for subscription to postgres - await new Promise((resolve) => setTimeout(resolve, 8000)) - const ok = await supabase.removeAllChannels() - expect(ok).toEqual(expect.arrayContaining(['ok'])) - - await this.insertProfile(supabase, user, user) - - // wait for 1 second to see if we receive any events - await new Promise((resolve) => setTimeout(resolve, 1000)) - expect(channel._isClosed).toBeTruthy() - } - - @step('Wait until channel is joined') - async waitForChannelJoined(channel: RealtimeChannel): Promise { - for (let i = 0; i < 30; i++) { - if (channel._isJoined()) { - return null - } - if (channel._isLeaving()) { - return new Error('Channel is leaving') - } - if (channel._isClosed()) { - return new Error('Channel is closed') - } - await new Promise((resolve) => setTimeout(resolve, 100)) - } - return new Error("Channel didn't join in 3 seconds") - } -} diff --git a/tests/features/javascript/rpc.spec.ts b/tests/features/javascript/rpc.spec.ts deleted file mode 100644 index 71883f2dd5c..00000000000 --- a/tests/features/javascript/rpc.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { suite, test, timeout } from '@testdeck/jest' -import { Severity } from 'allure-js-commons' - -import { SupabaseClient } from '@supabase/supabase-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' -import { PendingQuery, Row } from 'postgres' - -@suite('rpc') -@timeout(30000) -class Procedures extends Hooks { - @feature(FEATURE.RPC) - @severity(Severity.BLOCKER) - @description('When you call rpc then you are able to receive its result') - @test - async 'call rpc and get result'() { - await this.createFunction(Procedures.sql` - CREATE OR REPLACE FUNCTION public.test_procedure() RETURNS int language plpgsql as $$ - declare - profile_count integer; - begin - select count(*) - into profile_count - from profiles; - - return profile_count; - end; - $$;`) - - const { supabase, user } = await this.createSignedInSupaClient() - await this.insertProfile(supabase, user, user) - - const result = await this.callRpc(supabase, 'test_procedure') - expect(result.error).toBeNull() - expect(result.data).toBeDefined() - expect(result.data).toBeGreaterThanOrEqual(1) - - await this.dropFunction(Procedures.sql`test_procedure()`) - } - - @feature(FEATURE.RPC) - @severity(Severity.BLOCKER) - @description('When you call rpc, params should be passed properly') - @test - async 'call rpc method that has params'() { - await this.createFunction(Procedures.sql` - CREATE OR REPLACE FUNCTION public.test_procedure(filter text) - RETURNS int language plpgsql as $$ - declare - profile_count integer; - begin - select count(*) - into profile_count - from profiles - where username LIKE '%' || filter || '%'; - - return profile_count; - end; - $$;`) - - const { supabase, user } = await this.createSignedInSupaClient() - await this.insertProfile(supabase, user, user) - - const result = await this.callRpc(supabase, 'test_procedure', { filter: user.username }) - expect(result.error).toBeNull() - expect(result.data).toBeDefined() - expect(result.data).toBeGreaterThanOrEqual(1) - - await this.dropFunction(Procedures.sql`test_procedure(filter text)`) - } - - @feature(FEATURE.RPC) - @severity(Severity.NORMAL) - @description('When you call rpc with head param, no data should be returned') - @test - async 'call rpc with head option'() { - await this.createFunction(Procedures.sql` - CREATE OR REPLACE FUNCTION public.test_procedure() RETURNS int language plpgsql as $$ - declare - profile_count integer; - begin - select count(*) - into profile_count - from profiles; - - return profile_count; - end; - $$;`) - - const { supabase, user } = await this.createSignedInSupaClient() - await this.insertProfile(supabase, user, user) - - const result = await this.callRpc(supabase, 'test_procedure', {}, { head: true }) - expect(result.error).toBeNull() - expect(result.data).toBeNull() - - await this.dropFunction(Procedures.sql`test_procedure()`) - } - - @step('create function') - private async createFunction(body: PendingQuery) { - await Procedures.sql`${body}` - await Procedures.sql`NOTIFY pgrst, 'reload schema';` - } - - @step('drop function') - private async dropFunction(signature: PendingQuery) { - await Procedures.sql`DROP FUNCTION public.${signature};` - } - - @step('call supabase rpc') - private async callRpc( - supabase: SupabaseClient, - name: string, - args?: any, - options?: { - head?: boolean - count?: 'exact' | 'planned' | 'estimated' - } - ) { - let result = await supabase.rpc(name, args, options) - for (let i = 1; i <= 5; i++) { - if (result.error) { - await new Promise((resolve) => setTimeout(resolve, 0.5 * 1000 * i)) - result = await supabase.rpc(name, args, options) - } else { - break - } - } - return result - } -} diff --git a/tests/features/javascript/storage.spec.ts b/tests/features/javascript/storage.spec.ts deleted file mode 100644 index 455b1d51c75..00000000000 --- a/tests/features/javascript/storage.spec.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { params, suite, test } from '@testdeck/jest' -import { faker } from '@faker-js/faker' -import { Severity } from 'allure-js-commons' - -import { createClient, Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js' -import { Bucket } from '@supabase/storage-js' - -import { FEATURE } from '../templates/enums' -import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' -import { Hooks } from './hooks' -import fetch from 'cross-fetch' - -@suite('storage') -class Storage extends Hooks { - static buckets: Pick[] = [] as any - static async after() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const requests = [] - for (const bucket of this.buckets) { - requests.push( - new Promise((resolve) => - (async () => { - await supabase.storage.emptyBucket(bucket.name) - await supabase.storage.deleteBucket(bucket.name) - resolve(null) - })() - ) - ) - } - await Promise.all(requests) - await Hooks.after() - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('When you create public bucket then it has to be available') - @params({ public: true }) - @params({ public: false }) - async 'create bucket'(params: { public: boolean }) { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucketName = faker.unique(faker.random.word) - const { data: bucket, error } = await supabase.storage.createBucket(bucketName, { - public: params.public, - }) - expect(error).toBeNull() - expect(bucket).toBeDefined() - expect(bucket.name).toBe(bucketName) - Storage.buckets.push(bucket) - - const { data: gotBucket, error: error2 } = await supabase.storage.getBucket(bucketName) - expect(error2).toBeNull() - expect(gotBucket).toBeDefined() - expect(gotBucket.name).toBe(bucketName) - expect(gotBucket.public).toBe(params.public) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('There has to be default RLS policy to not allow users to create bucket') - @params({ public: true }) - @params({ public: false }) - async 'user cannot create bucket because of RLS'(params: { public: boolean }) { - const { supabase } = await this.createSignedInSupaClient() - const bucketName = faker.unique(faker.random.word) - - const { data: bucket, error } = await supabase.storage.createBucket(bucketName, { - public: params.public, - }) - expect(error).not.toBeNull() - expect(error.message).toContain('row-level security') - expect(error.message).toContain('"buckets"') - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('list buckets should return all buckets') - @test - async 'list buckets as admin'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - - const bucket1 = await this.createBucket() - const bucket2 = await this.createBucket() - - const { data: buckets, error } = await supabase.storage.listBuckets() - expect(buckets.length).toBeGreaterThanOrEqual(2) - expect(buckets.map((b) => b.name)).toEqual(expect.arrayContaining([bucket1.name, bucket2.name])) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('get bucket should return bucket info') - @test - async 'get bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const { data: gotBucket, error } = await supabase.storage.getBucket(bucket.id) - expect(error).toBeNull() - expect(gotBucket).toEqual(bucket) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('update bucket should change bucket both public->private and back') - @params({ public: true }) - @params({ public: false }) - async 'update bucket'(params: { public: boolean }) { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket(params.public) - - const { - data: { message }, - error, - } = await supabase.storage.updateBucket(bucket.id, { - public: !params.public, - }) - expect(error).toBeNull() - expect(message).toBe('Successfully updated') - - bucket.public = !params.public - const { data: gotBucket } = await supabase.storage.getBucket(bucket.id) - expect(gotBucket).toEqual(bucket) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('get bucket should return bucket info') - @test - async 'delete bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const { - data: { message }, - error, - } = await supabase.storage.deleteBucket(bucket.id) - expect(error).toBeNull() - expect(message).toBe('Successfully deleted') - - const { data: buckets } = await supabase.storage.listBuckets() - expect( - buckets.map((b) => { - return { name: b.name, id: b.id } - }) - ).not.toContain({ name: bucket.name, id: bucket.id }) - - Storage.buckets = Storage.buckets.filter((b) => b.name != bucket.name) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('upload to bucket') - @test - async 'upload to bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - const { - data: { path }, - error, - } = await supabase.storage.from(bucket.name).upload(file.path, file.data) - expect(error).toBeNull() - expect(path).toEqual(file.path) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('list files in bucket') - @test - async 'list files'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const files: { path: string; data: string }[] = [] - const requests: Promise[] = [] - for (let i = 0; i < 3; i++) { - requests.push( - new Promise((resolve) => { - const dir = faker.random.word() - const file = { - path: dir + '/' + faker.random.word() + '.txt', - data: faker.lorem.sentence(), - } - files.push(file) - const p1 = supabase.storage.from(bucket.name).upload(file.path, file.data) - const file2 = { - path: dir + '/' + faker.random.word() + '.txt', - data: faker.lorem.sentence(), - } - files.push(file2) - const p2 = supabase.storage.from(bucket.name).upload(file2.path, file2.data) - Promise.all([p1, p2]).finally(() => resolve()) - }) - ) - } - await Promise.all(requests) - - const { data: dirs, error: error1 } = await supabase.storage.from(bucket.name).list() - expect(error1).toBeNull() - expect(dirs.map((d) => d.name)).toEqual( - expect.arrayContaining([...new Set(files.map((f) => f.path.split('/')[0]))]) - ) - - const dir = files[0].path.split('/')[0] - const { data: filesInDir, error: error2 } = await supabase.storage.from(bucket.name).list(dir) - expect(error2).toBeNull() - expect(filesInDir.map((d) => d.name)).toEqual( - expect.arrayContaining( - files.filter((f) => f.path.split('/')[0] === dir).map((f) => f.path.split('/')[1]) - ) - ) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('download file') - @test - async 'download file from bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { data, error } = await supabase.storage.from(bucket.name).download(file.path) - expect(error).toBeNull() - expect(await data.text()).toEqual(file.data) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('move file') - @test - async '[skip-local] move file in bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - newPath: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { error } = await supabase.storage.from(bucket.name).move(file.path, file.newPath) - expect(error).toBeNull() - const { data: filesRoot } = await supabase.storage.from(bucket.name).list() - expect(filesRoot.map((f) => f.name)).not.toContain(file.path.split('/')[0]) - const { data: files } = await supabase.storage - .from(bucket.name) - .list(file.newPath.split('/')[0]) - expect(files.map((f) => f.name)).toEqual(expect.arrayContaining([file.newPath.split('/')[1]])) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('copy file') - @test - async '[skip-local] copy file in bucket'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - newPath: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { error } = await supabase.storage.from(bucket.name).copy(file.path, file.newPath) - expect(error).toBeNull() - - const { data: filesRoot } = await supabase.storage.from(bucket.name).list() - expect(filesRoot.map((f) => f.name)).toEqual( - expect.arrayContaining([file.path.split('/')[0], file.newPath.split('/')[0]]) - ) - const { data: filesNew } = await supabase.storage - .from(bucket.name) - .list(file.newPath.split('/')[0]) - expect(filesNew.map((f) => f.name)).toEqual( - expect.arrayContaining([file.newPath.split('/')[1]]) - ) - const { data: files } = await supabase.storage.from(bucket.name).list(file.path.split('/')[0]) - expect(files.map((f) => f.name)).toEqual(expect.arrayContaining([file.path.split('/')[1]])) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('get public link to file in the public bucket') - @test - async 'get public link to file'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { - data: { publicUrl }, - } = supabase.storage.from(bucket.name).getPublicUrl(file.path) - expect(publicUrl).toBeDefined() - expect(publicUrl.length).toBeGreaterThan(1) - expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`)) - - const resp = await fetch(publicUrl) - expect(resp.status).toEqual(200) - expect(await resp.text()).toEqual(file.data) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('get public link to file in the private bucket') - @test - async 'get public link to private file'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket(false) - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { - data: { publicUrl }, - } = supabase.storage.from(bucket.name).getPublicUrl(file.path) - expect(publicUrl).toBeDefined() - expect(publicUrl.length).toBeGreaterThan(1) - expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`)) - - const resp = await fetch(publicUrl) - expect(resp.status).toEqual(400) // todo 400 should be 404 too I guess - const error = await resp.json() - expect(error.statusCode).toBe('404') // todo should be a number? - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('get signed link to file in the bucket') - @params({ public: true }) - @params({ public: false }) - async 'get signed link to file'(params: { public: boolean }) { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket(params.public) - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { - data: { signedUrl }, - error, - } = await supabase.storage.from(bucket.name).createSignedUrl(file.path, 10000) - expect(error).toBeNull() - expect(signedUrl).toBeDefined() - expect(signedUrl.length).toBeGreaterThan(1) - expect(signedUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`)) - - const resp = await fetch(signedUrl) - expect(resp.status).toEqual(200) - expect(await resp.text()).toEqual(file.data) - } - - @feature(FEATURE.STORAGE) - @severity(Severity.BLOCKER) - @description('update file check if it will change') - @test - async 'update file'() { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucket = await this.createBucket() - - const file = { - path: faker.random.word() + '/' + faker.random.word() + '/' + faker.random.word() + '.txt', - data: faker.lorem.paragraph(), - } - await supabase.storage.from(bucket.name).upload(file.path, file.data) - - const { - data: { publicUrl }, - } = supabase.storage.from(bucket.name).getPublicUrl(file.path) - expect(publicUrl).toBeDefined() - expect(publicUrl.length).toBeGreaterThan(1) - expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`)) - - const newFile = { - path: file.path, - data: faker.lorem.paragraph(10), - } - await supabase.storage.from(bucket.name).update(newFile.path, newFile.data) - - const resp = await fetch(publicUrl) - expect(resp.status).toEqual(200) - expect(await resp.text()).toEqual(newFile.data) - } - - @step('create bucket') - async createBucket(pub = true) { - const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN) - const bucketName = faker.unique(faker.random.word) - - const { data: bucket, error } = await supabase.storage.createBucket(bucketName, { - public: pub, - }) - expect(error).toBeNull() - expect(bucket).toBeDefined() - expect(bucket.name).toBe(bucketName) - Storage.buckets.push(bucket) - - const { data: buckets } = await supabase.storage.listBuckets() - return buckets.find((b) => b.name === bucketName) - } -} diff --git a/tests/features/python/authentication.py b/tests/features/python/authentication.py deleted file mode 100644 index ba14ccc6149..00000000000 --- a/tests/features/python/authentication.py +++ /dev/null @@ -1,44 +0,0 @@ -import allure - -@allure.severity(allure.severity_level.BLOCKER) -@allure.suite("authentication") -@allure.feature("authentication") -class TestAuthentication(object): - @allure.description("""When user sign up then he should be logged in""") - def test_new_users(self): - self.create_supabase_anonymous_client() - self.sign_up_valid("email", "password") - self.check_logged_in() - self.check_log_out() - - def test_existing_users(self): - """ When user is already signed up then he should be able to logged in """ - self.create_valid_user() - self.sign_in_valid("email", "password") - self.check_logged_in() - self.check_log_out() - - @allure.step("Create Supabase anonymous client") - def create_supabase_anonymous_client(self): - pass - - @allure.step("I sign up with a valid {email} and {password}") - def sign_up_valid(self, email, password="password"): - pass - - @allure.step("Create a valid user") - def create_valid_user(self): - pass - - @allure.step("I sign in with a valid {email} and {password}") - def sign_in_valid(self, email, password="password"): - pass - - @allure.step("Check if I am logged in") - def check_logged_in(self): - pass - - @allure.step("Check if I am being able to log out") - def check_log_out(self): - pass - diff --git a/tests/features/templates/authentication.ts b/tests/features/templates/authentication.ts deleted file mode 100644 index 3a740a23c00..00000000000 --- a/tests/features/templates/authentication.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Severity } from 'allure-js-commons' - -import { FEATURE } from './enums' - -describe('authentication', () => { - beforeEach(() => { - reporter.feature(FEATURE.AUTHENTICATION) - reporter.severity(Severity.BLOCKER) - }) - - test('New users', () => { - reporter.description('When user sign up then he should be logged in') - - reporter.step('Create Supabase anonymous client', () => { - /* don't modify! */ - }) - - reporter.step('I sign up with a valid email and password', () => { - /* don't modify! */ - }) - - reporter.step('Check if I am logged in', () => { - /* don't modify! */ - }) - - reporter.step('Check if I am being able to log out', () => { - /* don't modify! */ - }) - }) - - test('Existing users', () => { - reporter.description('When user is already signed up then he should be able to logged in') - - reporter.step('Create a valid user', () => { - /* don't modify! */ - }) - - reporter.step('I sign in with a valid email and password', () => { - /* don't modify! */ - }) - - reporter.step('Check if I am logged in', () => { - /* don't modify! */ - }) - - reporter.step('Check if I am being able to log out', () => { - /* don't modify! */ - }) - }) -}) diff --git a/tests/features/templates/enums.ts b/tests/features/templates/enums.ts deleted file mode 100644 index fed746cf37a..00000000000 --- a/tests/features/templates/enums.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum FEATURE { - AUTHENTICATION = 'authentication', - AUTH_ADMIN = 'auth_admin', - REALTIME = 'realtime', - STORAGE = 'storage', - FUNCTIONS = 'functions', - RPC = 'rpc', - POSTGREST = 'postgrest', - FILTERS = 'postgrest_filters', - MODIFIERS = 'postgrest_modifiers', -} diff --git a/tests/jest.config.ts b/tests/jest.config.ts deleted file mode 100644 index 4e05c6122a6..00000000000 --- a/tests/jest.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Config } from '@jest/types' - -const config: Config.InitialOptions = { - preset: 'ts-jest', - transform: { - '^.+\\.ts?$': 'ts-jest', - }, - moduleFileExtensions: ['ts', 'js', 'cjs', 'json', 'node'], - globalSetup: '/.jest/jest-env.ts', - globalTeardown: '/.jest/teardown.ts', - // setupFiles: ['/.jest/jest-env.js'], - setupFilesAfterEnv: ['/.jest/jest-custom-reporter.ts'], - testRunner: 'jest-jasmine2', - testTimeout: 15000, - coverageThreshold: { - global: { - branches: 0, - functions: 0, - lines: 0, - statements: 0, - }, - }, -} -export default config diff --git a/tests/package.json b/tests/package.json deleted file mode 100644 index 354f06f3799..00000000000 --- a/tests/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "supabase-tests", - "version": "1.0.0", - "description": "These tests can be run with Docker.", - "scripts": { - "preinstall": "npx only-allow pnpm", - "docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d", - "docker:down": "cd ../docker && docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down", - "test": "jest", - "test:local": "jest --testNamePattern '^((?!\\[skip-local\\]).)*$' --testPathPattern '^((?!realtime).)*$' --detectOpenHandles", - "test:stage": "jest --testNamePattern '^((?!\\[skip-stage\\]).)*$' --detectOpenHandles", - "test:prod": "jest --detectOpenHandles", - "allure:generate": "rm -rf allure-report && node_modules/allure-commandline/bin/allure generate", - "allure:serve": "node_modules/allure-commandline/bin/allure serve", - "test:report": "pnpm run allure:generate && pnpm run allure:serve", - "clean": "rimraf node_modules", - "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,md}\"" - }, - "author": "Supabase, Inc.", - "license": "MIT", - "devDependencies": { - "@faker-js/faker": "^6.1.2", - "@supabase/supabase-js": "^2.44.3", - "@testdeck/jest": "^0.3.3", - "@types/jest": "^29.5.4", - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^6.19.1", - "@typescript-eslint/parser": "^6.19.1", - "allure-commandline": "^2.17.2", - "allure-js-commons": "^2.0.0-beta.14", - "cross-fetch": "^3.1.5", - "jest": "^29.7.0", - "jest-allure2-adapter": "^0.3.12", - "jest-environment-jsdom": "^29.7.0", - "postgres": "^3.0.5", - "ts-jest": "^29.1.1", - "typescript": "~5.5.0" - } -} diff --git a/tests/supabase/.gitignore b/tests/supabase/.gitignore deleted file mode 100644 index 773c7c3e0a1..00000000000 --- a/tests/supabase/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Supabase -.branches -.temp diff --git a/tests/supabase/config.toml b/tests/supabase/config.toml deleted file mode 100644 index 2d4498ba196..00000000000 --- a/tests/supabase/config.toml +++ /dev/null @@ -1,71 +0,0 @@ -# A string used to distinguish different Supabase projects on the same host. Defaults to the working -# directory name when running `supabase init`. -project_id = "tests" - -[api] -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. public and storage are always included. -schemas = [] -# Extra schemas to add to the search_path of every request. -extra_search_path = ["extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[db] -# Port to use for the local database URL. -port = 54322 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 14 - -[studio] -# Port to use for Supabase Studio. -port = 54323 - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -# Port to use for the email testing server web interface. -port = 54324 -smtp_port = 54325 -pop3_port = 54326 - -[auth] -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://localhost:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://localhost:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one -# week). -jwt_expiry = 3600 -# Allow/disallow new user signups to your project. -enable_signup = true - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false - -[auth.phone] -# Allow/disallow new user signups via phone to your project. -enable_signup = true -double_confirm_changes = true -enable_confirmations = false - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`. -[auth.external.apple] -enabled = false -client_id = "" -secret = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" diff --git a/tests/supabase/migrations/data.sql b/tests/supabase/migrations/data.sql deleted file mode 100644 index 88b84570933..00000000000 --- a/tests/supabase/migrations/data.sql +++ /dev/null @@ -1,50 +0,0 @@ -create table if not exists profiles ( - id uuid references auth.users not null, - updated_at timestamp with time zone, - username text unique, - avatar_url text, - website text, - - primary key (id), - unique(username), - constraint username_length check (char_length(username) >= 3) -); - -alter table profiles enable row level security; - -create policy "Public profiles are viewable by the owner." - on profiles for select - using ( auth.uid() = id ); - -create policy "Users can insert their own profile." - on profiles for insert - with check ( auth.uid() = id ); - -create policy "Users can update own profile." - on profiles for update - using ( auth.uid() = id ); - -create policy "Users can delete own profile." - on profiles for delete - using ( auth.uid() = id ); - --- Set up Realtime -drop publication if exists supabase_realtime; -create publication supabase_realtime; -alter publication supabase_realtime add table profiles; - --- Set up Storage -insert into storage.buckets (id, name) -values ('avatars', 'avatars') on conflict do nothing; - -create policy "Avatar images are publicly accessible." - on storage.objects for select - using ( bucket_id = 'avatars' ); - -create policy "Anyone can upload an avatar." - on storage.objects for insert - with check ( bucket_id = 'avatars' ); - -create policy "Anyone can update an avatar." - on storage.objects for update - with check ( bucket_id = 'avatars' ); diff --git a/tests/tsconfig.json b/tests/tsconfig.json deleted file mode 100644 index 021d5872aaa..00000000000 --- a/tests/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2021", - "module": "commonjs", - "esModuleInterop": true, - "inlineSources": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es6", "DOM"], - "removeComments": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "allowSyntheticDefaultImports": true, - "moduleResolution": "node", - "sourceMap": true, - "declaration": true, - "baseUrl": ".", - "paths": { - "*": ["node_modules/*", "./types/*"] - }, - "typeRoots": [ - "./node_modules/@types/", - "./node_modules/allure2-js-commons/dist/declarations/**/" - ] - }, - "exclude": ["node_modules"], - "compileOnSave": false -} From 98d914d1fbeb4cf5b27444e403e7cd3adfda2440 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 8 Jan 2025 21:30:15 +0100 Subject: [PATCH 008/265] fix: Fix the referer for Posthog (#32656) Fix the referer for posthog. --- packages/common/telemetry.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/common/telemetry.ts b/packages/common/telemetry.ts index fa1909bfd32..5bd26e90f9d 100644 --- a/packages/common/telemetry.ts +++ b/packages/common/telemetry.ts @@ -17,7 +17,8 @@ interface TelemetryData { export function handlePageTelemetry(API_URL: string, route: string, telemetryData: TelemetryData) { const { page_url } = telemetryData - const { search, language, viewport_height, viewport_width } = telemetryData.ph + const { search, language, viewport_height, viewport_width, referrer, user_agent } = + telemetryData.ph return post( `${API_URL}/telemetry/page`, @@ -26,12 +27,12 @@ export function handlePageTelemetry(API_URL: string, route: string, telemetryDat page_title: document.title, pathname: route, ph: { - referrer: document.referrer, + referrer: referrer ?? document.referrer, language, search, viewport_height, viewport_width, - user_agent: navigator.userAgent, + user_agent: user_agent ?? navigator.userAgent, }, }, { headers: { Version: '2' }, credentials: 'include' } From 3d4cd89a640e4a2373c831d46193264f36f285c6 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Wed, 8 Jan 2025 18:54:09 -0500 Subject: [PATCH 009/265] fix: remix pkce example (#32662) --- apps/docs/components/MDX/oauth_pkce_flow.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/docs/components/MDX/oauth_pkce_flow.mdx b/apps/docs/components/MDX/oauth_pkce_flow.mdx index 02beff87623..707c0e276c1 100644 --- a/apps/docs/components/MDX/oauth_pkce_flow.mdx +++ b/apps/docs/components/MDX/oauth_pkce_flow.mdx @@ -179,7 +179,6 @@ export async function loader({ request }: LoaderFunctionArgs) { const headers = new Headers() if (code) { - const cookies = parse(request.headers.get('Cookie') ?? '') const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { cookies: { getAll() { From eb42b2c24121a4e558a205422d820234b9ffab56 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 9 Jan 2025 10:55:14 +0800 Subject: [PATCH 010/265] Persist last selected database only for sql editor via local storage (#32598) * Persist last selected database only for sql editor via local storage * Add missing useEffect --- .../SQLEditor/UtilityPanel/UtilityActions.tsx | 24 +++++++++++-------- .../studio/components/ui/DatabaseSelector.tsx | 16 ++++++++----- apps/studio/lib/constants/index.ts | 1 + 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index 18f312ceabd..82d89def483 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -1,4 +1,3 @@ -import { useQueryClient } from '@tanstack/react-query' import { AlignLeft, Check, @@ -11,8 +10,8 @@ import { } from 'lucide-react' import { toast } from 'sonner' +import { useParams } from 'common' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import DatabaseSelector from 'components/ui/DatabaseSelector' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants' @@ -50,8 +49,7 @@ const UtilityActions = ({ executeQuery, }: UtilityActionsProps) => { const os = detectOS() - const client = useQueryClient() - const { project } = useProjectContext() + const { ref } = useParams() const snapV2 = useSqlEditorV2StateSnapshot() const [isAiOpen] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.SQL_EDITOR_AI_OPEN, true) @@ -59,6 +57,10 @@ const UtilityActions = ({ LOCAL_STORAGE_KEYS.SQL_EDITOR_INTELLISENSE, true ) + const [lastSelectedDb, setLastSelectedDb] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.SQL_EDITOR_LAST_SELECTED_DB(ref as string), + '' + ) const snippet = snapV2.snippets[id] const isFavorite = snippet !== undefined ? snippet.snippet.favorite : false @@ -70,12 +72,13 @@ const UtilityActions = ({ ) } - const addFavorite = async () => { - snapV2.addFavorite(id) - } + const addFavorite = () => snapV2.addFavorite(id) - const removeFavorite = async () => { - snapV2.removeFavorite(id) + const removeFavorite = () => snapV2.removeFavorite(id) + + const onSelectDatabase = (databaseId: string) => { + snapV2.resetResult(id) + setLastSelectedDb(databaseId) } return ( @@ -182,8 +185,9 @@ const UtilityActions = ({
snapV2.resetResult(id)} + onSelectId={onSelectDatabase} /> Date: Thu, 9 Jan 2025 12:36:32 +0100 Subject: [PATCH 014/265] chore: Move playwright tests to tests/local-studio-tests (#21488) * Move the playwright package to tests folder. * Update the workspace list. * Change the name of the playwright tests to local studio tests. --- .../{playwright.yml => local-studio-tests.yml} | 10 ++++++---- package.json | 2 +- pnpm-lock.yaml | 2 +- pnpm-workspace.yaml | 2 +- .../local-studio-tests}/.gitignore | 0 .../local-studio-tests}/README.md | 0 .../local-studio-tests}/package.json | 2 +- .../local-studio-tests}/playwright.config.ts | 4 ++-- .../local-studio-tests}/scripts/generate-env.js | 2 +- .../local-studio-tests}/supabase/.gitignore | 0 .../local-studio-tests}/supabase/config.toml | 2 +- .../local-studio-tests}/supabase/seed.sql | 0 .../tests/snapshot/spec/table-editor.spec.ts | 0 13 files changed, 14 insertions(+), 12 deletions(-) rename .github/workflows/{playwright.yml => local-studio-tests.yml} (81%) rename {playwright-tests => tests/local-studio-tests}/.gitignore (100%) rename {playwright-tests => tests/local-studio-tests}/README.md (100%) rename {playwright-tests => tests/local-studio-tests}/package.json (95%) rename {playwright-tests => tests/local-studio-tests}/playwright.config.ts (97%) rename {playwright-tests => tests/local-studio-tests}/scripts/generate-env.js (97%) rename {playwright-tests => tests/local-studio-tests}/supabase/.gitignore (100%) rename {playwright-tests => tests/local-studio-tests}/supabase/config.toml (99%) rename {playwright-tests => tests/local-studio-tests}/supabase/seed.sql (100%) rename {playwright-tests => tests/local-studio-tests}/tests/snapshot/spec/table-editor.spec.ts (100%) diff --git a/.github/workflows/playwright.yml b/.github/workflows/local-studio-tests.yml similarity index 81% rename from .github/workflows/playwright.yml rename to .github/workflows/local-studio-tests.yml index 015031e3153..9c64715b1e0 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/local-studio-tests.yml @@ -1,14 +1,16 @@ -name: Playwright Tests +name: Local Studio Tests on: push: branches: [master] paths: - 'apps/studio/**' + - 'tests/local-studio-tests/**' - 'pnpm-lock.yaml' pull_request: branches: [master] paths: - 'apps/studio/**' + - 'tests/local-studio-tests/**' - 'pnpm-lock.yaml' # Cancel old builds on new commit for same workflow + branch/PR @@ -37,14 +39,14 @@ jobs: - name: Install dependencies run: pnpm i - name: Install Playwright Browsers - run: pnpm -C playwright-tests exec playwright install --with-deps + run: pnpm -C tests/local-studio-tests exec playwright install --with-deps - name: Run Playwright tests - run: pnpm run test:playwright + run: pnpm test:local-studio # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. # continue-on-error: true - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report - path: playwright-tests/playwright-report/ + path: tests/local-studio-tests/playwright-report/ retention-days: 7 diff --git a/package.json b/package.json index 20584e4137e..910c69d43fb 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test:ui-patterns": "turbo run test --filter=ui-patterns", "test:studio": "turbo run test --filter=studio", "test:studio:watch": "turbo run test --filter=studio -- watch", - "test:playwright": "pnpm --prefix playwright-tests run test", + "test:local-studio": "pnpm --prefix tests/local-studio-tests run test", "perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/", "perf:meta": "ab -t 5 -c 20 -T application/json http://localhost:5555/tables", "generate:types": "supabase gen types typescript --local > ./supabase/functions/common/database-types.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5def67e2b75..d73f05dd8ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1911,7 +1911,7 @@ importers: specifier: ^2.0.3 version: 2.1.8(@types/node@20.12.11)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.4.11(typescript@5.5.2))(sass@1.72.0)(supports-color@8.1.1)(terser@5.32.0) - playwright-tests: + tests/local-studio-tests: devDependencies: '@playwright/test': specifier: ^1.41.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cb315b73f4c..b5304a2d449 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,7 @@ packages: - "apps/*" - "packages/*" - - "playwright-tests" + - "tests/*" catalog: '@types/node': ^20.0.0 diff --git a/playwright-tests/.gitignore b/tests/local-studio-tests/.gitignore similarity index 100% rename from playwright-tests/.gitignore rename to tests/local-studio-tests/.gitignore diff --git a/playwright-tests/README.md b/tests/local-studio-tests/README.md similarity index 100% rename from playwright-tests/README.md rename to tests/local-studio-tests/README.md diff --git a/playwright-tests/package.json b/tests/local-studio-tests/package.json similarity index 95% rename from playwright-tests/package.json rename to tests/local-studio-tests/package.json index 5fae3769ec1..74671d01b25 100644 --- a/playwright-tests/package.json +++ b/tests/local-studio-tests/package.json @@ -1,5 +1,5 @@ { - "name": "playwright-tests", + "name": "local-studio-tests", "version": "1.0.0", "description": "", "main": "index.js", diff --git a/playwright-tests/playwright.config.ts b/tests/local-studio-tests/playwright.config.ts similarity index 97% rename from playwright-tests/playwright.config.ts rename to tests/local-studio-tests/playwright.config.ts index c75031d9658..2568a2cdcee 100644 --- a/playwright-tests/playwright.config.ts +++ b/tests/local-studio-tests/playwright.config.ts @@ -73,8 +73,8 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { // using npm run dev instead of turbo because turbo doesn't stop the server after a test (doesn't handle SIGTERM). - command: 'npm run dev', - cwd: '../apps/studio', + command: 'pnpm dev', + cwd: '../../apps/studio', url: 'http://localhost:8082', reuseExistingServer: !process.env.CI, env: { diff --git a/playwright-tests/scripts/generate-env.js b/tests/local-studio-tests/scripts/generate-env.js similarity index 97% rename from playwright-tests/scripts/generate-env.js rename to tests/local-studio-tests/scripts/generate-env.js index 8c091e98d10..5804e6a5222 100644 --- a/playwright-tests/scripts/generate-env.js +++ b/tests/local-studio-tests/scripts/generate-env.js @@ -30,7 +30,7 @@ const defaultEnv = { const environment = { ...generatedEnv, ...defaultEnv } fs.writeFileSync( - '../apps/studio/.env.test', + '../../apps/studio/.env.test', Object.keys(environment) .map((key) => `${key}=${environment[key]}`) .join('\n') diff --git a/playwright-tests/supabase/.gitignore b/tests/local-studio-tests/supabase/.gitignore similarity index 100% rename from playwright-tests/supabase/.gitignore rename to tests/local-studio-tests/supabase/.gitignore diff --git a/playwright-tests/supabase/config.toml b/tests/local-studio-tests/supabase/config.toml similarity index 99% rename from playwright-tests/supabase/config.toml rename to tests/local-studio-tests/supabase/config.toml index f4c757c0299..82774ab1abb 100644 --- a/playwright-tests/supabase/config.toml +++ b/tests/local-studio-tests/supabase/config.toml @@ -1,6 +1,6 @@ # A string used to distinguish different Supabase projects on the same host. Defaults to the # working directory name when running `supabase init`. -project_id = "playwright-tests" +project_id = "local-studio-tests" [api] enabled = true diff --git a/playwright-tests/supabase/seed.sql b/tests/local-studio-tests/supabase/seed.sql similarity index 100% rename from playwright-tests/supabase/seed.sql rename to tests/local-studio-tests/supabase/seed.sql diff --git a/playwright-tests/tests/snapshot/spec/table-editor.spec.ts b/tests/local-studio-tests/tests/snapshot/spec/table-editor.spec.ts similarity index 100% rename from playwright-tests/tests/snapshot/spec/table-editor.spec.ts rename to tests/local-studio-tests/tests/snapshot/spec/table-editor.spec.ts From 724f0461709d0902a4751b6bcd87667b85b2b6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20K=C3=B6hler?= <74593656+Than-DE@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:47:14 +0100 Subject: [PATCH 015/265] fix(studio): auth email hook sheet padding (#32569) --- .../studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx b/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx index 03d9c00b3bf..7cd4e09bc52 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx @@ -431,7 +431,7 @@ export const CreateHookSheet = ({
) : ( -
+
Date: Thu, 9 Jan 2025 13:27:02 +0100 Subject: [PATCH 016/265] feat: make firebase tpa generally available (#32573) * feat: make firebase tpa generally available * Simplify the dropdown menu label. --------- Co-authored-by: Ivan Vasilov --- .../AddIntegrationDropdown.tsx | 5 +- .../CreateFirebaseAuthDialog.tsx | 123 +++++------------- .../ThirdPartyAuthForm/IntegrationCard.tsx | 32 ----- 3 files changed, 39 insertions(+), 121 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx index 2088848282b..71bb7e01fd4 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx @@ -4,6 +4,7 @@ import Image from 'next/image' import { IS_PLATFORM } from 'common' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' +import Link from 'next/link' import { Button, cn, @@ -19,7 +20,6 @@ import { getIntegrationTypeLabel, INTEGRATION_TYPES, } from './ThirdPartyAuthForm.utils' -import Link from 'next/link' interface AddIntegrationDropdownProps { buttonText?: string @@ -68,6 +68,9 @@ export const AddIntegrationDropdown = ({ + Select Provider + + {subscription?.plan.id === 'free' ? ( diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateFirebaseAuthDialog.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateFirebaseAuthDialog.tsx index 32de8cae74d..4c49a1fefc1 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateFirebaseAuthDialog.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateFirebaseAuthDialog.tsx @@ -1,5 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { AlertTriangle, Trash } from 'lucide-react' +import { Trash } from 'lucide-react' import { useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' @@ -7,11 +7,7 @@ import * as z from 'zod' import { useParams } from 'common' import { useCreateThirdPartyAuthIntegrationMutation } from 'data/third-party-auth/integration-create-mutation' -import { useFlag } from 'hooks/ui/useFlag' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Dialog, DialogContent, @@ -69,13 +65,6 @@ export const CreateFirebaseAuthIntegrationDialog = ({ }, }) - const allowedProjectIDs = useFlag('firebaseTPAAllowedProjectIDs') - - const isAvailable = - typeof allowedProjectIDs === 'string' - ? allowedProjectIDs.split(',').includes(projectRef!) - : !!allowedProjectIDs - useEffect(() => { if (visible) { form.reset({ @@ -109,11 +98,10 @@ export const CreateFirebaseAuthIntegrationDialog = ({ - {isAvailable ? ( - -
- {/* Enabled flag can't be changed for now because there's no update API call for integrations */} - {/* + + {/* Enabled flag can't be changed for now because there's no update API call for integrations */} + {/* */} -

- This will enable a JWT token from a specific Firebase project to access data from - this Supabase project. -

- ( - - - - - - )} - /> - - - - - This connection requires a Row Level Security (RLS) policy - - - You will need to manually add a RLS policy after creating this Firebase - connection. Otherwise, the Supabase project will be accessible to anyone with a - valid Firebase JWT token. - - - -
- ) : ( -

- Third-party Auth with Firebase is currently in a private alpha release.{' '} - - Please submit your interest for it - {' '} - and someone from the Supabase team will reach out. You can also try it out via the - CLI. -

- )} +

+ This will enable a JWT token from a specific Firebase project to access data from + this Supabase project. +

+ ( + + + + + + )} + /> + +
- {isAvailable ? ( - <> - {!isCreating && ( -
- -
- )} - - - - - ) : ( - <> - - - +
)} + + + diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/IntegrationCard.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/IntegrationCard.tsx index a8cb879cd4f..97156cfe595 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/IntegrationCard.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/IntegrationCard.tsx @@ -177,38 +177,6 @@ export const IntegrationCard = ({ )}
- {type === 'firebase' ? ( -
-
- -
-
-
-

Firebase provider uses a custom RLS policy

-

- Be aware that this provider requires an RLS policy to correctly work. -

-
- - - - - - - - - -
-
- ) : null} ) } From b09bc402256583762966f0b04677abf79c5cd8a1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 9 Jan 2025 23:20:52 +0800 Subject: [PATCH 017/265] Remove extend your database header (#32686) --- apps/studio/components/layouts/WizardLayout.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/studio/components/layouts/WizardLayout.tsx b/apps/studio/components/layouts/WizardLayout.tsx index 26989c9dcd5..e70430f653d 100644 --- a/apps/studio/components/layouts/WizardLayout.tsx +++ b/apps/studio/components/layouts/WizardLayout.tsx @@ -3,10 +3,10 @@ import { PropsWithChildren } from 'react' import { withAuth } from 'hooks/misc/withAuth' import { BASE_PATH } from 'lib/constants' +import { ChevronRight } from 'lucide-react' import type { Organization, Project } from 'types' import { FeedbackDropdown } from './ProjectLayout/LayoutHeader/FeedbackDropdown' import HelpPopover from './ProjectLayout/LayoutHeader/HelpPopover' -import { ChevronRight } from 'lucide-react' interface WizardLayoutProps { organization: Organization | null | undefined @@ -58,10 +58,6 @@ const Header = ({ organization, project }: WizardLayoutProps) => {

{project ? project.name : 'Create a new project'}

- -

- {project ? project.name : 'Extend your database'} -

{/* The End */}
From 84694717e399dd4e24b4ffa508a965c90395cb52 Mon Sep 17 00:00:00 2001 From: Wen Bo Xie <5532241+w3b6x9@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:26:50 -0500 Subject: [PATCH 018/265] docs: add OpenAI Telemetry opt in permissions (#32667) --- .../guides/platform/access-control.mdx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/docs/content/guides/platform/access-control.mdx b/apps/docs/content/guides/platform/access-control.mdx index e0ed0f6a5e1..2b31ca2b5c7 100644 --- a/apps/docs/content/guides/platform/access-control.mdx +++ b/apps/docs/content/guides/platform/access-control.mdx @@ -59,6 +59,7 @@ The table below shows the actions each role can take on the resources belonging | **Organization** | | | | | | | Organization Management | Update | | | | | | | Delete | | | | | +| OpenAI Telemetry Configuration[^2] | Update | | | | | | **Members** | | | | | | | Organization Members | List | | | | | | Owner | Add | | | | | @@ -75,7 +76,7 @@ The table below shows the actions each role can take on the resources belonging | | Remove | | | | | | Invite | Revoke | | | | | | | Resend | | | | | -| | Accept[^2] | | | | | +| | Accept[^3] | | | | | | **Billing** | | | | | | | Invoices | List | | | | | | Billing Email | View | | | | | @@ -115,7 +116,7 @@ The table below shows the actions each role can take on the resources belonging The table below shows the actions each role can take on the resources belonging to the project. -| Resource | Action | Owner | Admin | Developer | Read-Only[^3][^5] | +| Resource | Action | Owner | Admin | Developer | Read-Only[^4][^6] | | ------------------------------------------------------------------------------------------------------ | ---------------------- | :-------------------------------------: | :-------------------------------------: | :-------------------------------------: | :-------------------------------------------------------------: | | **Project** | | | | | | | Project Management | Transfer | | | | | @@ -179,7 +180,7 @@ The table below shows the actions each role can take on the resources belonging | | Create | | | | | | | Delete | | | | | | **Edge Functions Configuration** | | | | | | -| Secrets | View | | | | [^4] | +| Secrets | View | | | | [^5] | | | Create | | | | | | | Delete | | | | | | **SQL Editor** | | | | | | @@ -188,7 +189,7 @@ The table below shows the actions each role can take on the resources belonging | | Delete | | | | | | | View | | | | | | | List | | | | | -| | Run | | | | [^6] | +| | Run | | | | [^7] | | **Database** | | | | | | | Scheduled Backups | View | | | | | | | Download | | | | | @@ -258,8 +259,9 @@ The table below shows the actions each role can take on the resources belonging | | List | | | | | [^1]: Available on the Team and Enterprise Plans. -[^2]: Invites sent from a SSO account can only be accepted by another SSO account coming from the same identity provider. This is a security measure that prevents accidental invites to accounts not managed by your company's enterprise systems. -[^3]: Available on the Enterprise Plan. -[^4]: Read-Only role is able to access secrets. -[^5]: Listed permissions are for the API and Dashboard. -[^6]: Limited to executing SELECT queries. SQL Query Snippets run by the Read-Only role are run against the database using the **supabase_read_only_user**. This role has the [predefined Postgres role pg_read_all_data](https://www.postgresql.org/docs/current/predefined-roles.html). +[^2]: Sending anonymous data to OpenAI is opt in and can improve Studio AI Assistant's responses. +[^3]: Invites sent from a SSO account can only be accepted by another SSO account coming from the same identity provider. This is a security measure that prevents accidental invites to accounts not managed by your company's enterprise systems. +[^4]: Available on the Enterprise Plan. +[^5]: Read-Only role is able to access secrets. +[^6]: Listed permissions are for the API and Dashboard. +[^7]: Limited to executing SELECT queries. SQL Query Snippets run by the Read-Only role are run against the database using the **supabase_read_only_user**. This role has the [predefined Postgres role pg_read_all_data](https://www.postgresql.org/docs/current/predefined-roles.html). From c791dd9e29b4841d2a053218b3727bef89e40cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Fri, 10 Jan 2025 10:20:35 +0800 Subject: [PATCH 019/265] feat: credit top up (#32680) --- .../Billing/Payment/AddPaymentMethodForm.tsx | 7 +- .../BillingAddress/BillingAddress.tsx | 2 +- .../BillingBreakdown/BillingBreakdown.tsx | 2 +- .../BillingSettings/CreditBalance.tsx | 61 +-- .../BillingSettings/CreditTopUp.tsx | 364 ++++++++++++++++++ .../organization-credit-top-up-mutation.ts | 72 ++++ packages/api-types/types/api.d.ts | 49 +++ 7 files changed, 523 insertions(+), 34 deletions(-) create mode 100644 apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx create mode 100644 apps/studio/data/organizations/organization-credit-top-up-mutation.ts diff --git a/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx b/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx index cc4f888b760..7f184986165 100644 --- a/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx @@ -111,7 +111,7 @@ const AddPaymentMethodForm = ({ } return ( -
+
@@ -147,16 +147,17 @@ const AddPaymentMethodForm = ({ - +
) } diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingAddress/BillingAddress.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingAddress/BillingAddress.tsx index 612a56bdae2..0275aaae5f7 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingAddress/BillingAddress.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingAddress/BillingAddress.tsx @@ -82,7 +82,7 @@ const BillingAddress = () => { return ( -
+

Billing Address

This will be reflected in every upcoming invoice, past invoices are not affected diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx index 19ddf0ca50a..cb0489e80ba 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx @@ -39,7 +39,7 @@ const BillingBreakdown = () => { return ( -

+

Billing Breakdown

Current billing cycle: {billingCycleStart.format('MMM DD')} -{' '} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx index 8cf402fb944..7c3341232e9 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx @@ -1,4 +1,5 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' + import { useParams } from 'common' import { ScaffoldSection, @@ -6,14 +7,17 @@ import { ScaffoldSectionDetail, } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' +import { FormPanel } from 'components/ui/Forms/FormPanel' +import { FormSection, FormSectionContent } from 'components/ui/Forms/FormSection' import NoPermission from 'components/ui/NoPermission' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { Badge } from 'ui' -import { useOrgSubscriptionQuery } from '../../../../data/subscriptions/org-subscription-query' +import { useFlag } from 'hooks/ui/useFlag' +import { CreditTopUp } from './CreditTopUp' const CreditBalance = () => { const { slug } = useParams() + const creditTopUpEnabled = useFlag('creditTopUp') const canReadSubscriptions = useCheckPermissions( PermissionAction.BILLING_READ, @@ -39,14 +43,12 @@ const CreditBalance = () => { return ( -

+

Credit Balance

- {isCredit && You have credits available} - {isDebt && Outstanding payments}

- Charges will be deducted from your balance first + Credits will be applied to future invoices, before charging your payment method.

@@ -55,29 +57,30 @@ const CreditBalance = () => { ) : ( <> - {isLoading && ( -
- - - -
- )} + : null}> + + + {isError && ( + + )} - {isError && ( - - )} - - {isSuccess && ( -
- {isDebt &&

-

} -

$

-

{balance}

- {isCredit &&

/credits

} -
- )} + {isSuccess && ( +
+ Balance +
+ {isDebt &&

-

} +

$

+

{balance}

+ {isCredit &&

/credits

} +
+
+ )} +
+
+
)} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx new file mode 100644 index 00000000000..2f8a5d0de82 --- /dev/null +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx @@ -0,0 +1,364 @@ +import HCaptcha from '@hcaptcha/react-hcaptcha' +import { zodResolver } from '@hookform/resolvers/zod' +import { Elements, useStripe } from '@stripe/react-stripe-js' +import { loadStripe, PaymentIntentResult } from '@stripe/stripe-js' +import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants' +import { useQueryClient } from '@tanstack/react-query' +import { AlertCircle, Info } from 'lucide-react' +import { useTheme } from 'next-themes' +import Link from 'next/link' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { toast } from 'sonner' +import { z } from 'zod' + +import { ButtonTooltip } from 'components/ui/ButtonTooltip' +import { useOrganizationCreditTopUpMutation } from 'data/organizations/organization-credit-top-up-mutation' +import { subscriptionKeys } from 'data/subscriptions/keys' +import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { STRIPE_PUBLIC_KEY } from 'lib/constants' +import { useIsHCaptchaLoaded } from 'stores/hcaptcha-loaded-store' +import { + Alert_Shadcn_, + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Button, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + DialogTrigger, + Form_Shadcn_, + FormField_Shadcn_, + Input_Shadcn_, + LoadingLine, +} from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import PaymentMethodSelection from './Subscription/PaymentMethodSelection' + +const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) + +const FORM_ID = 'credit-top-up' + +const FormSchema = z.object({ + amount: z.coerce + .number() + .gte(100, 'Amount must be between $100 - $999.') + .lte(999, 'Amount must be between $100 - $999.') + .int('Amount must be a whole number.'), + paymentMethod: z.string(), +}) + +type CreditTopUpForm = z.infer + +export const CreditTopUp = ({ slug }: { slug: string | undefined }) => { + const { resolvedTheme } = useTheme() + const queryClient = useQueryClient() + + const canTopUpCredits = useCheckPermissions( + PermissionAction.BILLING_WRITE, + 'stripe.subscriptions' + ) + const isPermissionsLoaded = usePermissionsLoaded() + + const { + mutateAsync: topUpCredits, + isLoading: executingTopUp, + error: errorInitiatingTopUp, + } = useOrganizationCreditTopUpMutation({}) + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + amount: 100, + paymentMethod: '', + }, + }) + + const [topUpModalVisible, setTopUpModalVisible] = useState(false) + const [paymentConfirmationLoading, setPaymentConfirmationLoading] = useState(false) + const captchaLoaded = useIsHCaptchaLoaded() + const [captchaToken, setCaptchaToken] = useState(null) + const [captchaRef, setCaptchaRef] = useState(null) + + const captchaRefCallback = useCallback((node: any) => { + setCaptchaRef(node) + }, []) + + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef?.resetCaptcha() + } + + const initHcaptcha = async () => { + if (topUpModalVisible && captchaRef && captchaLoaded) { + let token = captchaToken + + try { + if (!token) { + const captchaResponse = await captchaRef.execute({ async: true }) + token = captchaResponse?.response ?? null + setCaptchaToken(token) + return token + } + } catch (error) { + return token + } + + return token + } + } + + useEffect(() => { + initHcaptcha() + }, [topUpModalVisible, captchaRef, captchaLoaded]) + + const [paymentIntentSecret, setPaymentIntentSecret] = useState('') + const [paymentIntentConfirmation, setPaymentIntentConfirmation] = useState() + + const onSubmit: SubmitHandler = async ({ amount, paymentMethod }) => { + setPaymentIntentConfirmation(undefined) + + const token = await initHcaptcha() + + await topUpCredits( + { + slug, + amount, + payment_method_id: paymentMethod, + hcaptchaToken: token, + }, + { + onSuccess: (data) => { + if (data.status === 'succeeded') { + onSuccessfulPayment() + } else { + setPaymentIntentSecret(data.payment_intent_secret || '') + } + + resetCaptcha() + }, + } + ) + } + + const options = useMemo(() => { + return { + clientSecret: paymentIntentSecret, + appearance: { theme: resolvedTheme?.includes('dark') ? 'night' : 'flat', labels: 'floating' }, + } as any + }, [paymentIntentSecret, resolvedTheme]) + + const onTopUpDialogVisibilityChange = (visible: boolean) => { + setTopUpModalVisible(visible) + if (!visible) { + setCaptchaRef(null) + setPaymentIntentConfirmation(undefined) + setPaymentIntentSecret('') + } + } + + const paymentIntentConfirmed = (paymentIntentConfirmation: PaymentIntentResult) => { + // Reset payment intent secret to ensure another attempt works as expected + setPaymentIntentSecret('') + setPaymentIntentConfirmation(paymentIntentConfirmation) + + if (paymentIntentConfirmation.paymentIntent?.status === 'succeeded') { + onSuccessfulPayment() + } + } + + const onSuccessfulPayment = async () => { + onTopUpDialogVisibilityChange(false) + await queryClient.invalidateQueries(subscriptionKeys.orgSubscription(slug)) + toast.success( + 'Successfully topped up balance. It may take a minute to reflect in your account.' + ) + } + + return ( +
+ onTopUpDialogVisibilityChange(open)}> + + + Top Up + + + + e.preventDefault()}> + { + // [Joshen] This is to ensure that hCaptcha popup remains clickable + if (document !== undefined) document.body.classList.add('!pointer-events-auto') + }} + onClose={() => { + if (document !== undefined) document.body.classList.remove('!pointer-events-auto') + }} + onVerify={(token) => { + setCaptchaToken(token) + if (document !== undefined) document.body.classList.remove('!pointer-events-auto') + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + + Top Up Credits + + On successful payment, an invoice will be issued and you'll be granted credits. + Credits will be applied to future invoices only and are not refundable. The topped up + credits do not expire. + + + + + + +
+ + ( + + + + )} + /> + + ( + form.setValue('paymentMethod', pm)} + selectedPaymentMethod={form.getValues('paymentMethod')} + /> + )} + /> + +

+ For larger discounted credit packages, please{' '} + + reach out. + +

+ + {paymentIntentConfirmation && paymentIntentConfirmation.error && ( + + + Error confirming payment + + {paymentIntentConfirmation.error.message} + + + )} + + {paymentIntentConfirmation?.paymentIntent && + paymentIntentConfirmation.paymentIntent.status === 'processing' && ( + + + Payment processing + + Your payment is processing and we are waiting for a confirmation from your + card issuer. If the payment goes through you'll automatically be credited. + Please check back later. + + + )} + + {errorInitiatingTopUp && ( + + + Error topping up balance + + {errorInitiatingTopUp.message} + + + )} +
+ + {!paymentIntentConfirmation?.paymentIntent && ( + + + + )} +
+
+ {stripePromise && paymentIntentSecret && ( + + + paymentIntentConfirmed(paymentIntentConfirmation) + } + onLoadingChange={(loading) => setPaymentConfirmationLoading(loading)} + paymentMethodId={form.getValues().paymentMethod} + /> + + )} +
+
+
+ ) +} + +const PaymentConfirmation = ({ + paymentIntentSecret, + onPaymentIntentConfirm, + onLoadingChange, + paymentMethodId, +}: { + paymentIntentSecret: string + paymentMethodId: string + onPaymentIntentConfirm: (response: PaymentIntentResult) => void + onLoadingChange: (loading: boolean) => void +}) => { + const stripe = useStripe() + + useEffect(() => { + if (stripe && paymentIntentSecret) { + onLoadingChange(true) + stripe! + .confirmCardPayment(paymentIntentSecret, { payment_method: paymentMethodId }) + .then((res) => { + onPaymentIntentConfirm(res) + onLoadingChange(false) + }) + .catch((err) => { + onLoadingChange(false) + }) + } + }, [paymentIntentSecret, stripe]) + + return +} diff --git a/apps/studio/data/organizations/organization-credit-top-up-mutation.ts b/apps/studio/data/organizations/organization-credit-top-up-mutation.ts new file mode 100644 index 00000000000..53d41c85955 --- /dev/null +++ b/apps/studio/data/organizations/organization-credit-top-up-mutation.ts @@ -0,0 +1,72 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { toast } from 'sonner' + +import { handleError, post } from 'data/fetchers' +import type { ResponseError } from 'types' + +export type OrganizationCreditTopUpVariables = { + payment_method_id: string + amount: number + slug?: string + hcaptchaToken?: string | null +} + +export async function topUpCredits({ + payment_method_id, + amount, + slug, + hcaptchaToken, +}: OrganizationCreditTopUpVariables) { + if (!payment_method_id) { + throw new Error('Payment method id required') + } + + if (!hcaptchaToken) { + throw new Error('Captcha not submitted') + } + + if (!slug) { + throw new Error('Slug is required') + } + + const { data, error } = await post(`/platform/organizations/{slug}/billing/credits/top-up`, { + params: { + path: { + slug, + }, + }, + body: { payment_method_id, amount, hcaptcha_token: hcaptchaToken }, + }) + + if (error) handleError(error) + + return data +} + +type OrganizationTaxIdUpdateData = Awaited> + +export const useOrganizationCreditTopUpMutation = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + return useMutation( + (vars) => topUpCredits(vars), + { + async onSuccess(data, variables, context) { + await onSuccess?.(data, variables, context) + }, + async onError(data, variables, context) { + if (onError === undefined) { + toast.error(`Failed to top up credits: ${data.message}`) + } else { + onError(data, variables, context) + } + }, + ...options, + } + ) +} diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts index 0035062a5cd..47efd7a6e4b 100644 --- a/packages/api-types/types/api.d.ts +++ b/packages/api-types/types/api.d.ts @@ -332,6 +332,10 @@ export interface paths { /** Retrieves a list of available Postgres versions available to the organization */ post: operations['OrganizationSlugController_getAvailableImageVersions'] } + '/platform/organizations/{slug}/billing/credits/top-up': { + /** Tops up the credit balance */ + post: operations['OrgCreditsController_createTopUp'] + } '/platform/organizations/{slug}/billing/invoices': { /** Gets invoices for the given organization */ get: operations['OrgInvoicesController_getInvoices'] @@ -3264,6 +3268,23 @@ export interface components { secret_access_key: string session_token: string } + CreditsTopUpRequestDto: { + amount: number + hcaptcha_token: string + payment_method_id: string + } + CreditsTopUpResponseDto: { + payment_intent_secret?: string + /** @enum {string} */ + status: + | 'canceled' + | 'processing' + | 'requires_action' + | 'requires_capture' + | 'requires_confirmation' + | 'requires_payment_method' + | 'succeeded' + } CustomerBillingAddress: { city?: string country: string @@ -8785,6 +8806,34 @@ export interface operations { } } } + /** Tops up the credit balance */ + OrgCreditsController_createTopUp: { + parameters: { + path: { + slug: string + } + } + requestBody: { + content: { + 'application/json': components['schemas']['CreditsTopUpRequestDto'] + } + } + responses: { + /** @description Top up has been successfully created. */ + 201: { + content: { + 'application/json': components['schemas']['CreditsTopUpResponseDto'] + } + } + 403: { + content: never + } + /** @description Failed to top up credit balance */ + 500: { + content: never + } + } + } /** Gets invoices for the given organization */ OrgInvoicesController_getInvoices: { parameters: { From 2781fb92850987ff076020cda96926a35e23a857 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 10 Jan 2025 16:28:26 +0800 Subject: [PATCH 020/265] Only show Connect UI in layout header if project is active healthy (#32698) * Only show Connect UI and branching UI in layout header if project is active healthy * Only hide connect UI * Opt for disabled state with tooltip instead --- .../components/interfaces/Connect/Connect.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index a5820b3a910..1d125531190 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -5,9 +5,12 @@ import { parseAsBoolean, useQueryState } from 'nuqs' import { useState } from 'react' import { DatabaseConnectionString } from 'components/interfaces/Connect/DatabaseConnectionString' +import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedProject } from 'hooks/misc/useSelectedProject' +import { PROJECT_STATUS } from 'lib/constants' import { Button, DIALOG_PADDING_X, @@ -31,6 +34,8 @@ import ConnectTabContent from './ConnectTabContent' const Connect = () => { const { ref: projectRef } = useParams() + const selectedProject = useSelectedProject() + const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY const [showConnect, setShowConnect] = useQueryState( 'showConnect', @@ -147,6 +152,25 @@ const Connect = () => { selectedGrandchild, }) + if (!isActiveHealthy) { + return ( + } + tooltip={{ + content: { + side: 'bottom', + text: 'Project is currently not active and cannot be connected', + }, + }} + > + Connect + + ) + } + return ( setShowConnect(!open ? null : open)}> From 0e930cf94066f5cc2e69b5a07584527c8be1b62f Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:21:53 +0100 Subject: [PATCH 021/265] r2np: new cost (#32632) * add new cost estimate * fix api types * update api types * restore api.d.ts * fix types * fix utils * clean utils getComputeHourlyPrice --------- Co-authored-by: Ivan Vasilov --- .../AdditionalMonthlySpend.tsx | 52 ++++++--------- .../ConfirmRestoreDialog.tsx | 5 +- .../CreateNewProjectDialog.tsx | 5 +- .../RestoreToNewProject.utils.ts | 64 +++++++++++++++++++ apps/studio/data/projects/clone-query.ts | 8 ++- .../backups/restore-to-new-project.tsx | 18 ++++++ 6 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/RestoreToNewProject.utils.ts diff --git a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/AdditionalMonthlySpend.tsx b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/AdditionalMonthlySpend.tsx index d78b34b0846..671f9520f5e 100644 --- a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/AdditionalMonthlySpend.tsx +++ b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/AdditionalMonthlySpend.tsx @@ -1,32 +1,10 @@ -import { instanceSizeSpecs } from 'data/projects/new-project.constants' -import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' - -export const AdditionalMonthlySpend = () => { - const { project } = useProjectContext() - - /** - * New project will have the same compute size and disk size as the original project - */ - function getAdditionalMonthlySpend() { - const currentProjectComputeSize = project?.infra_compute_size - if (!currentProjectComputeSize) { - return null - } - - if (currentProjectComputeSize === 'nano') { - return null - } - - const additionalMonthlySpend = instanceSizeSpecs[currentProjectComputeSize] - - return additionalMonthlySpend - } - - const additionalMonthlySpend = getAdditionalMonthlySpend() - if (!additionalMonthlySpend) { - return null - } +import { NewProjectPrice } from './RestoreToNewProject.utils' +export const AdditionalMonthlySpend = ({ + additionalMonthlySpend, +}: { + additionalMonthlySpend: NewProjectPrice +}) => { return (

@@ -36,9 +14,21 @@ export const AdditionalMonthlySpend = () => { Project Settings > Compute and Disk

-
-

Additional Monthly Compute + Disk Cost

-

${additionalMonthlySpend.priceMonthly}

+
+
+

Additional Monthly Compute

+

${additionalMonthlySpend.computePrice}

+
+
+

Additional Monthly Disk

+

${additionalMonthlySpend.diskPrice}

+
+
+

Total

+

+ ${additionalMonthlySpend.computePrice + additionalMonthlySpend.diskPrice} +

+
) diff --git a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/ConfirmRestoreDialog.tsx b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/ConfirmRestoreDialog.tsx index 3aa8006be67..cc5b5c40dfe 100644 --- a/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/ConfirmRestoreDialog.tsx +++ b/apps/studio/components/interfaces/Database/Backups/RestoreToNewProject/ConfirmRestoreDialog.tsx @@ -11,17 +11,20 @@ import { DialogTitle, } from 'ui' import { AdditionalMonthlySpend } from './AdditionalMonthlySpend' +import { NewProjectPrice } from './RestoreToNewProject.utils' interface ConfirmRestoreDialogProps { open: boolean onOpenChange: (value: boolean) => void onSelectContinue: () => void + additionalMonthlySpend: NewProjectPrice } export const ConfirmRestoreDialog = ({ open, onOpenChange, onSelectContinue, + additionalMonthlySpend, }: ConfirmRestoreDialogProps) => { const { project } = useProjectContext() const organization = useSelectedOrganization() @@ -64,7 +67,7 @@ export const ConfirmRestoreDialog = ({ - +
) From 14ea7a608e14771528a970c3d5a5bb8669da370d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 13 Jan 2025 18:20:51 +0800 Subject: [PATCH 027/265] docs: credit balance top up / slight copy changes (#32737) --- apps/docs/content/guides/platform/billing-faq.mdx | 6 +++++- .../Organization/BillingSettings/CreditBalance.tsx | 3 ++- .../General/TransferProjectPanel/TransferProjectButton.tsx | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/guides/platform/billing-faq.mdx b/apps/docs/content/guides/platform/billing-faq.mdx index f1bdf94d83e..79a3a4358a8 100644 --- a/apps/docs/content/guides/platform/billing-faq.mdx +++ b/apps/docs/content/guides/platform/billing-faq.mdx @@ -160,13 +160,17 @@ Visa, Mastercard, American Express, Japan Credit Bureau (JCB), China UnionPay (C #### What currency can I pay in? -All our invoices are issued in USD, but you can pay in any currency as long as your credit card provider allows charging in USD after conversion. +All our invoices are issued in USD, but you can pay in any currency so long as the credit card provider allows charging in USD after conversion. #### Can I change the payment method? Yes, you will have to add the new payment method before being allowed to remove the old one. This can be done from your dashboard on the [organization’s billing page](https://supabase.com/dashboard/org/_/billing). +#### Can I pay upfront for multilpe months? + +You can top up your credit balance to cover multiple months through your [organization’s billing page](https://supabase.com/dashboard/org/_/billing). + #### When are payments taken? Payments are taken at the beginning of each billing cycle. You will be charged once a month. You can see the current billing cycle and upcoming invoice in your [organization's billing settings](https://supabase.com/dashboard/org/_/billing). The subscription plan, custom domain and PITR addons are charged upfront, whereas usage-charges, including compute, are charged in hindsight based on your usage. diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx index 7c3341232e9..ab0cfee3adc 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx @@ -48,7 +48,8 @@ const CreditBalance = () => {

Credit Balance

- Credits will be applied to future invoices, before charging your payment method. + Credits will be applied to future invoices, before charging your payment method. If your + credit balance runs out, your default payment method will be charged.

diff --git a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx index 393ff0948c1..4554f49f50e 100644 --- a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx @@ -240,7 +240,7 @@ const TransferProjectButton = () => {

These members will need to either delete, pause, or upgrade one or more of - their projects before you can downgrade this project. + their projects before you can transfer this project.

)} From 14d5a79203bd8ce0acedfff4f8bdd7ecd5db6775 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Mon, 13 Jan 2025 11:22:04 +0100 Subject: [PATCH 028/265] Chore: add links to hidden www pages (#32658) * fix vector icon * add supasquad and community content program links to oss page * remove console log --- apps/www/components/OpenSource/Repos.tsx | 20 ++++++++++++++++++-- apps/www/data/MainProducts.tsx | 22 ++-------------------- apps/www/data/open-source/index.tsx | 5 ++--- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/www/components/OpenSource/Repos.tsx b/apps/www/components/OpenSource/Repos.tsx index 0575f1554db..2ff69e0a1fa 100644 --- a/apps/www/components/OpenSource/Repos.tsx +++ b/apps/www/components/OpenSource/Repos.tsx @@ -6,6 +6,7 @@ import { cn } from 'ui' import LogoLoader from 'ui/src/components/LogoLoader' import RepoCard from './RepoCard' +import { Pencil, Users, Users2 } from 'lucide-react' interface TabProps { label: string @@ -105,7 +106,7 @@ const Repos = ({ tabs }: Props) => { return (
-
+
{ Code of Conduct + + + SupaSquad + + + + Community Content Program +
@@ -209,7 +225,7 @@ const GitHubIcon = () => ( viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" - className="fill-foreground-lighter grouopp-hover:fill-foreground" + className="fill-foreground-lighter grouop-hover:fill-foreground w-4 h-4" > - // Integrate your favorite ML-models to{' '} - // store, index and search vector embeddings. - // - // ), - // description_short: 'AI toolkit to manage embeddings', - // label: '', - // url: '/vector', - // }, } export default MainProducts diff --git a/apps/www/data/open-source/index.tsx b/apps/www/data/open-source/index.tsx index 40ac7b11448..065808d8c6e 100644 --- a/apps/www/data/open-source/index.tsx +++ b/apps/www/data/open-source/index.tsx @@ -1,5 +1,4 @@ -import { products } from 'shared-data' -import ProductModules from '../ProductModules' +import { products, PRODUCT_MODULES } from 'shared-data' export default { metaTitle: 'Supabase Open Source Community', @@ -98,7 +97,7 @@ export default { }, { label: 'Vector', - icon: ProductModules.vector.icon[16], + icon: PRODUCT_MODULES.vector.icon[16], repos: [ 'embeddings-generator', 'headless-vector-search', From 5c969f973dcd4bc06bd68cf6cf4bedf85a8d8bf1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 13 Jan 2025 19:22:35 +0800 Subject: [PATCH 029/265] Fix view connection string (#32683) --- .../InfrastructureConfiguration/InstanceNode.tsx | 14 ++++++++++---- .../InfrastructureConfiguration/MapView.tsx | 15 ++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index 568166cc00d..4f52cec2d70 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -2,6 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { Database, DatabaseBackup, HelpCircle, Loader2, MoreVertical } from 'lucide-react' import Link from 'next/link' +import { parseAsBoolean, useQueryState } from 'nuqs' import { Handle, NodeProps, Position } from 'reactflow' import { useParams } from 'common' @@ -14,6 +15,7 @@ import { import { formatDatabaseID } from 'data/read-replicas/replicas.utils' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' +import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Badge, Button, @@ -178,8 +180,9 @@ export const ReplicaNode = ({ data }: NodeProps) => { onSelectDropReplica, } = data const { ref } = useParams() - const created = dayjs(inserted_at).format('DD MMM YYYY') + const dbSelectorState = useDatabaseSelectorStateSnapshot() const canManageReplicas = useCheckPermissions(PermissionAction.CREATE, 'projects') + const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const { data: databaseStatuses } = useReadReplicasStatusesQuery({ projectRef: ref }) const { replicaInitializationStatus } = @@ -197,6 +200,7 @@ export const ReplicaNode = ({ data }: NodeProps) => { error?: string }) ?? { status: undefined, progress: undefined, estimations: undefined, error: undefined } + const created = dayjs(inserted_at).format('DD MMM YYYY') const stage = progress !== undefined ? Number(progress.split('_')[0]) : 0 const stagePercent = stage / (Object.keys(INIT_PROGRESS).length - 1) @@ -340,10 +344,12 @@ export const ReplicaNode = ({ data }: NodeProps) => { { + setShowConnect(true) + dbSelectorState.setSelectedDatabaseId(id) + }} > - - View connection string - + View connection string { const { ref } = useParams() + const dbSelectorState = useDatabaseSelectorStateSnapshot() + const [mount, setMount] = useState(false) const [zoom, setZoom] = useState(1.5) const [center, setCenter] = useState<[number, number]>([14, 7]) @@ -59,6 +63,7 @@ const MapView = ({ region: { key: string; country?: string; name?: string } }>() const canManageReplicas = useCheckPermissions(PermissionAction.CREATE, 'projects') + const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const { data } = useReadReplicasQuery({ projectRef: ref }) const databases = data ?? [] @@ -293,12 +298,12 @@ const MapView = ({ { + setShowConnect(true) + dbSelectorState.setSelectedDatabaseId(database.identifier) + }} > - - View connection string - + View connection string Date: Mon, 13 Jan 2025 14:01:03 +0100 Subject: [PATCH 030/265] fix: Remove /members/invite API calls (#32707) Remove /invite API calls. --- .../TeamSettings/InviteMemberButton.tsx | 73 +++++----------- .../TeamSettings/MemberActions.tsx | 83 ++++++------------- ...anization-member-invite-create-mutation.ts | 76 ----------------- ...anization-member-invite-delete-mutation.ts | 69 --------------- 4 files changed, 45 insertions(+), 256 deletions(-) delete mode 100644 apps/studio/data/organizations/organization-member-invite-create-mutation.ts delete mode 100644 apps/studio/data/organizations/organization-member-invite-delete-mutation.ts diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx index 12521de5dc8..b197789ad89 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx @@ -1,6 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { isNil } from 'lodash' import { Check, ChevronsUpDown } from 'lucide-react' import Link from 'next/link' import { useEffect, useState } from 'react' @@ -12,7 +11,6 @@ import { useParams } from 'common' import InformationBox from 'components/ui/InformationBox' import { useOrganizationCreateInvitationMutation } from 'data/organization-members/organization-invitation-create-mutation' import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' -import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' import { useProjectsQuery } from 'data/projects/projects-query' import { @@ -115,8 +113,6 @@ export const InviteMemberButton = () => { ) const { mutate: inviteMember, isLoading: isInviting } = useOrganizationCreateInvitationMutation() - const { mutate: inviteMemberOld, isLoading: isInvitingOld } = - useOrganizationMemberInviteCreateMutation() const FormSchema = z.object({ email: z.string().email('Must be a valid email address').min(1, 'Email is required'), @@ -150,54 +146,27 @@ export const InviteMemberButton = () => { } } - if (hasAccessToProjectLevelPermissions) { - inviteMember( - { - slug, - email: values.email.toLowerCase(), - roleId: Number(values.role), - ...(!values.applyToOrg && values.projectRef ? { projects: [values.projectRef] } : {}), - }, - { - onSuccess: () => { - toast.success('Successfully sent invitation to new member') - setIsOpen(!isOpen) + inviteMember( + { + slug, + email: values.email.toLowerCase(), + roleId: Number(values.role), + ...(!values.applyToOrg && values.projectRef ? { projects: [values.projectRef] } : {}), + }, + { + onSuccess: () => { + toast.success('Successfully sent invitation to new member') + setIsOpen(!isOpen) - form.reset({ - email: '', - role: developerRole?.id.toString() ?? '', - applyToOrg: true, - projectRef: '', - }) - }, - } - ) - } else { - inviteMemberOld( - { - slug, - invitedEmail: values.email.toLowerCase(), - ownerId: profile.id, - roleId: Number(values.role), + form.reset({ + email: '', + role: developerRole?.id.toString() ?? '', + applyToOrg: true, + projectRef: '', + }) }, - { - onSuccess: (data) => { - if (isNil(data)) { - toast.error('Failed to add member') - } else { - toast.success('Successfully added new member') - setIsOpen(!isOpen) - form.reset({ - email: '', - role: developerRole?.id.toString() ?? '', - applyToOrg: true, - projectRef: '', - }) - } - }, - } - ) - } + } + ) } useEffect(() => { @@ -407,7 +376,7 @@ export const InviteMemberButton = () => { autoFocus {...field} autoComplete="off" - disabled={isInviting || isInvitingOld} + disabled={isInviting} placeholder="Enter email address" /> @@ -451,7 +420,7 @@ export const InviteMemberButton = () => { - diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index 2453cddf761..69243ae1304 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -9,8 +9,6 @@ import { useOrganizationCreateInvitationMutation } from 'data/organization-membe import { useOrganizationDeleteInvitationMutation } from 'data/organization-members/organization-invitation-delete-mutation' import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query' import { useOrganizationMemberDeleteMutation } from 'data/organizations/organization-member-delete-mutation' -import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' -import { useOrganizationMemberInviteDeleteMutation } from 'data/organizations/organization-member-invite-delete-mutation' import { useOrganizationMembersQuery, type OrganizationMember, @@ -95,19 +93,9 @@ export const MemberActions = ({ member }: MemberActionsProps) => { toast.error(`Failed to resend invitation: ${error.message}`) }, }) - const { mutate: inviteMemberOld, isLoading: isCreatingInviteOld } = - useOrganizationMemberInviteCreateMutation({ - onSuccess: () => { - toast.success('Resent the invitation') - }, - onError: (error) => { - toast.error(`Failed to resend the invidation: ${error.message}`) - }, - }) const { mutate: deleteInvitation, isLoading: isDeletingInvite } = useOrganizationDeleteInvitationMutation() - const { mutate: deleteInvitationOld } = useOrganizationMemberInviteDeleteMutation() const isLoading = isDeletingMember || isDeletingInvite || isCreatingInvite @@ -124,44 +112,28 @@ export const MemberActions = ({ member }: MemberActionsProps) => { if (!slug) return console.error('Slug is required') if (!invitedId) return console.error('Member invited ID is required') - if (isOptedIntoProjectLevelPermissions) { - deleteInvitation( - { slug, id: invitedId, skipInvalidation: true }, - { - onSuccess: () => { - if (!member.primary_email) return toast.error('Email is required') - const projectScopedRole = projectScopedRoles.find((role) => role.id === roleId) - if (projectScopedRole !== undefined) { - const projects = (projectScopedRole?.project_ids ?? []) - .map((id) => allProjects?.find((p) => p.id === id)?.ref) - .filter(Boolean) as string[] - inviteMember({ - slug, - email: member.primary_email, - roleId: projectScopedRole.base_role_id, - projects, - }) - } else { - inviteMember({ slug, email: member.primary_email, roleId }) - } - }, - } - ) - } else { - deleteInvitationOld( - { slug, invitedId, invalidateDetail: false }, - { - onSuccess: () => { - inviteMemberOld({ + deleteInvitation( + { slug, id: invitedId, skipInvalidation: true }, + { + onSuccess: () => { + if (!member.primary_email) return toast.error('Email is required') + const projectScopedRole = projectScopedRoles.find((role) => role.id === roleId) + if (projectScopedRole !== undefined) { + const projects = (projectScopedRole?.project_ids ?? []) + .map((id) => allProjects?.find((p) => p.id === id)?.ref) + .filter(Boolean) as string[] + inviteMember({ slug, - invitedEmail: member.primary_email!, - ownerId: invitedId, - roleId, + email: member.primary_email, + roleId: projectScopedRole.base_role_id, + projects, }) - }, - } - ) - } + } else { + inviteMember({ slug, email: member.primary_email, roleId }) + } + }, + } + ) } const handleRevokeInvitation = (member: OrganizationMember) => { @@ -169,17 +141,10 @@ export const MemberActions = ({ member }: MemberActionsProps) => { if (!slug) return console.error('Slug is required') if (!invitedId) return console.error('Member invited ID is required') - if (isOptedIntoProjectLevelPermissions) { - deleteInvitation( - { slug, id: invitedId }, - { onSuccess: () => toast.success('Successfully revoked the invitation.') } - ) - } else { - deleteInvitationOld( - { slug, invitedId }, - { onSuccess: () => toast.success('Successfully revoked the invitation.') } - ) - } + deleteInvitation( + { slug, id: invitedId }, + { onSuccess: () => toast.success('Successfully revoked the invitation.') } + ) } if (!canRemoveMember || (isPendingInviteAcceptance && !canResendInvite && !canRevokeInvite)) { diff --git a/apps/studio/data/organizations/organization-member-invite-create-mutation.ts b/apps/studio/data/organizations/organization-member-invite-create-mutation.ts deleted file mode 100644 index 83e174ec3ef..00000000000 --- a/apps/studio/data/organizations/organization-member-invite-create-mutation.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import { toast } from 'sonner' - -import { post } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' -import type { ResponseError } from 'types' -import { organizationKeys } from './keys' - -export type OrganizationMemberInviteCreateVariables = { - slug: string - invitedEmail: string - ownerId: number - roleId: number -} - -// [Joshen TODO] Should be deprecated now - double check before deleting - -export async function createOrganizationMemberInvite({ - slug, - invitedEmail, - ownerId, - roleId, -}: OrganizationMemberInviteCreateVariables) { - const response = await post(`${API_URL}/organizations/${slug}/members/invite`, { - invited_email: invitedEmail, - owner_id: ownerId, - role_id: roleId, - }) - if (response.error) { - throw response.error - } - - return response -} - -type OrganizationMemberInviteCreateData = Awaited> - -export const useOrganizationMemberInviteCreateMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions< - OrganizationMemberInviteCreateData, - ResponseError, - OrganizationMemberInviteCreateVariables - >, - 'mutationFn' -> = {}) => { - const queryClient = useQueryClient() - - return useMutation< - OrganizationMemberInviteCreateData, - ResponseError, - OrganizationMemberInviteCreateVariables - >((vars) => createOrganizationMemberInvite(vars), { - async onSuccess(data, variables, context) { - const { slug } = variables - - await Promise.all([ - queryClient.invalidateQueries(organizationKeys.members(slug)), - queryClient.invalidateQueries(organizationKeys.roles(slug)), - ]) - - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - toast.error(`Failed to invite member: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - }) -} diff --git a/apps/studio/data/organizations/organization-member-invite-delete-mutation.ts b/apps/studio/data/organizations/organization-member-invite-delete-mutation.ts deleted file mode 100644 index 75c37016f44..00000000000 --- a/apps/studio/data/organizations/organization-member-invite-delete-mutation.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import { toast } from 'sonner' - -import { del, handleError } from 'data/fetchers' -import type { ResponseError } from 'types' -import { organizationKeys } from './keys' - -export type OrganizationMemberInviteDeleteVariables = { - slug: string - invitedId: number - invalidateDetail?: boolean -} - -// [Joshen TODO] Should be deprecated now - double check before deleting - -export async function deleteOrganizationMemberInvite({ - slug, - invitedId, -}: OrganizationMemberInviteDeleteVariables) { - const { data, error } = await del('/platform/organizations/{slug}/members/invite', { - params: { path: { slug }, query: { invited_id: invitedId } }, - }) - if (error) handleError(error) - return data -} - -type OrganizationMemberInviteDeleteData = Awaited> - -export const useOrganizationMemberInviteDeleteMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions< - OrganizationMemberInviteDeleteData, - ResponseError, - OrganizationMemberInviteDeleteVariables - >, - 'mutationFn' -> = {}) => { - const queryClient = useQueryClient() - - return useMutation< - OrganizationMemberInviteDeleteData, - ResponseError, - OrganizationMemberInviteDeleteVariables - >((vars) => deleteOrganizationMemberInvite(vars), { - async onSuccess(data, variables, context) { - const { slug, invalidateDetail } = variables - - await Promise.all([ - ...(invalidateDetail ?? true - ? [queryClient.invalidateQueries(organizationKeys.members(slug))] - : []), - queryClient.invalidateQueries(organizationKeys.roles(slug)), - ]) - - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - toast.error(`Failed to revoke invitation: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - }) -} From ef2ba4a1fc63190417635836a09cd3ca03ad8015 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 13 Jan 2025 23:19:10 +0800 Subject: [PATCH 031/265] Add null placeholder to RowEditor foreign key input field (#32744) --- .../TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 6305af0fd15..54024583553 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -21,7 +21,6 @@ import { DATETIME_TYPES, JSON_TYPES, TEXT_TYPES } from '../SidePanelEditor.const import { DateTimeInput } from './DateTimeInput' import type { EditValue, RowField } from './RowEditor.types' import { isValueTruncated } from './RowEditor.utils' -import { checkDomainOfScale } from 'recharts/types/util/ChartUtils' export interface InputFieldProps { field: RowField @@ -98,6 +97,7 @@ const InputField = ({ Date: Mon, 13 Jan 2025 23:29:28 +0800 Subject: [PATCH 032/265] Chore/query performance fixes and improvements (#32747) * Show tab max value in seconds if > 1000ms * Fix incorrect sort option in initial state with no URL state * Fix trim of undefined in Query Performance when retrieving index --- .../QueryPerformance/QueryPerformance.tsx | 103 ++++++++++-------- .../QueryPerformanceFilterBar.tsx | 2 +- .../retrieve-index-from-select-query.ts | 25 +++-- 3 files changed, 77 insertions(+), 53 deletions(-) diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx index 569187d08cb..e7ac3638bde 100644 --- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx +++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx @@ -126,52 +126,67 @@ export const QueryPerformance = ({ }} > - {QUERY_PERFORMANCE_TABS.map((tab) => ( - - {tab.id === page && ( -
- )} + {QUERY_PERFORMANCE_TABS.map((tab) => { + const tabMax = Number(tab.max) + const maxValue = + tab.id !== QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT + ? tabMax > 1000 + ? (tabMax / 1000).toFixed(2) + : tabMax.toLocaleString() + : tabMax.toLocaleString() -
- {tab.label} - - - - - {tab.description} - -
- {tab.isLoading ? ( - - ) : tab.max === undefined ? ( - - No data yet - - ) : ( - - {Number(tab.max).toLocaleString()} - {tab.id !== QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT ? 'ms' : ' calls'} - - )} + return ( + + {tab.id === page && ( +
+ )} - {tab.id === page && ( -
- )} - - ))} +
+ {tab.label} + + + + + {tab.description} + +
+ {tab.isLoading ? ( + + ) : tab.max === undefined ? ( + + No data yet + + ) : ( + + {/* {Number(tab.max).toLocaleString()} */} + {maxValue} + {tab.id !== QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT + ? tabMax > 1000 + ? 's' + : 'ms' + : ' calls'} + + )} + + {tab.id === page && ( +
+ )} + + ) + })} diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx index 8c189b95eaf..debc3a41c48 100644 --- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx +++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx @@ -73,7 +73,7 @@ export const QueryPerformanceFilterBar = ({ } function getSortButtonLabel() { - if (defaultSortByValue?.order === 'desc') { + if (sortByValue?.order === 'desc') { return 'Sorted by latency - high to low' } else { return 'Sorted by latency - low to high' diff --git a/apps/studio/data/database/retrieve-index-from-select-query.ts b/apps/studio/data/database/retrieve-index-from-select-query.ts index 8ef31cde994..1d0b7d0f023 100644 --- a/apps/studio/data/database/retrieve-index-from-select-query.ts +++ b/apps/studio/data/database/retrieve-index-from-select-query.ts @@ -102,19 +102,28 @@ export const useGetIndexesFromSelectQuery = = {} -) => - useQuery( +) => { + // [Joshen] Only get indexes for queries starting with these + const isValidQueryForIndexing = + query !== undefined && + (query.trim().toLowerCase().startsWith('select') || + query.trim().toLowerCase().startsWith('with pgrst_source')) + + return useQuery< + GetInvolvedIndexesFromSelectQueryData, + GetInvolvedIndexesFromSelectQueryError, + TData + >( databaseKeys.indexesFromQuery(projectRef, query), () => getInvolvedIndexesInSelectQuery({ projectRef, connectionString, query }), { retry: false, enabled: - (enabled && - typeof projectRef !== 'undefined' && - typeof query !== 'undefined' && - query !== undefined && - (query.startsWith('select') || query.startsWith('SELECT'))) || - query.trim().toLowerCase().startsWith('with pgrst_source'), + enabled && + typeof projectRef !== 'undefined' && + typeof query !== 'undefined' && + isValidQueryForIndexing, ...options, } ) +} From 1b3a1befa38668079bc7655ac0c1ae43b9033002 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 13 Jan 2025 23:48:08 +0800 Subject: [PATCH 033/265] Use whitespace-pre in TextConfirmModal (#32748) --- packages/ui-patterns/Dialogs/TextConfirmModal.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ui-patterns/Dialogs/TextConfirmModal.tsx b/packages/ui-patterns/Dialogs/TextConfirmModal.tsx index 39dc7971598..e62ae51bf2b 100644 --- a/packages/ui-patterns/Dialogs/TextConfirmModal.tsx +++ b/packages/ui-patterns/Dialogs/TextConfirmModal.tsx @@ -21,9 +21,9 @@ import { Input_Shadcn_, cn, } from 'ui' -import { Admonition } from './../admonition' import { DialogHeader } from 'ui/src/components/shadcn/ui/dialog' import { z } from 'zod' +import { Admonition } from './../admonition' export interface TextConfirmModalProps { loading: boolean @@ -154,8 +154,11 @@ const TextConfirmModal = forwardRef< render={({ field }) => ( - Type {confirmString} to - confirm. + Type{' '} + + {confirmString} + {' '} + to confirm. Date: Mon, 13 Jan 2025 19:10:31 +0000 Subject: [PATCH 034/265] feat: update mgmt api docs (#32732) Co-authored-by: phamhieu <689843+phamhieu@users.noreply.github.com> --- apps/docs/spec/api_v1_openapi.json | 272 ++++-- apps/docs/spec/common-api-sections.json | 18 + .../transforms/api_v1_openapi_deparsed.json | 808 +++++++++++++----- 3 files changed, 853 insertions(+), 245 deletions(-) diff --git a/apps/docs/spec/api_v1_openapi.json b/apps/docs/spec/api_v1_openapi.json index 5ecb7b3a1a1..808f2c415a9 100644 --- a/apps/docs/spec/api_v1_openapi.json +++ b/apps/docs/spec/api_v1_openapi.json @@ -2061,6 +2061,95 @@ "security": [{ "bearer": [] }] } }, + "/v1/projects/{ref}/restore": { + "get": { + "operationId": "v1-list-available-restore-versions", + "summary": "Lists available restore versions for the given project", + "parameters": [ + { "name": "ref", "required": true, "in": "path", "schema": { "type": "string" } } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectAvailableRestoreVersionsResponse" + } + } + } + }, + "403": { "description": "" } + }, + "tags": ["Projects"], + "security": [{ "bearer": [] }] + }, + "post": { + "operationId": "v1-restore-a-project", + "summary": "Restores the given project", + "parameters": [ + { "name": "ref", "required": true, "in": "path", "schema": { "type": "string" } } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/RestoreProjectBodyDto" } + } + } + }, + "responses": { "200": { "description": "" }, "403": { "description": "" } }, + "tags": ["Projects"], + "security": [{ "bearer": [] }] + } + }, + "/v1/projects/{ref}/restore/cancel": { + "post": { + "operationId": "v1-cancel-a-project-restoration", + "summary": "Cancels the given project restoration", + "parameters": [ + { "name": "ref", "required": true, "in": "path", "schema": { "type": "string" } } + ], + "responses": { "200": { "description": "" }, "403": { "description": "" } }, + "tags": ["Projects"], + "security": [{ "bearer": [] }] + } + }, + "/v1/projects/{ref}/analytics/endpoints/logs.all": { + "get": { + "operationId": "getLogs", + "summary": "Gets project's logs", + "parameters": [ + { + "name": "iso_timestamp_end", + "required": false, + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "iso_timestamp_start", + "required": false, + "in": "query", + "schema": { "type": "string" } + }, + { "name": "sql", "required": false, "in": "query", "schema": { "type": "string" } }, + { "name": "ref", "required": true, "in": "path", "schema": { "type": "string" } } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/V1AnalyticsResponse" } + } + } + }, + "403": { "description": "" } + }, + "tags": ["Analytics"], + "security": [{ "bearer": [] }] + } + }, "/v1/projects/{ref}/database/query": { "post": { "operationId": "v1-run-a-query", @@ -2711,43 +2800,43 @@ "BranchDetailResponse": { "type": "object", "properties": { + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "db_port": { "type": "integer" }, "ref": { "type": "string" }, "postgres_version": { "type": "string" }, "postgres_engine": { "type": "string" }, "release_channel": { "type": "string" }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" - }, "db_host": { "type": "string" }, "db_user": { "type": "string" }, "db_pass": { "type": "string" }, "jwt_secret": { "type": "string" } }, "required": [ + "status", "db_port", "ref", "postgres_version", "postgres_engine", "release_channel", - "status", "db_host" ] }, @@ -2763,6 +2852,7 @@ "git_branch": { "type": "string" }, "persistent": { "type": "boolean" }, "status": { + "type": "string", "enum": [ "CREATING_PROJECT", "RUNNING_MIGRATIONS", @@ -2770,8 +2860,7 @@ "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED" - ], - "type": "string" + ] } } }, @@ -2784,14 +2873,8 @@ "deprecated": true, "description": "This field is deprecated and will not be populated." }, - "id": { "type": "string" }, - "name": { "type": "string" }, - "project_ref": { "type": "string" }, - "parent_project_ref": { "type": "string" }, - "is_default": { "type": "boolean" }, - "git_branch": { "type": "string" }, - "persistent": { "type": "boolean" }, "status": { + "type": "string", "enum": [ "CREATING_PROJECT", "RUNNING_MIGRATIONS", @@ -2799,20 +2882,26 @@ "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED" - ], - "type": "string" + ] }, + "id": { "type": "string" }, + "name": { "type": "string" }, + "project_ref": { "type": "string" }, + "parent_project_ref": { "type": "string" }, + "is_default": { "type": "boolean" }, + "git_branch": { "type": "string" }, + "persistent": { "type": "boolean" }, "created_at": { "type": "string" }, "updated_at": { "type": "string" } }, "required": [ + "status", "id", "name", "project_ref", "parent_project_ref", "is_default", "persistent", - "status", "created_at", "updated_at" ] @@ -2853,29 +2942,29 @@ "description": "Creation timestamp", "example": "2023-03-29T16:32:59Z" }, - "database": { "$ref": "#/components/schemas/V1DatabaseResponse" }, "status": { + "type": "string", "enum": [ + "INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", + "UNKNOWN", "GOING_DOWN", - "INACTIVE", "INIT_FAILED", "REMOVED", - "RESTARTING", - "UNKNOWN", + "RESTORING", "UPGRADING", "PAUSING", - "RESTORING", "RESTORE_FAILED", + "RESTARTING", "PAUSE_FAILED", "RESIZING" - ], - "type": "string" - } + ] + }, + "database": { "$ref": "#/components/schemas/V1DatabaseResponse" } }, - "required": ["id", "organization_id", "name", "region", "created_at", "database", "status"] + "required": ["id", "organization_id", "name", "region", "created_at", "status", "database"] }, "V1CreateProjectBodyDto": { "type": "object", @@ -2970,24 +3059,24 @@ "example": "2023-03-29T16:32:59Z" }, "status": { + "type": "string", "enum": [ + "INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", + "UNKNOWN", "GOING_DOWN", - "INACTIVE", "INIT_FAILED", "REMOVED", - "RESTARTING", - "UNKNOWN", + "RESTORING", "UPGRADING", "PAUSING", - "RESTORING", "RESTORE_FAILED", + "RESTARTING", "PAUSE_FAILED", "RESIZING" - ], - "type": "string" + ] } }, "required": ["id", "organization_id", "name", "region", "created_at", "status"] @@ -3061,7 +3150,7 @@ "type": { "type": "string", "enum": ["sql"] }, "visibility": { "type": "string", "enum": ["user", "project", "org", "public"] }, "name": { "type": "string" }, - "description": { "type": "string" }, + "description": { "type": "string", "nullable": true }, "project": { "$ref": "#/components/schemas/SnippetProject" }, "owner": { "$ref": "#/components/schemas/SnippetUser" }, "updated_by": { "$ref": "#/components/schemas/SnippetUser" } @@ -3073,6 +3162,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by" @@ -3104,7 +3194,7 @@ "type": { "type": "string", "enum": ["sql"] }, "visibility": { "enum": ["user", "project", "org", "public"], "type": "string" }, "name": { "type": "string" }, - "description": { "type": "string" }, + "description": { "type": "string", "nullable": true }, "project": { "$ref": "#/components/schemas/SnippetProject" }, "owner": { "$ref": "#/components/schemas/SnippetUser" }, "updated_by": { "$ref": "#/components/schemas/SnippetUser" }, @@ -3117,6 +3207,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by", @@ -3627,14 +3718,20 @@ "properties": { "enabled": { "type": "boolean" } }, "required": ["enabled"] }, + "StorageFeatureS3Protocol": { + "type": "object", + "properties": { "enabled": { "type": "boolean" } }, + "required": ["enabled"] + }, "StorageFeatures": { "type": "object", "properties": { "imageTransformation": { "$ref": "#/components/schemas/StorageFeatureImageTransformation" - } + }, + "s3Protocol": { "$ref": "#/components/schemas/StorageFeatureS3Protocol" } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] }, "StorageConfigResponse": { "type": "object", @@ -3724,11 +3821,11 @@ "SupavisorConfigResponse": { "type": "object", "properties": { + "database_type": { "type": "string", "enum": ["PRIMARY", "READ_REPLICA"] }, "db_port": { "type": "integer" }, "default_pool_size": { "type": "integer", "nullable": true }, "max_client_conn": { "type": "integer", "nullable": true }, "identifier": { "type": "string" }, - "database_type": { "enum": ["PRIMARY", "READ_REPLICA"], "type": "string" }, "is_using_scram_auth": { "type": "boolean" }, "db_user": { "type": "string" }, "db_host": { "type": "string" }, @@ -3737,11 +3834,11 @@ "pool_mode": { "enum": ["transaction", "session"], "type": "string" } }, "required": [ + "database_type", "db_port", "default_pool_size", "max_client_conn", "identifier", - "database_type", "is_using_scram_auth", "db_user", "db_host", @@ -4337,6 +4434,73 @@ }, "required": ["id", "type", "inserted_at", "updated_at"] }, + "ProjectAvailableRestoreVersion": { + "type": "object", + "properties": { + "version": { "type": "string" }, + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"] + }, + "postgres_engine": { "type": "string", "enum": ["13", "14", "15", "17-oriole"] } + }, + "required": ["version", "release_channel", "postgres_engine"] + }, + "GetProjectAvailableRestoreVersionsResponse": { + "type": "object", + "properties": { + "available_versions": { + "type": "array", + "items": { "$ref": "#/components/schemas/ProjectAvailableRestoreVersion" } + } + }, + "required": ["available_versions"] + }, + "RestoreProjectBodyDto": { + "type": "object", + "properties": { + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"], + "description": "Release channel version. If not provided, GeneralAvailability will be used." + }, + "postgres_engine": { + "type": "string", + "enum": ["15", "17-oriole"], + "description": "Postgres engine version. If not provided, the latest version from the given release channel will be used." + } + } + }, + "V1AnalyticsResponse": { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "properties": { + "code": { "type": "number" }, + "errors": { + "type": "array", + "items": { + "properties": { + "domain": { "type": "string" }, + "location": { "type": "string" }, + "locationType": { "type": "string" }, + "message": { "type": "string" }, + "reason": { "type": "string" } + } + } + }, + "message": { "type": "string" }, + "status": { "type": "string" } + } + }, + { "type": "string" } + ] + }, + "result": { "type": "array", "items": { "type": "object" } } + } + }, "V1RunQueryBody": { "type": "object", "properties": { "query": { "type": "string" } }, diff --git a/apps/docs/spec/common-api-sections.json b/apps/docs/spec/common-api-sections.json index 821e282f141..5c82c7615df 100644 --- a/apps/docs/spec/common-api-sections.json +++ b/apps/docs/spec/common-api-sections.json @@ -375,6 +375,12 @@ "type": "category", "title": "Projects", "items": [ + { + "id": "v1-cancel-a-project-restoration", + "title": "Cancel a project restoration", + "slug": "v1-cancel-a-project-restoration", + "type": "operation" + }, { "id": "v1-create-a-project", "title": "Create a project", @@ -435,12 +441,24 @@ "slug": "v1-list-all-projects", "type": "operation" }, + { + "id": "v1-list-available-restore-versions", + "title": "List available restore versions", + "slug": "v1-list-available-restore-versions", + "type": "operation" + }, { "id": "v1-pause-a-project", "title": "Pause a project", "slug": "v1-pause-a-project", "type": "operation" }, + { + "id": "v1-restore-a-project", + "title": "Restore a project", + "slug": "v1-restore-a-project", + "type": "operation" + }, { "id": "v1-update-network-restrictions", "title": "Update network restrictions", diff --git a/apps/docs/spec/transforms/api_v1_openapi_deparsed.json b/apps/docs/spec/transforms/api_v1_openapi_deparsed.json index 50b2c52dae6..45725e0d4dd 100644 --- a/apps/docs/spec/transforms/api_v1_openapi_deparsed.json +++ b/apps/docs/spec/transforms/api_v1_openapi_deparsed.json @@ -78,6 +78,26 @@ "schema": { "type": "object", "properties": { + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "db_port": { "type": "integer" }, @@ -93,26 +113,6 @@ "release_channel": { "type": "string" }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" - }, "db_host": { "type": "string" }, @@ -127,12 +127,12 @@ } }, "required": [ + "status", "db_port", "ref", "postgres_version", "postgres_engine", "release_channel", - "status", "db_host" ] } @@ -187,6 +187,7 @@ "type": "boolean" }, "status": { + "type": "string", "enum": [ "CREATING_PROJECT", "RUNNING_MIGRATIONS", @@ -194,8 +195,7 @@ "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED" - ], - "type": "string" + ] } } } @@ -219,6 +219,17 @@ "deprecated": true, "description": "This field is deprecated and will not be populated." }, + "status": { + "type": "string", + "enum": [ + "CREATING_PROJECT", + "RUNNING_MIGRATIONS", + "MIGRATIONS_PASSED", + "MIGRATIONS_FAILED", + "FUNCTIONS_DEPLOYED", + "FUNCTIONS_FAILED" + ] + }, "id": { "type": "string" }, @@ -240,17 +251,6 @@ "persistent": { "type": "boolean" }, - "status": { - "enum": [ - "CREATING_PROJECT", - "RUNNING_MIGRATIONS", - "MIGRATIONS_PASSED", - "MIGRATIONS_FAILED", - "FUNCTIONS_DEPLOYED", - "FUNCTIONS_FAILED" - ], - "type": "string" - }, "created_at": { "type": "string" }, @@ -259,13 +259,13 @@ } }, "required": [ + "status", "id", "name", "project_ref", "parent_project_ref", "is_default", "persistent", - "status", "created_at", "updated_at" ] @@ -462,6 +462,26 @@ "description": "Creation timestamp", "example": "2023-03-29T16:32:59Z" }, + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "database": { "type": "object", "properties": { @@ -483,26 +503,6 @@ } }, "required": ["host", "version", "postgres_engine", "release_channel"] - }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" } }, "required": [ @@ -511,8 +511,8 @@ "name", "region", "created_at", - "database", - "status" + "status", + "database" ] } } @@ -654,24 +654,24 @@ "example": "2023-03-29T16:32:59Z" }, "status": { + "type": "string", "enum": [ + "INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", + "UNKNOWN", "GOING_DOWN", - "INACTIVE", "INIT_FAILED", "REMOVED", - "RESTARTING", - "UNKNOWN", + "RESTORING", "UPGRADING", "PAUSING", - "RESTORING", "RESTORE_FAILED", + "RESTARTING", "PAUSE_FAILED", "RESIZING" - ], - "type": "string" + ] } }, "required": ["id", "organization_id", "name", "region", "created_at", "status"] @@ -1069,7 +1069,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "nullable": true }, "project": { "type": "object", @@ -1118,6 +1119,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by" @@ -1189,7 +1191,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "nullable": true }, "project": { "type": "object", @@ -1253,6 +1256,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by", @@ -1885,6 +1889,17 @@ "deprecated": true, "description": "This field is deprecated and will not be populated." }, + "status": { + "type": "string", + "enum": [ + "CREATING_PROJECT", + "RUNNING_MIGRATIONS", + "MIGRATIONS_PASSED", + "MIGRATIONS_FAILED", + "FUNCTIONS_DEPLOYED", + "FUNCTIONS_FAILED" + ] + }, "id": { "type": "string" }, @@ -1906,17 +1921,6 @@ "persistent": { "type": "boolean" }, - "status": { - "enum": [ - "CREATING_PROJECT", - "RUNNING_MIGRATIONS", - "MIGRATIONS_PASSED", - "MIGRATIONS_FAILED", - "FUNCTIONS_DEPLOYED", - "FUNCTIONS_FAILED" - ], - "type": "string" - }, "created_at": { "type": "string" }, @@ -1925,13 +1929,13 @@ } }, "required": [ + "status", "id", "name", "project_ref", "parent_project_ref", "is_default", "persistent", - "status", "created_at", "updated_at" ] @@ -2034,6 +2038,17 @@ "deprecated": true, "description": "This field is deprecated and will not be populated." }, + "status": { + "type": "string", + "enum": [ + "CREATING_PROJECT", + "RUNNING_MIGRATIONS", + "MIGRATIONS_PASSED", + "MIGRATIONS_FAILED", + "FUNCTIONS_DEPLOYED", + "FUNCTIONS_FAILED" + ] + }, "id": { "type": "string" }, @@ -2055,17 +2070,6 @@ "persistent": { "type": "boolean" }, - "status": { - "enum": [ - "CREATING_PROJECT", - "RUNNING_MIGRATIONS", - "MIGRATIONS_PASSED", - "MIGRATIONS_FAILED", - "FUNCTIONS_DEPLOYED", - "FUNCTIONS_FAILED" - ], - "type": "string" - }, "created_at": { "type": "string" }, @@ -2074,13 +2078,13 @@ } }, "required": [ + "status", "id", "name", "project_ref", "parent_project_ref", "is_default", "persistent", - "status", "created_at", "updated_at" ] @@ -3445,6 +3449,26 @@ "description": "Creation timestamp", "example": "2023-03-29T16:32:59Z" }, + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "database": { "type": "object", "properties": { @@ -3466,26 +3490,6 @@ } }, "required": ["host", "version", "postgres_engine", "release_channel"] - }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" } }, "required": [ @@ -3494,8 +3498,8 @@ "name", "region", "created_at", - "database", - "status" + "status", + "database" ] } } @@ -4801,9 +4805,18 @@ } }, "required": ["enabled"] + }, + "s3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] } }, "required": ["fileSizeLimit", "features"] @@ -4865,9 +4878,18 @@ } }, "required": ["enabled"] + }, + "s3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] } } } @@ -5310,6 +5332,10 @@ "items": { "type": "object", "properties": { + "database_type": { + "type": "string", + "enum": ["PRIMARY", "READ_REPLICA"] + }, "db_port": { "type": "integer" }, @@ -5324,10 +5350,6 @@ "identifier": { "type": "string" }, - "database_type": { - "enum": ["PRIMARY", "READ_REPLICA"], - "type": "string" - }, "is_using_scram_auth": { "type": "boolean" }, @@ -5349,11 +5371,11 @@ } }, "required": [ + "database_type", "db_port", "default_pool_size", "max_client_conn", "identifier", - "database_type", "is_using_scram_auth", "db_user", "db_host", @@ -8176,6 +8198,257 @@ ] } }, + "/v1/projects/{ref}/restore": { + "get": { + "operationId": "v1-list-available-restore-versions", + "summary": "Lists available restore versions for the given project", + "parameters": [ + { + "name": "ref", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "available_versions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"] + }, + "postgres_engine": { + "type": "string", + "enum": ["13", "14", "15", "17-oriole"] + } + }, + "required": ["version", "release_channel", "postgres_engine"] + } + } + }, + "required": ["available_versions"] + } + } + } + }, + "403": { + "description": "" + } + }, + "tags": ["Projects"], + "security": [ + { + "bearer": [] + } + ] + }, + "post": { + "operationId": "v1-restore-a-project", + "summary": "Restores the given project", + "parameters": [ + { + "name": "ref", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"], + "description": "Release channel version. If not provided, GeneralAvailability will be used." + }, + "postgres_engine": { + "type": "string", + "enum": ["15", "17-oriole"], + "description": "Postgres engine version. If not provided, the latest version from the given release channel will be used." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "" + }, + "403": { + "description": "" + } + }, + "tags": ["Projects"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/v1/projects/{ref}/restore/cancel": { + "post": { + "operationId": "v1-cancel-a-project-restoration", + "summary": "Cancels the given project restoration", + "parameters": [ + { + "name": "ref", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + }, + "403": { + "description": "" + } + }, + "tags": ["Projects"], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/v1/projects/{ref}/analytics/endpoints/logs.all": { + "get": { + "operationId": "getLogs", + "summary": "Gets project's logs", + "parameters": [ + { + "name": "iso_timestamp_end", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "iso_timestamp_start", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "sql", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "ref", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "properties": { + "code": { + "type": "number" + }, + "errors": { + "type": "array", + "items": { + "properties": { + "domain": { + "type": "string" + }, + "location": { + "type": "string" + }, + "locationType": { + "type": "string" + }, + "message": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + } + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + { + "type": "string" + } + ] + }, + "result": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "403": { + "description": "" + } + }, + "tags": ["Analytics"], + "security": [ + { + "bearer": [] + } + ] + } + }, "/v1/projects/{ref}/database/query": { "post": { "operationId": "v1-run-a-query", @@ -10166,6 +10439,26 @@ "BranchDetailResponse": { "type": "object", "properties": { + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "db_port": { "type": "integer" }, @@ -10181,26 +10474,6 @@ "release_channel": { "type": "string" }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" - }, "db_host": { "type": "string" }, @@ -10215,12 +10488,12 @@ } }, "required": [ + "status", "db_port", "ref", "postgres_version", "postgres_engine", "release_channel", - "status", "db_host" ] }, @@ -10242,6 +10515,7 @@ "type": "boolean" }, "status": { + "type": "string", "enum": [ "CREATING_PROJECT", "RUNNING_MIGRATIONS", @@ -10249,8 +10523,7 @@ "MIGRATIONS_FAILED", "FUNCTIONS_DEPLOYED", "FUNCTIONS_FAILED" - ], - "type": "string" + ] } } }, @@ -10266,6 +10539,17 @@ "deprecated": true, "description": "This field is deprecated and will not be populated." }, + "status": { + "type": "string", + "enum": [ + "CREATING_PROJECT", + "RUNNING_MIGRATIONS", + "MIGRATIONS_PASSED", + "MIGRATIONS_FAILED", + "FUNCTIONS_DEPLOYED", + "FUNCTIONS_FAILED" + ] + }, "id": { "type": "string" }, @@ -10287,17 +10571,6 @@ "persistent": { "type": "boolean" }, - "status": { - "enum": [ - "CREATING_PROJECT", - "RUNNING_MIGRATIONS", - "MIGRATIONS_PASSED", - "MIGRATIONS_FAILED", - "FUNCTIONS_DEPLOYED", - "FUNCTIONS_FAILED" - ], - "type": "string" - }, "created_at": { "type": "string" }, @@ -10306,13 +10579,13 @@ } }, "required": [ + "status", "id", "name", "project_ref", "parent_project_ref", "is_default", "persistent", - "status", "created_at", "updated_at" ] @@ -10385,6 +10658,26 @@ "description": "Creation timestamp", "example": "2023-03-29T16:32:59Z" }, + "status": { + "type": "string", + "enum": [ + "INACTIVE", + "ACTIVE_HEALTHY", + "ACTIVE_UNHEALTHY", + "COMING_UP", + "UNKNOWN", + "GOING_DOWN", + "INIT_FAILED", + "REMOVED", + "RESTORING", + "UPGRADING", + "PAUSING", + "RESTORE_FAILED", + "RESTARTING", + "PAUSE_FAILED", + "RESIZING" + ] + }, "database": { "type": "object", "properties": { @@ -10406,29 +10699,9 @@ } }, "required": ["host", "version", "postgres_engine", "release_channel"] - }, - "status": { - "enum": [ - "ACTIVE_HEALTHY", - "ACTIVE_UNHEALTHY", - "COMING_UP", - "GOING_DOWN", - "INACTIVE", - "INIT_FAILED", - "REMOVED", - "RESTARTING", - "UNKNOWN", - "UPGRADING", - "PAUSING", - "RESTORING", - "RESTORE_FAILED", - "PAUSE_FAILED", - "RESIZING" - ], - "type": "string" } }, - "required": ["id", "organization_id", "name", "region", "created_at", "database", "status"] + "required": ["id", "organization_id", "name", "region", "created_at", "status", "database"] }, "V1CreateProjectBodyDto": { "type": "object", @@ -10541,24 +10814,24 @@ "example": "2023-03-29T16:32:59Z" }, "status": { + "type": "string", "enum": [ + "INACTIVE", "ACTIVE_HEALTHY", "ACTIVE_UNHEALTHY", "COMING_UP", + "UNKNOWN", "GOING_DOWN", - "INACTIVE", "INIT_FAILED", "REMOVED", - "RESTARTING", - "UNKNOWN", + "RESTORING", "UPGRADING", "PAUSING", - "RESTORING", "RESTORE_FAILED", + "RESTARTING", "PAUSE_FAILED", "RESIZING" - ], - "type": "string" + ] } }, "required": ["id", "organization_id", "name", "region", "created_at", "status"] @@ -10700,7 +10973,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "nullable": true }, "project": { "type": "object", @@ -10749,6 +11023,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by" @@ -10783,7 +11058,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "nullable": true }, "project": { "type": "object", @@ -10832,6 +11108,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by" @@ -10883,7 +11160,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "nullable": true }, "project": { "type": "object", @@ -10947,6 +11225,7 @@ "type", "visibility", "name", + "description", "project", "owner", "updated_by", @@ -12175,6 +12454,15 @@ }, "required": ["enabled"] }, + "StorageFeatureS3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] + }, "StorageFeatures": { "type": "object", "properties": { @@ -12186,9 +12474,18 @@ } }, "required": ["enabled"] + }, + "s3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] }, "StorageConfigResponse": { "type": "object", @@ -12208,9 +12505,18 @@ } }, "required": ["enabled"] + }, + "s3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] } }, "required": ["fileSizeLimit", "features"] @@ -12235,9 +12541,18 @@ } }, "required": ["enabled"] + }, + "s3Protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": ["enabled"] } }, - "required": ["imageTransformation"] + "required": ["imageTransformation", "s3Protocol"] } } }, @@ -12436,6 +12751,10 @@ "SupavisorConfigResponse": { "type": "object", "properties": { + "database_type": { + "type": "string", + "enum": ["PRIMARY", "READ_REPLICA"] + }, "db_port": { "type": "integer" }, @@ -12450,10 +12769,6 @@ "identifier": { "type": "string" }, - "database_type": { - "enum": ["PRIMARY", "READ_REPLICA"], - "type": "string" - }, "is_using_scram_auth": { "type": "boolean" }, @@ -12475,11 +12790,11 @@ } }, "required": [ + "database_type", "db_port", "default_pool_size", "max_client_conn", "identifier", - "database_type", "is_using_scram_auth", "db_user", "db_host", @@ -13985,6 +14300,117 @@ }, "required": ["id", "type", "inserted_at", "updated_at"] }, + "ProjectAvailableRestoreVersion": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"] + }, + "postgres_engine": { + "type": "string", + "enum": ["13", "14", "15", "17-oriole"] + } + }, + "required": ["version", "release_channel", "postgres_engine"] + }, + "GetProjectAvailableRestoreVersionsResponse": { + "type": "object", + "properties": { + "available_versions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"] + }, + "postgres_engine": { + "type": "string", + "enum": ["13", "14", "15", "17-oriole"] + } + }, + "required": ["version", "release_channel", "postgres_engine"] + } + } + }, + "required": ["available_versions"] + }, + "RestoreProjectBodyDto": { + "type": "object", + "properties": { + "release_channel": { + "type": "string", + "enum": ["internal", "alpha", "beta", "ga", "withdrawn", "preview"], + "description": "Release channel version. If not provided, GeneralAvailability will be used." + }, + "postgres_engine": { + "type": "string", + "enum": ["15", "17-oriole"], + "description": "Postgres engine version. If not provided, the latest version from the given release channel will be used." + } + } + }, + "V1AnalyticsResponse": { + "type": "object", + "properties": { + "error": { + "oneOf": [ + { + "properties": { + "code": { + "type": "number" + }, + "errors": { + "type": "array", + "items": { + "properties": { + "domain": { + "type": "string" + }, + "location": { + "type": "string" + }, + "locationType": { + "type": "string" + }, + "message": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + } + }, + "message": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + { + "type": "string" + } + ] + }, + "result": { + "type": "array", + "items": { + "type": "object" + } + } + } + }, "V1RunQueryBody": { "type": "object", "properties": { From c388cbe9d21e20324d9441f08382a057337b8374 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 14 Jan 2025 10:32:22 +0800 Subject: [PATCH 035/265] Add disk autoscale configuration parameters in compute and disk settings (#32628) * Add disk autoscale configuration parameters in compute and disk settings * TS fix * Fix * Fixes based on comments * Address all feedbacks * Small tweak --------- Co-authored-by: Alaister Young --- .../DiskManagement/DiskManagement.schema.ts | 33 ++- .../DiskManagement/DiskManagementForm.tsx | 112 ++++++--- .../DiskManagementPanelForm.tsx | 1 + .../DiskManagementReviewAndSubmitDialog.tsx | 76 +++++- .../DiskManagement/fields/AutoScaleFields.tsx | 226 ++++++++++++++++++ .../ui/DiskManagement.constants.tsx | 7 + .../DiskManagement/ui/DiskSpaceBar.tsx | 16 +- .../DeployNewReplicaPanel.tsx | 11 +- .../config/disk-autoscale-config-query.ts | 42 ++++ .../disk-autoscale-config-update-mutation.ts | 69 ++++++ apps/studio/data/config/keys.ts | 2 + .../pages/api/projects/[ref]/content/count.ts | 2 +- .../pages/project/[ref]/reports/database.tsx | 8 +- .../pages/project/[ref]/settings/database.tsx | 5 +- 14 files changed, 536 insertions(+), 74 deletions(-) create mode 100644 apps/studio/components/interfaces/DiskManagement/fields/AutoScaleFields.tsx create mode 100644 apps/studio/data/config/disk-autoscale-config-query.ts create mode 100644 apps/studio/data/config/disk-autoscale-config-update-mutation.ts diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts index 217242a53ee..027d44fb0d7 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.schema.ts @@ -1,4 +1,5 @@ import { z } from 'zod' +import { ComputeInstanceAddonVariantId } from './DiskManagement.types' import { calculateDiskSizeRequiredForIopsWithGp3, calculateDiskSizeRequiredForIopsWithIo2, @@ -7,7 +8,6 @@ import { calculateMaxIopsAllowedForDiskSizeWithio2, formatNumber, } from './DiskManagement.utils' -import { ComputeInstanceAddonVariantId } from './DiskManagement.types' import { DISK_LIMITS, DiskType, IOPS_RANGE, THROUGHPUT_RANGE } from './ui/DiskManagement.constants' const baseSchema = z.object({ @@ -21,11 +21,31 @@ const baseSchema = z.object({ computeSize: z .custom((val): val is ComputeInstanceAddonVariantId => true) .describe('Compute size'), + growthPercent: z + .number() + .int('Value must be an integer') + .min(10, 'Growth percent must be at least 10%') + .max(100, 'Growth percent cannot exceed 100%') + .optional() + .nullable(), + minIncrementGb: z + .number() + .int('Value must be an integer') + .min(1, 'Minimum increment must be at least 1 GB') + .max(200, 'Minimum increment cannot exceed 200 GB') + .optional() + .nullable(), + maxSizeGb: z + .number() + .int('Value must be an integer') + .max(60000, 'Maximum size cannot exceed 60TB') + .optional() + .nullable(), }) export const CreateDiskStorageSchema = (defaultTotalSize: number) => { const schema = baseSchema.superRefine((data, ctx) => { - const { storageType, totalSize, provisionedIOPS, throughput, computeSize } = data + const { storageType, totalSize, provisionedIOPS, throughput, maxSizeGb } = data if (totalSize < defaultTotalSize) { ctx.addIssue({ @@ -35,6 +55,15 @@ export const CreateDiskStorageSchema = (defaultTotalSize: number) => { }) } + // Validate maxSizeGb cannot be lower than totalSize + if (!!maxSizeGb && maxSizeGb < totalSize) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Max disk size cannot be lower than the current disk size. Must be at least ${formatNumber(totalSize)} GB.`, + path: ['maxSizeGb'], + }) + } + if (storageType === 'io2') { // Validation rules for io2 diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 2080541b90b..84a224ed922 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -18,6 +18,8 @@ import { useRemainingDurationForDiskAttributeUpdate, } from 'data/config/disk-attributes-query' import { useUpdateDiskAttributesMutation } from 'data/config/disk-attributes-update-mutation' +import { useDiskAutoscaleCustomConfigQuery } from 'data/config/disk-autoscale-config-query' +import { useUpdateDiskAutoscaleConfigMutation } from 'data/config/disk-autoscale-config-update-mutation' import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query' import { setProjectStatus } from 'data/projects/projects-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' @@ -46,6 +48,7 @@ import { DiskManagementMessage } from './DiskManagement.types' import { mapComputeSizeNameToAddonVariantId } from './DiskManagement.utils' import { DiskMangementRestartRequiredSection } from './DiskManagementRestartRequiredSection' import { DiskManagementReviewAndSubmitDialog } from './DiskManagementReviewAndSubmitDialog' +import { AutoScaleFields } from './fields/AutoScaleFields' import { ComputeSizeField } from './fields/ComputeSizeField' import { DiskSizeField } from './fields/DiskSizeField' import { IOPSField } from './fields/IOPSField' @@ -61,11 +64,8 @@ import { NoticeBar } from './ui/NoticeBar' import { SpendCapDisabledSection } from './ui/SpendCapDisabledSection' export function DiskManagementForm() { - const { - project, - // isLoading is used to avoid a useCheckPermissions() race condition - isLoading: isProjectLoading, - } = useProjectContext() + // isLoading is used to avoid a useCheckPermissions() race condition + const { project, isLoading: isProjectLoading } = useProjectContext() const org = useSelectedOrganization() const { ref: projectRef } = useParams() const queryClient = useQueryClient() @@ -136,12 +136,15 @@ export function DiskManagementForm() { const { data: subscription, isSuccess: isSubscriptionSuccess } = useOrgSubscriptionQuery({ orgSlug: org?.slug, }) + const { data: diskAutoscaleConfig, isSuccess: isDiskAutoscaleConfigSuccess } = + useDiskAutoscaleCustomConfigQuery({ projectRef }) /** * Handle default values */ // @ts-ignore const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 } + const { growth_percent, max_size_gb, min_increment_gb } = diskAutoscaleConfig ?? {} const defaultValues = { storageType: type ?? DiskType.GP3, provisionedIOPS: iops, @@ -150,6 +153,9 @@ export function DiskManagementForm() { computeSize: project?.infra_compute_size ? mapComputeSizeNameToAddonVariantId(project?.infra_compute_size) : undefined, + growthPercent: growth_percent, + minIncrementGb: min_increment_gb, + maxSizeGb: max_size_gb, } const form = useForm({ @@ -169,6 +175,7 @@ export function DiskManagementForm() { isDiskUtilizationSuccess && isReadReplicasSuccess && isSubscriptionSuccess && + isDiskAutoscaleConfigSuccess && isCooldownSuccess const isRequestingChanges = data?.requested_modification !== undefined @@ -181,6 +188,9 @@ export function DiskManagementForm() { const usedPercentage = (usedSize / totalSize) * 100 const isFlyArchitecture = project?.cloud_provider === 'FLY' + const disableIopsThroughputConfig = + RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(form.watch('computeSize')) && + subscription?.plan.id !== 'free' const disableDiskInputs = isRequestingChanges || @@ -205,19 +215,23 @@ export function DiskManagementForm() { // this is to suppress to toast message onError: () => {}, onSuccess: () => { - /** - * Manually set project status to RESIZING - * Project status should be RESIZING on next project status request. - */ + //Manually set project status to RESIZING, Project status should be RESIZING on next project status request. setProjectStatus(queryClient, projectRef!, PROJECT_STATUS.RESIZING) }, }) + const { mutateAsync: updateDiskAutoscaleConfig, isLoading: isUpdatingDiskAutoscaleConfig } = + useUpdateDiskAutoscaleConfigMutation({ + // this is to suppress to toast message + onError: () => {}, + }) - const isUpdatingConfig = isUpdatingDisk || isUpdatingCompute + const isUpdatingConfig = isUpdatingDisk || isUpdatingCompute || isUpdatingDiskAutoscaleConfig const onSubmit = async (data: DiskStorageSchemaType) => { let payload = data + let willUpdateDiskConfiguration = false setMessageState(null) + try { if ( payload.storageType !== form.formState.defaultValues?.storageType || @@ -225,6 +239,8 @@ export function DiskManagementForm() { payload.throughput !== form.formState.defaultValues?.throughput || payload.totalSize !== form.formState.defaultValues?.totalSize ) { + willUpdateDiskConfiguration = true + if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(payload.computeSize)) { payload.provisionedIOPS = IOPS_RANGE[DiskType.GP3].min } @@ -238,6 +254,19 @@ export function DiskManagementForm() { }) } + if ( + payload.growthPercent !== form.formState.defaultValues?.growthPercent || + payload.minIncrementGb !== form.formState.defaultValues?.minIncrementGb || + payload.maxSizeGb !== form.formState.defaultValues?.maxSizeGb + ) { + await updateDiskAutoscaleConfig({ + projectRef, + growthPercent: payload.growthPercent, + minIncrementGb: payload.minIncrementGb, + maxSizeGb: payload.maxSizeGb, + }) + } + if (payload.computeSize !== form.formState.defaultValues?.computeSize) { await updateSubscriptionAddon({ projectRef: projectRef, @@ -250,6 +279,9 @@ export function DiskManagementForm() { setIsDialogOpen(false) form.reset(data as DiskStorageSchemaType) + toast.success( + `Successfully updated disk settings!${willUpdateDiskConfiguration ? ' The requested changes will be applied to your disk shortly.' : ''}` + ) } catch (error: unknown) { setMessageState({ message: error instanceof Error ? error.message : 'An unknown error occurred', @@ -365,9 +397,9 @@ export function DiskManagementForm() {
Advanced disk settings - - Specify additional settings for your disk, including IOPS, throughput, and - disk type. + + Specify additional settings for your disk, including autoscaling + configuration, IOPS, throughput, and disk type.
- - { - form.setValue('computeSize', 'ci_large') - }} - > - Change to LARGE Compute - - } - /> - - +
+ +
+ +
+ + { + form.setValue('computeSize', 'ci_large') + }} + > + Change to LARGE Compute + + } + /> + + +
diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementPanelForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementPanelForm.tsx index b898d8e009c..c8166b8933c 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementPanelForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementPanelForm.tsx @@ -74,6 +74,7 @@ import { import { NoticeBar } from './ui/NoticeBar' import { SpendCapDisabledSection } from './ui/SpendCapDisabledSection' +// [Joshen] Only used for non AWS projects export function DiskManagementPanelForm() { const { project } = useProjectContext() const org = useSelectedOrganization() diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx index 536403d8694..f909aba99b4 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx @@ -45,7 +45,7 @@ import { } from './DiskManagement.utils' import { DiskMangementRestartRequiredSection } from './DiskManagementRestartRequiredSection' import { BillingChangeBadge } from './ui/BillingChangeBadge' -import { DiskType } from './ui/DiskManagement.constants' +import { DISK_AUTOSCALE_CONFIG_DEFAULTS, DiskType } from './ui/DiskManagement.constants' import { DiskMangementCoolDownSection } from './ui/DiskManagementCoolDownSection' const TableHeaderRow = () => ( @@ -209,6 +209,9 @@ export const DiskManagementReviewAndSubmitDialog = ({ numReplicas, }) + const hasComputeChanges = + form.formState.defaultValues?.computeSize !== form.getValues('computeSize') + const hasDiskConfigChanges = form.formState.defaultValues?.provisionedIOPS !== form.getValues('provisionedIOPS') || (form.formState.defaultValues?.throughput !== form.getValues('throughput') && @@ -248,15 +251,19 @@ export const DiskManagementReviewAndSubmitDialog = ({ Changes will be applied shortly once confirmed. -
- - -
- + {(hasComputeChanges || hasDiskConfigChanges) && ( + <> +
+ + +
+ + + )} @@ -325,11 +332,56 @@ export const DiskManagementReviewAndSubmitDialog = ({ priceTooltip={numReplicas > 0 ? replicaTooltipText : undefined} /> )} + {form.formState.defaultValues?.growthPercent !== form.getValues('growthPercent') && ( + + )} + {form.formState.defaultValues?.minIncrementGb !== form.getValues('minIncrementGb') && ( + + )} + {form.formState.defaultValues?.maxSizeGb !== form.getValues('maxSizeGb') && ( + + )}
- {/* */} -
- {showNewDiskManagementUI ? ( + {project?.cloud_provider === 'AWS' ? (
- {job.jobname} + + {job.jobname ?? 'No name provided'} +
{isLoading ? ( diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx index 96952fd468e..89da2c34e28 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx @@ -60,7 +60,10 @@ export const CronjobsTab = () => {
) - const filteredCronJobs = (cronJobs ?? []).filter((cj) => cj?.jobname?.includes(searchQuery || '')) + const filteredCronJobs = + searchQuery.length > 0 + ? (cronJobs ?? []).filter((cj) => cj?.jobname?.includes(searchQuery || '')) + : cronJobs ?? [] const onOpenCreateJobSheet = () => { sendEvent({ action: TelemetryActions.CRON_JOB_CREATE_CLICKED }) From 692244cc102b269a02f780de3ea3384cbe846a6f Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:45:04 +0100 Subject: [PATCH 041/265] rm monitoring tunnel (#32763) * rm monitoring tunnel * undo formatting * format w prettier i guess * rm route * Add CSP rule for sentry events. --------- Co-authored-by: Ivan Vasilov --- apps/studio/next.config.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js index 604c300a633..62e55d13e9b 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.js @@ -49,6 +49,8 @@ const GITHUB_USER_CONTENT_URL = 'https://raw.githubusercontent.com' const GITHUB_USER_AVATAR_URL = 'https://avatars.githubusercontent.com' const GOOGLE_USER_AVATAR_URL = 'https://lh3.googleusercontent.com' const VERCEL_LIVE_URL = 'https://vercel.live' +const SENTRY_URL = 'https://*.ingest.sentry.io' + // used by vercel live preview const PUSHER_URL = 'https://*.pusher.com' const PUSHER_URL_WS = 'wss://*.pusher.com' @@ -65,7 +67,7 @@ const csp = [ process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' || process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' ? [ - `default-src 'self' ${DEFAULT_SRC_URLS} ${SUPABASE_STAGING_PROJECTS_URL} ${SUPABASE_STAGING_PROJECTS_URL_WS} ${VERCEL_LIVE_URL} ${PUSHER_URL_WS} ${SUPABASE_DOCS_PROJECT_URL};`, + `default-src 'self' ${DEFAULT_SRC_URLS} ${SUPABASE_STAGING_PROJECTS_URL} ${SUPABASE_STAGING_PROJECTS_URL_WS} ${VERCEL_LIVE_URL} ${PUSHER_URL_WS} ${SUPABASE_DOCS_PROJECT_URL} ${SENTRY_URL};`, `script-src 'self' 'unsafe-eval' 'unsafe-inline' ${SCRIPT_SRC_URLS} ${VERCEL_LIVE_URL} ${PUSHER_URL};`, `frame-src 'self' ${FRAME_SRC_URLS} ${VERCEL_LIVE_URL};`, `img-src 'self' blob: data: ${IMG_SRC_URLS} ${SUPABASE_STAGING_PROJECTS_URL} ${VERCEL_URL};`, @@ -74,7 +76,7 @@ const csp = [ `worker-src 'self' blob: data:;`, ] : [ - `default-src 'self' ${DEFAULT_SRC_URLS} ${PUSHER_URL_WS};`, + `default-src 'self' ${DEFAULT_SRC_URLS} ${PUSHER_URL_WS} ${SENTRY_URL};`, `script-src 'self' 'unsafe-eval' 'unsafe-inline' ${SCRIPT_SRC_URLS} ${VERCEL_LIVE_URL} ${PUSHER_URL};`, `frame-src 'self' ${FRAME_SRC_URLS} ${VERCEL_LIVE_URL};`, `img-src 'self' blob: data: ${IMG_SRC_URLS} ;`, @@ -585,12 +587,6 @@ module.exports = enabled: true, }, - // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. - // This can increase your server load as well as your hosting bill. - // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- - // side errors will fail. - tunnelRoute: '/monitoring', - // Hides source maps from generated client bundles hideSourceMaps: true, From 44d3204b2f4143271da67e83321b8e9ba177838f Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 14 Jan 2025 11:21:35 -0330 Subject: [PATCH 042/265] Chore/fix set to now (#32749) * Reuse set to now logic * Ensure set to now cta is visible if column is not nullable * Minor refactor --------- Co-authored-by: Joshen Lim --- .../grid/components/editor/DateTimeEditor.tsx | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx index 1676996991d..129cf6440be 100644 --- a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx +++ b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx @@ -50,6 +50,17 @@ function BaseEditor({ onRowChange({ ...row, [column.key]: value }, true) } + const setToNow = () => { + const formattedNow = dayjs().format( + type === 'date' + ? 'YYYY-MM-DD' + : type === 'datetimetz' + ? 'YYYY-MM-DDTHH:mm:ssZ' + : 'YYYY-MM-DDTHH:mm:ss' + ) + saveChanges(formattedNow) + } + useEffect(() => { try { ref.current?.focus({ preventScroll: true }) @@ -75,7 +86,7 @@ function BaseEditor({ setInputValue(e.target.value)} className="border-0 rounded-none bg-dash-sidebar outline-none !ring-0 !ring-offset-0" /> @@ -123,7 +134,7 @@ function BaseEditor({
- {isNullable && ( + {isNullable ? ( <> )}
From a143d8905088860a85be925900ffd95247497b70 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 14 Jan 2025 23:21:11 +0800 Subject: [PATCH 043/265] Add set null cta in ForeignRowSelector (#32762) * Add set null cta in ForeignRowSelector * Update UI for foreign key input field in RowEditor --- .../ForeignRowSelector/ForeignRowSelector.tsx | 42 +++++++++++++++---- .../SidePanelEditor/RowEditor/InputField.tsx | 24 ++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx index 1840497c8e4..51942156df5 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' +import { useParams } from 'common' import { formatFilterURLParams, formatSortURLParams, @@ -15,7 +16,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import { useTableEditorQuery } from 'data/table-editor/table-editor-query' import { useTableRowsQuery } from 'data/table-rows/table-rows-query' import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state' -import { SidePanel } from 'ui' +import { Button, SidePanel } from 'ui' import ActionBar from '../../ActionBar' import { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types' import Pagination from './Pagination' @@ -34,11 +35,22 @@ const ForeignRowSelector = ({ onSelect, closePanel, }: ForeignRowSelectorProps) => { + const { id } = useParams() const { project } = useProjectContext() + const { data: selectedTable } = useTableEditorQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + id: !!id ? Number(id) : undefined, + }) const { tableId: _tableId, schema: schemaName, table: tableName, columns } = foreignKey ?? {} const tableId = _tableId ? Number(_tableId) : undefined + // [Joshen] Only show Set NULL CTA if its a 1:1 foreign key, and source column is nullable + // As this wouldn't be straightforward for composite foreign keys + const sourceColumn = (selectedTable?.columns ?? []).find((c) => c.name === columns?.[0].source) + const isNullable = (columns ?? []).length === 1 && sourceColumn?.is_nullable + const { data: table } = useTableEditorQuery({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -135,13 +147,27 @@ const ForeignRowSelector = ({
- +
+ + {isNullable && ( +
+ +
+ )} +
{data.rows.length > 0 ? ( diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index 54024583553..71135739733 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -1,5 +1,5 @@ import { includes, noop } from 'lodash' -import { Edit, Edit2, Link } from 'lucide-react' +import { Edit, Edit2 } from 'lucide-react' import { Button, @@ -120,15 +120,19 @@ const InputField = ({ error={errors[field.name]} onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })} actions={ - + + + - -
- - ) : ( - <> - -

{title}

- - {!readOnly && ( -
- } - onClick={() => setShowCode(!showCode)} - tooltip={{ - content: { side: 'bottom', text: showCode ? 'Hide query' : 'Show query' }, - }} - /> - - {!isInSQLEditor || isInNewSnippet ? ( - } - onClick={() => { - handleEditInSQLEditor() - sendEvent({ - action: TelemetryActions.ASSISTANT_EDIT_IN_SQL_EDITOR_CLICKED, - properties: { - isInSQLEditor, - isInNewSnippet, - }, - }) - }} - tooltip={{ content: { side: 'bottom', text: 'Edit in SQL Editor' } }} - /> - ) : ( - - - } - tooltip={{ content: { side: 'bottom', text: 'Edit in SQL Editor' } }} - /> - - - snapV2.setDiffContent(sql, DiffType.Addition)} - > - Insert code - - snapV2.setDiffContent(sql, DiffType.Modification)} - > - Replace code - - snapV2.setDiffContent(sql, DiffType.NewSnippet)} - > - Create new snippet - - - - )} - - } - loading={isExecuting} - onClick={() => { - handleExecute() - if (isReadOnlySelect(sql)) { - sendEvent({ - action: TelemetryActions.ASSISTANT_SUGGESTION_RUN_QUERY_CLICKED, - properties: { queryType: 'select' }, - }) - } - }} - tooltip={{ - content: { - side: 'bottom', - className: 'max-w-56 text-center', - text: isExecuting ? ( - - ) : ( - 'Run query' - ), - }, - }} - /> -
- )} - - )} -
- - {showCode && ( - code]:m-0 [&>code>span]:text-foreground' - )} - /> - )} - - {/* Results Section */} - {results !== undefined && results.length > 0 && isChart && xAxis && yAxis ? ( -
- - - - - } /> - - - -
- ) : ( -
- {error !== undefined ? ( - -
- {errorContent.length > 0 ? ( -
- {errorContent.map((errorText: string, i: number) => ( -
-                            {errorText}
-                          
- ))} - -
- ) : ( - <> -

{error.error}

- - - )} -
- - } - /> - ) : results !== undefined && results.length > 0 ? ( - <> -
- -
-
-

- {results.length} rows - {results.length >= 100 && ` (Limited to only 100 rows)`} -

-
- - ) : ( - results !== undefined && - results.length === 0 && ( -
-

Success. No rows returned.

-
- ) - )} -
- )} -
- ) -} - -export { SqlSnippetWrapper as SqlSnippet } diff --git a/apps/studio/components/ui/QueryBlock/BlockViewConfiguration.tsx b/apps/studio/components/ui/QueryBlock/BlockViewConfiguration.tsx new file mode 100644 index 00000000000..ef3819933e8 --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/BlockViewConfiguration.tsx @@ -0,0 +1,124 @@ +import { BarChart2, Settings2, Table } from 'lucide-react' + +import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' +import { + Checkbox_Shadcn_, + Label_Shadcn_, + Popover_Shadcn_, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectGroup_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + ToggleGroup, + ToggleGroupItem, +} from 'ui' +import { ButtonTooltip } from '../ButtonTooltip' + +interface BlockViewConfigurationProps { + columns: string[] + view: 'chart' | 'table' + isChart: boolean + lockColumns?: boolean + chartConfig?: ChartConfig + changeView: (value: 'chart' | 'table') => void + updateChartConfig: (config: ChartConfig) => void +} + +export const BlockViewConfiguration = ({ + columns, + view, + isChart, + lockColumns = false, + chartConfig, + changeView, + updateChartConfig, +}: BlockViewConfigurationProps) => { + return ( + + + } + tooltip={{ content: { side: 'bottom', text: 'View data' } }} + /> + + +
+ + + +

As table

+ + + +

As chart

+
+ + + {isChart && chartConfig && ( + <> + updateChartConfig({ ...chartConfig, xKey: value })} + > + + X Axis {chartConfig?.xKey && `- ${chartConfig.xKey}`} + + + + {columns.map((key) => ( + + {key} + + ))} + + + + + updateChartConfig({ ...chartConfig, yKey: value })} + > + + Y Axis {chartConfig?.yKey && `- ${chartConfig.yKey}`} + + + + {columns.map((key) => ( + + {key} + + ))} + + + + +
+ + + updateChartConfig({ + ...chartConfig, + cumulative: !chartConfig?.cumulative, + }) + } + /> + Cumulative + +
+ + )} + + + + ) +} diff --git a/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx b/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx new file mode 100644 index 00000000000..590e430db25 --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx @@ -0,0 +1,77 @@ +import { Edit } from 'lucide-react' +import { useRouter } from 'next/router' + +import { DiffType } from 'components/interfaces/SQLEditor/SQLEditor.types' +import useNewQuery from 'components/interfaces/SQLEditor/hooks' +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { TelemetryActions } from 'lib/constants/telemetry' +import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui' +import { ButtonTooltip } from '../ButtonTooltip' + +interface EditQueryButtonProps { + title: string + sql: string +} + +export const EditQueryButton = ({ sql, title }: EditQueryButtonProps) => { + const router = useRouter() + const { newQuery } = useNewQuery() + const sqlEditorSnap = useSqlEditorV2StateSnapshot() + + const isInSQLEditor = router.pathname.includes('/sql') + const isInNewSnippet = router.pathname.endsWith('/sql') + + const { mutate: sendEvent } = useSendEventMutation() + + const handleEditInSQLEditor = () => { + if (isInSQLEditor) { + sqlEditorSnap.setDiffContent(sql, DiffType.Addition) + } else { + newQuery(sql, title) + } + } + + return !isInSQLEditor || isInNewSnippet ? ( + } + onClick={() => { + handleEditInSQLEditor() + sendEvent({ + action: TelemetryActions.ASSISTANT_EDIT_IN_SQL_EDITOR_CLICKED, + properties: { + isInSQLEditor, + isInNewSnippet, + }, + }) + }} + tooltip={{ content: { side: 'bottom', text: 'Edit in SQL Editor' } }} + /> + ) : ( + + + } + tooltip={{ content: { side: 'bottom', text: 'Edit in SQL Editor' } }} + /> + + + sqlEditorSnap.setDiffContent(sql, DiffType.Addition)}> + Insert code + + sqlEditorSnap.setDiffContent(sql, DiffType.Modification)}> + Replace code + + sqlEditorSnap.setDiffContent(sql, DiffType.NewSnippet)}> + Create new snippet + + + + ) +} diff --git a/apps/studio/components/ui/QueryBlock/ParametersPopover.tsx b/apps/studio/components/ui/QueryBlock/ParametersPopover.tsx new file mode 100644 index 00000000000..a314fead4e1 --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/ParametersPopover.tsx @@ -0,0 +1,164 @@ +import { format } from 'date-fns' +import { ArrowUpDown } from 'lucide-react' +import { useEffect, useState } from 'react' + +import { DatePicker } from 'components/ui/DatePicker' +import { Parameter } from 'lib/sql-parameters' +import { + Button, + Input_Shadcn_, + Label_Shadcn_, + Popover_Shadcn_, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, +} from 'ui' + +interface ParametersPopoverProps { + parameters: Parameter[] + parameterValues?: Record + onSubmit?: (parameters: Record) => void + onChange?: (parameters: Record) => void +} + +export const ParametersPopover = ({ + parameters, + parameterValues = {}, + onSubmit, + onChange, +}: ParametersPopoverProps) => { + const [tempParameters, setTempParameters] = useState>( + parameters.reduce( + (acc, param) => ({ + ...acc, + [param.name]: parameterValues[param.name] ?? param.defaultValue ?? '', + }), + {} + ) + ) + + // Update temp parameters when external parameters or values change + useEffect(() => { + setTempParameters( + parameters.reduce( + (acc, param) => ({ + ...acc, + [param.name]: parameterValues[param.name] ?? param.defaultValue ?? '', + }), + {} + ) + ) + }, [parameters, parameterValues]) + + const handleParameterChange = (name: string, value: string) => { + const newParameters = { + ...tempParameters, + [name]: value, + } + setTempParameters(newParameters) + onChange?.(newParameters) + } + + const renderInput = (param: Parameter) => { + const currentValue = tempParameters[param.name] ?? parameterValues[param.name] ?? '' + + if (param.type === 'date') { + // Check if the current value is a valid date + const isValidDate = currentValue && !isNaN(new Date(currentValue).getTime()) + + if (isValidDate) { + return ( + { + if (date && date.to) { + handleParameterChange(param.name, date.to) + } else { + handleParameterChange(param.name, '') + } + }} + > + {currentValue ? format(new Date(currentValue), 'dd MMM') : 'Pick a date'} + + ) + } + + // Fallback to regular input if date is invalid + return ( + handleParameterChange(param.name, e.target.value)} + /> + ) + } + + if (param.type === 'enum' && param.possibleValues) { + return ( + handleParameterChange(param.name, value)} + > + + + + + {param.possibleValues.map((value) => ( + + {value} + + ))} + + + ) + } + + return ( + handleParameterChange(param.name, e.target.value)} + /> + ) + } + + return ( + + + + + )} + + + + ) +} diff --git a/apps/studio/components/ui/QueryBlock/QueryBlock.tsx b/apps/studio/components/ui/QueryBlock/QueryBlock.tsx new file mode 100644 index 00000000000..40db5cf35a0 --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/QueryBlock.tsx @@ -0,0 +1,326 @@ +import { Code, Play } from 'lucide-react' +import { ReactNode, useEffect, useMemo, useState } from 'react' +import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts' +import { toast } from 'sonner' + +import { useParams } from 'common' +import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' +import Results from 'components/interfaces/SQLEditor/UtilityPanel/Results' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' +import { Parameter, parseParameters, processParameterizedSql } from 'lib/sql-parameters' +import { + Button, + ChartContainer, + ChartTooltip, + ChartTooltipContent, + cn, + CodeBlock, + SQL_ICON, +} from 'ui' +import { Admonition } from 'ui-patterns' +import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' +import { containsUnknownFunction, isReadOnlySelect } from '../AIAssistantPanel/AIAssistant.utils' +import { ButtonTooltip } from '../ButtonTooltip' +import { BlockViewConfiguration } from './BlockViewConfiguration' +import { EditQueryButton } from './EditQueryButton' +import { ParametersPopover } from './ParametersPopover' +import { getCumulativeResults } from './QueryBlock.utils' + +const DEFAULT_CHART_CONFIG: ChartConfig = { + type: 'bar', + cumulative: false, + xKey: '', + yKey: '', + showLabels: false, + showGrid: false, +} + +interface QueryBlockProps { + id?: string + label: string + sql: string + chartConfig?: ChartConfig + maxHeight?: number + parameterValues?: Record + actions?: ReactNode // Any other actions specific to the parent to be rendered in the header + isChart?: boolean + isLoading?: boolean + runQuery?: boolean + lockColumns?: boolean + onSetParameter?: (params: Parameter[]) => void + onUpdateChartConfig?: (config: ChartConfig) => void +} + +// [Joshen ReportsV2] JFYI we may adjust this in subsequent PRs when we implement this into Reports V2 +// First iteration here is just to make this work with the AI Assistant first +export const QueryBlock = ({ + id, + label, + sql, + chartConfig = DEFAULT_CHART_CONFIG, + maxHeight = 250, + parameterValues: extParameterValues, + actions, + isChart = false, + isLoading = false, + runQuery = false, + lockColumns = false, + onSetParameter, + onUpdateChartConfig, +}: QueryBlockProps) => { + const { ref } = useParams() + const { project } = useProjectContext() + + const [showSql, setShowSql] = useState(!isChart) + const [view, setView] = useState<'table' | 'chart'>(isChart ? 'chart' : 'table') + // [Joshen] Thinking cumulative could just be a UI state here to prevent unnecessary re-rendering + const [cumulative, setCumulative] = useState(false) + const [queryResult, setQueryResult] = useState() + const [parameterValues, setParameterValues] = useState>({}) + const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>() + + const { xKey, yKey } = chartConfig + const showChart = + isChart && (queryResult ?? []).length > 0 && !!xKey && !!yKey && view === 'chart' + + const parameters = useMemo(() => { + if (!sql) return [] + return parseParameters(sql) + }, [sql]) + const combinedParameterValues = { ...extParameterValues, ...parameterValues } + + const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({ + onSuccess: (data) => setQueryResult(data.result), + }) + + const handleExecute = () => { + if (!sql || isLoading) return + + if (!isReadOnlySelect(sql)) { + const hasUnknownFunctions = containsUnknownFunction(sql) + return setShowWarning(hasUnknownFunctions ? 'hasUnknownFunctions' : 'hasWriteOperation') + } + + try { + const processedSql = processParameterizedSql(sql, combinedParameterValues) + execute({ + projectRef: ref, + connectionString: project?.connectionString, + sql: processedSql, + }) + } catch (error: any) { + toast.error(`Failed to execute query: ${error.message}`) + } + } + + // Run once on mount to parse parameters and notify parent + useEffect(() => { + if (!!sql && onSetParameter) { + const params = parseParameters(sql) + onSetParameter(params) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sql]) + + useEffect(() => { + if (!!sql && !isLoading && runQuery && isReadOnlySelect(sql) && !!project) { + handleExecute() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sql, isLoading, runQuery, project]) + + return ( +
+
+ +

{label}

+ + {/* QueryBlock actions */} +
+ } + onClick={() => setShowSql(!showSql)} + tooltip={{ + content: { side: 'bottom', text: showSql ? 'Hide query' : 'Show query' }, + }} + /> + + {queryResult && ( + <> + {/* [Joshen ReportsV2] Won't see this just yet as this is intended for Reports V2 */} + {parameters.length > 0 && ( + + )} + {isChart && ( + { + if (onUpdateChartConfig) onUpdateChartConfig(config) + setCumulative(config.cumulative) + }} + /> + )} + + )} + + + + } + loading={isExecuting || isLoading} + disabled={isLoading} + onClick={handleExecute} + tooltip={{ + content: { + side: 'bottom', + className: 'max-w-56 text-center', + text: isExecuting ? ( +

{`Query is running. You may cancel ongoing queries via the [SQL Editor](/project/${ref}/sql?viewOngoingQueries=true).`}

+ ) : ( + 'Run query' + ), + }, + }} + /> + + {actions} +
+
+ + {showWarning && ( + +

+ {showWarning === 'hasWriteOperation' + ? 'This query contains write operations.' + : 'This query involves running a function.'}{' '} + Are you sure you want to execute it? +

+

+ Make sure you are not accidentally removing something important. +

+
+ + +
+
+ )} + + {/* QueryBlock output */} + {showSql && ( +
+ code]:m-0 [&>code>span]:text-foreground' + )} + /> +
+ )} + + {isExecuting && queryResult === undefined && ( +
+ +
+ )} + + {showChart ? ( +
+ + + + + } /> + + + +
+ ) : ( + <> + {queryResult && ( +
+ +
+ )} + + )} +
+ ) +} diff --git a/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts b/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts new file mode 100644 index 00000000000..f3310842cc0 --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts @@ -0,0 +1,19 @@ +import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' + +// Add helper function for cumulative results +export const getCumulativeResults = (results: { rows: any[] }, config: ChartConfig) => { + if (!results?.rows?.length) { + return [] + } + + const cumulativeResults = results.rows.reduce((acc, row) => { + const prev = acc[acc.length - 1] || {} + const next = { + ...row, + [config.yKey]: (prev[config.yKey] || 0) + row[config.yKey], + } + return [...acc, next] + }, []) + + return cumulativeResults +} diff --git a/apps/studio/components/ui/QueryBlock/QueryBlockWrapper.tsx b/apps/studio/components/ui/QueryBlock/QueryBlockWrapper.tsx new file mode 100644 index 00000000000..79bac51df5d --- /dev/null +++ b/apps/studio/components/ui/QueryBlock/QueryBlockWrapper.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react' + +import { useParams } from 'common' +import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' +import { useContentIdQuery } from 'data/content/content-id-query' +import { Parameter } from 'lib/sql-parameters' +import { SqlSnippets } from 'types' +import { QueryBlock } from './QueryBlock' + +interface QueryBlockWrapperProps { + item: { + label: string + id?: string + sql?: string + isSnippet?: boolean + isChart?: boolean + chartConfig?: ChartConfig + results?: any + } + maxHeight?: number + lockColumns?: boolean + runQuery?: boolean + isLoading?: boolean + parameterValues?: Record + onUpdateChartConfig: (config: ChartConfig) => void + onSetParameter?: (params: Parameter[]) => void +} + +// [Joshen ReportsV2] I'm actually not sure if this wrapper but keeping for now till it becomes clearer +// when we progress on with the work for ReportsV2. Currently the only logic here is just fetching data for QueryBlock +export const QueryBlockWrapper = ({ + item, + maxHeight, + lockColumns = false, + runQuery = false, + isLoading = false, + parameterValues, + onUpdateChartConfig, + onSetParameter, +}: QueryBlockWrapperProps) => { + const { ref: projectRef } = useParams() + const [sql, setSql] = useState(item.sql || '') + + useContentIdQuery( + { projectRef, id: item.id }, + { + enabled: item.isSnippet && !!item.id, + onSuccess: (data) => setSql((data.content as SqlSnippets.Content).sql), + } + ) + + return ( + + ) +} diff --git a/apps/studio/lib/sql-parameters.ts b/apps/studio/lib/sql-parameters.ts new file mode 100644 index 00000000000..2311e0f5dda --- /dev/null +++ b/apps/studio/lib/sql-parameters.ts @@ -0,0 +1,114 @@ +export interface Parameter { + name: string + value: string + defaultValue?: string + type?: string + possibleValues?: string[] + occurrences: number +} + +// [Joshen TODO] We'll want tests for this to ensure that this runs properly + +export const parseParameters = (sql: string | undefined) => { + if (!sql) return [] + + // Parse @set parameter defaults with optional type information + const setParamRegex = /@set\s+(\w+)(?::([^=]+))?\s*=\s*([^;\n]+)/g + const paramDefaults: Record = + {} + let match + + while ((match = setParamRegex.exec(sql)) !== null) { + const [_, paramName, paramType, paramValue] = match + if (!paramName || !paramValue?.trim()) continue + + const typeInfo = paramType?.trim() + + let type: string | undefined + let possibleValues: string[] | undefined + + if (typeInfo) { + // Handle union types (value1 | value2 | value3) + if (typeInfo.includes('|')) { + possibleValues = typeInfo.split('|').map((v) => v.trim()) + type = 'enum' + } else { + type = typeInfo.trim() + } + } + + paramDefaults[paramName] = { + value: paramValue.trim(), + type, + possibleValues, + } + } + + // Find all :parameter occurrences and count them + const paramRegex = /:(\w+)/g + const paramOccurrences: Record = {} + const uniqueParams = new Set() + + while ((match = paramRegex.exec(sql)) !== null) { + const [_, paramName] = match + paramOccurrences[paramName] = (paramOccurrences[paramName] || 0) + 1 + uniqueParams.add(paramName) + } + + // Create parameter objects for unique parameters + return Array.from(uniqueParams).map((paramName) => ({ + name: paramName, + value: paramDefaults[paramName]?.value || '', + defaultValue: paramDefaults[paramName]?.value, + type: paramDefaults[paramName]?.type, + possibleValues: paramDefaults[paramName]?.possibleValues, + occurrences: paramOccurrences[paramName], + })) +} + +export const processParameterizedSql = (sql: string, parameters: Record) => { + // Parse @set parameter defaults with type information from SQL + const setParamRegex = /@set\s+(\w+)(?::([^=]+))?\s*=\s*([^;\n]+)/g + const paramDefaults: Record = + {} + let match + + while ((match = setParamRegex.exec(sql)) !== null) { + const [_, paramName, paramType, paramValue] = match + if (!paramName || !paramValue?.trim()) continue + + const typeInfo = paramType?.trim() + let type: string | undefined + let possibleValues: string[] | undefined + + if (typeInfo) { + if (typeInfo.includes('|')) { + possibleValues = typeInfo.split('|').map((v) => v.trim()) + type = 'enum' + } else { + type = typeInfo.trim() + } + } + + paramDefaults[paramName] = { + value: paramValue.trim(), + type, + possibleValues, + } + } + + // Remove @set lines from SQL + let processedSql = sql.replace(/@set\s+\w+(?:\s*:\s*[^=]+)?\s*=\s*[^;\n]+[\n;]*/g, '') + + // Replace :parameters with values + const paramRegex = /:(\w+)/g + processedSql = processedSql.replace(paramRegex, (match, paramName) => { + const value = parameters[paramName] ?? paramDefaults[paramName]?.value + if (value === undefined) { + throw new Error(`Missing value for parameter: ${paramName}`) + } + return value + }) + + return processedSql +} diff --git a/apps/studio/state/app-state.ts b/apps/studio/state/app-state.ts index 40acb74d84f..5b411b4e16f 100644 --- a/apps/studio/state/app-state.ts +++ b/apps/studio/state/app-state.ts @@ -210,6 +210,13 @@ export const appState = proxy({ ...value, } }, + + saveLatestMessage: (message: any) => { + appState.aiAssistantPanel = { + ...appState.aiAssistantPanel, + messages: [...appState.aiAssistantPanel.messages, message], + } + }, }) // Set up localStorage subscription diff --git a/apps/studio/styles/main.scss b/apps/studio/styles/main.scss index a4ed805f793..f71742f907d 100644 --- a/apps/studio/styles/main.scss +++ b/apps/studio/styles/main.scss @@ -8,7 +8,7 @@ @import '~ui/build/css/themes/classic-dark.css'; :root { - --chart-1: 12 76% 61%; + --chart-1: hsl(var(--brand-default)); --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; @@ -16,7 +16,7 @@ } .dark { - --chart-1: 220 70% 50%; + --chart-1: hsl(var(--brand-default)); --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; From c7ebd917403d58ba577beb3e349b746846747d1a Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 15 Jan 2025 16:42:50 +0800 Subject: [PATCH 057/265] Revert "Temp disable count query" (#32788) * Revert "Temp disable count query (#31256)" This reverts commit 05e06673b4d17e31166a5bdaea71081a0b1f73ca. * update to use new api * only include params if they're not undefined --- .../SQLEditorNavV2/SQLEditorNav.tsx | 34 +++++++-------- .../data/content/content-count-query.ts | 42 ++++++++----------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx index 31e1163ecb4..c830b2af4e7 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx @@ -166,13 +166,12 @@ export const SQLEditorNav = ({ ) const folders = useSnippetFolders(projectRef as string) - const { data: privateSnippetCountData } = useContentCountQuery({ + const { data: snippetCountData } = useContentCountQuery({ projectRef, type: 'sql', - visibility: 'user', name: debouncedSearchText, }) - const numPrivateSnippets = privateSnippets.length + const numPrivateSnippets = snippetCountData?.private ?? 0 const privateSnippetsTreeState = useMemo( () => @@ -237,13 +236,7 @@ export const SQLEditorNav = ({ ) }, [favoriteSqlSnippetsData?.pages, snippet, sort]) - const { data: favoritedSnippetCountData } = useContentCountQuery({ - projectRef, - type: 'sql', - favorite: true, - name: debouncedSearchText, - }) - const numFavoriteSnippets = favoriteSnippets.length + const numFavoriteSnippets = snippetCountData?.favorites ?? 0 const favoritesTreeState = useMemo( () => @@ -306,13 +299,7 @@ export const SQLEditorNav = ({ ) }, [sharedSqlSnippetsData?.pages, snippet, sort]) - const { data: sharedSnippetCountData } = useContentCountQuery({ - projectRef, - type: 'sql', - visibility: 'project', - name: debouncedSearchText, - }) - const numProjectSnippets = sharedSnippets.length + const numProjectSnippets = snippetCountData?.shared ?? 0 const projectSnippetsTreeState = useMemo( () => @@ -557,7 +544,9 @@ export const SQLEditorNav = ({ onOpenChange={setShowSharedSnippets} className="px-0" > - + 0 ? ` (${numProjectSnippets})` : ''}`} + /> {isLoadingSharedSqlSnippets ? ( @@ -613,7 +602,9 @@ export const SQLEditorNav = ({ open={showFavoriteSnippets} onOpenChange={setShowFavoriteSnippets} > - + 0 ? ` (${numFavoriteSnippets})` : ''}`} + /> {isLoadingFavoriteSqlSnippets ? ( @@ -674,7 +665,10 @@ export const SQLEditorNav = ({ onOpenChange={setShowPrivateSnippets} className="px-0" > - + 0 ? ` (${numPrivateSnippets})` : ''}`} + /> {isLoading ? ( diff --git a/apps/studio/data/content/content-count-query.ts b/apps/studio/data/content/content-count-query.ts index d7c61b9ad39..513e42b54f8 100644 --- a/apps/studio/data/content/content-count-query.ts +++ b/apps/studio/data/content/content-count-query.ts @@ -1,34 +1,30 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import { operations } from 'api-types' import { get, handleError } from 'data/fetchers' import { ResponseError } from 'types' import { contentKeys } from './keys' -import { SqlSnippet } from './sql-snippets-query' -type GetContentFilters = { - type: 'sql' | 'report' | 'log_sql' - visibility?: SqlSnippet['visibility'] - favorite?: boolean - name?: string -} - -interface getContentCountVariables extends GetContentFilters { - projectRef?: string -} +type GetContentCountVariables = + operations['ContentController_getContentCountV2']['parameters']['query'] & { + projectRef?: string + } export async function getContentCount( - { projectRef, type, visibility, favorite, name }: getContentCountVariables, + { projectRef, type, name }: GetContentCountVariables, signal?: AbortSignal ) { if (typeof projectRef === 'undefined') throw new Error('projectRef is required') - const query: GetContentFilters = { type } - if (visibility) query.visibility = visibility - if (favorite) query.favorite = favorite - if (name) query.name = name - const { data, error } = await get('/platform/projects/{ref}/content/count', { - params: { path: { ref: projectRef }, query }, + params: { + path: { ref: projectRef }, + query: { + ...(type && { type }), + ...(name && { name }), + }, + }, + headers: { Version: '2' }, signal, }) @@ -40,20 +36,16 @@ export type ContentIdData = Awaited> export type ContentIdError = ResponseError export const useContentCountQuery = ( - { projectRef, type, visibility, favorite, name }: getContentCountVariables, + { projectRef, type, name }: GetContentCountVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => useQuery( contentKeys.count(projectRef, type, { - visibility, - favorite, name, }), - ({ signal }) => getContentCount({ projectRef, type, visibility, favorite, name }, signal), + ({ signal }) => getContentCount({ projectRef, type, name }, signal), { - // count query is causing an api issue — disabling temporarily - //enabled: enabled && typeof projectRef !== 'undefined', - enabled: false, + enabled: enabled && typeof projectRef !== 'undefined', ...options, } ) From a2aa5bc83d69c6f56f085bf9b192bddf0494a9fa Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:22:13 +0100 Subject: [PATCH 058/265] feature: Adds logs chart with stacked charts with total errors (#32742) * add logschart with stacked bars * use ui charts pkg * fix contrast * move to ui-patterns * undo areachart * add recharts peerDep * update pckgjson and lock w recharts * feat: enable client-side rendering for LogsBarChart component * fix registry --- apps/design-system/__registry__/index.tsx | 11 ++ apps/design-system/config/docs.ts | 5 + .../content/docs/fragments/logs-bar-chart.mdx | 21 +++ .../default/example/logs-bar-chart.tsx | 20 +++ apps/design-system/registry/examples.ts | 5 + .../Settings/Logs/LogEventChart.tsx | 27 ---- .../Settings/Logs/Logs.constants.ts | 10 ++ .../interfaces/Settings/Logs/Logs.types.ts | 3 +- .../interfaces/Settings/Logs/Logs.utils.ts | 24 +++- .../Settings/Logs/LogsPreviewer.tsx | 37 ++--- .../components/ui/Charts/Charts.constants.ts | 2 + .../pages/projects/LogEventChart.test.tsx | 35 ----- packages/ui-patterns/LogsBarChart/index.tsx | 131 ++++++++++++++++++ packages/ui-patterns/index.tsx | 1 + packages/ui-patterns/package.json | 1 + .../ui/src/components/shadcn/ui/chart.tsx | 2 +- pnpm-lock.yaml | 68 +-------- 17 files changed, 252 insertions(+), 151 deletions(-) create mode 100644 apps/design-system/content/docs/fragments/logs-bar-chart.mdx create mode 100644 apps/design-system/registry/default/example/logs-bar-chart.tsx delete mode 100644 apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx delete mode 100644 apps/studio/tests/pages/projects/LogEventChart.test.tsx create mode 100644 packages/ui-patterns/LogsBarChart/index.tsx diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index ba7caca35fa..8d55cc7b07a 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2161,6 +2161,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "logs-bar-chart": { + name: "logs-bar-chart", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/logs-bar-chart")), + source: "", + files: ["registry/default/example/logs-bar-chart.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "chart-area-axes": { name: "chart-area-axes", type: "components:block", diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index fa2e91a1c20..471a9c89158 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -117,6 +117,11 @@ export const docsConfig: DocsConfig = { href: '/docs/fragments/multi-select', items: [], }, + { + title: 'Logs Bar Chart', + href: '/docs/fragments/logs-bar-chart', + items: [], + }, ], }, { diff --git a/apps/design-system/content/docs/fragments/logs-bar-chart.mdx b/apps/design-system/content/docs/fragments/logs-bar-chart.mdx new file mode 100644 index 00000000000..7127ecd2eac --- /dev/null +++ b/apps/design-system/content/docs/fragments/logs-bar-chart.mdx @@ -0,0 +1,21 @@ +--- +title: Logs Bar Chart +description: A stacked bar chart that displays logs errors and successes. +fragment: true +--- + + + +## Usage + +This component is used to display the bar chart in logs pages. + +It requires that the `data` prop is an array of objects with the following shape: + +```ts +type LogsBarChartDatum = { + timestamp: string + ok_count: number + error_count: number +} +``` diff --git a/apps/design-system/registry/default/example/logs-bar-chart.tsx b/apps/design-system/registry/default/example/logs-bar-chart.tsx new file mode 100644 index 00000000000..b477e491133 --- /dev/null +++ b/apps/design-system/registry/default/example/logs-bar-chart.tsx @@ -0,0 +1,20 @@ +import { LogsBarChart } from 'ui-patterns/LogsBarChart' + +export default function LogsBarChartDemo() { + const data = Array.from({ length: 100 }, (_, i) => { + const date = new Date() + date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart + + return { + timestamp: date.toISOString(), + ok_count: Math.floor(Math.random() * 100), // Random value 0-99 + error_count: Math.floor(Math.random() * 50), // Random value 0-50 + } + }).reverse() + + return ( +
+ +
+ ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index eab719afe0e..58d7c1dd508 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -1201,4 +1201,9 @@ export const examples: Registry = [ type: 'docs:example', files: ['example/expanding-textarea-demo.tsx'], }, + { + name: 'logs-bar-chart', + type: 'components:example', + files: ['example/logs-bar-chart.tsx'], + }, ] diff --git a/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx b/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx deleted file mode 100644 index a5f1b84782f..00000000000 --- a/apps/studio/components/interfaces/Settings/Logs/LogEventChart.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import BarChart from 'components/ui/Charts/BarChart' -import type { Datum } from 'components/ui/Charts/Charts.types' -import type { EventChartData } from './Logs.types' - -export interface LogEventChartProps { - data: EventChartData[] - onBarClick: (isoTimestamp: string) => void - className?: string -} - -const LogEventChart = ({ data, onBarClick, className }: LogEventChartProps) => ( - { - if (!datum?.timestamp) return - onBarClick(datum.timestamp as string) - }} - customDateFormat="MMM D, HH:mm:s" - /> -) -export default LogEventChart diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts index ee10cbcf03a..4a79934d899 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -673,6 +673,16 @@ export const LOGS_TAILWIND_CLASSES = { } export const PREVIEWER_DATEPICKER_HELPERS: DatetimeHelper[] = [ + { + text: 'Last 15 minutes', + calcFrom: () => dayjs().subtract(15, 'minute').startOf('minute').toISOString(), + calcTo: () => '', + }, + { + text: 'Last 30 minutes', + calcFrom: () => dayjs().subtract(30, 'minute').startOf('minute').toISOString(), + calcTo: () => '', + }, { text: 'Last hour', calcFrom: () => dayjs().subtract(1, 'hour').startOf('hour').toISOString(), diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts index 565c00e41b1..eda734318a1 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts @@ -51,7 +51,8 @@ export interface CountData { } export interface EventChartData extends Datum { - count: number + error_count: number + ok_count: number timestamp: string } diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts index 527194f63af..01e41eb9c35 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -321,14 +321,16 @@ export const genChartQuery = ( ) => { const [startOffset, trunc] = calcChartStart(params) const where = genWhereStatement(table, filters) + const errorCondition = getErrorCondition(table) let joins = genCrossJoinUnnests(table) - return ` + const q = ` SELECT -- log-event-chart timestamp_trunc(t.timestamp, ${trunc}) as timestamp, - count(t.timestamp) as count + count(CASE WHEN NOT (${errorCondition}) THEN 1 END) as ok_count, + count(CASE WHEN ${errorCondition} THEN 1 END) as error_count, FROM ${table} t ${joins} @@ -342,6 +344,7 @@ timestamp ORDER BY timestamp ASC ` + return q } type TsPair = [string | '', string | ''] @@ -579,3 +582,20 @@ export function checkForWildcard(query: string) { const wildcardRegex = /\*/ return wildcardRegex.test(queryWithoutCount) } + +function getErrorCondition(table: LogsTableName): string { + switch (table) { + case 'edge_logs': + return 'response.status_code >= 400' + case 'postgres_logs': + return "parsed.error_severity IN ('ERROR', 'FATAL', 'PANIC')" + case 'auth_logs': + return "metadata.level = 'error' OR metadata.status >= 400" + case 'function_edge_logs': + return 'response.status_code >= 400' + case 'function_logs': + return "metadata.level IN ('error', 'fatal')" + default: + return 'false' // Default to no errors if table type is unknown + } +} diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx index 8af8a9c74c3..12e6a7b7283 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx @@ -13,7 +13,6 @@ import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Button, cn } from 'ui' -import LogEventChart from './LogEventChart' import LogTable from './LogTable' import { LOGS_TABLES, LOG_ROUTES_WITH_REPLICA_SUPPORT, LogsTableName } from './Logs.constants' import type { Filters, LogSearchCallback, LogTemplate, QueryType } from './Logs.types' @@ -21,6 +20,8 @@ import { ensureNoTimestampConflict, maybeShowUpgradePrompt } from './Logs.utils' import UpgradePrompt from './UpgradePrompt' import { useSelectedLog } from 'hooks/analytics/useSelectedLog' import useSingleLog from 'hooks/analytics/useSingleLog' +import { LogsBarChart } from 'ui-patterns/LogsBarChart' +import NoDataPlaceholder from 'components/ui/Charts/NoDataPlaceholder' /** * Acts as a container component for the entire log display @@ -93,21 +94,6 @@ export const LogsPreviewer = ({ params.iso_timestamp_start as string ) - useEffect(() => { - setFilters((prev) => ({ - ...prev, - search_query: s, - database: db, - })) - if (ite || its) { - setParams((prev) => ({ - ...prev, - iso_timestamp_start: its || '', - iso_timestamp_end: ite || '', - })) - } - }, [db, s, ite, its]) - // Show the prompt on page load based on query params useEffect(() => { if (its) { @@ -239,23 +225,28 @@ export const LogsPreviewer = ({
0 ? 'mb-4 h-28 opacity-100' : 'h-0 opacity-0') + (showChart && logData.length > 0 ? 'mb-2 mt-1 opacity-100' : 'h-0 opacity-0') } >
{showChart && ( - { + onBarClick={(datum) => { + if (!datum?.timestamp) return handleSearch('event-chart-bar-click', { query: filters.search_query as string, - to: isoTimestamp as string, + to: datum.timestamp.toString(), from: null, }) }} + EmptyState={ + + } /> )}
diff --git a/apps/studio/components/ui/Charts/Charts.constants.ts b/apps/studio/components/ui/Charts/Charts.constants.ts index a8bb2e8aca5..a3caa2fd19f 100644 --- a/apps/studio/components/ui/Charts/Charts.constants.ts +++ b/apps/studio/components/ui/Charts/Charts.constants.ts @@ -7,6 +7,8 @@ export const CHART_COLORS = { AXIS: 'hsl(var(--background-overlay-hover))', GREEN_1: 'hsl(var(--brand-default))', // #3ECF8E GREEN_2: 'hsl(var(--brand-500))', + RED_1: 'hsl(var(--destructive-default))', + RED_2: 'hsl(var(--destructive-500))', } // refer to packages/ui/radix-colors.js for full list of colors diff --git a/apps/studio/tests/pages/projects/LogEventChart.test.tsx b/apps/studio/tests/pages/projects/LogEventChart.test.tsx deleted file mode 100644 index 32a81a4b82e..00000000000 --- a/apps/studio/tests/pages/projects/LogEventChart.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { afterEach, beforeEach, test, vi } from 'vitest' - -import { screen } from '@testing-library/react' -import LogEventChart from 'components/interfaces/Settings/Logs/LogEventChart' -import { render } from '../../helpers' - -const { ResizeObserver } = window - -beforeEach(() => { - delete (window as any).ResizeObserver - window.ResizeObserver = vi.fn().mockImplementation(() => ({ - observe: vi.fn(), - unobserve: vi.fn(), - disconnect: vi.fn(), - })) -}) - -afterEach(() => { - window.ResizeObserver = ResizeObserver -}) - -test('renders chart', async () => { - const mockFn = vi.fn() - const tsMicro = new Date().getTime() * 1000 - render( - - ) - await screen.findByText(/Logs \/ Time/) -}) diff --git a/packages/ui-patterns/LogsBarChart/index.tsx b/packages/ui-patterns/LogsBarChart/index.tsx new file mode 100644 index 00000000000..51cfe6e44d6 --- /dev/null +++ b/packages/ui-patterns/LogsBarChart/index.tsx @@ -0,0 +1,131 @@ +'use client' +import dayjs from 'dayjs' +import { ReactNode, useState } from 'react' +import { Bar, Cell, BarChart as RechartBarChart, XAxis, YAxis } from 'recharts' +import type { CategoricalChartState } from 'recharts/types/chart/types' +import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, cn } from 'ui' + +const CHART_COLORS = { + TICK: 'hsl(var(--background-overlay-hover))', + AXIS: 'hsl(var(--background-overlay-hover))', + GREEN_1: 'hsl(var(--brand-default))', + GREEN_2: 'hsl(var(--brand-500))', + RED_1: 'hsl(var(--destructive-default))', + RED_2: 'hsl(var(--destructive-500))', +} +type LogsBarChartDatum = { + timestamp: string + error_count: number + ok_count: number +} +export const LogsBarChart = ({ + data, + onBarClick, + EmptyState, + DateTimeFormat = 'MMM D, YYYY, hh:mma', +}: { + data: LogsBarChartDatum[] + onBarClick?: (datum: LogsBarChartDatum, tooltipData?: CategoricalChartState) => void + EmptyState?: ReactNode + DateTimeFormat?: string +}) => { + const [focusDataIndex, setFocusDataIndex] = useState(null) + + if (data.length === 0) { + if (EmptyState) return EmptyState + return null + } + + const startDate = dayjs(data[0]['timestamp']).format(DateTimeFormat) + const endDate = dayjs(data[data?.length - 1]?.['timestamp']).format(DateTimeFormat) + + return ( +
+ + { + if (e.activeTooltipIndex !== focusDataIndex) { + setFocusDataIndex(e.activeTooltipIndex) + } + }} + onMouseLeave={() => setFocusDataIndex(null)} + onClick={(tooltipData) => { + const datum = tooltipData?.activePayload?.[0]?.payload + if (onBarClick) onBarClick(datum, tooltipData) + }} + > + + + dayjs(v).format(DateTimeFormat)} + /> + } + /> + + {/* Error bars */} + + {data?.map((_entry: LogsBarChartDatum, index: number) => ( + + ))} + + + {/* Success bars */} + + {data?.map((_entry: LogsBarChartDatum, index: number) => ( + + ))} + + + + {data && ( +
+ {startDate} + {endDate} +
+ )} +
+ ) +} diff --git a/packages/ui-patterns/index.tsx b/packages/ui-patterns/index.tsx index 198641b0d1e..e244cb346f1 100644 --- a/packages/ui-patterns/index.tsx +++ b/packages/ui-patterns/index.tsx @@ -22,3 +22,4 @@ export * from './PromoToast' export * from './admonition' export * from './ComputeBadge' export * from './TimestampInfo' +export * from './LogsBarChart' diff --git a/packages/ui-patterns/package.json b/packages/ui-patterns/package.json index 2d8650f3d3c..b3d18fc06f3 100644 --- a/packages/ui-patterns/package.json +++ b/packages/ui-patterns/package.json @@ -34,6 +34,7 @@ "react-tooltip": "*", "react-use": "^17.5.0", "reactflow": "*", + "recharts": "^2.8.0", "remark-gfm": "^4.0.0", "sonner": "^1.5.0", "sql-formatter": "^15.3.1", diff --git a/packages/ui/src/components/shadcn/ui/chart.tsx b/packages/ui/src/components/shadcn/ui/chart.tsx index 3959978584e..60f896af271 100644 --- a/packages/ui/src/components/shadcn/ui/chart.tsx +++ b/packages/ui/src/components/shadcn/ui/chart.tsx @@ -216,7 +216,7 @@ const ChartTooltipContent = React.forwardRef< >
{nestLabel ? tooltipLabel : null} - + {itemConfig?.label || item.name}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd4ae6a9f58..ac06ba55d98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,7 +206,7 @@ importers: version: 14.2.3 '@next/mdx': specifier: ^14.2.3 - version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0)) + version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0)) '@octokit/auth-app': specifier: ^6.0.3 version: 6.0.3 @@ -988,7 +988,7 @@ importers: version: 14.2.3 '@next/mdx': specifier: ^14.2.3 - version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0)) + version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0)) '@octokit/auth-app': specifier: ^6.0.3 version: 6.0.3 @@ -1852,6 +1852,9 @@ importers: reactflow: specifier: '*' version: 11.10.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + recharts: + specifier: ^2.8.0 + version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0) remark-gfm: specifier: ^4.0.0 version: 4.0.0(supports-color@8.1.1) @@ -14005,15 +14008,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2))': - dependencies: - '@mdx-js/mdx': 2.3.0(supports-color@8.1.1) - source-map: 0.7.4 - webpack: 5.94.0(esbuild@0.20.2) - transitivePeerDependencies: - - supports-color - optional: true - '@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@mdx-js/mdx': 2.3.0(supports-color@8.1.1) @@ -14155,14 +14149,7 @@ snapshots: dependencies: glob: 10.3.10 - '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0))': - dependencies: - source-map: 0.7.4 - optionalDependencies: - '@mdx-js/loader': 2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)) - '@mdx-js/react': 2.3.0(react@18.2.0) - - '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0))': + '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0))': dependencies: source-map: 0.7.4 optionalDependencies: @@ -24787,18 +24774,6 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - terser-webpack-plugin@5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.32.0 - webpack: 5.94.0(esbuild@0.20.2) - optionalDependencies: - esbuild: 0.20.2 - optional: true - terser-webpack-plugin@5.3.10(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -25653,37 +25628,6 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(esbuild@0.20.2): - dependencies: - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.11.3 - acorn-import-attributes: 1.9.5(acorn@8.11.3) - browserslist: 4.23.3 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2)) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - optional: true - whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 From 265b4cddd747c6cb5569a4b211a34cec06c89462 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:22:57 +0100 Subject: [PATCH 059/265] SQL Chart fixes (#32769) * add labels * fix labels, hope to fix Y axis issue * Tiny fix * Make ChartConfig y scrollable * Only show flip CTA if values are all numerical * revert hiding flip button --------- Co-authored-by: Joshen Lim --- .../SQLEditor/UtilityPanel/ChartConfig.tsx | 256 +++++++++--------- apps/studio/components/ui/Charts/BarChart.tsx | 5 +- 2 files changed, 134 insertions(+), 127 deletions(-) diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx index e7bf396d3c0..bb21c0f4d6a 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx @@ -1,9 +1,9 @@ -import { Checkbox } from '@ui/components/shadcn/ui/checkbox' -import BarChart from 'components/ui/Charts/BarChart' -import NoDataPlaceholder from 'components/ui/Charts/NoDataPlaceholder' import dayjs from 'dayjs' import { ArrowUpDown } from 'lucide-react' import { useMemo } from 'react' + +import BarChart from 'components/ui/Charts/BarChart' +import NoDataPlaceholder from 'components/ui/Charts/NoDataPlaceholder' import { Button, Checkbox_Shadcn_, @@ -51,7 +51,12 @@ type ChartConfigProps = { config: ChartConfig onConfigChange: (config: ChartConfig) => void } -export function ChartConfig({ results = { rows: [] }, config, onConfigChange }: ChartConfigProps) { + +export const ChartConfig = ({ + results = { rows: [] }, + config, + onConfigChange, +}: ChartConfigProps) => { // If a result key is not valid, it will be filtered out const resultKeys = useMemo(() => { return Object.keys(results.rows[0] || {}).filter((key) => { @@ -87,131 +92,134 @@ export function ChartConfig({ results = { rows: [] }, config, onConfigChange }: return ( - <> - - {config.type === 'bar' && ( - { - const value = resultToRender[+idx][config.xKey] - if (xKeyDateFormat === 'date') { - return dayjs(value).format('MMM D YYYY') - } - return value - }, + + {config.type === 'bar' && ( + { + const value = resultToRender[+idx][config.xKey] + if (xKeyDateFormat === 'date') { + return dayjs(value).format('MMM D YYYY') + } + return value + }, + }} + YAxisProps={{ + tickFormatter: (value: number) => value.toLocaleString(), + hide: !config.showLabels, + domain: [0, 'dataMax'], + }} + /> + )} + + + +
+

Chart options

+ {config.xKey && config.yKey && ( + )} - - - -
-
-

Chart options

- {config.xKey && config.yKey && ( - - )} -
+
- { - onConfigChange({ ...config, xKey: value }) - }} - > - - X Axis {config.xKey && `- ${config.xKey}`} - - - - {resultKeys.map((key) => ( - - {key} - - ))} - - - - { - onConfigChange({ ...config, yKey: value }) - }} - > - - Y Axis {config.yKey && `- ${config.yKey}`} - - - - {resultKeys.map((key) => ( - - {key} - - ))} - - - -
- - onConfigChange({ ...config, cumulative: !config.cumulative })} - /> - Cumulative - +
+ X Axis + { + onConfigChange({ ...config, xKey: value }) + }} + > + {config.xKey || 'Select X Axis'} + + + {resultKeys.map((key) => ( + + {key} + + ))} + + + +
- - onConfigChange({ ...config, showLabels: !config.showLabels })} - /> - Show labels - +
+ Y Axis + { + onConfigChange({ ...config, yKey: value }) + }} + > + {config.yKey || 'Select Y Axis'} + + + {resultKeys.map((key) => ( + + {key} + + ))} + + + +
+
+ + onConfigChange({ ...config, cumulative: !config.cumulative })} + /> + Cumulative + - - onConfigChange({ ...config, showGrid: !config.showGrid })} - /> - Show grid - -
- - - + + onConfigChange({ ...config, showLabels: !config.showLabels })} + /> + Show labels + + + + onConfigChange({ ...config, showGrid: !config.showGrid })} + /> + Show grid + +
+
) } diff --git a/apps/studio/components/ui/Charts/BarChart.tsx b/apps/studio/components/ui/Charts/BarChart.tsx index aa2ec9766b6..e4d00370887 100644 --- a/apps/studio/components/ui/Charts/BarChart.tsx +++ b/apps/studio/components/ui/Charts/BarChart.tsx @@ -2,14 +2,13 @@ import dayjs from 'dayjs' import { ComponentProps, useState } from 'react' import { Bar, + CartesianGrid, Cell, Legend, BarChart as RechartBarChart, Tooltip, XAxis, - Label, YAxis, - CartesianGrid, } from 'recharts' import { CHART_COLORS, DateTimeFormats } from 'components/ui/Charts/Charts.constants' @@ -155,7 +154,7 @@ const BarChart = ({ dataKey={yAxisKey} fill={CHART_COLORS.GREEN_1} animationDuration={300} - // max bar size required to prevent bars from expanding to max width. + // Max bar size required to prevent bars from expanding to max width. maxBarSize={48} > {data?.map((_entry: Datum, index: any) => ( From 462c3c8cfcd0fda188526d7cc6e9849f77042439 Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:42:06 +0100 Subject: [PATCH 060/265] status check (#30204) --- .../interfaces/Home/ServiceStatus.tsx | 17 ++++++++++++++++- .../data/service-status/service-status-query.ts | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Home/ServiceStatus.tsx b/apps/studio/components/interfaces/Home/ServiceStatus.tsx index 146c58ef5a0..5eaac7e374c 100644 --- a/apps/studio/components/interfaces/Home/ServiceStatus.tsx +++ b/apps/studio/components/interfaces/Home/ServiceStatus.tsx @@ -6,7 +6,11 @@ import { useEffect, useState } from 'react' import { useParams } from 'common' import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query' import { usePostgresServiceStatusQuery } from 'data/service-status/postgres-service-status-query' -import { useProjectServiceStatusQuery } from 'data/service-status/service-status-query' +import { + ProjectServiceStatus, + ProjectServiceStatusData, + useProjectServiceStatusQuery, +} from 'data/service-status/service-status-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { @@ -21,6 +25,7 @@ import { PopoverSeparator } from '@ui/components/shadcn/ui/popover' const SERVICE_STATUS_THRESHOLD = 5 // minutes const StatusMessage = ({ + status, isLoading, isSuccess, isProjectNew, @@ -28,10 +33,14 @@ const StatusMessage = ({ isLoading: boolean isSuccess: boolean isProjectNew: boolean + status?: ProjectServiceStatus }) => { if (isLoading) return 'Checking status' if (isProjectNew) return 'Coming up...' if (isSuccess) return 'No issues' + if (status === 'UNHEALTHY') return 'Unhealthy' + if (status === 'COMING_UP') return 'Coming up...' + if (status === 'ACTIVE_HEALTHY') return 'Healthy' return 'Unable to connect' } @@ -98,6 +107,7 @@ const ServiceStatus = () => { isLoading: boolean isSuccess?: boolean logsUrl: string + status?: ProjectServiceStatus }[] = [ { name: 'Database', @@ -113,6 +123,7 @@ const ServiceStatus = () => { docsUrl: undefined, isLoading, isSuccess: restStatus?.healthy, + status: restStatus?.status, logsUrl: '/logs/postgrest-logs', }, ...(authEnabled @@ -123,6 +134,7 @@ const ServiceStatus = () => { docsUrl: undefined, isLoading, isSuccess: authStatus?.healthy, + status: authStatus?.status, logsUrl: '/logs/auth-logs', }, ] @@ -135,6 +147,7 @@ const ServiceStatus = () => { docsUrl: undefined, isLoading, isSuccess: realtimeStatus?.healthy, + status: realtimeStatus?.status, logsUrl: '/logs/realtime-logs', }, ] @@ -147,6 +160,7 @@ const ServiceStatus = () => { docsUrl: undefined, isLoading, isSuccess: storageStatus?.healthy, + status: storageStatus?.status, logsUrl: '/logs/storage-logs', }, ] @@ -235,6 +249,7 @@ const ServiceStatus = () => { isLoading={service.isLoading} isSuccess={!!service.isSuccess} isProjectNew={isProjectNew} + status={service.status} />

diff --git a/apps/studio/data/service-status/service-status-query.ts b/apps/studio/data/service-status/service-status-query.ts index 66064d92d2f..4b47577a932 100644 --- a/apps/studio/data/service-status/service-status-query.ts +++ b/apps/studio/data/service-status/service-status-query.ts @@ -29,6 +29,7 @@ export async function getProjectServiceStatus( } export type ProjectServiceStatusData = Awaited> +export type ProjectServiceStatus = ProjectServiceStatusData[0]['status'] export type ProjectServiceStatusError = ResponseError export const useProjectServiceStatusQuery = ( From 8e06301d363d46f55057733792d0027ea7414811 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Wed, 15 Jan 2025 14:43:18 +0100 Subject: [PATCH 061/265] docs: remove private alpha docs from firebase tpa (#32797) --- .../guides/auth/third-party/firebase-auth.mdx | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx index f3762f9476e..0bf453afd5d 100644 --- a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx +++ b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx @@ -4,19 +4,13 @@ title: 'Firebase Auth' subtitle: 'Use Firebase Auth with your Supabase project' --- - - -Creating a Firebase Auth third-party integration with your Supabase project is currently limited to a private alpha release. We are still trying to improve the developer experience around securing your project. [Register your interest in Third-Party Auth with Firebase](https://forms.supabase.com/third-party-auth-with-firebase) and the team will reach out to you. - - - Firebase Auth can be used as a third-party authentication provider alongside Supabase Auth, or standalone, with your Supabase project. ## Getting started 1. First you need to add an integration to connect your Supabase project with your Firebase project. You will need to get the Project ID in the [Firebase Console](https://console.firebase.google.com/u/0/project/_/settings/general). 2. Add a new Third-party Auth integration in your project's [Authentication settings](/dashboard/project/_/settings/auth). -3. If you are using Third Party Auth in local development, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to prevent unauthorized access from unrelated Firebase projects. +3. If you are using Third Party Auth when self hosting, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to **prevent unauthorized access from unrelated Firebase projects**. 4. Assign the `role: 'authenticated'` [custom user claim](https://firebase.google.com/docs/auth/admin/custom-claims) to all your users. 5. Finally set up the Supabase client in your application. @@ -33,7 +27,7 @@ import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { accessToken: async () => { - ;(await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null + return await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null }, }) ``` @@ -123,19 +117,21 @@ enabled = true project_id = "" ``` -## Adding an extra layer of security to your project's RLS policies +## Adding an extra layer of security to your project's RLS policies (self-hosting only) - + -While Third-Party Auth with Firebase is in a private alpha, it is strongly recommended to follow this guide closely. Once it becomes generally available this method would not be necessary to secure your project against unauthorized access from other Firebase projects. +**Please follow this section carefully to prevent unauthorized access to your project's data when self-hosting.** -You should also follow this best practice if self-hosting. +When using the Supabase hosted platform, following this step is optional. -Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project. To guard against this, creating and maintaining the following RLS policies for **all of your tables in the `public` schema** is very important. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels. +Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project. -To achieve this we recommend using a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html). +When using the Supabase hosted platform, JWTs coming from Firebase project IDs you have not registered will be rejected before they reach your database. When self-hosting implementing this mechanism is your responsibility. An easy way to guard against this is to create and maintain the following RLS policies for **all of your tables in the `public` schema**. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels. + +It's recommended you use a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html). Restrictive RLS policies differ from regular (or permissive) policies in that they use the `as restrictive` clause when being defined. They do not grant permissions, but rather restrict any existing or future permissions. They're great for cases like this where the technical limitations of Firebase Auth remain separate from your app's logic. From 8a648073de42dcdbd87e58f74d197bdb33dbb90d Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 15 Jan 2025 17:16:32 +0100 Subject: [PATCH 062/265] chore: Remove unused published_at field from older blog posts (#32802) Remove unused published_at field from older blog posts. --- apps/www/_blog/2023-04-14-dbdev.mdx | 1 - apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx | 1 - apps/www/_blog/2023-04-14-pg-tle.mdx | 1 - apps/www/_blog/2023-04-14-supabase-studio-2.0.mdx | 1 - apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx | 1 - apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx | 1 - apps/www/_blog/2023-08-10-supabase-integrations-marketplace.mdx | 1 - apps/www/_blog/2023-08-10-using-supabase-with-vercel.mdx | 1 - apps/www/_blog/2023-08-11-launch-week-8-community-highlights.mdx | 1 - apps/www/_blog/2023-08-11-supabase-soc2-hipaa.mdx | 1 - apps/www/_blog/2023-08-11-supavisor-1-million.mdx | 1 - .../www/_blog/2023-10-03-postgres-dynamic-table-partitioning.mdx | 1 - apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx | 1 - .../2023-12-08-postgres-language-server-implementing-parser.mdx | 1 - apps/www/_blog/2023-12-11-studio-introducing-assistant.mdx | 1 - apps/www/_blog/2023-12-12-edge-functions-node-npm.mdx | 1 - apps/www/_blog/2023-12-12-pg-graphql-postgres-functions.mdx | 1 - apps/www/_blog/2023-12-13-postgrest-12.mdx | 1 - apps/www/_blog/2023-12-13-supabase-branching.mdx | 1 - .../_blog/2023-12-13-supavisor-postgres-connection-pooler.mdx | 1 - .../_blog/2023-12-14-supabase-auth-identity-linking-hooks.mdx | 1 - apps/www/_blog/2023-12-14-supabase-wrappers-v02.mdx | 1 - apps/www/_blog/2023-12-15-client-libraries-v2.mdx | 1 - apps/www/_blog/2023-12-15-introducing-read-replicas.mdx | 1 - apps/www/_blog/2023-12-15-postgres-on-fly-by-supabase.mdx | 1 - apps/www/_blog/2023-12-19-launch-week-x-best-launches.mdx | 1 - apps/www/_blog/2024-01-04-launch-week-x-hackathon-winners.mdx | 1 - 27 files changed, 27 deletions(-) diff --git a/apps/www/_blog/2023-04-14-dbdev.mdx b/apps/www/_blog/2023-04-14-dbdev.mdx index 78070f16696..efd222e82a9 100644 --- a/apps/www/_blog/2023-04-14-dbdev.mdx +++ b/apps/www/_blog/2023-04-14-dbdev.mdx @@ -8,7 +8,6 @@ tags: - launch-week - postgres date: '2023-04-14' -published_at: '2023-04-14T08:00:00.000-07:00' toc_depth: 3 author: oli_rice,daltjoh-aws image: launch-week-7/day-5-one-more-thing/day-5-dbdev-og.jpg diff --git a/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx b/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx index 04a92d19f13..cb0fcbfb062 100644 --- a/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx +++ b/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx @@ -7,7 +7,6 @@ categories: tags: - launch-week date: '2023-04-14' -published_at: '2023-04-14T07:00:00.000-07:00' toc_depth: 3 author: paul_copplestone image: launch-week-7/day-5-community-highlights/day-5-community-highlights-og.jpg diff --git a/apps/www/_blog/2023-04-14-pg-tle.mdx b/apps/www/_blog/2023-04-14-pg-tle.mdx index 6e76a0ff4b8..d7ff966eb24 100644 --- a/apps/www/_blog/2023-04-14-pg-tle.mdx +++ b/apps/www/_blog/2023-04-14-pg-tle.mdx @@ -8,7 +8,6 @@ tags: - launch-week - postgres date: '2023-04-14' -published_at: '2023-04-14T07:00:00.000-07:00' toc_depth: 3 author: michel,daltjoh-aws image: launch-week-7/day-5-supabase-pg-tle/day-5-postgres-tle-thumb.jpg diff --git a/apps/www/_blog/2023-04-14-supabase-studio-2.0.mdx b/apps/www/_blog/2023-04-14-supabase-studio-2.0.mdx index fded26b63ba..bd2e544e8c5 100644 --- a/apps/www/_blog/2023-04-14-supabase-studio-2.0.mdx +++ b/apps/www/_blog/2023-04-14-supabase-studio-2.0.mdx @@ -8,7 +8,6 @@ tags: - launch-week - studio date: '2023-04-14' -published_at: '2023-04-14T09:00:00.000-07:00' toc_depth: 3 author: alaister,joshenlim,jonny,saltcod image: launch-week-7/day-5-supabase-studio-2.0/day-5-supabase-studio-2.0-og.jpg diff --git a/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx b/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx index 8783455ab4b..511ef63d659 100644 --- a/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx +++ b/apps/www/_blog/2023-04-21-whats-new-in-pg-graphql-v1-2.mdx @@ -9,7 +9,6 @@ tags: - graphql - planetpg date: '2023-04-21' -published_at: '2023-04-21:00:00.000-07:00' toc_depth: 3 author: oli_rice image: launch-week-6/pggraphql/og-pg-graphql.png diff --git a/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx b/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx index a4b5d9a4e44..4299c3340e6 100644 --- a/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx +++ b/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx @@ -9,7 +9,6 @@ tags: - studio - AI date: '2023-08-09' -published_at: '2023-08-09T09:00:00.000-07:00' toc_depth: 3 author: alaister,gregnr,joshenlim image: launch-week-8/day-3/og-day3.jpg diff --git a/apps/www/_blog/2023-08-10-supabase-integrations-marketplace.mdx b/apps/www/_blog/2023-08-10-supabase-integrations-marketplace.mdx index a897532afbf..1ca21d5be0e 100644 --- a/apps/www/_blog/2023-08-10-supabase-integrations-marketplace.mdx +++ b/apps/www/_blog/2023-08-10-supabase-integrations-marketplace.mdx @@ -8,7 +8,6 @@ tags: - launch-week - integrations date: '2023-08-10' -published_at: '2023-08-10T09:00:00.001-07:00' toc_depth: 3 author: thor_schaeff image: launch-week-8/day-4/integration-marketplace-og.jpg diff --git a/apps/www/_blog/2023-08-10-using-supabase-with-vercel.mdx b/apps/www/_blog/2023-08-10-using-supabase-with-vercel.mdx index 018594c8d0d..e9a09d93472 100644 --- a/apps/www/_blog/2023-08-10-using-supabase-with-vercel.mdx +++ b/apps/www/_blog/2023-08-10-using-supabase-with-vercel.mdx @@ -8,7 +8,6 @@ tags: - launch-week - integrations date: '2023-08-10' -published_at: '2023-08-10T09:00:00.000-07:00' toc_depth: 3 author: jonny,jonmeyers_io image: launch-week-8/day-4/vercel-and-supabase-og.jpg diff --git a/apps/www/_blog/2023-08-11-launch-week-8-community-highlights.mdx b/apps/www/_blog/2023-08-11-launch-week-8-community-highlights.mdx index e107a226ca2..3637cdf4da2 100644 --- a/apps/www/_blog/2023-08-11-launch-week-8-community-highlights.mdx +++ b/apps/www/_blog/2023-08-11-launch-week-8-community-highlights.mdx @@ -7,7 +7,6 @@ categories: tags: - launch-week date: '2023-08-11' -published_at: '2023-08-11T06:00:00.000-06:00' toc_depth: 3 author: paul_copplestone image: launch-week-8/day-5-community/community-highlights-OG.jpg diff --git a/apps/www/_blog/2023-08-11-supabase-soc2-hipaa.mdx b/apps/www/_blog/2023-08-11-supabase-soc2-hipaa.mdx index 6f7e57a3f2e..7246749156c 100644 --- a/apps/www/_blog/2023-08-11-supabase-soc2-hipaa.mdx +++ b/apps/www/_blog/2023-08-11-supabase-soc2-hipaa.mdx @@ -8,7 +8,6 @@ tags: - launch-week - security date: '2023-08-11' -published_at: '2023-08-11T09:00:00.000-07:00' toc_depth: 3 author: inian image: launch-week-8/day-5/OG-day5-compliance.jpg diff --git a/apps/www/_blog/2023-08-11-supavisor-1-million.mdx b/apps/www/_blog/2023-08-11-supavisor-1-million.mdx index b1e075234b4..58399333c6b 100644 --- a/apps/www/_blog/2023-08-11-supavisor-1-million.mdx +++ b/apps/www/_blog/2023-08-11-supavisor-1-million.mdx @@ -9,7 +9,6 @@ tags: - supavisor - postgres date: '2023-08-11' -published_at: '2023-08-11T09:00:00.000-07:00' toc_depth: 3 author: egor_romanov,chasers,stas image: launch-week-8/day-5/supavisor-og.jpg diff --git a/apps/www/_blog/2023-10-03-postgres-dynamic-table-partitioning.mdx b/apps/www/_blog/2023-10-03-postgres-dynamic-table-partitioning.mdx index 2fcfe631377..5cc4648b017 100644 --- a/apps/www/_blog/2023-10-03-postgres-dynamic-table-partitioning.mdx +++ b/apps/www/_blog/2023-10-03-postgres-dynamic-table-partitioning.mdx @@ -6,7 +6,6 @@ categories: tags: - postgres date: '2023-10-03' -published_at: '2023-10-03T07:00:00.000-07:00' toc_depth: 3 author: michel image: partitions/dynamic-table-partitioning-og.png diff --git a/apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx b/apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx index 63e112c7b9e..46e20fbbdc2 100644 --- a/apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx +++ b/apps/www/_blog/2023-12-08-how-design-works-at-supabase.mdx @@ -8,7 +8,6 @@ tags: - launch-week - design date: '2023-12-08' -published_at: '2023-12-08T08:59:59.000-07:00' toc_depth: 3 author: jonny,marijana,fsansalvadore image: lwx-how-design-works-at-supabase/og.png diff --git a/apps/www/_blog/2023-12-08-postgres-language-server-implementing-parser.mdx b/apps/www/_blog/2023-12-08-postgres-language-server-implementing-parser.mdx index 33c59e47e3c..dbd4c4a142f 100644 --- a/apps/www/_blog/2023-12-08-postgres-language-server-implementing-parser.mdx +++ b/apps/www/_blog/2023-12-08-postgres-language-server-implementing-parser.mdx @@ -9,7 +9,6 @@ tags: - postgres - planetpg date: '2023-12-08' -published_at: '2023-12-08T09:00:00.000-07:00' toc_depth: 3 author: philipp image: lwx-postgres-language-server/postgres-language-server-og-02.jpg diff --git a/apps/www/_blog/2023-12-11-studio-introducing-assistant.mdx b/apps/www/_blog/2023-12-11-studio-introducing-assistant.mdx index d8c7c843410..08c4d6fee22 100644 --- a/apps/www/_blog/2023-12-11-studio-introducing-assistant.mdx +++ b/apps/www/_blog/2023-12-11-studio-introducing-assistant.mdx @@ -8,7 +8,6 @@ tags: - launch-week - studio date: '2023-12-11' -published_at: '2023-12-11T09:00:00.000-07:00' toc_depth: 3 author: alaister,ivasilov,joshenlim image: launch-week-x/day-1/day-1-supabase-assistant-thumb.png diff --git a/apps/www/_blog/2023-12-12-edge-functions-node-npm.mdx b/apps/www/_blog/2023-12-12-edge-functions-node-npm.mdx index 380e30c8ef4..a8e3c21eb45 100644 --- a/apps/www/_blog/2023-12-12-edge-functions-node-npm.mdx +++ b/apps/www/_blog/2023-12-12-edge-functions-node-npm.mdx @@ -8,7 +8,6 @@ tags: - launch-week - edge-functions date: '2023-12-12' -published_at: '2023-12-12T09:05:00.000-07:00' toc_depth: 3 author: laktek,andreespirela image: launch-week-x/day-2/og-edge-runtime.png diff --git a/apps/www/_blog/2023-12-12-pg-graphql-postgres-functions.mdx b/apps/www/_blog/2023-12-12-pg-graphql-postgres-functions.mdx index a70247345cd..a21ab94c096 100644 --- a/apps/www/_blog/2023-12-12-pg-graphql-postgres-functions.mdx +++ b/apps/www/_blog/2023-12-12-pg-graphql-postgres-functions.mdx @@ -9,7 +9,6 @@ tags: - pg-graphql - planetpg date: '2023-12-12' -published_at: '2023-12-12:00:00.000-07:00' toc_depth: 3 author: oli_rice image: lwx-pg-graphql/pg-graphql-og.png diff --git a/apps/www/_blog/2023-12-13-postgrest-12.mdx b/apps/www/_blog/2023-12-13-postgrest-12.mdx index 5807b395bc9..65d088451a2 100644 --- a/apps/www/_blog/2023-12-13-postgrest-12.mdx +++ b/apps/www/_blog/2023-12-13-postgrest-12.mdx @@ -9,7 +9,6 @@ tags: - postgrest - planetpg date: '2023-12-13' -published_at: '2023-12-13:00:00.000-07:00' toc_depth: 3 author: oli_rice image: lwx-postgrest-12/postgrest12-OG.png diff --git a/apps/www/_blog/2023-12-13-supabase-branching.mdx b/apps/www/_blog/2023-12-13-supabase-branching.mdx index e0c3c96a514..efd7028ee95 100644 --- a/apps/www/_blog/2023-12-13-supabase-branching.mdx +++ b/apps/www/_blog/2023-12-13-supabase-branching.mdx @@ -9,7 +9,6 @@ tags: - supavisor - postgres date: '2023-12-13' -published_at: '2023-12-13:00:00.000-09:00' toc_depth: 3 author: qiao,jonny image: lwx-supabase-branching/branching-og.png diff --git a/apps/www/_blog/2023-12-13-supavisor-postgres-connection-pooler.mdx b/apps/www/_blog/2023-12-13-supavisor-postgres-connection-pooler.mdx index a3687d25628..0389b110508 100644 --- a/apps/www/_blog/2023-12-13-supavisor-postgres-connection-pooler.mdx +++ b/apps/www/_blog/2023-12-13-supavisor-postgres-connection-pooler.mdx @@ -10,7 +10,6 @@ tags: - postgres - planetpg date: '2023-12-13' -published_at: '2023-12-13:00:00.000-07:00' toc_depth: 3 author: stas image: lwx-supavisor/supavisor-og.png diff --git a/apps/www/_blog/2023-12-14-supabase-auth-identity-linking-hooks.mdx b/apps/www/_blog/2023-12-14-supabase-auth-identity-linking-hooks.mdx index cd16399c4a5..a3345b21d90 100644 --- a/apps/www/_blog/2023-12-14-supabase-auth-identity-linking-hooks.mdx +++ b/apps/www/_blog/2023-12-14-supabase-auth-identity-linking-hooks.mdx @@ -8,7 +8,6 @@ tags: - launch-week - auth date: '2023-12-14' -published_at: '2023-12-14:00:05.000-07:00' toc_depth: 3 author: stojan,joel,kangmingtay image: lwx-supabase-auth/auth-og.png diff --git a/apps/www/_blog/2023-12-14-supabase-wrappers-v02.mdx b/apps/www/_blog/2023-12-14-supabase-wrappers-v02.mdx index 3391e953b95..3f42afc202f 100644 --- a/apps/www/_blog/2023-12-14-supabase-wrappers-v02.mdx +++ b/apps/www/_blog/2023-12-14-supabase-wrappers-v02.mdx @@ -9,7 +9,6 @@ tags: - foreign-data-wrappers - planetpg date: '2023-12-14' -published_at: '2023-12-14:00:00.000-07:00' toc_depth: 3 author: bo_lu image: lwx-wrappers/wrappers-OG.png diff --git a/apps/www/_blog/2023-12-15-client-libraries-v2.mdx b/apps/www/_blog/2023-12-15-client-libraries-v2.mdx index 63ba4c7a993..c3da42d147f 100644 --- a/apps/www/_blog/2023-12-15-client-libraries-v2.mdx +++ b/apps/www/_blog/2023-12-15-client-libraries-v2.mdx @@ -7,7 +7,6 @@ categories: tags: - launch-week date: '2023-12-15' -published_at: '2023-12-15:00:05.000-07:00' toc_depth: 3 author: tyler_shukert,andrew_smith,thor_schaeff image: lwx-client-libs/client-libs-OG.png diff --git a/apps/www/_blog/2023-12-15-introducing-read-replicas.mdx b/apps/www/_blog/2023-12-15-introducing-read-replicas.mdx index 98bf47767dd..61d28325867 100644 --- a/apps/www/_blog/2023-12-15-introducing-read-replicas.mdx +++ b/apps/www/_blog/2023-12-15-introducing-read-replicas.mdx @@ -8,7 +8,6 @@ tags: - launch-week - postgres date: '2023-12-15' -published_at: '2023-12-15:01:00.000-07:00' toc_depth: 3 author: angelico_de_los_reyes image: lwx-read-replicas/introducing-read-replicas-og.png diff --git a/apps/www/_blog/2023-12-15-postgres-on-fly-by-supabase.mdx b/apps/www/_blog/2023-12-15-postgres-on-fly-by-supabase.mdx index 32d2d08319f..944f97c3ab0 100644 --- a/apps/www/_blog/2023-12-15-postgres-on-fly-by-supabase.mdx +++ b/apps/www/_blog/2023-12-15-postgres-on-fly-by-supabase.mdx @@ -8,7 +8,6 @@ tags: - launch-week - postgres date: '2023-12-15' -published_at: '2023-12-15:00:00.000-07:00' toc_depth: 3 author: inian,paul_copplestone image: lwx-supafly/fly-postgres-og.png diff --git a/apps/www/_blog/2023-12-19-launch-week-x-best-launches.mdx b/apps/www/_blog/2023-12-19-launch-week-x-best-launches.mdx index 52c50a774d1..ab4d82d7423 100644 --- a/apps/www/_blog/2023-12-19-launch-week-x-best-launches.mdx +++ b/apps/www/_blog/2023-12-19-launch-week-x-best-launches.mdx @@ -8,7 +8,6 @@ tags: - launch-week - postgres date: '2023-12-19' -published_at: '2023-12-19:00:00.000-07:00' toc_depth: 3 author: paul_copplestone image: lwx-best-launches/top-10-og.png diff --git a/apps/www/_blog/2024-01-04-launch-week-x-hackathon-winners.mdx b/apps/www/_blog/2024-01-04-launch-week-x-hackathon-winners.mdx index 1365992b90b..4b7ea9b1f6a 100644 --- a/apps/www/_blog/2024-01-04-launch-week-x-hackathon-winners.mdx +++ b/apps/www/_blog/2024-01-04-launch-week-x-hackathon-winners.mdx @@ -8,7 +8,6 @@ tags: - launch-week - hackathon date: '2024-01-04' -published_at: '2024-01-04:00:00.000-07:00' toc_depth: 3 author: andrew_smith,tyler_shukert image: lwx-hackathon-winners/lwx-hackathon-winners.png From 592af2ffa2edb86869c1ccd0433404dbbb5335bc Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Wed, 15 Jan 2025 18:03:22 +0100 Subject: [PATCH 063/265] feat: careers jobsCount in nav (#32771) * add jobsCount to careers nav * badge styling * mobile career counter * refactor static content generation * refactor * fix gh number formatting * remove comment * remove component * fix fresh local dev & update spacing --- .../www/components/Nav/DevelopersDropdown.tsx | 12 ++++- apps/www/components/Nav/GitHubButton.tsx | 40 ++++++--------- apps/www/components/Nav/MobileMenu.tsx | 2 + apps/www/package.json | 4 +- apps/www/public/rss.xml | 28 +++++----- ...logPosts.mjs => generateStaticContent.mjs} | 51 +++++++++++++++++-- .../ui/src/components/TextLink/TextLink.tsx | 9 +++- 7 files changed, 100 insertions(+), 46 deletions(-) rename apps/www/scripts/{generateLatestBlogPosts.mjs => generateStaticContent.mjs} (59%) diff --git a/apps/www/components/Nav/DevelopersDropdown.tsx b/apps/www/components/Nav/DevelopersDropdown.tsx index db69f35ebc5..022725b7522 100644 --- a/apps/www/components/Nav/DevelopersDropdown.tsx +++ b/apps/www/components/Nav/DevelopersDropdown.tsx @@ -2,7 +2,10 @@ import { ChevronRight } from 'lucide-react' import Link from 'next/link' import { data as DevelopersData } from 'data/Developers' -import blogPosts from '~/.contentlayer/generated/LatestBlogPost/_index.json' with { type: 'json' } +import { + jobsCount, + latestBlogPosts, +} from '~/.contentlayer/generated/staticContent/_index.json' with { type: 'json' } type LinkProps = { text: string @@ -29,6 +32,11 @@ const DevelopersDropdown = () => ( > {Icon && } {link.text} + {link.text === 'Careers' && jobsCount > 0 && ( + + ({jobsCount}) + + )} (
    - {blogPosts?.map((post) => ( + {latestBlogPosts?.map((post) => (
  • { - const [stars, setStars] = useState('63') - const kFormatter = (num: number) => { - const kFormat = num / 1000 - const decimals = kFormat.toFixed(2).split('.')[1] - const firstDecimal = decimals.split('')[0] - const showDecimals = firstDecimal !== '0' + const kFormat = Math.floor(num / 1000) + const lastTwoDigits = num % 1000 - return kFormat.toFixed(showDecimals ? 1 : 0) + const decimalPart = Math.floor((lastTwoDigits % 100) / 10) + const hundreds = Math.floor(lastTwoDigits / 100) + + const isAlmostNextThousand = decimalPart >= 8 && hundreds >= 9 + + const showDecimals = + (!isAlmostNextThousand && hundreds >= 1) || (hundreds === 0 && decimalPart >= 8) + + return showDecimals + ? `${kFormat}.${decimalPart >= 8 ? hundreds + 1 : hundreds}K` + : `${isAlmostNextThousand ? kFormat + 1 : kFormat}K` } - useEffect(() => { - async function fetchOctoData() { - const { Octokit } = await import('@octokit/core') - const octokit = new Octokit() - const res = await octokit.request('GET /repos/{org}/{repo}', { - org: 'supabase', - repo: 'supabase', - type: 'public', - }) - - setStars(kFormatter(res.data?.stargazers_count)) - } - fetchOctoData() - }, []) - return ( diff --git a/apps/www/components/Nav/MobileMenu.tsx b/apps/www/components/Nav/MobileMenu.tsx index ee7c686c4a5..bedfd7b9926 100644 --- a/apps/www/components/Nav/MobileMenu.tsx +++ b/apps/www/components/Nav/MobileMenu.tsx @@ -13,6 +13,7 @@ import { useKey } from 'react-use' import { useIsLoggedIn, useIsUserLoading } from 'common' import { ChevronRight } from 'lucide-react' import ProductModulesData from '~/data/ProductModules' +import { jobsCount } from '~/.contentlayer/generated/staticContent/_index.json' with { type: 'json' } interface Props { open: boolean @@ -105,6 +106,7 @@ const MobileMenu = ({ open, setOpen, menu }: Props) => { key={link.text} url={link.url} label={link.text} + counter={link.text === 'Careers' && jobsCount > 0 ? jobsCount : undefined} className="focus-visible:ring-offset-4 focus-visible:ring-offset-background-overlay !mt-0" /> ))} diff --git a/apps/www/package.json b/apps/www/package.json index c1ae0dd5a22..7d02406b3c2 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -5,14 +5,14 @@ "private": true, "scripts": { "preinstall": "npx only-allow pnpm", - "dev": "next --port 3000", + "dev": "pnpm run content:build && next --port 3000", "build": "pnpm run content:build && next build", "export": "next export", "start": "next start", "lint": "next lint", "clean": "rimraf node_modules", "typecheck": "pnpm run content:build && tsc --noEmit", - "content:build": "contentlayer2 build && node scripts/generateLatestBlogPosts.mjs", + "content:build": "contentlayer2 build && node scripts/generateStaticContent.mjs", "prettier": "prettier --write \"./{pages,components,lib,stores,styles,tests}/**/*.{ts,tsx,md,js,jsx,json}\"", "postbuild": "node ./internals/generate-sitemap.mjs" }, diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index 0ad02776e2c..3f239826b71 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -42,20 +42,6 @@ Technical deep dive into the new DBOS integration for Supabase Tue, 10 Dec 2024 00:00:00 -0700 - - https://supabase.com/blog/database-build-v2 - database.build v2: Bring-your-own-LLM - https://supabase.com/blog/database-build-v2 - Use any OpenAI API compatible LLMs in database.build - Fri, 06 Dec 2024 00:00:00 -0700 - - - https://supabase.com/blog/restore-to-a-new-project - Restore to a New Project - https://supabase.com/blog/restore-to-a-new-project - Effortlessly Clone Data into a New Supabase Project - Fri, 06 Dec 2024 00:00:00 -0700 - https://supabase.com/blog/hack-the-base Hack the Base! with Supabase @@ -70,6 +56,20 @@ Highlights from Launch Week 13 Fri, 06 Dec 2024 00:00:00 -0700 + + https://supabase.com/blog/database-build-v2 + database.build v2: Bring-your-own-LLM + https://supabase.com/blog/database-build-v2 + Use any OpenAI API compatible LLMs in database.build + Fri, 06 Dec 2024 00:00:00 -0700 + + + https://supabase.com/blog/restore-to-a-new-project + Restore to a New Project + https://supabase.com/blog/restore-to-a-new-project + Effortlessly Clone Data into a New Supabase Project + Fri, 06 Dec 2024 00:00:00 -0700 + https://supabase.com/blog/supabase-queues Supabase Queues diff --git a/apps/www/scripts/generateLatestBlogPosts.mjs b/apps/www/scripts/generateStaticContent.mjs similarity index 59% rename from apps/www/scripts/generateLatestBlogPosts.mjs rename to apps/www/scripts/generateStaticContent.mjs index 3a8a36f3cfe..0fbfdeaf9cc 100644 --- a/apps/www/scripts/generateLatestBlogPosts.mjs +++ b/apps/www/scripts/generateStaticContent.mjs @@ -1,6 +1,8 @@ import { promises as fs } from 'fs' import path from 'path' import { fileURLToPath } from 'url' + +// Latest Blog Posts import { allBlogPosts } from '../.contentlayer/generated/index.mjs' /** @@ -27,8 +29,46 @@ const latestBlogPosts = allBlogPosts .slice(0, 2) .map(({ title, url, description }) => ({ title, url, description })) +let stars = 0 + +// GitHub Stars +const fetchOctoData = async () => { + const { Octokit } = await import('@octokit/core') + const octokit = new Octokit() + const res = await octokit.request('GET /repos/{org}/{repo}', { + org: 'supabase', + repo: 'supabase', + type: 'public', + }) + + return res.data?.stargazers_count +} + +try { + stars = await fetchOctoData() +} catch (error) { + throw error +} + +// Careers Jobs count +const getCareerCount = async () => { + const job_res = await fetch('https://api.ashbyhq.com/posting-api/job-board/supabase') + const job_data = await job_res.json() + + return job_data.jobs.length +} + +let careersCount = 0 + +try { + careersCount = await getCareerCount() +} catch (error) { + throw error +} + +// Create folder for static content const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const folderPath = path.join(__dirname, '../.contentlayer/generated/LatestBlogPost') +const folderPath = path.join(__dirname, '../.contentlayer/generated/staticContent') try { await fs.mkdir(folderPath, { recursive: true }) } catch (error) { @@ -38,5 +78,10 @@ try { // Folder already exists, continue silently } -const filePath = path.join(__dirname, '../.contentlayer/generated/LatestBlogPost/_index.json') -await fs.writeFile(filePath, JSON.stringify(latestBlogPosts), 'utf8') +// Write static content to file +const filePath = path.join(__dirname, '../.contentlayer/generated/staticContent/_index.json') +await fs.writeFile( + filePath, + JSON.stringify({ latestBlogPosts: latestBlogPosts, jobsCount: careersCount, githubStars: stars }), + 'utf8' +) diff --git a/packages/ui/src/components/TextLink/TextLink.tsx b/packages/ui/src/components/TextLink/TextLink.tsx index 11a49169456..0df330769ba 100644 --- a/packages/ui/src/components/TextLink/TextLink.tsx +++ b/packages/ui/src/components/TextLink/TextLink.tsx @@ -8,6 +8,7 @@ interface Props { label: string url?: string className?: string + counter?: number hasChevron?: boolean chevronAnimation?: 'translate' | 'fadeIn' target?: '_blank' | '_self' @@ -17,6 +18,7 @@ function TextLink({ url = '', label, className, + counter, hasChevron = true, chevronAnimation = 'translate', target = '_self', @@ -26,7 +28,7 @@ function TextLink({ {`${label} about ${url}`} {label} + {counter && ( + + ({counter}) + + )} {hasChevron && (
    Date: Wed, 15 Jan 2025 15:20:43 -0300 Subject: [PATCH 064/265] Chore: update edge function example (#32808) Update auth.mdx --- apps/docs/content/guides/functions/auth.mdx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx index 639e667ff34..d940cf65a02 100644 --- a/apps/docs/content/guides/functions/auth.mdx +++ b/apps/docs/content/guides/functions/auth.mdx @@ -16,12 +16,15 @@ import { createClient } from 'jsr:@supabase/supabase-js@2' Deno.serve(async (req: Request) => { - const authHeader = req.headers.get('Authorization')! const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', - { global: { headers: { Authorization: authHeader } } } - ) + ); + + // Get the session or user object + const authHeader = req.headers.get('Authorization')!; + const token = authHeader.replace('Bearer ', ''); + const { data } = await supabaseClient.auth.getUser(token); }) ``` From f233c787941a07b06b28910b47214ddb3e3851cd Mon Sep 17 00:00:00 2001 From: Rodrigo Mansueli Date: Wed, 15 Jan 2025 16:28:41 -0300 Subject: [PATCH 065/265] Chore: update also last example on the page (#32809) --- apps/docs/content/guides/functions/auth.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx index d940cf65a02..7b97b497278 100644 --- a/apps/docs/content/guides/functions/auth.mdx +++ b/apps/docs/content/guides/functions/auth.mdx @@ -71,11 +71,13 @@ Deno.serve(async (req: Request) => { const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', - { global: { headers: { Authorization: req.headers.get('Authorization')! } } } - ) + ); - // Database queries will have RLS policies enforced - const { data, error } = await supabaseClient.from('profiles').select('*') + // Get the session or user object + const authHeader = req.headers.get('Authorization')!; + const token = authHeader.replace('Bearer ', ''); + const { data: userData } = await supabaseClient.auth.getUser(token); + const { data, error } = await supabaseClient.from('profiles').select('*'); return new Response(JSON.stringify({ data }), { headers: { 'Content-Type': 'application/json' }, From 9349d22360e26da53576ba8772d2d931218ead1b Mon Sep 17 00:00:00 2001 From: Lakshan Perera Date: Thu, 16 Jan 2025 06:30:32 +1100 Subject: [PATCH 066/265] chore(docs): Added static_files to CLI config (#32801) --- apps/docs/spec/cli_v1_config.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/docs/spec/cli_v1_config.yaml b/apps/docs/spec/cli_v1_config.yaml index fca79cb6203..da477bb28c1 100644 --- a/apps/docs/spec/cli_v1_config.yaml +++ b/apps/docs/spec/cli_v1_config.yaml @@ -1497,6 +1497,19 @@ parameters: [functions.my_function] entrypoint = "path/to/custom/function.ts" + - id: 'functions.function_name.static_files' + title: 'functions..static_files' + tags: ['edge-functions'] + required: false + description: | + Specify an array of static files to be bundled with the function. Supports glob patterns. + links: + - name: '`supabase functions` CLI subcommands' + link: 'https://supabase.com/docs/reference/cli/supabase-functions' + usage: | + [functions.my_function] + static_files = [ "./functions/MY_FUNCTION_NAME/*.html", "./functions/MY_FUNCTION_NAME/custom.wasm" ] + - id: 'analytics.enabled' title: 'analytics.enabled' tags: ['analytics'] From cdb95a60366d7ff3d90ef0bec3b3ce1b30e9dbe0 Mon Sep 17 00:00:00 2001 From: Lakshan Perera Date: Thu, 16 Jan 2025 06:37:21 +1100 Subject: [PATCH 067/265] chore: add an example for using wasm modules in Edge Functions (#32810) Co-authored-by: Rodrigo Mansueli --- examples/edge-functions/supabase/config.toml | 3 + .../wasm-modules/add-wasm/Cargo.lock | 133 ++++++++++++++++++ .../wasm-modules/add-wasm/Cargo.toml | 12 ++ .../wasm-modules/add-wasm/src/lib.rs | 6 + .../supabase/functions/wasm-modules/index.ts | 16 +++ 5 files changed, 170 insertions(+) create mode 100644 examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.lock create mode 100644 examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml create mode 100644 examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs create mode 100644 examples/edge-functions/supabase/functions/wasm-modules/index.ts diff --git a/examples/edge-functions/supabase/config.toml b/examples/edge-functions/supabase/config.toml index 99281efbabb..dbf1d6d2d1b 100644 --- a/examples/edge-functions/supabase/config.toml +++ b/examples/edge-functions/supabase/config.toml @@ -73,3 +73,6 @@ objects_path = "./buckets/images" [functions.kysely-postgres] import_map = "./functions/import_map.json" + +[functions.wasm-modules] +static_files = [ "./functions/wasm-modules/add-wasm/pkg/add_wasm_bg.wasm"] diff --git a/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.lock b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.lock new file mode 100644 index 00000000000..b624e908a6b --- /dev/null +++ b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.lock @@ -0,0 +1,133 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "add-wasm" +version = "0.1.0" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] diff --git a/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml new file mode 100644 index 00000000000..ab11b6eb564 --- /dev/null +++ b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "add-wasm" +version = "0.1.0" +description = "A simple wasm module that adds two numbers" +license = "MIT/Apache-2.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2" diff --git a/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs new file mode 100644 index 00000000000..57fd95b9f99 --- /dev/null +++ b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn add(a: u32, b: u32) -> u32 { + a + b + 100 +} diff --git a/examples/edge-functions/supabase/functions/wasm-modules/index.ts b/examples/edge-functions/supabase/functions/wasm-modules/index.ts new file mode 100644 index 00000000000..66d5085cf24 --- /dev/null +++ b/examples/edge-functions/supabase/functions/wasm-modules/index.ts @@ -0,0 +1,16 @@ +const wasmBuffer = await Deno.readFile( + new URL("./add-wasm/pkg/add_wasm_bg.wasm", import.meta.url), +); + +const { instance } = await WebAssembly.instantiate( + wasmBuffer, +); +const { add } = instance.exports; + +Deno.serve(async (req) => { + const { a, b } = await req.json(); + return new Response( + JSON.stringify({ result: add(a, b) }), + { headers: { "Content-Type": "application/json" } }, + ); +}); From e63a082ddad4d40dd0d0e4cd11b39cb147e608be Mon Sep 17 00:00:00 2001 From: Lakshan Perera Date: Thu, 16 Jan 2025 09:55:31 +1100 Subject: [PATCH 068/265] chore(docs): Added a guide on using wasm modules in Edge Functions (#32811) * chore(docs): Added a guide on using wasm modules in Edge Functions * update example * WASM -> Wasm * fix page title --- .../NavigationMenu.constants.ts | 4 + apps/docs/content/guides/functions/wasm.mdx | 87 +++++++++++++++++++ examples/edge-functions/supabase/config.toml | 2 +- .../wasm-modules/add-wasm/src/lib.rs | 2 +- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/guides/functions/wasm.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 7d1ca8e9e5d..b5be6e4812a 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1359,6 +1359,10 @@ export const functions: NavMenuConstant = { name: 'Running AI Models', url: '/guides/functions/ai-models', }, + { + name: 'Wasm modules', + url: '/guides/functions/wasm', + }, { name: 'Deploying with CI / CD pipelines', url: '/guides/functions/cicd-workflow', diff --git a/apps/docs/content/guides/functions/wasm.mdx b/apps/docs/content/guides/functions/wasm.mdx new file mode 100644 index 00000000000..af0bf32a0f5 --- /dev/null +++ b/apps/docs/content/guides/functions/wasm.mdx @@ -0,0 +1,87 @@ +--- +id: 'function-wasm' +title: 'Using Wasm modules' +description: 'How to use WebAssembly in Edge Functions.' +subtitle: 'How to use WebAssembly in Edge Functions.' +--- + +Edge Functions supports running [WebAssembly (Wasm)](https://developer.mozilla.org/en-US/docs/WebAssembly) modules. WebAssembly is useful if you want to optimize code that's slower to run in JavaScript or require low-level manipulation. + +It also gives you the option to port existing libraries written in other languages to be used with JavaScript. For example, [MagickWasm](https://supabase.com/docs/guides/functions/examples/image-manipulation), which does image manipulation and transforms, is a port of an existing C library to WebAssembly. + +### Writing a Wasm module + +You can use different languages and SDKs to write Wasm modules. For this tutorial, we will write a simple Wasm module in Rust that adds two numbers. + +Follow this [guide on writing Wasm modules in Rust](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm) to setup your dev environment. + +Create a new Edge Function called `wasm-add`. + +```bash +supabase functions new wasm-add +``` + +Create a new Cargo project for the Wasm module inside the function's directory: + +```bash +cd supabase/functions/wasm-add +cargo new --lib add-wasm +``` + +Add the following code to `add-wasm/src/lib.rs`. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs" +lines={[[1, -1]]} +meta="lib.rs" +/> + +Update the `add-wasm/Cargo.toml` to include the `wasm-bindgen` dependency. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/add-wasm/Cargo.toml" +lines={[[1, -1]]} +meta="Cargo.toml" +/> + +After that we can build the package, by running: + +```bash +wasm-pack build --target deno +``` + +This will produce a Wasm binary file inside `add-wasm/pkg` directory. + +### Calling the Wasm module from the Edge Function + +Now let's update the Edge Function to call `add` from the Wasm module. + +<$CodeSample +path="edge-functions/supabase/functions/wasm-modules/index.ts" +lines={[[1, -1]]} +meta="index.ts" +/> + + + Supabase Edge Functions currently use Deno 1.46. From [Deno 2.1, importing Wasm + modules](https://deno.com/blog/v2.1) will require even less boilerplate code. + + +### Bundle and deploy the Edge Function + +Before deploying the Edge Function, we need to ensure it bundles the Wasm module with it. We can do this by defining it in the `static_files` for the function in `superbase/config.toml`. + + + You will need update Supabase CLI to 2.7.0 or higher for the `static_files` support. + + +```toml +[functions.wasm-add] +static_files = [ "./functions/wasm-add/add-wasm/pkg/*.wasm"] +``` + +Deploy the function by running: + +```bash +supabase functions deploy wasm-add +``` diff --git a/examples/edge-functions/supabase/config.toml b/examples/edge-functions/supabase/config.toml index dbf1d6d2d1b..1a5297f00e3 100644 --- a/examples/edge-functions/supabase/config.toml +++ b/examples/edge-functions/supabase/config.toml @@ -75,4 +75,4 @@ objects_path = "./buckets/images" import_map = "./functions/import_map.json" [functions.wasm-modules] -static_files = [ "./functions/wasm-modules/add-wasm/pkg/add_wasm_bg.wasm"] +static_files = [ "./functions/wasm-modules/add-wasm/pkg/*.wasm"] diff --git a/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs index 57fd95b9f99..ad4ffde34f4 100644 --- a/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs +++ b/examples/edge-functions/supabase/functions/wasm-modules/add-wasm/src/lib.rs @@ -2,5 +2,5 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn add(a: u32, b: u32) -> u32 { - a + b + 100 + a + b } From c5fd032a7803720edc190551138a82737020d0fa Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Thu, 16 Jan 2025 13:12:23 +0800 Subject: [PATCH 069/265] chore: clarify supported file paths for static files (#32818) --- apps/docs/spec/cli_v1_config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/docs/spec/cli_v1_config.yaml b/apps/docs/spec/cli_v1_config.yaml index da477bb28c1..82bf9967123 100644 --- a/apps/docs/spec/cli_v1_config.yaml +++ b/apps/docs/spec/cli_v1_config.yaml @@ -1503,6 +1503,8 @@ parameters: required: false description: | Specify an array of static files to be bundled with the function. Supports glob patterns. + + NOTE: only file paths within `functions` directory are supported at the moment. links: - name: '`supabase functions` CLI subcommands' link: 'https://supabase.com/docs/reference/cli/supabase-functions' From 6b7f3c84a59455699341dd465cb6c69da9e87d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 16 Jan 2025 14:15:40 +0800 Subject: [PATCH 070/265] chore: bump stripe deps (#32819) Tested locally --- apps/studio/package.json | 6 +-- pnpm-lock.yaml | 111 ++++++++++++++++++++++++++++++--------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/apps/studio/package.json b/apps/studio/package.json index c08199dd6d6..9803a11ce32 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -25,7 +25,7 @@ "@dagrejs/dagre": "^1.0.4", "@graphiql/react": "^0.19.4", "@graphiql/toolkit": "^0.9.1", - "@hcaptcha/react-hcaptcha": "^1.8.1", + "@hcaptcha/react-hcaptcha": "^1.11.1", "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.3", "@hookform/resolvers": "^3.1.1", @@ -35,8 +35,8 @@ "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7", "@sentry/nextjs": "^8.34.0", - "@stripe/react-stripe-js": "^2.5.0", - "@stripe/stripe-js": "^3.0.5", + "@stripe/react-stripe-js": "^3.1.1", + "@stripe/stripe-js": "^5.5.0", "@supabase/auth-js": "2.64.2", "@supabase/pg-meta": "workspace:*", "@supabase/realtime-js": "2.10.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac06ba55d98..4e9331aaf4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,7 +206,7 @@ importers: version: 14.2.3 '@next/mdx': specifier: ^14.2.3 - version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0)) + version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0)) '@octokit/auth-app': specifier: ^6.0.3 version: 6.0.3 @@ -521,8 +521,8 @@ importers: specifier: ^0.9.1 version: 0.9.1(@types/node@20.12.11)(graphql-ws@5.14.1(graphql@16.8.1))(graphql@16.8.1) '@hcaptcha/react-hcaptcha': - specifier: ^1.8.1 - version: 1.9.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^1.11.1 + version: 1.11.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@headlessui/react': specifier: ^1.7.17 version: 1.7.17(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -551,11 +551,11 @@ importers: specifier: ^8.34.0 version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0) '@stripe/react-stripe-js': - specifier: ^2.5.0 - version: 2.5.0(@stripe/stripe-js@3.0.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^3.1.1 + version: 3.1.1(@stripe/stripe-js@5.5.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@stripe/stripe-js': - specifier: ^3.0.5 - version: 3.0.5 + specifier: ^5.5.0 + version: 5.5.0 '@supabase/auth-js': specifier: 2.64.2 version: 2.64.2 @@ -988,7 +988,7 @@ importers: version: 14.2.3 '@next/mdx': specifier: ^14.2.3 - version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0)) + version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0)) '@octokit/auth-app': specifier: ^6.0.3 version: 6.0.3 @@ -2812,11 +2812,11 @@ packages: '@har-sdk/openapi-sampler@2.2.0': resolution: {integrity: sha512-WcuGKCsFQzMlJOaeKFxMUx89EULvTvZ5GvPAeAhC4yUKz7khhN34YkuLsbn4z2BLLtrJ0oxduF5+Wa2kB3tw8w==} - '@hcaptcha/loader@1.1.2': - resolution: {integrity: sha512-YEBXCGNUBpPJUd5UZg64jBU6YMOhDjGdzS6dUCI9P8uIJttuQz3P76wVmK9kdKOuvLUaB7HwVBBmNv/bkAaIEw==} + '@hcaptcha/loader@1.2.4': + resolution: {integrity: sha512-3MNrIy/nWBfyVVvMPBKdKrX7BeadgiimW0AL/a/8TohNtJqxoySKgTJEXOQvYwlHemQpUzFrIsK74ody7JiMYw==} - '@hcaptcha/react-hcaptcha@1.9.2': - resolution: {integrity: sha512-FKsfxi2PV0UTFp3TvW8L02z2MHZjhBVOvH1OznR4ccxjugCZa8ix4weP/wKRUaLHcKwmCfXSu35Agen41aP1mQ==} + '@hcaptcha/react-hcaptcha@1.11.1': + resolution: {integrity: sha512-g6TwatNIzBtOR3RM4mxzvTUQGs5T9HMN+4fcNGHn7wUVThvmazThUs0vImI836bSkGpJS8n0rOYvv1UZ47q8Vw==} peerDependencies: react: '>= 16.3.0' react-dom: '>= 16.3.0' @@ -4893,15 +4893,15 @@ packages: resolution: {integrity: sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==} engines: {node: '>=14.0.0'} - '@stripe/react-stripe-js@2.5.0': - resolution: {integrity: sha512-ys5bnrufNoBlUPRblWkiN7dUCX3s2noBZS0Lf5GkHYO9x3SXJMZccif53IdP4X6tZyNLn7aSMgZBUZqsdzrvhg==} + '@stripe/react-stripe-js@3.1.1': + resolution: {integrity: sha512-+JzYFgUivVD7koqYV7LmLlt9edDMAwKH7XhZAHFQMo7NeRC+6D2JmQGzp9tygWerzwttwFLlExGp4rAOvD6l9g==} peerDependencies: - '@stripe/stripe-js': ^1.44.1 || ^2.0.0 || ^3.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@stripe/stripe-js': ^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + react: '>=16.8.0 <20.0.0' + react-dom: '>=16.8.0 <20.0.0' - '@stripe/stripe-js@3.0.5': - resolution: {integrity: sha512-0BTd2bC+8gSKty7jXKRntLnvmWgFTmpZkY0oP7ADBYfdV0JtuGoDbfQawui0W5eUlwT3CcKh2IeRV4SPOBIeXQ==} + '@stripe/stripe-js@5.5.0': + resolution: {integrity: sha512-lkfjyAd34aeMpTKKcEVfy8IUyEsjuAT3t9EXr5yZDtdIUncnZpedl/xLV16Dkd4z+fQwixScsCCDxSMNtBOgpQ==} engines: {node: '>=12.16'} '@supabase/auth-js@2.64.2': @@ -13655,12 +13655,12 @@ snapshots: randexp: 0.5.3 tslib: 2.6.2 - '@hcaptcha/loader@1.1.2': {} + '@hcaptcha/loader@1.2.4': {} - '@hcaptcha/react-hcaptcha@1.9.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@hcaptcha/react-hcaptcha@1.11.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@babel/runtime': 7.24.7 - '@hcaptcha/loader': 1.1.2 + '@hcaptcha/loader': 1.2.4 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -14008,6 +14008,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2))': + dependencies: + '@mdx-js/mdx': 2.3.0(supports-color@8.1.1) + source-map: 0.7.4 + webpack: 5.94.0(esbuild@0.20.2) + transitivePeerDependencies: + - supports-color + optional: true + '@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@mdx-js/mdx': 2.3.0(supports-color@8.1.1) @@ -14149,7 +14158,14 @@ snapshots: dependencies: glob: 10.3.10 - '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0))': + '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0))': + dependencies: + source-map: 0.7.4 + optionalDependencies: + '@mdx-js/loader': 2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)) + '@mdx-js/react': 2.3.0(react@18.2.0) + + '@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0))': dependencies: source-map: 0.7.4 optionalDependencies: @@ -16308,14 +16324,14 @@ snapshots: '@smithy/util-buffer-from': 2.0.0 tslib: 2.6.2 - '@stripe/react-stripe-js@2.5.0(@stripe/stripe-js@3.0.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@stripe/react-stripe-js@3.1.1(@stripe/stripe-js@5.5.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@stripe/stripe-js': 3.0.5 + '@stripe/stripe-js': 5.5.0 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@stripe/stripe-js@3.0.5': {} + '@stripe/stripe-js@5.5.0': {} '@supabase/auth-js@2.64.2': dependencies: @@ -24774,6 +24790,18 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + terser-webpack-plugin@5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.32.0 + webpack: 5.94.0(esbuild@0.20.2) + optionalDependencies: + esbuild: 0.20.2 + optional: true + terser-webpack-plugin@5.3.10(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -25628,6 +25656,37 @@ snapshots: - esbuild - uglify-js + webpack@5.94.0(esbuild@0.20.2): + dependencies: + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.11.3 + acorn-import-attributes: 1.9.5(acorn@8.11.3) + browserslist: 4.23.3 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2)) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + optional: true + whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 From 77b76e399a99e0d18c769191b0b21935daebb4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 16 Jan 2025 14:32:18 +0800 Subject: [PATCH 071/265] chore: remove react fiber (#32821) Unused, Alaister just added it after I removed it because his subconscious likes to see me suffer --- apps/www/package.json | 1 - pnpm-lock.yaml | 110 ------------------------------------------ 2 files changed, 111 deletions(-) diff --git a/apps/www/package.json b/apps/www/package.json index 7d02406b3c2..326607bce4c 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -28,7 +28,6 @@ "@octokit/plugin-paginate-graphql": "^4.0.0", "@octokit/rest": "^20.0.2", "@radix-ui/react-dialog": "^1.0.5", - "@react-three/fiber": "^7.0.29", "@supabase/supabase-js": "^2.44.3", "@vercel/og": "^0.6.2", "ai-commands": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e9331aaf4a..fc0fee2bbb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1007,9 +1007,6 @@ importers: '@radix-ui/react-dialog': specifier: ^1.0.5 version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@react-three/fiber': - specifier: ^7.0.29 - version: 7.0.29(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(three@0.170.0) '@supabase/supabase-js': specifier: ^2.44.3 version: 2.44.3 @@ -4424,16 +4421,6 @@ packages: '@react-dnd/shallowequal@4.0.2': resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==} - '@react-three/fiber@7.0.29': - resolution: {integrity: sha512-TE+/iaGrjluGsPwN2RkLXxKD4ak+unsh0YKIFKGcuAOW9jYcAyMGKAtgFO8MS7/HNCrZZQQSg23sJ8IPgtdkDg==} - peerDependencies: - react: '>=17.0' - react-dom: '>=17.0' - three: '>=0.133' - peerDependenciesMeta: - react-dom: - optional: true - '@reactflow/background@11.3.6': resolution: {integrity: sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==} peerDependencies: @@ -5447,9 +5434,6 @@ packages: '@types/react-grid-layout@1.3.3': resolution: {integrity: sha512-VLgUoEN0PaAL3GeRsmYbHuoN0tpDNoRhlkdzHK4Ll8shHW6e16YpNCZWb7P9vrlZdzFtY75cM/hPr8p0ukZQCw==} - '@types/react-reconciler@0.26.7': - resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==} - '@types/react-redux@7.1.27': resolution: {integrity: sha512-xj7d9z32p1K/eBmO+OEy+qfaWXtcPlN8f1Xk3Ne0p/ZRQ867RI5bQ/bpBtxbqU1AHNhKJSgGvld/P2myU2uYkg==} @@ -10388,9 +10372,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-merge-refs@1.1.0: - resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==} - react-onclickoutside@6.13.0: resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==} peerDependencies: @@ -10404,12 +10385,6 @@ packages: react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - react-reconciler@0.26.2: - resolution: {integrity: sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==} - engines: {node: '>=0.10.0'} - peerDependencies: - react: ^17.0.2 - react-redux@7.2.9: resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} peerDependencies: @@ -10500,9 +10475,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-0 || ^18.0.0 - react-three-fiber@0.0.0-deprecated: - resolution: {integrity: sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A==} - react-tooltip@4.5.1: resolution: {integrity: sha512-Zo+CSFUGXar1uV+bgXFFDe7VeS2iByeIp5rTgTcc2HqtuOS5D76QapejNNfx320MCY91TlhTQat36KGFTqgcvw==} engines: {npm: '>=6.13'} @@ -10535,12 +10507,6 @@ packages: react: '*' tslib: '*' - react-use-measure@2.1.1: - resolution: {integrity: sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==} - peerDependencies: - react: '>=16.13' - react-dom: '>=16.13' - react-use@17.5.0: resolution: {integrity: sha512-PbfwSPMwp/hoL847rLnm/qkjg3sTRCvn6YhUZiHaUa3FA6/aNoFX79ul5Xt70O1rK+9GxSVqkY0eTwMdsR/bWg==} peerDependencies: @@ -10878,9 +10844,6 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.20.2: - resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} - scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} @@ -11900,11 +11863,6 @@ packages: url-template@2.0.8: resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} - use-asset@1.0.4: - resolution: {integrity: sha512-7/hqDrWa0iMnCoET9W1T07EmD4Eg/Wmoj/X8TGBc++ECRK4m5yTsjP4O6s0yagbxfqIOuUkIxe2/sA+VR2GxZA==} - peerDependencies: - react: '>=17.0' - use-callback-ref@1.3.0: resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} @@ -11957,10 +11915,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utility-types@3.11.0: - resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} - engines: {node: '>= 4'} - uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true @@ -12390,15 +12344,6 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zustand@3.7.2: - resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==} - engines: {node: '>=12.7.0'} - peerDependencies: - react: '>=16.8' - peerDependenciesMeta: - react: - optional: true - zustand@4.4.7: resolution: {integrity: sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw==} engines: {node: '>=12.7.0'} @@ -15619,24 +15564,6 @@ snapshots: '@react-dnd/shallowequal@4.0.2': {} - '@react-three/fiber@7.0.29(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(three@0.170.0)': - dependencies: - '@babel/runtime': 7.24.7 - '@types/react-reconciler': 0.26.7 - react: 18.2.0 - react-merge-refs: 1.1.0 - react-reconciler: 0.26.2(react@18.2.0) - react-three-fiber: 0.0.0-deprecated - react-use-measure: 2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - resize-observer-polyfill: 1.5.1 - scheduler: 0.20.2 - three: 0.170.0 - use-asset: 1.0.4(react@18.2.0) - utility-types: 3.11.0 - zustand: 3.7.2(react@18.2.0) - optionalDependencies: - react-dom: 18.2.0(react@18.2.0) - '@reactflow/background@11.3.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@reactflow/core': 11.10.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -16986,10 +16913,6 @@ snapshots: dependencies: '@types/react': 18.3.3 - '@types/react-reconciler@0.26.7': - dependencies: - '@types/react': 18.3.3 - '@types/react-redux@7.1.27': dependencies: '@types/hoist-non-react-statics': 3.3.2 @@ -23442,8 +23365,6 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-merge-refs@1.1.0: {} - react-onclickoutside@6.13.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 @@ -23457,13 +23378,6 @@ snapshots: react-fast-compare: 3.2.2 warning: 4.0.3 - react-reconciler@0.26.2(react@18.2.0): - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react: 18.2.0 - scheduler: 0.20.2 - react-redux@7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.24.7 @@ -23563,8 +23477,6 @@ snapshots: prop-types: 15.8.1 react: 18.2.0 - react-three-fiber@0.0.0-deprecated: {} - react-tooltip@4.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: prop-types: 15.8.1 @@ -23595,12 +23507,6 @@ snapshots: react: 18.2.0 tslib: 2.6.2 - react-use-measure@2.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): - dependencies: - debounce: 1.2.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-use@17.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@types/js-cookie': 2.2.7 @@ -24137,11 +24043,6 @@ snapshots: xmlchars: 2.2.0 optional: true - scheduler@0.20.2: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - scheduler@0.23.0: dependencies: loose-envify: 1.4.0 @@ -25277,11 +25178,6 @@ snapshots: url-template@2.0.8: {} - use-asset@1.0.4(react@18.2.0): - dependencies: - fast-deep-equal: 3.1.3 - react: 18.2.0 - use-callback-ref@1.3.0(@types/react@18.3.3)(react@18.2.0): dependencies: react: 18.2.0 @@ -25318,8 +25214,6 @@ snapshots: util-deprecate@1.0.2: {} - utility-types@3.11.0: {} - uuid@7.0.3: {} uuid@8.3.2: {} @@ -25888,10 +25782,6 @@ snapshots: zod@3.23.8: {} - zustand@3.7.2(react@18.2.0): - optionalDependencies: - react: 18.2.0 - zustand@4.4.7(@types/react@18.3.3)(react@18.2.0): dependencies: use-sync-external-store: 1.2.0(react@18.2.0) From cfb18eb27274005f2e853cee89c3fc1c842b866e Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Thu, 16 Jan 2025 10:53:02 +0100 Subject: [PATCH 072/265] docs: add minimal jwt example with custom access token hook (#32770) --- .../auth-hooks/custom-access-token-hook.mdx | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/guides/auth/auth-hooks/custom-access-token-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/custom-access-token-hook.mdx index 70f151d9f52..a689d23dd79 100644 --- a/apps/docs/content/guides/auth/auth-hooks/custom-access-token-hook.mdx +++ b/apps/docs/content/guides/auth/auth-hooks/custom-access-token-hook.mdx @@ -10,8 +10,8 @@ Claims returned must conform to our specification. Supabase Auth will check for These are the fields currently available on an access token: -Required Claims: `aud`, `exp`, `iat`, `sub`, `email`, `phone`, `role`, `aal`, `session_id` -Optional Claims: `jti`, `iss`, `nbf`, `app_metadata`, `user_metadata`, `amr` +Required Claims: `iss`, `aud`, `exp`, `iat`, `sub`, `role`, `aal`, `session_id` +Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`, `email`, `phone` **Inputs** @@ -205,8 +205,57 @@ Return these only if your hook processed the input without errors. scrollable size="small" type="underlined" - defaultActiveId="add-admin-role" + defaultActiveId="minimal-jwt" > + + +Sometimes the size of the JWT can be a problem especially if you're using a [Server-Side Rendering framework](/docs/guides/auth/server-side). Common situations where the JWT can get too large include: + +- The user has a particularly large name, email address or phone number +- The default JWT has too many claims coming from OAuth providers +- A large avatar URL is included + +To lower the size of the JWT you can define a Custom Access Token hook like the one below which will instruct the Auth server to issue a JWT with only the listed claims. Check the documentation above on what JWT claims must be present and cannot be removed. + +Refer to the [Postgres JSON functions](https://www.postgresql.org/docs/current/functions-json.html) on how to manipulate `jsonb` objects. + +```sql +create or replace function public.custom_access_token_hook(event jsonb) +returns jsonb +language plpgsql +as $$ + declare + original_claims jsonb; + new_claims jsonb; + claim text; + begin + original_claims = event->'claims'; + new_claims = '{}'::jsonb; + + foreach claim in array array[ + -- add claims you want to keep here + 'iss', + 'aud', + 'exp', + 'iat', + 'sub', + 'role', + 'aal', + 'session_id' + ] loop + if original_claims ? claim then + -- original_claims contains one of the listed claims, set it on new_claims + new_claims = jsonb_set(new_claims, array[claim], original_claims->claim); + end if; + end loop; + + return jsonb_build_object('claims', new_claims); + end +$$; +``` + + + You can allow registered admin users to perform restricted actions by granting an `admin` claim to their token. From 9ed9938fe093802bf0c184ac97cf0fc57a2df39d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 16 Jan 2025 18:26:46 +0800 Subject: [PATCH 073/265] Add log out route (#32820) --- apps/studio/pages/logout.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 apps/studio/pages/logout.tsx diff --git a/apps/studio/pages/logout.tsx b/apps/studio/pages/logout.tsx new file mode 100644 index 00000000000..40307dd9275 --- /dev/null +++ b/apps/studio/pages/logout.tsx @@ -0,0 +1,29 @@ +import { useRouter } from 'next/router' +import { useEffect } from 'react' +import { toast } from 'sonner' + +import { Loading } from 'components/ui/Loading' +import { useSignOut } from 'lib/auth' +import { NextPageWithLayout } from 'types' + +const LogoutPage: NextPageWithLayout = () => { + const router = useRouter() + const signOut = useSignOut() + + useEffect(() => { + const logout = async () => { + await signOut() + toast('Successfully logged out') + await router.push('/sign-in') + } + logout() + }, []) + + return ( +
    + +
    + ) +} + +export default LogoutPage From 80f4c61f2d4867489d191ee43d53c608c40ced3f Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Thu, 16 Jan 2025 12:28:33 +0100 Subject: [PATCH 074/265] fix: additional ingest hosts for sentry (#32825) Sentry has US and Europe ingest hosts (us, de). Allow these in CSP --- apps/studio/next.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js index 62e55d13e9b..a8c453c94e2 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.js @@ -49,7 +49,8 @@ const GITHUB_USER_CONTENT_URL = 'https://raw.githubusercontent.com' const GITHUB_USER_AVATAR_URL = 'https://avatars.githubusercontent.com' const GOOGLE_USER_AVATAR_URL = 'https://lh3.googleusercontent.com' const VERCEL_LIVE_URL = 'https://vercel.live' -const SENTRY_URL = 'https://*.ingest.sentry.io' +const SENTRY_URL = + 'https://*.ingest.sentry.io https://*.ingest.us.sentry.io https://*.ingest.de.sentry.io' // used by vercel live preview const PUSHER_URL = 'https://*.pusher.com' From 16f950d6155dec01cf8cc5d88c3b094df0c26666 Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Fri, 17 Jan 2025 00:27:26 +0800 Subject: [PATCH 075/265] Update import-maps.mdx to document deno.json (#32830) * Update import-maps.mdx to document deno.json * Update import-maps.mdx * Update import-maps.mdx * Update import-maps.mdx * Run prettier --- .../content/guides/functions/import-maps.mdx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/docs/content/guides/functions/import-maps.mdx b/apps/docs/content/guides/functions/import-maps.mdx index 6fcaf870d68..b9abf701bf6 100644 --- a/apps/docs/content/guides/functions/import-maps.mdx +++ b/apps/docs/content/guides/functions/import-maps.mdx @@ -51,11 +51,11 @@ In the Deno ecosystem, each function should be treated as an independent project - Makes deployments more predictable and maintainable - Allows for different versions of the same dependency across functions -For these reasons, we recommend maintaining separate configuration files (`deno.json`/`deno.jsonc`, `.npmrc`, or `import_map.json`) within each function's directory, even if it means duplicating some configurations. +For these reasons, we recommend maintaining separate configuration files (`deno.json`, `.npmrc`, or `import_map.json`) within each function's directory, even if it means duplicating some configurations. There are two ways to manage your dependencies in Supabase Edge Functions: -### Using deno.jsonc (recommended) +### Using deno.json (recommended) @@ -63,7 +63,7 @@ This feature requires Supabase CLI version 1.215.0 or higher. -Each function should have its own `deno.jsonc` or `deno.json` file to manage dependencies and configure Deno-specific settings. This ensures proper isolation between functions and is the recommended approach for deployment. For a complete list of supported options, see the [official Deno configuration documentation](https://docs.deno.com/runtime/manual/getting_started/configuration_file). +Each function should have its own `deno.json` file to manage dependencies and configure Deno-specific settings. This ensures proper isolation between functions and is the recommended approach for deployment. For a complete list of supported options, see the [official Deno configuration documentation](https://docs.deno.com/runtime/manual/getting_started/configuration_file). ```json supabase/functions/my-function/deno.json { @@ -80,24 +80,24 @@ The recommended file structure for deployment: ├── functions │ ├── function-one │ │ ├── index.ts - │ │ ├─- deno.jsonc # Function-specific Deno configuration + │ │ ├─- deno.json # Function-specific Deno configuration │ │ └── .npmrc # Function-specific npm configuration (if needed) │ └── function-two │ ├── index.ts - │ ├─- deno.jsonc # Function-specific Deno configuration + │ ├─- deno.json # Function-specific Deno configuration │ └── .npmrc # Function-specific npm configuration (if needed) └── config.toml ``` - While it's possible to use a global `deno.json` or `deno.jsonc` in the `/supabase/functions` - directory for local development, this approach is not recommended for deployment. Each function - should maintain its own configuration to ensure proper isolation and dependency management. + While it's possible to use a global `deno.json` in the `/supabase/functions` directory for local + development, this approach is not recommended for deployment. Each function should maintain its + own configuration to ensure proper isolation and dependency management. ### Using import maps (legacy) -Import Maps are a legacy way to manage dependencies, similar to a `package.json` file. While still supported, we recommend using `deno.jsonc`. If both exist, `deno.jsonc` takes precedence. +Import Maps are a legacy way to manage dependencies, similar to a `package.json` file. While still supported, we recommend using `deno.json`. If both exist, `deno.json` takes precedence. Each function should have its own `import_map.json` file for proper isolation: @@ -161,7 +161,7 @@ To use private npm packages, create a `.npmrc` file within your function directo └── functions └── my-function ├── index.ts - ├── deno.jsonc + ├── deno.json └── .npmrc # Function-specific npm configuration ``` From bf49d8ab7debba10030b56bbd086de2c7076207c Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Thu, 16 Jan 2025 18:54:06 +0100 Subject: [PATCH 076/265] chore: Fix the local studio tests (#32836) * Set a fixed version of ubuntu for running the local studio tests. * Fix a possible yaml issue. * Revert to ubuntu-latest. Try bumping the playwright package. * Add init script to force the navigation menu to not expand. * Another try. * Extend the timeout. * Another try. * Try. * T. * T. * Revert some of the unneeded changes. --- .../NavigationBar/NavigationBar.tsx | 8 +- .../NavigationBar/NavigationIconLink.tsx | 5 +- pnpm-lock.yaml | 100 +++++++++--------- tests/local-studio-tests/package.json | 2 +- 4 files changed, 61 insertions(+), 54 deletions(-) diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx index b7a426be08d..17cfc2ff933 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx @@ -60,10 +60,12 @@ const NavigationBar = () => { const [userDropdownOpen, setUserDropdownOpenState] = useState(false) - const [allowNavPanelToExpand] = useLocalStorageQuery( + const [storedAllowNavPanel] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.EXPAND_NAVIGATION_PANEL, true ) + // Don't allow the nav panel to expand in playwright tests + const allowNavPanelToExpand = process.env.NEXT_PUBLIC_NODE_ENV !== 'test' && storedAllowNavPanel return (
    @@ -100,10 +102,12 @@ export const NavContent = () => { const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled() const [userDropdownOpen, setUserDropdownOpenState] = useState(false) - const [allowNavPanelToExpand] = useLocalStorageQuery( + const [storedAllowNavPanel] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.EXPAND_NAVIGATION_PANEL, true ) + // Don't allow the nav panel to expand in playwright tests + const allowNavPanelToExpand = process.env.NEXT_PUBLIC_NODE_ENV !== 'test' && storedAllowNavPanel const { projectAuthAll: authEnabled, diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx index 10b58a17030..76ef56f4be1 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationIconLink.tsx @@ -23,10 +23,13 @@ const NavigationIconLink = forwardRef { const snap = useAppStateSnapshot() - const [allowNavPanelToExpand] = useLocalStorageQuery( + const [storedAllowNavPanel] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.EXPAND_NAVIGATION_PANEL, true ) + // Don't allow the nav panel to expand in playwright tests + const allowNavPanelToExpand = process.env.NEXT_PUBLIC_NODE_ENV !== 'test' && storedAllowNavPanel + const iconClasses = [ 'absolute left-0 top-0 flex rounded items-center h-10 w-10 items-center justify-center text-foreground-lighter', // Layout 'group-hover/item:text-foreground-light', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc0fee2bbb4..9d4957b36be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,10 +76,10 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-contentlayer2: specifier: 0.4.6 - version: 0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) + version: 0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -329,7 +329,7 @@ importers: version: 1.0.1 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) @@ -338,13 +338,13 @@ importers: version: 1.0.1 next-seo: specifier: ^6.5.0 - version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) nuqs: specifier: ^1.19.1 - version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) + version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -549,7 +549,7 @@ importers: version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@sentry/nextjs': specifier: ^8.34.0 - version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0) + version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0) '@stripe/react-stripe-js': specifier: ^3.1.1 version: 3.1.1(@stripe/stripe-js@5.5.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -588,7 +588,7 @@ importers: version: 10.3.1(react@18.2.0) '@vercel/flags': specifier: ^2.6.0 - version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.2.1(supports-color@8.1.1)(vite@5.2.14(@types/node@20.12.11)(sass@1.72.0)(terser@5.32.0)) @@ -681,13 +681,13 @@ importers: version: 0.33.0 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) nuqs: specifier: ^1.17.8 - version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) + version: 1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)) openai: specifier: ^4.20.1 version: 4.71.1(encoding@0.1.13)(zod@3.23.8) @@ -958,7 +958,7 @@ importers: version: 2.4.11(typescript@5.5.2) next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) + version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) postcss: specifier: ^8.4.31 version: 8.4.38 @@ -1069,16 +1069,16 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-contentlayer2: specifier: 0.5.3 - version: 0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) + version: 0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1) next-seo: specifier: ^6.5.0 - version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1326,7 +1326,7 @@ importers: version: 0.7.12 '@vercel/flags': specifier: ^2.6.0 - version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) api-types: specifier: workspace:* version: link:../api-types @@ -1341,7 +1341,7 @@ importers: version: 4.17.21 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1610,7 +1610,7 @@ importers: version: 0.436.0(react@18.2.0) next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1812,7 +1812,7 @@ importers: version: 0.33.0 next: specifier: 'catalog:' - version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + version: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) next-themes: specifier: '*' version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -1909,7 +1909,7 @@ importers: version: link:../api-types next-router-mock: specifier: ^0.9.13 - version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) + version: 0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0) typescript: specifier: ~5.5.0 version: 5.5.2 @@ -1920,8 +1920,8 @@ importers: tests/local-studio-tests: devDependencies: '@playwright/test': - specifier: ^1.41.2 - version: 1.41.2 + specifier: ^1.49.0 + version: 1.49.1 packages: @@ -3575,9 +3575,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.41.2': - resolution: {integrity: sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==} - engines: {node: '>=16'} + '@playwright/test@1.49.1': + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} hasBin: true '@polka/url@1.0.0-next.25': @@ -9970,14 +9970,14 @@ packages: pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} - playwright-core@1.41.2: - resolution: {integrity: sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==} - engines: {node: '>=16'} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} hasBin: true - playwright@1.41.2: - resolution: {integrity: sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==} - engines: {node: '>=16'} + playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} hasBin: true pluralize@8.0.0: @@ -14671,9 +14671,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.41.2': + '@playwright/test@1.49.1': dependencies: - playwright: 1.41.2 + playwright: 1.49.1 '@polka/url@1.0.0-next.25': {} @@ -15857,7 +15857,7 @@ snapshots: '@sentry/types': 8.34.0 '@sentry/utils': 8.34.0 - '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0)(supports-color@8.1.1) '@opentelemetry/semantic-conventions': 1.27.0 @@ -15872,7 +15872,7 @@ snapshots: '@sentry/vercel-edge': 8.34.0 '@sentry/webpack-plugin': 2.22.3(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) chalk: 3.0.0 - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -17066,11 +17066,11 @@ snapshots: '@use-gesture/core': 10.3.1 react: 18.2.0 - '@vercel/flags@2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@vercel/flags@2.6.0(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: jose: 5.2.1 optionalDependencies: - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -22304,12 +22304,12 @@ snapshots: neo-async@2.6.2: {} - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): + next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.4.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.4.3 contentlayer2: 0.4.6(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -22318,12 +22318,12 @@ snapshots: - markdown-wasm - supports-color - next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): + next-contentlayer2@0.5.3(contentlayer2@0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.20.2)(markdown-wasm@1.2.0)(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.5.3 contentlayer2: 0.5.3(esbuild@0.20.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -22347,14 +22347,14 @@ snapshots: dependencies: js-yaml-loader: 1.2.2 - next-router-mock@0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0): + next-router-mock@0.9.13(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react@18.2.0): dependencies: - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 - next-seo@6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next-seo@6.5.0(next@14.2.21(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -22363,7 +22363,7 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0): + next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0): dependencies: '@next/env': 14.2.21 '@swc/helpers': 0.5.5 @@ -22385,7 +22385,7 @@ snapshots: '@next/swc-win32-ia32-msvc': 14.2.21 '@next/swc-win32-x64-msvc': 14.2.21 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.41.2 + '@playwright/test': 1.49.1 sass: 1.72.0 transitivePeerDependencies: - '@babel/core' @@ -22537,10 +22537,10 @@ snapshots: number-flow@0.3.7: {} - nuqs@1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)): + nuqs@1.19.1(next@14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)): dependencies: mitt: 3.0.1 - next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.41.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) + next: 14.2.21(@babel/core@7.24.7(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0) nwsapi@2.2.7: optional: true @@ -22940,11 +22940,11 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 - playwright-core@1.41.2: {} + playwright-core@1.49.1: {} - playwright@1.41.2: + playwright@1.49.1: dependencies: - playwright-core: 1.41.2 + playwright-core: 1.49.1 optionalDependencies: fsevents: 2.3.2 diff --git a/tests/local-studio-tests/package.json b/tests/local-studio-tests/package.json index 74671d01b25..99cd16f1586 100644 --- a/tests/local-studio-tests/package.json +++ b/tests/local-studio-tests/package.json @@ -17,6 +17,6 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/test": "^1.41.2" + "@playwright/test": "^1.49.0" } } From 31e21d5518012b9671ec265946d26c543a366175 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:00:17 -0500 Subject: [PATCH 077/265] fix: troubleshooting sync script (#32806) Fix some issues with the script: - GraphQL query strings were built from template literals without escaping, leading to errors when sending to GitHub API, now added escaping - Lack of visibility when GitHub API errors because the actual errors array is buried below the expanded portion of the object output, now drilling down to the usable portion of the returned error - When the sync bot auto-adds the database ID, it also reformats the TOML frontmatter, which leads to a spurious "content changed" update when the checksums don't match in the next check run. Resolve this by normalizing the frontmatter before calculating the checksum. --- .../features/docs/Troubleshooting.script.mjs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/docs/features/docs/Troubleshooting.script.mjs b/apps/docs/features/docs/Troubleshooting.script.mjs index 698525aaea3..cd13d7d48c9 100644 --- a/apps/docs/features/docs/Troubleshooting.script.mjs +++ b/apps/docs/features/docs/Troubleshooting.script.mjs @@ -125,7 +125,7 @@ async function syncTroubleshootingEntries() { if (result.status === 'rejected') { console.error( `[ERROR] Failed to insert and/or update for ${troubleshootingEntries[index].filePath}:\n%O`, - result.reason + result.reason?.errors ?? result.reason ) hasErrors = true } @@ -169,7 +169,14 @@ function calculateChecksum(content) { extensions: [gfm(), mdxjs()], mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()], }) - const normalized = toMarkdown(mdast, { extensions: [gfmToMarkdown(), mdxToMarkdown()] }) + const bodyNormalized = toMarkdown(mdast, { extensions: [gfmToMarkdown(), mdxToMarkdown()] }) + + const { data, content: body } = matter(bodyNormalized, { + language: 'toml', + engines: { toml: toml.parse.bind(toml) }, + }) + const newFrontmatter = stringify(data) + const normalized = `---\n${newFrontmatter}\n---\n${body}` return createHash('sha256').update(normalized).digest('base64') } @@ -261,12 +268,19 @@ function addCanonicalUrl(entry) { return content } +/** + * @param {string} str + */ +function escapeGraphQlString(str) { + return str.replace(/"/g, '\\"') +} + /** * @param {TroubleshootingEntry} entry */ async function createGithubDiscussion(entry) { console.log(`[INFO] Creating GitHub discussion for ${entry.data.title}`) - const content = addCanonicalUrl(entry) + const content = escapeGraphQlString(addCanonicalUrl(entry)) const mutation = ` mutation { @@ -364,7 +378,7 @@ async function updateGithubDiscussion(entry) { throw error } - const content = addCanonicalUrl(entry) + const content = escapeGraphQlString(addCanonicalUrl(entry)) const mutation = ` mutation { updateDiscussion(input: { From 840805672ee29c32c7aaa5b9fc35e1db3730bcc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:11:52 -0500 Subject: [PATCH 078/265] [bot] sync troubleshooting guides to db (#32785) Co-authored-by: github-docs-sync-bot --- ...plicate-key-violates-unique-constraint-error-pi6DnC.mdx | 7 ++++--- ...nt-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx | 7 ++++--- .../new-variable-is-null-in-a-trigger-function--l9AOZ.mdx | 5 +++-- ...artitioning-an-existing-table-with-same-name-VEnbzK.mdx | 5 +++-- ...esolving-42p01-relation-does-not-exist-error-W4_9-V.mdx | 7 ++++--- ...hy-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx | 7 ++++--- ...mpty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx | 5 +++-- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/docs/content/troubleshooting/inserting-into-sequenceserial-table-causes-duplicate-key-violates-unique-constraint-error-pi6DnC.mdx b/apps/docs/content/troubleshooting/inserting-into-sequenceserial-table-causes-duplicate-key-violates-unique-constraint-error-pi6DnC.mdx index 2a3ab48a9df..62d23123f6a 100644 --- a/apps/docs/content/troubleshooting/inserting-into-sequenceserial-table-causes-duplicate-key-violates-unique-constraint-error-pi6DnC.mdx +++ b/apps/docs/content/troubleshooting/inserting-into-sequenceserial-table-causes-duplicate-key-violates-unique-constraint-error-pi6DnC.mdx @@ -1,9 +1,10 @@ --- -title = 'Inserting into Sequence/Serial Table Causes "duplicate key violates unique constraint" Error' +title = "Inserting into Sequence/Serial Table Causes \"duplicate key violates unique constraint\" Error" github_url = "https://github.com/orgs/supabase/discussions/21134" date_created = "2024-02-09T03:10:39+00:00" -topics = ["database"] -keywords = ["duplicate", "sequence", "table", "key", "constraint"] +topics = [ "database" ] +keywords = [ "duplicate", "sequence", "table", "key", "constraint" ] +database_id = "a1156bbe-289e-4462-a32d-2b0ca03691e4" [[errors]] message = "duplicate key violates unique constraint" diff --git a/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx b/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx index ab6243de1b6..af72fe2edce 100644 --- a/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx +++ b/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx @@ -1,9 +1,10 @@ --- -title = '"insufficient privilege" when accessing pg_stat_statements' +title = "\"insufficient privilege\" when accessing pg_stat_statements" github_url = "https://github.com/orgs/supabase/discussions/20126" date_created = "2024-01-03T10:59:49+00:00" -topics = ["database"] -keywords = ["privilege", "permissions", "postgres"] +topics = [ "database" ] +keywords = [ "privilege", "permissions", "postgres" ] +database_id = "1da315be-38ac-487a-b178-f865a1a73c09" [[errors]] message = "insufficient privilege" diff --git a/apps/docs/content/troubleshooting/new-variable-is-null-in-a-trigger-function--l9AOZ.mdx b/apps/docs/content/troubleshooting/new-variable-is-null-in-a-trigger-function--l9AOZ.mdx index 3774aeb2130..09ffe5768d7 100644 --- a/apps/docs/content/troubleshooting/new-variable-is-null-in-a-trigger-function--l9AOZ.mdx +++ b/apps/docs/content/troubleshooting/new-variable-is-null-in-a-trigger-function--l9AOZ.mdx @@ -2,8 +2,9 @@ title = "NEW variable is null in a trigger function." github_url = "https://github.com/orgs/supabase/discussions/15934" date_created = "2023-07-20T19:05:52+00:00" -topics = ["database", "functions"] -keywords = ["trigger", "function", "statement", "row", "NEW", "OLD", "SQL"] +topics = [ "database", "functions" ] +keywords = [ "trigger", "function", "statement", "row", "NEW", "OLD", "SQL" ] +database_id = "f5cabb97-aae3-4fb5-b597-4e7857821753" --- When you create a function and call it from a trigger the NEW variable is only passed in if the trigger is on ROW operations diff --git a/apps/docs/content/troubleshooting/partitioning-an-existing-table-with-same-name-VEnbzK.mdx b/apps/docs/content/troubleshooting/partitioning-an-existing-table-with-same-name-VEnbzK.mdx index fe9d29fbabc..300ef52dd87 100644 --- a/apps/docs/content/troubleshooting/partitioning-an-existing-table-with-same-name-VEnbzK.mdx +++ b/apps/docs/content/troubleshooting/partitioning-an-existing-table-with-same-name-VEnbzK.mdx @@ -2,8 +2,9 @@ title = "Partitioning an existing table with same name" github_url = "https://github.com/orgs/supabase/discussions/21380" date_created = "2024-02-20T08:39:24+00:00" -topics = ["database"] -keywords = ["partitioning", "table", "postgresql", "constraint", "index"] +topics = [ "database" ] +keywords = [ "partitioning", "table", "postgresql", "constraint", "index" ] +database_id = "3142f418-028e-4ab1-8f82-a7096be90422" --- Our documentation on table partitioning here: https://supabase.com/docs/guides/database/partitions diff --git a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx index c2f982faa0f..e2661ae6854 100644 --- a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx +++ b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx @@ -2,12 +2,13 @@ title = "Resolving 42P01: relation does not exist error" github_url = "https://github.com/orgs/supabase/discussions/29577" date_created = "2024-09-29T15:52:41+00:00" -topics = ["database"] -keywords = ["relation", "schema", "capitalization"] +topics = [ "database" ] +keywords = [ "relation", "schema", "capitalization" ] +database_id = "72e93bc8-597d-4c72-8d86-4997679196fd" [[errors]] code = "42P01" -message = 'relation "" does not exist' +message = "relation \"\" does not exist" [[errors]] code = "42501" diff --git a/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx b/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx index c6019993177..5bc3e0e6717 100644 --- a/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx +++ b/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx @@ -2,11 +2,12 @@ title = "Why are there gaps in my Postgres id sequence?" github_url = "https://github.com/orgs/supabase/discussions/22299" date_created = "2024-03-27T18:04:39+00:00" -topics = ["database"] -keywords = ["sequence", "id", "rollback", "deletions", "upserts"] +topics = [ "database" ] +keywords = [ "sequence", "id", "rollback", "deletions", "upserts" ] +database_id = "0c0369db-2968-4ea7-9f99-c5f5ce55acad" [[errors]] -message = 'Failed to run sql query: duplicate key value violates unique constraint "{table}_pkey"' +message = "Failed to run sql query: duplicate key value violates unique constraint \"{table}_pkey\"" --- Most database tables have a primary key that is a made-up number, and that number is usually made by a sequence. diff --git a/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx b/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx index daa5ac57e85..ec43a9b2e05 100644 --- a/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx +++ b/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx @@ -2,8 +2,9 @@ title = "Why is my select returning an empty data array and I have data in the table?" github_url = "https://github.com/orgs/supabase/discussions/12895" date_created = "2023-03-08T16:38:02+00:00" -topics = ["database", "auth"] -keywords = ["rls", "policy", "filter", "jwt"] +topics = [ "database", "auth" ] +keywords = [ "rls", "policy", "filter", "jwt" ] +database_id = "6fa68831-6db8-4836-a1b4-e9e51296d529" --- Usually this means you have RLS (row level security) enabled and no policy, or do not meet the policy. It can also mean you have a filter and have no rows matching that. From 7587d5048e6599ffa31f20f82990fc2ebf5ceffc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:20:49 -0500 Subject: [PATCH 079/265] [bot] fix lint errors (#32784) Co-authored-by: github-docs-bot --- ...resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx | 2 +- ...why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx index e2661ae6854..48d7260e8f0 100644 --- a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx +++ b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx @@ -48,7 +48,7 @@ rename to table_name; --- -### Cause 3: table or function actually does not exist +### Cause 3: Table or function actually does not exist One may have never made the table or dropped it deliberately or accidentally. This can be quickly checked with the following query: diff --git a/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx b/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx index 5bc3e0e6717..83a866c42fc 100644 --- a/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx +++ b/apps/docs/content/troubleshooting/why-are-there-gaps-in-my-postgres-id-sequence-Frifus.mdx @@ -20,7 +20,7 @@ It's also important to understand the distinction that sequences guarantee **uni If you don't know the name of your sequence, it's often formed based on a standard naming convention: table_name_id_seq, where table_name is the name of your table and id is the name of your serial column. -### Common Reasons for Gaps in Sequences +### Common reasons for gaps in sequences 1. Rollbacks One of the most common reasons for a gap is the rollback of a transaction. If you initiate a transaction that includes an insert operation, the sequence responsible for generating the ID for the new row increments. If, for any reason, the transaction doesn't complete successfully—perhaps due to a constraint violation or a deliberate decision to rollback—the insert operation is undone, but the sequence value used is not returned or reused. The documentation explains that as well: @@ -36,7 +36,7 @@ If you don't know the name of your sequence, it's often formed based on a standa 4. Upserts When an upsert is executed, it can still increase the sequence even if it is set to do nothing on conflicts -### Checking for Gaps +### Checking for gaps To check for gaps in the sequence of IDs in a PostgreSQL table, you can use a SQL query that compares the sequence of IDs to a generated series of numbers that spans the same range: @@ -52,7 +52,7 @@ WHERE This query should help you pinpoint where gaps exist. -### Encountering Errors and Adjusting Sequences +### Encountering errors and adjusting sequences In operations involving sequences, you might encounter an error message such as: From a0e3ab86b0e805cc5ea8bdfdfdc0c4090dca2b62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:55:26 -0500 Subject: [PATCH 080/265] [bot] fix lint errors (#32843) * [bot] fix lint errors * add exception * Apply suggestions from code review --------- Co-authored-by: github-docs-bot Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --- supa-mdx-lint/Rule001HeadingCase.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/supa-mdx-lint/Rule001HeadingCase.toml b/supa-mdx-lint/Rule001HeadingCase.toml index af93da95cdd..ffd5daa14d1 100644 --- a/supa-mdx-lint/Rule001HeadingCase.toml +++ b/supa-mdx-lint/Rule001HeadingCase.toml @@ -196,6 +196,7 @@ may_uppercase = [ "Vercel Marketplace", "Visual Studio Code", "Vue", + "Wasm", "Web", "WebAssembly", "WebP", From d633ced97d522a091721c81536d7327c51a349ca Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Fri, 17 Jan 2025 09:59:21 +1000 Subject: [PATCH 081/265] Feat/new project creation experiment (#31237) * some early ideas * better approach * fix globe stuff * add initial step * reset and submit button for chat * cleanup * remove www * more clean up * mobile and more cleanup * fixes * update advanced and security * fix project checks * connect form * fix reset and tidied up other things * add layout options * a/b test * add offset * add ab test * remove www packages * remove www * package * update coordinates * feat: create ProjectCreationInitialStepSubmittedEvent * feat: add event for capturing intention of prompting * fix: bring pnpm-lock up to date * fix ts errors * fix globe * fix globe animation * fix ts * remove imports and tools * update module resolution * global redirect * delay rendering * Update apps/studio/pages/new/[slug].tsx Co-authored-by: Alaister Young * take directly to v2 via project create dropdown * move redirect to useEffect * feat: distinguish events between initial and second step * feat: emit an event when create project is clicked on new/v2 * fix: correct event type defs * remove unused imports * Various clean ups * improvements * Minor refactors * clean up * ts fixes --------- Co-authored-by: Long Hoang <1732217+loong@users.noreply.github.com> Co-authored-by: Alaister Young Co-authored-by: Joshen Lim --- .../Database/Schemas/SchemaTableNode.tsx | 15 +- .../Database/Schemas/Schemas.utils.ts | 10 +- .../components/interfaces/HomePageActions.tsx | 9 +- .../ProjectCreation/AdvancedConfiguration.tsx | 205 +-- .../ProjectCreation/InitialStep.tsx | 82 + .../PostgresVersionSelector.tsx | 4 +- .../ProjectCreation/ProjectVisual.tsx | 231 +++ .../ProjectCreation/RegionSelector.tsx | 9 +- .../interfaces/ProjectCreation/SchemaFlow.tsx | 86 + .../ProjectCreation/SchemaGenerator.tsx | 214 +++ .../ProjectCreation/SecurityOptions.tsx | 285 ++-- .../interfaces/SchemaVisualizer/index.tsx | 139 ++ apps/studio/components/ui/Globe.tsx | 103 ++ apps/studio/components/ui/ScrollGradient.tsx | 44 + .../data/telemetry/send-event-mutation.ts | 8 + apps/studio/lib/constants/telemetry.ts | 71 + apps/studio/middleware.ts | 1 + apps/studio/package.json | 3 + apps/studio/pages/api/ai/onboarding/design.ts | 99 ++ apps/studio/pages/new/[slug].tsx | 17 + apps/studio/pages/new/v2/[slug].tsx | 1267 ++++++++++++++ apps/studio/tsconfig.json | 2 +- packages/shared-data/regions.ts | 86 +- pnpm-lock.yaml | 1479 ++++++++++++++--- 24 files changed, 3995 insertions(+), 474 deletions(-) create mode 100644 apps/studio/components/interfaces/ProjectCreation/InitialStep.tsx create mode 100644 apps/studio/components/interfaces/ProjectCreation/ProjectVisual.tsx create mode 100644 apps/studio/components/interfaces/ProjectCreation/SchemaFlow.tsx create mode 100644 apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx create mode 100644 apps/studio/components/interfaces/SchemaVisualizer/index.tsx create mode 100644 apps/studio/components/ui/Globe.tsx create mode 100644 apps/studio/components/ui/ScrollGradient.tsx create mode 100644 apps/studio/pages/api/ai/onboarding/design.ts create mode 100644 apps/studio/pages/new/v2/[slug].tsx diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx index f3f9597eabe..f60cc72a3d9 100644 --- a/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx @@ -1,8 +1,8 @@ -import { DiamondIcon, ExternalLink, Fingerprint, Hash, Key, Link2, Table2 } from 'lucide-react' -import { Handle, NodeProps } from 'reactflow' +import { DiamondIcon, ExternalLink, Fingerprint, Hash, Key, Table2 } from 'lucide-react' import Link from 'next/link' +import { Handle, NodeProps } from 'reactflow' -import { Button, cn, Tooltip_Shadcn_, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_ } from 'ui' +import { Button, cn } from 'ui' // ReactFlow is scaling everything by the factor of 2 const TABLE_NODE_WIDTH = 320 @@ -24,7 +24,12 @@ export type TableNodeData = { }[] } -const TableNode = ({ data, targetPosition, sourcePosition }: NodeProps) => { +const TableNode = ({ + data, + targetPosition, + sourcePosition, + placeholder, +}: NodeProps & { placeholder?: boolean }) => { // Important styles is a nasty hack to use Handles (required for edges calculations), but do not show them in the UI. // ref: https://github.com/wbkd/react-flow/discussions/2698 const hiddenNodeConnector = '!h-px !w-px !min-w-0 !min-h-0 !cursor-grab !border-0 !opacity-0' @@ -60,7 +65,7 @@ const TableNode = ({ data, targetPosition, sourcePosition }: NodeProps {data.name}
    - {data.id && ( + {data.id && !placeholder && ( + )} +
    + )} +
    + {messages.length > 0 && + messages[messages.length - 1].role === 'assistant' && + messages[messages.length - 1].content.length > 0 && + ((isOneOff && !hasSql) || !isOneOff) && ( +
    +

    + m.role === 'assistant').slice(-1)[0]?.content || '' + } + /> +

    +
    + )} +
    +