-
Notifications
You must be signed in to change notification settings - Fork 117
feat: add directions for publishing to no schema component #6602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
a6b7b57
2fe2883
969ad58
05a5f87
b49e5e4
94e15e4
d7675d4
c2add1f
57b4608
60c2aa7
eb353a7
9fb922c
d3b3d3d
d145440
80d12d5
75405b0
f3a1557
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'hive': patch | ||
--- | ||
|
||
Added directions for publishing on no schema component |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ComponentProps<'code'>> = ({ children, className, ...props }) => { | ||
const [copied, startCopyTimer] = useTimed(1500); | ||
const [ref, hovering] = useHover(); | ||
const codeRef = useRef<HTMLElement | null>(null); | ||
// in case this browser does not support this newer API... | ||
const navigatorClipboardSupport = typeof navigator.clipboard?.writeText === 'function'; | ||
return ( | ||
<span | ||
ref={ref} | ||
className="relative flex cursor-text items-center gap-2 break-all rounded-md border border-gray-600 bg-black p-4 pr-14 font-mono text-sm" | ||
// Make this element able to be focused by setting tabIndex. | ||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex | ||
tabIndex={0} | ||
onFocus={() => { | ||
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); | ||
} | ||
}} | ||
> | ||
<code | ||
ref={codeRef} | ||
className={cn('whitespace-pre-line', className)} | ||
// always show code blocks in ltr | ||
dir="ltr" | ||
{...props} | ||
> | ||
{children} | ||
</code> | ||
<button | ||
hidden={!navigatorClipboardSupport} | ||
data-hovering={hovering || copied} | ||
className="absolute right-3 top-2 cursor-pointer rounded-md border border-gray-600 p-2 opacity-0 hover:text-orange-600 data-[hovering=true]:opacity-100 data-[hovering=true]:transition-opacity" | ||
onClick={async ev => { | ||
const value = children?.valueOf().toString(); | ||
if (value) { | ||
ev.preventDefault(); | ||
await navigator.clipboard.writeText(value); | ||
startCopyTimer(); | ||
} | ||
}} | ||
title="Copy to clipboard" | ||
> | ||
{copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />} | ||
</button> | ||
</span> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Card | ||
className={cn('flex grow cursor-default flex-col items-center gap-y-2 py-4', className)} | ||
className={cn('flex grow cursor-default flex-col items-center gap-y-2 p-4', className)} | ||
data-cy="empty-list" | ||
> | ||
<img | ||
|
@@ -30,6 +34,7 @@ export const EmptyList = ({ | |
/> | ||
<Heading className="text-center">{title}</Heading> | ||
<span className="text-center text-sm font-medium text-gray-500">{description}</span> | ||
{children} | ||
{docsUrl && <DocsLink href={docsUrl}>Read about it in the documentation</DocsLink>} | ||
</Card> | ||
); | ||
|
@@ -43,13 +48,61 @@ export const noSchema = ( | |
/> | ||
); | ||
|
||
export const noSchemaVersion = ( | ||
<EmptyList | ||
title="Hive is waiting for your first schema" | ||
description="You can publish a schema with Hive CLI and Hive Client" | ||
docsUrl="/features/schema-registry#publish-a-schema" | ||
/> | ||
); | ||
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 = ( | ||
<> | ||
<div className="flex w-full justify-center py-2 text-xs text-gray-500"> | ||
It's recommended to check that the schema is valid and compatible with the state of the | ||
registry before publishing. | ||
</div> | ||
<div className="flex w-full justify-center"> | ||
<Code> | ||
{`hive schema:check ${isDistributed ? '--service <service-name> --url <url> ' : ''}<path/schema.graphql>`} | ||
</Code> | ||
</div> | ||
</> | ||
); | ||
} else if (recommendedAction === 'publish') { | ||
children = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify schema checking is working correctly Since this is a critical user-facing instruction, let's verify that the schema:check command parameters shown here match what's expected by the Hive CLI. 🏁 Script executed: #!/bin/bash
# Search for schema:check usage examples in documentation or tests
rg -A 2 "schema:check" --type md Length of output: 2515 Critical: Schema Check Command Parameters Mismatch
|
||
<> | ||
{isDistributed && ( | ||
<div className="flex w-full justify-center py-2 text-xs text-gray-500"> | ||
For distributed systems, it's recommended to publish the schema after the service is | ||
deployed. | ||
</div> | ||
)} | ||
<div className="flex w-full justify-center"> | ||
<Code> | ||
{`hive schema:publish ${isDistributed ? '--service <service-name> --url <url> ' : ''}<path/schema.graphql>`} | ||
</Code> | ||
</div> | ||
</> | ||
); | ||
} | ||
} | ||
|
||
return ( | ||
<EmptyList | ||
title="Hive is waiting for your first schema" | ||
description="You can publish a schema with Hive CLI and Hive Client" | ||
docsUrl="/features/schema-registry#publish-a-schema" | ||
> | ||
{children} | ||
</EmptyList> | ||
); | ||
}; | ||
|
||
export const noValidSchemaVersion = ( | ||
<EmptyList | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { useCallback, useRef, useState } from 'react'; | ||
|
||
export function useHover() { | ||
const [hovering, setHovering] = useState(false); | ||
const previousNode = useRef<Node | null>(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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
export function useTimed(wait: number = 1000) { | ||
const [timer, setTimer] = useState<NodeJS.Timeout | null>(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; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.