diff --git a/packages/web/app/src/components/ui/command.tsx b/packages/web/app/src/components/ui/command.tsx index 4627d6824..cdfc48c8c 100644 --- a/packages/web/app/src/components/ui/command.tsx +++ b/packages/web/app/src/components/ui/command.tsx @@ -2,11 +2,10 @@ import React from 'react'; import { Command as CommandPrimitive } from 'cmdk'; -import { Search, XIcon } from 'lucide-react'; +import { Search } from 'lucide-react'; import { Dialog, DialogContent } from '@/components/ui/dialog'; import { cn } from '@/lib/utils'; import { type DialogProps } from '@radix-ui/react-dialog'; -import { Button } from './button'; const Command = React.forwardRef< React.ElementRef, @@ -45,7 +44,7 @@ const CommandInput = React.forwardRef< React.ElementRef, CommandInputProps >(({ className, ...props }, ref) => ( -
+
- {props.closeFn && ( - - )}
)); diff --git a/packages/web/app/src/pages/target.tsx b/packages/web/app/src/pages/target.tsx index 3ae63e234..ee9af4557 100644 --- a/packages/web/app/src/pages/target.tsx +++ b/packages/web/app/src/pages/target.tsx @@ -1,7 +1,6 @@ -import { ChangeEventHandler, ReactElement, useCallback, useEffect, useRef, useState } from 'react'; -import { Check, ChevronsUpDown } from 'lucide-react'; +import { ReactElement, useState } from 'react'; +import { ChevronsUpDown, XIcon } from 'lucide-react'; import { useQuery } from 'urql'; -import { useDebouncedCallback } from 'use-debounce'; import { Page, TargetLayout } from '@/components/layouts/target'; import { MarkAsValid } from '@/components/target/history/MarkAsValid'; import { Button } from '@/components/ui/button'; @@ -18,18 +17,24 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { QueryError } from '@/components/ui/query-error'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Accordion } from '@/components/v2/accordion'; -import { noSchema, noSchemaVersion } from '@/components/v2/empty-list'; +import { EmptyList, noSchema, noSchemaVersion } from '@/components/v2/empty-list'; import { GraphQLBlock, GraphQLHighlight } from '@/components/v2/graphql-block'; import { DocumentType, FragmentType, graphql, useFragment } from '@/gql'; import { ProjectType, RegistryModel } from '@/gql/graphql'; import { TargetAccessScope, useTargetAccess } from '@/lib/access/target'; -import { cn } from '@/lib/utils'; import { Link, useRouter } from '@tanstack/react-router'; type CompositeSchema = Extract< DocumentType, { - __typename?: 'CompositeSchema'; + __typename: 'CompositeSchema'; + } +>; + +type SingleSchema = Extract< + DocumentType, + { + __typename: 'SingleSchema'; } >; @@ -39,21 +44,11 @@ function isCompositeSchema( return schema.__typename === 'CompositeSchema'; } -function SchemaBlock({ schema, scrollToMe }: { schema: CompositeSchema; scrollToMe: boolean }) { - const ref = useRef(null); - const scrolled = useRef(false); - - useEffect(() => { - if (scrollToMe && ref.current && scrolled.current === false) { - ref.current.scrollIntoView({ behavior: 'smooth' }); - scrolled.current = true; - } - }, [scrollToMe]); - +function SchemaBlock({ schema }: { schema: CompositeSchema }) { return ( -
+
{schema.service ?? 'SDL'}
@@ -69,83 +64,47 @@ function SchemaBlock({ schema, scrollToMe }: { schema: CompositeSchema; scrollTo ); } -const Schemas_ProjectFragment = graphql(` - fragment Schemas_ProjectFragment on Project { - id - type - } -`); - -function Schemas({ - filterService, - openItems, - setOpenItems, - ...props -}: { - project: FragmentType; - schemas: FragmentType[]; - filterService?: string; - openItems: string[]; - setOpenItems: (items: string[]) => void; -}): ReactElement { - const project = useFragment(Schemas_ProjectFragment, props.project); - const schemas = useFragment(SchemaView_SchemaFragment, props.schemas); - - if (project.type === ProjectType.Single) { - const [schema] = schemas; +function Schemas(props: { schemas?: readonly CompositeSchema[]; schema?: SingleSchema }) { + if (props.schema) { return ( ); } - const filteredSchemas = schemas.filter(isCompositeSchema).filter(schema => { - if (filterService && 'service' in schema && schema.service) { - return schema.service.toLowerCase() === filterService.toLowerCase(); - } - return true; - }); + if (!props.schemas) { + console.error('No schema or schemas props provided'); + return null; + } - // Display format should be defined based on the length of `schemas`, and not `filteredSchemas`. - // Otherwise, the accordion will be displayed by default but the list (disabled accordion) when filtering. - const displayFormat = schemas.length > 1 ? 'dynamic' : 'static'; + if (props.schemas.length > 1) { + return ( + + {props.schemas.map(schema => ( + + ))} + + ); + } + + const schema = props.schemas[0]; + + if (!schema) { + return ( + + ); + } return ( -
- {displayFormat === 'dynamic' ? ( - setOpenItems(items)} - > - {filteredSchemas.map(schema => ( - - ))} - - ) : ( - setOpenItems(items)} - > - {filteredSchemas.map(schema => ( - - ))} - - )} -
+ + + ); } @@ -165,25 +124,21 @@ const SchemaView_ProjectFragment = graphql(` cleanId type registryModel - ...Schemas_ProjectFragment } `); const SchemaView_SchemaFragment = graphql(` fragment SchemaView_SchemaFragment on Schema { + __typename ... on SingleSchema { id - author source - commit } ... on CompositeSchema { id - author source service url - commit } } `); @@ -209,33 +164,23 @@ function SchemaView(props: { organization: FragmentType; project: FragmentType; target: FragmentType; - highlightedService: string | null; }): ReactElement | null { const organization = useFragment(SchemaView_OrganizationFragment, props.organization); const project = useFragment(SchemaView_ProjectFragment, props.project); const target = useFragment(SchemaView_TargetFragment, props.target); - const [filterService, setFilterService] = useState(props.highlightedService ?? ''); - const [term, setTerm] = useState(props.highlightedService ?? ''); - const [openItems, setOpenItems] = useState([]); - const debouncedFilter = useDebouncedCallback((value: string) => { - setFilterService(value); - }, 500); - const handleChange = useCallback>( - event => { - const value = event.target.value; - debouncedFilter(value); - setFilterService(value); - setTerm(value); - setOpen(false); - setOpenItems(prevItems => [...new Set([...prevItems, value])]); - }, - [debouncedFilter, setTerm], - ); - const reset = useCallback(() => { - setOpenItems([]); - setFilterService(''); - setTerm(''); - }, [setFilterService]); + const router = useRouter(); + const selectedServiceName = + 'service' in router.latestLocation.search && + typeof router.latestLocation.search.service === 'string' + ? router.latestLocation.search.service + : null; + + const [open, setOpen] = useState(false); + const reset = () => { + void router.navigate({ + search: {}, + }); + }; const isDistributed = project.type === ProjectType.Federation || project.type === ProjectType.Stitching; @@ -258,95 +203,86 @@ function SchemaView(props: { return noSchema; } - const canMarkAsValid = project.registryModel === RegistryModel.Legacy; - const showExtra = canManage; + const canMarkAsValid = project.registryModel === RegistryModel.Legacy && canManage; - const [open, setOpen] = useState(false); const schemas = useFragment(SchemaView_SchemaFragment, target.latestSchemaVersion?.schemas.nodes); const compositeSchemas = schemas?.filter(isCompositeSchema) as CompositeSchema[]; + const singleSchema = schemas?.filter(schema => !isCompositeSchema(schema))[0] as + | SingleSchema + | undefined; + const schemasToDisplay = selectedServiceName + ? compositeSchemas.filter(schema => schema.service === selectedServiceName) + : compositeSchemas; return ( <> - {showExtra ? ( -
-
- {isDistributed && ( - - - - - - - - No schema found. - - -
- {compositeSchemas?.map(schema => ( - { - const selectedSchema = compositeSchemas.find( - s => s.service === currentValue, - ); - if (selectedSchema) { - setOpenItems(prevItems => [ - ...new Set([...prevItems, selectedSchema.id]), - ]); - handleChange({ target: { value: currentValue } } as any); - } - }} - className="cursor-pointer" - > - - {schema.service} - - ))} -
-
-
-
-
-
- )} - {canMarkAsValid ? ( - <> - {' '} - - ) : null} -
+
+
+ {isDistributed && schemas && schemas.length > 1 && ( + + + + + {selectedServiceName ? ( + + ) : null} + + + + No results. + + + {compositeSchemas?.map(schema => ( + { + setOpen(false); + void router.navigate({ + search: { service: serviceName }, + }); + }} + className="cursor-pointer truncate" + > +
+
{schema.service}
+
{schema.url}
+
+
+ ))} +
+
+
+
+
+ )} + {canMarkAsValid ? ( + <> + {' '} + + ) : null}
- ) : null} - +
+ {isDistributed ? : } ); } @@ -368,7 +304,6 @@ const TargetSchemaPageQuery = graphql(` `); function TargetSchemaPage(props: { organizationId: string; projectId: string; targetId: string }) { - const router = useRouter(); const [query] = useQuery({ query: TargetSchemaPageQuery, variables: { @@ -386,9 +321,6 @@ function TargetSchemaPage(props: { organizationId: string; projectId: string; ta const currentProject = query.data?.project; const target = query.data?.target; - // TODO(router) check if it works - const serviceNameFromHash = router.latestLocation.hash?.replace('service-', '') ?? null; - return (
{query.fetching ? null : currentOrganization && currentProject && target ? ( - + ) : null}
diff --git a/packages/web/app/src/stories/schema-filter.stories.tsx b/packages/web/app/src/stories/schema-filter.stories.tsx index f4c3a4f1a..b163d09c1 100644 --- a/packages/web/app/src/stories/schema-filter.stories.tsx +++ b/packages/web/app/src/stories/schema-filter.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable import/no-extraneous-dependencies */ import { useState } from 'react'; import { Check, ChevronsUpDown } from 'lucide-react'; import { Button } from '@/components/ui/button';