diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/EditSecretModal.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/EditSecretModal.tsx index 02bc97521dd66..b87b878264895 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/EditSecretModal.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/EditSecretModal.tsx @@ -1,158 +1,207 @@ -import { isEmpty } from 'lodash' -import { Eye, EyeOff } from 'lucide-react' -import { useEffect, useState } from 'react' +import { useState } from 'react' +import { EyeOff, Eye } from 'lucide-react' +import { type SubmitHandler, useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { z } from 'zod' import { toast } from 'sonner' +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + Form_Shadcn_, + FormControl_Shadcn_, + FormField_Shadcn_, + Input_Shadcn_, +} from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query' import { useVaultSecretUpdateMutation } from 'data/vault/vault-secret-update-mutation' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import type { VaultSecret } from 'types' -import { Button, Form, Input, Modal } from 'ui' interface EditSecretModalProps { - selectedSecret: VaultSecret | undefined + visible: boolean + secret: VaultSecret onClose: () => void } -const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => { - const { data: project } = useSelectedProjectQuery() - const [showSecretValue, setShowSecretValue] = useState(false) +const SecretSchema = z.object({ + name: z.string().min(1, 'Please provide a name for your secret'), + description: z.string().optional(), + secret: z.string().min(1, 'Please enter your secret value'), +}) - const { mutateAsync: updateSecret } = useVaultSecretUpdateMutation() +const formId = 'edit-vault-secret-form' - let INITIAL_VALUES = { - name: selectedSecret?.name ?? '', - description: selectedSecret?.description ?? '', - secret: selectedSecret?.decryptedSecret ?? '', +const EditSecretModal = ({ visible, secret, onClose }: EditSecretModalProps) => { + const [showSecretValue, setShowSecretValue] = useState(false) + const { data: project } = useSelectedProjectQuery() + const { data, isLoading: isLoadingSecretValue } = useVaultSecretDecryptedValueQuery( + { + projectRef: project?.ref, + id: secret.id, + connectionString: project?.connectionString, + }, + { enabled: !!project?.ref } + ) + const values = { + name: secret.name ?? '', + description: secret.description ?? '', + secret: secret.decryptedSecret ?? data ?? '', } + const form = useForm>({ + resolver: zodResolver(SecretSchema), + defaultValues: values, + values, + }) - useEffect(() => { - if (selectedSecret !== undefined) { - setShowSecretValue(false) - } - }, [selectedSecret]) - - const validate = (values: any) => { - const errors: any = {} - if (values.name.length === 0) errors.name = 'Please provide a name for your secret' - if (values.secret.length === 0) errors.secret = 'Please enter your secret value' - return errors - } + const { mutate: updateSecret, isLoading: isSubmitting } = useVaultSecretUpdateMutation() - const onUpdateSecret = async (values: any, { setSubmitting }: any) => { + const onSubmit: SubmitHandler> = async (values) => { if (!project) return console.error('Project is required') - try { - const payload: Partial = {} - if (values.name !== selectedSecret?.name) payload.name = values.name - if (values.description !== selectedSecret?.description) - payload.description = values.description - payload.secret = values.secret + const payload: Partial = { + secret: values.secret, + } + if (values.name !== secret.name) payload.name = values.name + if (values.description !== secret.description) payload.description = values.description - if (!isEmpty(payload) && selectedSecret) { - setSubmitting(true) - const res = await updateSecret({ + if (Object.keys(payload).length > 0) { + updateSecret( + { projectRef: project.ref, connectionString: project?.connectionString, - id: selectedSecret.id, + id: secret.id, ...payload, - }) - if (!res.error) { - toast.success('Successfully updated secret') - setSubmitting(false) - onClose() - } else { - toast.error(`Failed to update secret: ${res.error.message}`) - setSubmitting(false) + }, + { + onSuccess: () => { + toast.success('Successfully updated secret') + onClose() + }, + onError: (error) => { + toast.error(`Failed to update secret: ${error.message}`) + }, } - } - } finally { + ) } } return ( - Edit secret} + { + if (!open) { + form.reset() + onClose() + } + }} > -
- {({ isSubmitting, resetForm }: any) => { - const { - data, - isLoading: isLoadingSecretValue, - isSuccess: isSuccessSecretValue, - // [Joshen] JFYI this is breaking rules of hooks, will be fixed once we move to - // using react hook form instead - // eslint-disable-next-line react-hooks/rules-of-hooks - } = useVaultSecretDecryptedValueQuery( - { - projectRef: project?.ref!, - id: selectedSecret?.id!, - connectionString: project?.connectionString, - }, - { enabled: selectedSecret !== undefined && !!(project?.ref && selectedSecret?.id) } - ) - - // [Joshen] JFYI this is breaking rules of hooks, will be fixed once we move to - // using react hook form instead - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => { - if (selectedSecret !== undefined && isSuccessSecretValue) { - resetForm({ - values: { ...INITIAL_VALUES, secret: data }, - initialValues: { ...INITIAL_VALUES, secret: data }, - }) - } - }, [selectedSecret, isSuccessSecretValue]) - - return isLoadingSecretValue ? ( - - - - ) : ( - <> - - - - - - - - - ) - }} -
-
+ + + Edit secret + + + {isLoadingSecretValue ? ( + + + + ) : ( + <> + + +
+ ( + + + + + + )} + /> + ( + + + + + + )} + /> + ( + + +
+ +
+
+
+ )} + /> + +
+
+ + + + + + )} +
+ ) } diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx index f7b1f5719ff51..20a1ace90eb87 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx @@ -19,17 +19,17 @@ import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-react' import type { VaultSecret } from 'types' +import EditSecretModal from './EditSecretModal' interface SecretRowProps { secret: VaultSecret - onSelectEdit: (secret: VaultSecret) => void onSelectRemove: (secret: VaultSecret) => void } -const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) => { +const SecretRow = ({ secret, onSelectRemove }: SecretRowProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() - + const [modal, setModal] = useState(null) const [revealSecret, setRevealSecret] = useState(false) const name = secret?.name ?? 'No name provided' @@ -46,6 +46,8 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) => } ) + const onCloseModal = () => setModal(null) + return (
@@ -94,9 +96,10 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) => {secret.updated_at === secret.created_at ? 'Added' : 'Updated'} on{' '} {dayjs(secret.updated_at).format('MMM D, YYYY')}

