From 2bec311971bfc76fac2470920f909f2d2b92442e Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 23 Jul 2025 10:06:53 +0300 Subject: [PATCH 1/4] fix: Make the database.new redirect work (#37370) * Add a special case for /new/last-visited-org * Handle the case where last visited org is null. Don't show an admonition if the org slug is _. * Smol fix to disable oauth apps request if slug is _, and also dont prematurely show no perms UI state --------- Co-authored-by: Joshen Lim --- .../interfaces/Organization/OrgNotFound.tsx | 24 ++++++++++--------- apps/studio/pages/new/[slug].tsx | 18 ++++++++++++-- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/OrgNotFound.tsx b/apps/studio/components/interfaces/Organization/OrgNotFound.tsx index 673c8e116c0c2..2da3c4f293dc5 100644 --- a/apps/studio/components/interfaces/Organization/OrgNotFound.tsx +++ b/apps/studio/components/interfaces/Organization/OrgNotFound.tsx @@ -15,17 +15,19 @@ export const OrgNotFound = ({ slug }: { slug?: string }) => { return ( <> - - The selected organization does not exist or you don't have permission to access it.{' '} - {slug ? ( - <> - Contact the owner or administrator to create a new project in the {slug}{' '} - organization. - - ) : ( - <>Contact the owner or administrator to create a new project. - )} - + {slug !== '_' && ( + + The selected organization does not exist or you don't have permission to access it.{' '} + {slug ? ( + <> + Contact the owner or administrator to create a new project in the {slug}{' '} + organization. + + ) : ( + <>Contact the owner or administrator to create a new project. + )} + + )}

Select an organization to create your new project from

diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index 2ba15120023c5..99d23500a1b87 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -146,6 +146,15 @@ const Wizard: NextPageWithLayout = () => { '' ) + // This is to make the database.new redirect work correctly. The database.new redirect should be set to supabase.com/dashboard/new/last-visited-org + if (slug === 'last-visited-org') { + if (lastVisitedOrganization) { + router.replace(`/new/${lastVisitedOrganization}`, undefined, { shallow: true }) + } else { + router.replace(`/new/_`, undefined, { shallow: true }) + } + } + const { mutate: sendEvent } = useSendEventMutation() const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate') @@ -156,7 +165,10 @@ const Wizard: NextPageWithLayout = () => { { enabled: isFreePlan } ) - const { data: approvedOAuthApps } = useAuthorizedAppsQuery({ slug }, { enabled: !isFreePlan }) + const { data: approvedOAuthApps } = useAuthorizedAppsQuery( + { slug }, + { enabled: !isFreePlan && slug !== '_' } + ) const hasOAuthApps = approvedOAuthApps && approvedOAuthApps.length > 0 @@ -623,7 +635,9 @@ const Wizard: NextPageWithLayout = () => { /> )} - {!isAdmin && !orgNotFound && } + {isOrganizationsSuccess && !isAdmin && !orgNotFound && ( + + )} {orgNotFound && } From af5e591b843751f950e56bee8e4c4b9660cb0bc2 Mon Sep 17 00:00:00 2001 From: Drake Costa Date: Wed, 23 Jul 2025 01:54:31 -0700 Subject: [PATCH 2/4] Refactor `NewAccessTokenButton` to use shadcn components (#36972) * chore: Refactor `NewAccessTokenButton` to use shadcn components This brings `NewAccessTokenButton` into conformance with the latest UI patterns of using shadcn based components, zod schemas, and react-hook-form for form submission. I based this refactor on the implementation of `CreateSecretAPIKeyDialog`, which already made use of the above. * add success and failure toasts * add smoke test for `NewAccessTokenButton`, fix vitest config This adds a minimal test for `NewAccessTokenButton` but more importantly fixes a critical bug in the vitest configuration for `studio`. Because `restoreMocks: true` was set in the config, this meant that the mock implementation for `window.matchMedia` defined in the `tests/vitestSetup.ts` file was being **reset** to an empty function before each test suite was run. While this didn't appear to be a problem before, that appears to be because none of the existing tests had `motion.div` in their component trees. Since `motion.div` calls `addListener` on a media query as part of it's lifecycle, that meant that any test which included it would fail as `addListener` would be undefined in this situation. Removing `restoreMocks: true` from the config results in the desired mocking behavior. This change is necessary in order to test any component that has the `FormItemLayout` component, as it uses `framer-motion` to animate in error messages for input elements. * add warning comment in case of future config regression * update test cases, reset form on dialog close, testing setup fixes This fixes an issue with the polyfills for the testing environment, where a call to `useMutation` would fail as a result of `TransformStream` being set to `null`. Basic tests for access token creation and form resetting added. Adds `@faker-js/faker` to the studio app devDependencies to generate mock data for `msw` endpoint response. Adds `shx` to the docs app devDependencies to ensure that the `codegen:examples` script runs cross-platform. * ensure mocked date is a string * update testing setup, pollyfills and add rich type support to addAPIMock * Update studio testing setup files Improves API mocking type safety and polyfills browser APIs necessary to run tests with framer-motion components * chore: Refactor `NewAccessTokenButton` to use shadcn components This brings `NewAccessTokenButton` into conformance with the latest UI patterns of using shadcn based components, zod schemas, and react-hook-form for form submission. I based this refactor on the implementation of `CreateSecretAPIKeyDialog`, which already made use of the above. * add success and failure toasts * add smoke test for `NewAccessTokenButton`, fix vitest config This adds a minimal test for `NewAccessTokenButton` but more importantly fixes a critical bug in the vitest configuration for `studio`. Because `restoreMocks: true` was set in the config, this meant that the mock implementation for `window.matchMedia` defined in the `tests/vitestSetup.ts` file was being **reset** to an empty function before each test suite was run. While this didn't appear to be a problem before, that appears to be because none of the existing tests had `motion.div` in their component trees. Since `motion.div` calls `addListener` on a media query as part of it's lifecycle, that meant that any test which included it would fail as `addListener` would be undefined in this situation. Removing `restoreMocks: true` from the config results in the desired mocking behavior. This change is necessary in order to test any component that has the `FormItemLayout` component, as it uses `framer-motion` to animate in error messages for input elements. * update test cases, reset form on dialog close, testing setup fixes This fixes an issue with the polyfills for the testing environment, where a call to `useMutation` would fail as a result of `TransformStream` being set to `null`. Basic tests for access token creation and form resetting added. Adds `@faker-js/faker` to the studio app devDependencies to generate mock data for `msw` endpoint response. Adds `shx` to the docs app devDependencies to ensure that the `codegen:examples` script runs cross-platform. * ensure mocked date is a string * update testing setup, pollyfills and add rich type support to addAPIMock * fix imports * fix missing listen call for msw, resolve test type error * fix imports * Update studio testing setup files Improves API mocking type safety and polyfills browser APIs necessary to run tests with framer-motion components * fix missing listen call for msw, resolve test type error * fix imports * Shift test file * Smol fix * Nit --------- Co-authored-by: Joshen Lim --- apps/docs/package.json | 3 +- .../Account/NewAccessTokenButton.test.tsx | 97 +++++++ .../Account/NewAccessTokenButton.tsx | 266 ++++++++++-------- apps/studio/package.json | 1 + apps/studio/tests/vitestSetup.ts | 9 +- pnpm-lock.yaml | 127 ++++++++- 6 files changed, 377 insertions(+), 126 deletions(-) create mode 100644 apps/studio/components/interfaces/Account/NewAccessTokenButton.test.tsx diff --git a/apps/docs/package.json b/apps/docs/package.json index 1dd7f1f831cd9..96c1cdb065a5c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -9,7 +9,7 @@ "build:llms": "tsx ./scripts/llms.ts", "build:sitemap": "tsx ./internals/generate-sitemap.ts", "clean": "rimraf .next .turbo node_modules features/docs/generated examples __generated__", - "codegen:examples": "cp -r ../../examples ./examples", + "codegen:examples": "shx cp -r ../../examples ./examples", "codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts", "codegen:references": "tsx features/docs/Reference.generated.script.ts", "codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --cache --write \"content/**/*.mdx\"", @@ -149,6 +149,7 @@ "openapi-types": "^12.1.3", "postcss": "^8.5.3", "shiki": "^3.2.1", + "shx": "^0.4.0", "simple-git": "^3.24.0", "slugify": "^1.6.6", "smol-toml": "^1.3.1", diff --git a/apps/studio/components/interfaces/Account/NewAccessTokenButton.test.tsx b/apps/studio/components/interfaces/Account/NewAccessTokenButton.test.tsx new file mode 100644 index 0000000000000..18cadf221a234 --- /dev/null +++ b/apps/studio/components/interfaces/Account/NewAccessTokenButton.test.tsx @@ -0,0 +1,97 @@ +import { faker } from '@faker-js/faker' +import { screen, waitFor } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { render } from 'tests/helpers' +import { addAPIMock } from 'tests/lib/msw' +import NewAccessTokenButton from './NewAccessTokenButton' + +describe(`NewAccessTokenButton`, () => { + beforeEach(() => { + addAPIMock({ + method: `post`, + path: `/platform/profile/access-tokens`, + response: { + name: faker.lorem.word(), + scope: faker.helpers.arrayElement(['V0', undefined]), + created_at: faker.date.past().toISOString(), + id: faker.number.int(), + token_alias: faker.lorem.words(), + token: faker.lorem.words(), + }, + }) + }) + + it(`generates regular tokens`, async () => { + const onCreateToken = vi.fn() + render() + + const dialogTrigger = screen.getByRole(`button`, { name: `Generate new token` }) + await userEvent.click(dialogTrigger) + + const nameInput = screen.getByLabelText(`Name`) + await userEvent.type(nameInput, `test{enter}`) + + await waitFor(() => expect(onCreateToken).toHaveBeenCalledTimes(1)) + }) + + it(`generates experimental tokens`, async () => { + const onCreateToken = vi.fn() + render() + + const dropdownTrigger = screen.getByTitle(`Choose token scope`) + await userEvent.click(dropdownTrigger) + + const experimentalMenuItem = await screen.findByRole(`menuitem`, { + name: `Generate token for experimental API`, + }) + await userEvent.click(experimentalMenuItem) + + await waitFor(async () => { + await expect( + screen.findByRole(`heading`, { name: `Generate token for experimental API` }) + ).resolves.toBeInTheDocument() + await expect(screen.findByRole(`alert`)).resolves.toBeInTheDocument() + }) + + const nameInput = screen.getByLabelText(`Name`) + await userEvent.type(nameInput, `test{enter}`) + + await waitFor(() => expect(onCreateToken).toHaveBeenCalledTimes(1)) + }) + + it(`resets the form on close/cancel`, async () => { + render() + + // pass 1: open dialog and fill form + const dialogTrigger = screen.getByRole(`button`, { name: `Generate new token` }) + await userEvent.click(dialogTrigger) + + let nameInput = screen.getByLabelText(`Name`) + await userEvent.type(nameInput, `cancel button test`) + expect(nameInput).toHaveValue(`cancel button test`) + + // reset the form by pressing the cancel button + const cancelButton = screen.getByRole(`button`, { name: `Cancel` }) + await userEvent.click(cancelButton) + + // pass 2: check that the form is reset, then fill it again + await userEvent.click(dialogTrigger) + + nameInput = screen.getByLabelText(`Name`) + expect(nameInput).not.toHaveValue(`cancel button test`) + + await userEvent.type(nameInput, `close modal test`) + expect(nameInput).toHaveValue(`close modal test`) + + // reset the form by closing the dialog + await userEvent.keyboard(`{Escape}`) + + // pass 3: check that the form has been rest again + await userEvent.click(dialogTrigger) + + nameInput = screen.getByLabelText(`Name`) + expect(nameInput).not.toHaveValue(`close modal test`) + }) +}) diff --git a/apps/studio/components/interfaces/Account/NewAccessTokenButton.tsx b/apps/studio/components/interfaces/Account/NewAccessTokenButton.tsx index 360546f9f4a1c..17a976ac4c8f9 100644 --- a/apps/studio/components/interfaces/Account/NewAccessTokenButton.tsx +++ b/apps/studio/components/interfaces/Account/NewAccessTokenButton.tsx @@ -1,157 +1,187 @@ +import { zodResolver } from '@hookform/resolvers/zod' import { ChevronDown, ExternalLink } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' +import { type SubmitHandler, useForm } from 'react-hook-form' +import { toast } from 'sonner' +import { z } from 'zod' import { useAccessTokenCreateMutation } from 'data/access-tokens/access-tokens-create-mutation' import { Button, + Dialog, + DialogContent, DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, - Form, - Input, - Modal, + Form_Shadcn_, + FormControl_Shadcn_, + FormField_Shadcn_, + Input_Shadcn_, } from 'ui' import { Admonition } from 'ui-patterns' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' export interface NewAccessTokenButtonProps { onCreateToken: (token: any) => void } +const TokenSchema = z.object({ + tokenName: z.string().min(1, 'Please enter a name for the token'), +}) + +const formId = 'new-access-token-form' + const NewAccessTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) => { - const [isOpen, setIsOpen] = useState(false) + const [visible, setVisible] = useState(false) const [tokenScope, setTokenScope] = useState<'V0' | undefined>(undefined) - const validate = (values: any) => { - const errors: any = {} - if (!values.tokenName) errors.tokenName = 'Please enter a name for the token' - return errors - } - - const { mutate: createAccessToken, isLoading } = useAccessTokenCreateMutation({ - onSuccess: (res) => { - onCreateToken(res) - setIsOpen(false) - }, + const form = useForm>({ + resolver: zodResolver(TokenSchema), + defaultValues: { tokenName: '' }, + mode: 'onSubmit', }) + const { mutate: createAccessToken, isLoading } = useAccessTokenCreateMutation() - const onFormSubmit = async (values: any) => { - createAccessToken({ name: values.tokenName, scope: tokenScope }) + const onSubmit: SubmitHandler> = async (values) => { + createAccessToken( + { name: values.tokenName, scope: tokenScope }, + { + onSuccess: (data) => { + toast.success(`Your access token "${data.name}" is ready.`) + form.reset() + onCreateToken(data) + setVisible(false) + }, + } + ) } return ( <> -
-
-
+
+ + + - - -
-
+
+

Generate token for experimental API

+
+ + +
- setIsOpen(!isOpen)} - header={ -
-
- {tokenScope === 'V0' ? 'Generate token for experimental API' : 'Generate New Token'} -
-
- } + { + if (!open) form.reset() + setVisible(open) + }} > -
- {() => ( - <> - {tokenScope === 'V0' && ( - - -

- These include deleting organizations and projects which cannot be undone. - As such, be very careful when using this API. -

-
- -
- - } - /> -
- )} - - + + + {tokenScope === 'V0' ? 'Generate token for experimental API' : 'Generate New Token'} + + + + + {tokenScope === 'V0' && ( + +

+ These include deleting organizations and projects which cannot be undone. As + such, be very careful when using this API. +

+
+ +
+ + } + /> + )} + + + ( + + + + + + )} /> -
- -
- - -
-
- - )} -
-
+ + + + + + + + + ) } diff --git a/apps/studio/package.json b/apps/studio/package.json index 3085a3d6de7f8..038669dd94734 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -146,6 +146,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { + "@faker-js/faker": "^9.9.0", "@graphql-codegen/cli": "5.0.5", "@graphql-typed-document-node/core": "^3.2.0", "@radix-ui/react-use-escape-keydown": "^1.0.3", diff --git a/apps/studio/tests/vitestSetup.ts b/apps/studio/tests/vitestSetup.ts index 00ac0696c074a..e32fd66226fb0 100644 --- a/apps/studio/tests/vitestSetup.ts +++ b/apps/studio/tests/vitestSetup.ts @@ -40,10 +40,11 @@ beforeAll(() => { routerMock.useParser(createDynamicRouteParser(['/projects/[ref]'])) }) -afterAll(() => mswServer.close()) - -afterEach(() => mswServer.resetHandlers()) - afterEach(() => { + mswServer.resetHandlers() cleanup() }) + +afterAll(() => { + mswServer.close() +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f2a902f58768..a239e511204cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,6 +615,9 @@ importers: shiki: specifier: ^3.2.1 version: 3.2.1 + shx: + specifier: ^0.4.0 + version: 0.4.0 simple-git: specifier: ^3.24.0 version: 3.24.0(supports-color@8.1.1) @@ -1009,6 +1012,9 @@ importers: specifier: ^4.4.2 version: 4.4.2 devDependencies: + '@faker-js/faker': + specifier: ^9.9.0 + version: 9.9.0 '@graphql-codegen/cli': specifier: 5.0.5 version: 5.0.5(@parcel/watcher@2.5.1)(@types/node@22.13.14)(encoding@0.1.13)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(supports-color@8.1.1)(typescript@5.5.2) @@ -2184,7 +2190,7 @@ importers: version: 6.6.3 '@testing-library/react': specifier: ^16.0.0 - version: 16.0.0(@testing-library/dom@10.1.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/lodash': specifier: 4.17.5 version: 4.17.5 @@ -3659,6 +3665,10 @@ packages: resolution: {integrity: sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==} engines: {node: '>=14.0.0', npm: '>=6.0.0'} + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fal-works/esbuild-plugin-global-externals@2.1.2': resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} @@ -8421,6 +8431,10 @@ packages: resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} engines: {node: '>=18'} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + '@testing-library/jest-dom@6.6.3': resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} @@ -11481,6 +11495,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@1.0.0: + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} + execa@7.2.0: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} @@ -11969,6 +11987,10 @@ packages: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} engines: {node: '>=10'} + get-stream@4.1.0: + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -12601,6 +12623,10 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + intersection-observer@0.10.0: resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==} @@ -12882,6 +12908,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -14514,6 +14544,10 @@ packages: engines: {node: '>= 4'} hasBin: true + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14776,6 +14810,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -15889,6 +15927,10 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -16420,6 +16462,11 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shelljs@0.9.2: + resolution: {integrity: sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==} + engines: {node: '>=18'} + hasBin: true + shiki@1.6.0: resolution: {integrity: sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==} @@ -16447,6 +16494,11 @@ packages: should@13.2.3: resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} + shx@0.4.0: + resolution: {integrity: sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==} + engines: {node: '>=18'} + hasBin: true + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -16820,6 +16872,10 @@ packages: resolution: {integrity: sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==} engines: {node: '>=0.10.0'} + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -20296,7 +20352,7 @@ snapshots: '@contentlayer2/core': 0.5.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.5.3 chokidar: 3.5.3 - fast-glob: 3.3.2 + fast-glob: 3.3.3 gray-matter: 4.0.3 imagescript: 1.3.0 micromatch: 4.0.8 @@ -20751,6 +20807,8 @@ snapshots: '@faker-js/faker@7.6.0': {} + '@faker-js/faker@9.9.0': {} + '@fal-works/esbuild-plugin-global-externals@2.1.2': {} '@fastify/ajv-compiler@3.6.0': @@ -27715,6 +27773,17 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.10 + '@types/aria-query': 5.0.2 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + '@testing-library/jest-dom@6.6.3': dependencies: '@adobe/css-tools': 4.4.0 @@ -27735,6 +27804,16 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@testing-library/react@16.0.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.10 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@testing-library/user-event@14.6.1(@testing-library/dom@10.1.0)': dependencies: '@testing-library/dom': 10.1.0 @@ -27748,7 +27827,7 @@ snapshots: '@ts-morph/common@0.19.0': dependencies: - fast-glob: 3.3.2 + fast-glob: 3.3.3 minimatch: 7.4.6 mkdirp: 2.1.6 path-browserify: 1.0.1 @@ -31203,6 +31282,16 @@ snapshots: dependencies: eventsource-parser: 3.0.2 + execa@1.0.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 4.1.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + execa@7.2.0: dependencies: cross-spawn: 7.0.6 @@ -31782,6 +31871,10 @@ snapshots: get-stdin@8.0.0: {} + get-stream@4.1.0: + dependencies: + pump: 3.0.3 + get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -32710,6 +32803,8 @@ snapshots: internmap@2.0.3: {} + interpret@1.4.0: {} + intersection-observer@0.10.0: {} invariant@2.2.4: @@ -32964,6 +33059,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@1.1.0: {} + is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -35374,6 +35471,10 @@ snapshots: shell-quote: 1.8.1 string.prototype.padend: 3.1.5 + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -35688,6 +35789,8 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-finally@1.0.0: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -36991,6 +37094,10 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.6.11 + rechoir@0.6.2: + dependencies: + resolve: 1.22.10 + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -37816,6 +37923,13 @@ snapshots: shell-quote@1.8.1: {} + shelljs@0.9.2: + dependencies: + execa: 1.0.0 + fast-glob: 3.3.3 + interpret: 1.4.0 + rechoir: 0.6.2 + shiki@1.6.0: dependencies: '@shikijs/core': 1.6.0 @@ -37859,6 +37973,11 @@ snapshots: should-type-adaptors: 1.1.0 should-util: 1.0.1 + shx@0.4.0: + dependencies: + minimist: 1.2.8 + shelljs: 0.9.2 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -38292,6 +38411,8 @@ snapshots: strip-color@0.1.0: {} + strip-eof@1.0.0: {} + strip-final-newline@3.0.0: {} strip-indent@3.0.0: From 5f76c85dd191dea92f902c0716a120916658526a Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Wed, 23 Jul 2025 17:18:07 +0800 Subject: [PATCH 3/4] chore: cleanup telemetry codeowners (#37396) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3feaae032a117..043d489e6172d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ /packages/ui/ @supabase/design /packages/shared-data/pricing.ts @roryw10 @supabase/billing /packages/shared-data/plans.ts @roryw10 @supabase/billing -/packages/common/telemetry-constants.ts @4L3k51 @supabase/growth-eng +/packages/common/telemetry-constants.ts @supabase/growth-eng /apps/studio/ @supabase/Dashboard From 7505c2923e595f3b64bc14ab0f0227b2e04c2ea5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 23 Jul 2025 17:40:19 +0800 Subject: [PATCH 4/4] Update AI model check (#37395) * Update AI model chekc * Add comment * Fix test --- apps/studio/lib/ai/model.test.ts | 2 +- apps/studio/lib/ai/model.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/studio/lib/ai/model.test.ts b/apps/studio/lib/ai/model.test.ts index 0f4f75eca3d31..8418ad36893b9 100644 --- a/apps/studio/lib/ai/model.test.ts +++ b/apps/studio/lib/ai/model.test.ts @@ -18,7 +18,7 @@ describe('getModel', () => { beforeEach(() => { vi.resetAllMocks() - vi.stubEnv('AWS_BEDROCK_PROFILE', 'test') + vi.stubEnv('AWS_BEDROCK_ROLE_ARN', 'test') }) afterEach(() => { diff --git a/apps/studio/lib/ai/model.ts b/apps/studio/lib/ai/model.ts index 57698f2aaeb2a..79e52cd4e2015 100644 --- a/apps/studio/lib/ai/model.ts +++ b/apps/studio/lib/ai/model.ts @@ -33,10 +33,10 @@ export const ModelErrorMessage = export async function getModel(routingKey?: string, isLimited?: boolean): Promise { const hasAwsCredentials = await checkAwsCredentials() - const hasAwsBedrockprofile = !!process.env.AWS_BEDROCK_PROFILE + const hasAwsBedrockRoleArn = !!process.env.AWS_BEDROCK_ROLE_ARN const hasOpenAIKey = !!process.env.OPENAI_API_KEY - if (hasAwsBedrockprofile && hasAwsCredentials) { + if (hasAwsBedrockRoleArn && hasAwsCredentials) { const bedrockModel = IS_THROTTLED || isLimited ? BEDROCK_NORMAL_MODEL : BEDROCK_PRO_MODEL const bedrock = createRoutedBedrock(routingKey) @@ -45,6 +45,7 @@ export async function getModel(routingKey?: string, isLimited?: boolean): Promis } } + // [Joshen] Only for local/self-hosted, hosted should always only use bedrock if (hasOpenAIKey) { return { model: openai(OPENAI_MODEL),