diff --git a/.changeset/fuzzy-plums-repeat.md b/.changeset/fuzzy-plums-repeat.md new file mode 100644 index 0000000000..bec4ce73b3 --- /dev/null +++ b/.changeset/fuzzy-plums-repeat.md @@ -0,0 +1,5 @@ +--- +'hive': patch +--- + +Added directions for publishing on no schema component diff --git a/packages/web/app/src/components/ui/code.tsx b/packages/web/app/src/components/ui/code.tsx new file mode 100644 index 0000000000..a30ef16712 --- /dev/null +++ b/packages/web/app/src/components/ui/code.tsx @@ -0,0 +1,58 @@ +import { useRef, type ComponentProps, type FC } from 'react'; +import cn from 'clsx'; +import { useHover } from '@/lib/hooks/use-hover'; +import { useTimed } from '@/lib/hooks/use-timed'; +import { CheckIcon, CopyIcon } from './icon'; + +export const Code: FC> = ({ children, className, ...props }) => { + const [copied, startCopyTimer] = useTimed(1500); + const [ref, hovering] = useHover(); + const codeRef = useRef(null); + // in case this browser does not support this newer API... + const navigatorClipboardSupport = typeof navigator.clipboard?.writeText === 'function'; + return ( + { + if (codeRef.current) { + const selection = window.getSelection(); + const range = document.createRange(); + range.setStart(codeRef.current, 0); + range.setEnd(codeRef.current, codeRef.current.childNodes.length); + selection?.removeAllRanges(); + selection?.addRange(range); + } + }} + > + + {children} + + + + ); +}; diff --git a/packages/web/app/src/components/ui/empty-list.tsx b/packages/web/app/src/components/ui/empty-list.tsx index 4eab0c95ee..4c24edb26e 100644 --- a/packages/web/app/src/components/ui/empty-list.tsx +++ b/packages/web/app/src/components/ui/empty-list.tsx @@ -1,7 +1,9 @@ -import { ReactElement } from 'react'; +import { ReactElement, ReactNode } from 'react'; import magnifier from '../../../public/images/figures/magnifier.svg?url'; +import { ProjectType } from '@/gql/graphql'; import { cn } from '@/lib/utils'; import { Card } from './card'; +import { Code } from './code'; import { DocsLink } from './docs-note'; import { Heading } from './heading'; @@ -10,15 +12,17 @@ export const EmptyList = ({ description, docsUrl, className, + children, }: { title: string; description: string; docsUrl?: string | null; + children?: ReactNode | null; className?: string; }): ReactElement => { return ( {title} {description} + {children} {docsUrl && Read about it in the documentation} ); @@ -43,13 +48,61 @@ export const noSchema = ( /> ); -export const noSchemaVersion = ( - -); +export const NoSchemaVersion = ({ + projectType = null, + recommendedAction = 'none', +}: { + projectType: ProjectType | null; + recommendedAction: 'publish' | 'check' | 'none'; +}): ReactElement => { + let children: ReactElement | null = null; + if (recommendedAction !== 'none') { + const isDistributed = + projectType === ProjectType.Federation || projectType === ProjectType.Stitching; + + if (recommendedAction === 'check') { + children = ( + <> +
+ It's recommended to check that the schema is valid and compatible with the state of the + registry before publishing. +
+
+ + {`hive schema:check ${isDistributed ? '--service --url ' : ''} --target "//" `} + +
+ + ); + } else if (recommendedAction === 'publish') { + children = ( + <> + {isDistributed && ( +
+ For distributed systems, it's recommended to publish the schema after the service is + deployed. +
+ )} +
+ + {`hive schema:publish ${isDistributed ? '--service --url ' : ''} --target "//" `} + +
+ + ); + } + } + + return ( + + {children} + + ); +}; export const noValidSchemaVersion = ( ( ); -export const CheckIcon = ({ className }: IconProps): ReactElement => ( +export const CheckIcon = ({ className, size }: IconProps & { size?: number }): ReactElement => ( (null); + + const handleMouseEnter = useCallback(() => { + setHovering(true); + }, []); + + const handleMouseLeave = useCallback(() => { + setHovering(false); + }, []); + + const customRef = useCallback( + (node: HTMLElement) => { + if (previousNode.current?.nodeType === Node.ELEMENT_NODE) { + previousNode.current.removeEventListener('mouseenter', handleMouseEnter); + previousNode.current.removeEventListener('mouseleave', handleMouseLeave); + } + + if (node?.nodeType === Node.ELEMENT_NODE) { + node.addEventListener('mouseenter', handleMouseEnter); + node.addEventListener('mouseleave', handleMouseLeave); + } + + previousNode.current = node; + }, + [handleMouseEnter, handleMouseLeave], + ); + + return [customRef, hovering] as const; +} diff --git a/packages/web/app/src/lib/hooks/use-timed.ts b/packages/web/app/src/lib/hooks/use-timed.ts new file mode 100644 index 0000000000..c8ea243c13 --- /dev/null +++ b/packages/web/app/src/lib/hooks/use-timed.ts @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; + +export function useTimed(wait: number = 1000) { + const [timer, setTimer] = useState(null); + useEffect(() => { + return () => { + if (timer) { + clearTimeout(timer); + } + }; + }, [timer]); + + const handler = () => { + if (timer) { + clearTimeout(timer); + } + setTimer( + setTimeout(() => { + setTimer(null); + }, wait), + ); + }; + return [timer !== null, handler] as const; +} diff --git a/packages/web/app/src/pages/target-apps.tsx b/packages/web/app/src/pages/target-apps.tsx index 291243581a..40cfc5b712 100644 --- a/packages/web/app/src/pages/target-apps.tsx +++ b/packages/web/app/src/pages/target-apps.tsx @@ -6,7 +6,7 @@ import { Page, TargetLayout } from '@/components/layouts/target'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { CardDescription } from '@/components/ui/card'; -import { EmptyList, noSchemaVersion } from '@/components/ui/empty-list'; +import { EmptyList, NoSchemaVersion } from '@/components/ui/empty-list'; import { Meta } from '@/components/ui/meta'; import { SubPageLayoutHeader } from '@/components/ui/page-content-layout'; import { QueryError } from '@/components/ui/query-error'; @@ -61,6 +61,10 @@ const TargetAppsViewQuery = graphql(` id __typename } + project { + id + type + } viewerCanViewAppDeployments appDeployments(first: 20, after: $after) { pageInfo { @@ -254,7 +258,10 @@ function TargetAppsView(props: { ) : !data.data?.target?.latestSchemaVersion ? ( - noSchemaVersion + ) : !data.data.target.appDeployments ? (
- {hasActiveSchemaCheck ? 'List is empty' : 'Your schema check list is empty'} + {hasActiveSchemaCheck ? ( + 'List is empty' + ) : ( + + )}
{hasActiveSchemaCheck diff --git a/packages/web/app/src/pages/target-explorer-deprecated.tsx b/packages/web/app/src/pages/target-explorer-deprecated.tsx index 00fe9ef162..c8ea236049 100644 --- a/packages/web/app/src/pages/target-explorer-deprecated.tsx +++ b/packages/web/app/src/pages/target-explorer-deprecated.tsx @@ -7,7 +7,7 @@ import { SchemaExplorerProvider } from '@/components/target/explorer/provider'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { DateRangePicker, presetLast7Days } from '@/components/ui/date-range-picker'; -import { noSchemaVersion } from '@/components/ui/empty-list'; +import { NoSchemaVersion } from '@/components/ui/empty-list'; import { Link } from '@/components/ui/link'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; @@ -165,6 +165,10 @@ const DeprecatedSchemaExplorer_DeprecatedSchemaQuery = graphql(` targetSlug: $targetSlug } ) { + project { + id + type + } id slug latestSchemaVersion { @@ -303,7 +307,10 @@ function DeprecatedSchemaExplorer(props: { /> ) : ( - noSchemaVersion + )} )} diff --git a/packages/web/app/src/pages/target-explorer-type.tsx b/packages/web/app/src/pages/target-explorer-type.tsx index 6f0e060c6d..7e7343c4cd 100644 --- a/packages/web/app/src/pages/target-explorer-type.tsx +++ b/packages/web/app/src/pages/target-explorer-type.tsx @@ -19,7 +19,7 @@ import { } from '@/components/target/explorer/provider'; import { GraphQLScalarTypeComponent } from '@/components/target/explorer/scalar-type'; import { GraphQLUnionTypeComponent } from '@/components/target/explorer/union-type'; -import { noSchemaVersion } from '@/components/ui/empty-list'; +import { NoSchemaVersion } from '@/components/ui/empty-list'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; @@ -162,6 +162,10 @@ const TargetExplorerTypenamePageQuery = graphql(` } } } + project { + id + type + } } operationsStats( selector: { @@ -263,7 +267,10 @@ function TypeExplorerPageContent(props: { styleDeprecated /> ) : type ? ( - noSchemaVersion + ) : (
Not found
)} diff --git a/packages/web/app/src/pages/target-explorer-unused.tsx b/packages/web/app/src/pages/target-explorer-unused.tsx index 07f66d29d7..37d5eafce5 100644 --- a/packages/web/app/src/pages/target-explorer-unused.tsx +++ b/packages/web/app/src/pages/target-explorer-unused.tsx @@ -6,7 +6,7 @@ import { SchemaVariantFilter } from '@/components/target/explorer/filter'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { DateRangePicker, presetLast7Days } from '@/components/ui/date-range-picker'; -import { EmptyList, noSchemaVersion } from '@/components/ui/empty-list'; +import { EmptyList, NoSchemaVersion } from '@/components/ui/empty-list'; import { Link } from '@/components/ui/link'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; @@ -237,6 +237,10 @@ const UnusedSchemaExplorer_UnusedSchemaQuery = graphql(` ...UnusedSchemaView_UnusedSchemaExplorerFragment } } + project { + id + type + } } operationsStats( selector: { @@ -355,7 +359,10 @@ function UnusedSchemaExplorer(props: { /> ) : ( - noSchemaVersion + )} )} diff --git a/packages/web/app/src/pages/target-explorer.tsx b/packages/web/app/src/pages/target-explorer.tsx index 9e6b1cabc0..857a1b0351 100644 --- a/packages/web/app/src/pages/target-explorer.tsx +++ b/packages/web/app/src/pages/target-explorer.tsx @@ -16,7 +16,7 @@ import { useSchemaExplorerContext, } from '@/components/target/explorer/provider'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { noSchemaVersion, noValidSchemaVersion } from '@/components/ui/empty-list'; +import { NoSchemaVersion, noValidSchemaVersion } from '@/components/ui/empty-list'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; @@ -135,6 +135,10 @@ const TargetExplorerPageQuery = graphql(` ...ExplorerPage_SchemaExplorerFragment } } + project { + id + type + } } operationsStats( selector: { @@ -269,7 +273,10 @@ function ExplorerPageContent(props: { ) : latestSchemaVersion ? ( noValidSchemaVersion ) : ( - noSchemaVersion + )} )} diff --git a/packages/web/app/src/pages/target-history.tsx b/packages/web/app/src/pages/target-history.tsx index bca7b936ed..90e71a6d0c 100644 --- a/packages/web/app/src/pages/target-history.tsx +++ b/packages/web/app/src/pages/target-history.tsx @@ -3,7 +3,7 @@ import { useQuery } from 'urql'; import { Page, TargetLayout } from '@/components/layouts/target'; import { BadgeRounded } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { noSchemaVersion } from '@/components/ui/empty-list'; +import { NoSchemaVersion } from '@/components/ui/empty-list'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; import { QueryError } from '@/components/ui/query-error'; @@ -176,6 +176,10 @@ const TargetHistoryPageQuery = graphql(` targetSlug: $targetSlug } ) { + project { + id + type + } id latestSchemaVersion { id @@ -268,7 +272,12 @@ function HistoryPageContent(props: { Versions Recently published schemas. - {query.fetching ? null : noSchemaVersion} + {query.fetching ? null : ( + + )} ); } diff --git a/packages/web/app/src/pages/target.tsx b/packages/web/app/src/pages/target.tsx index b92b055a38..b43a3fb6c1 100644 --- a/packages/web/app/src/pages/target.tsx +++ b/packages/web/app/src/pages/target.tsx @@ -10,7 +10,7 @@ import { CommandInput, CommandItem, } from '@/components/ui/command'; -import { EmptyList, noSchema, noSchemaVersion } from '@/components/ui/empty-list'; +import { EmptyList, noSchema, NoSchemaVersion } from '@/components/ui/empty-list'; import { Meta } from '@/components/ui/meta'; import { Subtitle, Title } from '@/components/ui/page'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; @@ -169,7 +169,7 @@ function SchemaView(props: { const { latestSchemaVersion } = target; if (!latestSchemaVersion) { - return noSchemaVersion; + return ; } if (!latestSchemaVersion.schemas.nodes.length) {