+ -
) diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx index 8484346130f92..43388850602f8 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx @@ -9,22 +9,29 @@ import { DocsButton } from 'components/ui/DocsButton' import { useVaultSecretsQuery } from 'data/vault/vault-secrets-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import type { VaultSecret } from 'types' -import { Button, Input, Listbox, Separator } from 'ui' +import { + Button, + Input, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, + Separator, +} from 'ui' import AddNewSecretModal from './AddNewSecretModal' import DeleteSecretModal from './DeleteSecretModal' -import EditSecretModal from './EditSecretModal' import SecretRow from './SecretRow' +import type { VaultSecret } from 'types' export const SecretsManagement = () => { const { search } = useParams() const { data: project } = useSelectedProjectQuery() const [searchValue, setSearchValue] = useState('') - const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at') const [showAddSecretModal, setShowAddSecretModal] = useState(false) - const [selectedSecretToEdit, setSelectedSecretToEdit] = useState() const [selectedSecretToRemove, setSelectedSecretToRemove] = useState() + const [selectedSort, setSelectedSort] = useState('updated_at') const canManageSecrets = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') @@ -49,7 +56,7 @@ export const SecretsManagement = () => { if (selectedSort === 'updated_at') { return Number(new Date(s.updated_at)) } else { - return s[selectedSort] + return s[selectedSort as keyof VaultSecret] } } ) @@ -81,26 +88,21 @@ export const SecretsManagement = () => { : [] } /> -
- - + + + + <>Sort by {selectedSort} + + + + Updated at - - + + Name - - -
+ + +
@@ -134,11 +136,7 @@ export const SecretsManagement = () => { {secrets.map((secret, idx) => { return ( - + {idx !== secrets.length - 1 && } ) @@ -167,10 +165,6 @@ export const SecretsManagement = () => {
- setSelectedSecretToEdit(undefined)} - /> setSelectedSecretToRemove(undefined)} diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/__tests__/EditSecretModal.test.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/__tests__/EditSecretModal.test.tsx new file mode 100644 index 0000000000000..92fd59d67e162 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/__tests__/EditSecretModal.test.tsx @@ -0,0 +1,103 @@ +import { useState } from 'react' +import { describe, expect, it, beforeEach, vi } from 'vitest' +import { mockAnimationsApi } from 'jsdom-testing-mocks' +import { screen, waitFor, fireEvent } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' +import { render } from 'tests/helpers' +import { addAPIMock } from 'tests/lib/msw' +import { ProjectContextProvider } from 'components/layouts/ProjectLayout/ProjectContext' +import EditSecretModal from '../EditSecretModal' +import { routerMock } from 'tests/lib/route-mock' + +const secret = { + id: '47ca58b4-01c5-4a71-8814-c73856b02e0e', + name: 'test', + description: 'new text', + secret: 'NASR0SoksURJ0OorMJ9FzraTzcqSWk5u1PQa2r4c3w9rUVc=', + created_at: '2025-07-13 12:57:47.62208+00', + updated_at: '2025-07-13 14:51:37.818223+00', +} + +const Page = ({ onClose }: { onClose: () => void }) => { + const [open, setOpen] = useState(false) + + return ( + + + + { + setOpen(false) + onClose() + }} + /> + + ) +} + +mockAnimationsApi() + +describe(`EditSecretModal`, () => { + beforeEach(() => { + // useSelectedProjectQuery -> useParams + routerMock.setCurrentUrl(`/project/default/integrations/vault/secrets`) + // 'http://localhost:3000/api/platform/projects/default' + addAPIMock({ + method: `get`, + path: `/platform/projects/:ref`, + // @ts-expect-error + response: { + cloud_provider: 'localhost', + id: 1, + inserted_at: '2021-08-02T06:40:40.646Z', + name: 'Default Project', + organization_id: 1, + ref: 'default', + region: 'local', + status: 'ACTIVE_HEALTHY', + }, + }) + // 'http://localhost:3000/api/platform/pg-meta/default/query?key=projects-default-secrets-47ca58b4-01c5-4a71-8814-c73856b02e0e' + // 'http://localhost:3000/api/platform/pg-meta/default/query?key=' + // useVaultSecretDecryptedValueQuery + useVaultSecretUpdateMutation + // both call the same endpoint but execute different SQL queries + addAPIMock({ + method: `post`, + path: `/platform/pg-meta/:ref/query`, + // @ts-expect-error this path erroneously has a `never` return type when it should be `unknown` since it executes a SQL query + response: [{ decrypted_secret: 'bar', update_secret: '' }], + }) + }) + + it(`renders a modal pre-filled with the secret's values`, async () => { + const onClose = vi.fn() + render() + + const openButton = screen.getByRole(`button`, { name: `Open` }) + await userEvent.click(openButton) + + await screen.findByRole(`dialog`) + + const nameInput = screen.getByLabelText(`Name`) + const descriptionInput = screen.getByLabelText(`Description`) + const valueInput = screen.getByLabelText(`Secret value`) + const togglePasswordButton = screen.getByRole(`button`, { name: `Show secret value` }) + const submitButton = screen.getByRole(`button`, { name: `Update secret` }) + + expect(nameInput).toHaveValue(secret.name) + expect(descriptionInput).toHaveValue(secret.description) + expect(valueInput).toHaveAttribute(`type`, `password`) + await userEvent.click(togglePasswordButton) + expect(valueInput).toHaveAttribute(`type`, `text`) + + await userEvent.type(nameInput, `updated-name`) + await userEvent.clear(descriptionInput) + await userEvent.type(valueInput, `qux`) + + fireEvent.click(submitButton) + + await waitFor(() => expect(onClose).toHaveBeenCalledOnce()) + }) +}) diff --git a/apps/studio/tests/lib/msw.ts b/apps/studio/tests/lib/msw.ts index 383df91d8c8df..070b38ff79812 100644 --- a/apps/studio/tests/lib/msw.ts +++ b/apps/studio/tests/lib/msw.ts @@ -44,11 +44,14 @@ export const addAPIMock =

, M> | HttpResponseResolver -}) => { +}: SuccessResponse, M> extends never + ? // Don't require a mocked response when the API doesn't return one + { method: M; path: P; response?: never } + : { + method: M + path: P + response: SuccessResponse, M> | HttpResponseResolver + }) => { const fullPath = `${API_URL}${path}` console.log('[MSW] Adding mock:', method.toUpperCase(), fullPath) diff --git a/apps/studio/tests/lib/route-mock.ts b/apps/studio/tests/lib/route-mock.ts index 9eeed08ce5314..4dca8b2149834 100644 --- a/apps/studio/tests/lib/route-mock.ts +++ b/apps/studio/tests/lib/route-mock.ts @@ -1,11 +1,26 @@ +import { glob } from 'fs/promises' import _routerMock from 'next-router-mock' import { createDynamicRouteParser } from 'next-router-mock/dynamic-routes' +import { normalize, posix, sep, join } from 'path' export const routerMock = _routerMock -routerMock.useParser( - createDynamicRouteParser([ - // These paths should match those found in the `/pages` folder: - '/projects/[ref]', - ]) -) +const pipe = + (...fns: Array<(arg: T) => T>) => + (value: T) => + fns.reduce((acc, fn) => fn(acc), value) +// normalize file paths to posix format (windows FS support) +const toPosix = (str: string) => str.replaceAll(sep, posix.sep) +const pagesRoot = toPosix(join(process.cwd(), `./pages`)) +// remove the base filepath +const removePrefix = (str: string) => str.replace(pagesRoot, ``) +// remove the file extension or index segment +const removeExt = (str: string) => str.replace(/.tsx|\/index.tsx/, ``) + +// Glob the `pages/` directory for all dynamic route segments +const paths = [ + ...(await Array.fromAsync(glob(`${pagesRoot}/**/*\].tsx`))), + ...(await Array.fromAsync(glob(`${pagesRoot}/**/**\]/**/index.tsx`))), +].map(pipe(normalize, toPosix, removePrefix, removeExt)) + +routerMock.useParser(createDynamicRouteParser(paths)) diff --git a/apps/studio/tests/setup/polyfills.ts b/apps/studio/tests/setup/polyfills.ts index f8702bb7e714b..e37e5482f5aa5 100644 --- a/apps/studio/tests/setup/polyfills.ts +++ b/apps/studio/tests/setup/polyfills.ts @@ -35,3 +35,5 @@ Object.defineProperties(globalThis, { ReadableStream: { value: ReadableStream }, TransformStream: { value: TransformStream }, }) + +window.HTMLElement.prototype.hasPointerCapture = vi.fn() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a940cf978c9d8..f22f313f01b3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -537,7 +537,7 @@ importers: devDependencies: '@graphiql/toolkit': specifier: ^0.9.1 - version: 0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0) + version: 0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.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) @@ -609,7 +609,7 @@ importers: version: 13.2.2 graphiql: specifier: ^4.0.2 - version: 4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) openapi-types: specifier: ^12.1.3 version: 12.1.3 @@ -11203,6 +11203,7 @@ packages: resolution: {integrity: sha512-t0q23FIpvHDTtnORW+bDJziGsal5uh9RJTJ1fyH8drd4lICOoXhJ5pLMUZ5C0VQei6dNmwTzzoTRgMkO9JgHEQ==} peerDependencies: eslint: '>= 5' + bundledDependencies: [] eslint-plugin-import@2.31.0: resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} @@ -20653,9 +20654,9 @@ snapshots: '@gar/promisify@1.1.3': {} - '@graphiql/plugin-doc-explorer@0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@graphiql/plugin-doc-explorer@0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@headlessui/react': 2.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) graphql: 16.11.0 react: 18.3.1 @@ -20669,10 +20670,10 @@ snapshots: - '@types/react-dom' - graphql-ws - '@graphiql/plugin-history@0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@graphiql/plugin-history@0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@graphiql/toolkit': 0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@graphiql/toolkit': 0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0) react: 18.3.1 react-compiler-runtime: 19.1.0-rc.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1) @@ -20712,9 +20713,9 @@ snapshots: - '@types/react-dom' - graphql-ws - '@graphiql/react@0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@graphiql/react@0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@graphiql/toolkit': 0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0) + '@graphiql/toolkit': 0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0) '@radix-ui/react-dialog': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': 2.1.12(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tooltip': 1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -20741,13 +20742,13 @@ snapshots: - '@types/react-dom' - graphql-ws - '@graphiql/toolkit@0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)': + '@graphiql/toolkit@0.11.2(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.11.0 meros: 1.3.0(@types/node@22.13.14) optionalDependencies: - graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.18.1) + graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.18.3) transitivePeerDependencies: - '@types/node' @@ -20761,13 +20762,13 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@graphiql/toolkit@0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)': + '@graphiql/toolkit@0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.11.0 meros: 1.3.0(@types/node@22.13.14) optionalDependencies: - graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.18.1) + graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.18.3) transitivePeerDependencies: - '@types/node' @@ -31234,11 +31235,11 @@ snapshots: graphemer@1.4.0: {} - graphiql@4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + graphiql@4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@graphiql/plugin-doc-explorer': 0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@graphiql/plugin-history': 0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@graphiql/plugin-doc-explorer': 0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@graphiql/plugin-history': 0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.2.1)(@types/node@22.13.14)(@types/react-dom@18.3.0)(@types/react@18.3.3)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3))(graphql@16.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) graphql: 16.11.0 react: 18.3.1 react-compiler-runtime: 19.1.0-rc.1(react@18.3.1) @@ -31325,13 +31326,6 @@ snapshots: dependencies: graphql: 16.11.0 - graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.1): - dependencies: - graphql: 16.11.0 - optionalDependencies: - ws: 8.18.1 - optional: true - graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.3): dependencies: graphql: 16.11.0