import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { CombinedError, useQuery } from 'urql'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { ProductUpdatesLink } from '@/components/ui/docs-note'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { FragmentType, graphql, useFragment } from '@/gql'; import { UpdateSchemaCompositionInput } from '@/gql/graphql'; import { useNotifications } from '@/lib/hooks'; import { zodResolver } from '@hookform/resolvers/zod'; import { CheckIcon, Cross2Icon, ReloadIcon, UpdateIcon } from '@radix-ui/react-icons'; const ExternalCompositionStatus_TestQuery = graphql(` query ExternalCompositionStatus_TestQuery($selector: TestExternalSchemaCompositionInput!) { testExternalSchemaComposition(selector: $selector) { ok { id isNativeFederationEnabled externalSchemaComposition { endpoint } } error { message } } } `); const ExternalCompositionSettings_OrganizationFragment = graphql(` fragment ExternalCompositionSettings_OrganizationFragment on Organization { id slug } `); const ExternalCompositionSettings_ProjectFragment = graphql(` fragment ExternalCompositionSettings_ProjectFragment on Project { id slug isNativeFederationEnabled externalSchemaComposition { endpoint } } `); const ExternalCompositionSettings_UpdateResultFragment = graphql(` fragment ExternalCompositionSettings_UpdateResultFragment on UpdateSchemaCompositionResult { ok { updatedProject { id externalSchemaComposition { endpoint } } } error { message ... on UpdateSchemaCompositionExternalError { inputErrors { endpoint secret } } } } `); enum TestState { LOADING, ERROR, SUCCESS, } const ExternalCompositionStatus = ({ projectSlug, organizationSlug, }: { projectSlug: string; organizationSlug: string; }) => { const [{ data, error: gqlError, fetching }, executeTestQuery] = useQuery({ query: ExternalCompositionStatus_TestQuery, variables: { selector: { projectSlug, organizationSlug, }, }, requestPolicy: 'network-only', }); const error = gqlError?.message ?? data?.testExternalSchemaComposition?.error?.message; const testState = fetching ? TestState.LOADING : error ? TestState.ERROR : data?.testExternalSchemaComposition?.ok?.externalSchemaComposition?.endpoint ? TestState.SUCCESS : null; const [hidden, setHidden] = useState(); useEffect(() => { // only hide the success icon after the duration if (testState !== TestState.SUCCESS) return; const timerId = setTimeout(() => { if (testState === TestState.SUCCESS) { setHidden(false); } }, 5000); return () => { clearTimeout(timerId); }; }, [testState]); return ( {testState === TestState.LOADING ? ( e.preventDefault()} /> Connecting... ) : ( { e.preventDefault(); setHidden(true); executeTestQuery(); }} /> Execute test )} {testState === TestState.ERROR ? ( e.preventDefault()} /> {error} ) : null} {testState === TestState.SUCCESS && !hidden ? ( e.preventDefault()} /> Service is available ) : null} ); }; const formSchema = z.object({ endpoint: z .string({ required_error: 'Please provide an endpoint', }) .url({ message: 'Invalid URL', }), secret: z .string({ required_error: 'Please provide a secret', }) .min(2, 'Too short') .max(256, 'Max 256 characters long'), }); type FormValues = z.infer; export const ExternalCompositionSettings = (props: { project: FragmentType; organization: FragmentType; activeCompositionMode: 'native' | 'external' | 'legacy'; onMutate: ( input: UpdateSchemaCompositionInput, ) => Promise< FragmentType | CombinedError >; }) => { const project = useFragment(ExternalCompositionSettings_ProjectFragment, props.project); const organization = useFragment( ExternalCompositionSettings_OrganizationFragment, props.organization, ); const notify = useNotifications(); const [error, setError] = useState(); const [isMutating, setIsMutating] = useState(false); const form = useForm({ resolver: zodResolver(formSchema), mode: 'onChange', defaultValues: { endpoint: project.externalSchemaComposition?.endpoint ?? '', secret: '', }, disabled: isMutating, }); function onSubmit(values: FormValues) { setError(undefined); setIsMutating(true); void props .onMutate({ project: { bySelector: { projectSlug: project.slug, organizationSlug: organization.slug, }, }, method: { external: { endpoint: values.endpoint, secret: values.secret, }, }, }) .then(result => { setIsMutating(false); if (result instanceof CombinedError) { notify(result.message, 'error'); setError(result.message); } else { // actually not a hook // eslint-disable-next-line react-hooks/rules-of-hooks const updateResult = useFragment( ExternalCompositionSettings_UpdateResultFragment, result, ); if (updateResult.ok) { const endpoint = updateResult.ok.updatedProject.externalSchemaComposition?.endpoint; notify('External composition enabled.', 'success'); if (endpoint) { form.reset( { endpoint, secret: '', }, { keepDirty: false, keepDirtyValues: false, }, ); } } else if (updateResult.error) { notify(updateResult.error.message, 'error'); setError(updateResult.error.message); if (updateResult.error.__typename === 'UpdateSchemaCompositionExternalError') { if (updateResult.error.inputErrors?.endpoint) { form.setError('endpoint', { type: 'manual', message: updateResult.error.inputErrors.endpoint, }); } if (updateResult.error.inputErrors?.secret) { form.setError('secret', { type: 'manual', message: updateResult.error.inputErrors.secret, }); } } } } }); } return (

For advanced users, you can configure an endpoint for external schema compositions. This can be used to implement custom composition logic.

Read about external schema composition in our documentation.
( HTTP Endpoint A POST request will be sent to that endpoint
{!form.formState.isDirty && project.externalSchemaComposition?.endpoint ? ( ) : null}
)} /> ( Secret The secret is needed to sign and verify the request. )} />
{error &&
{error}
}
); };