11import { Fragment , useCallback , useMemo , useState } from 'react' ;
22import { format } from 'date-fns' ;
3- import { BadgeCheck , ChevronDown , ChevronUp , GitCompareIcon , Loader2 } from 'lucide-react' ;
3+ import {
4+ BadgeCheck ,
5+ ChevronDown ,
6+ ChevronUp ,
7+ CircleQuestionMarkIcon ,
8+ GitCompareIcon ,
9+ InfoIcon ,
10+ Loader2 ,
11+ } from 'lucide-react' ;
412import { useMutation , useQuery } from 'urql' ;
513import { SchemaEditor } from '@/components/schema-editor' ;
614import {
@@ -192,22 +200,62 @@ const BreakingChangesTitle = () => {
192200 ) ;
193201} ;
194202
203+ const PolicyInfo = ( ) => {
204+ return (
205+ < TooltipProvider delayDuration = { 0 } >
206+ < Tooltip >
207+ < TooltipTrigger >
208+ < InfoIcon className = "ml-2 inline-block" size = { 14 } />
209+ </ TooltipTrigger >
210+ < TooltipContent align = "start" >
211+ Schema policy checks run on the composed API schema. Line numbers
212+ < br />
213+ reflect that and will not match the lines from the source schema.
214+ </ TooltipContent >
215+ </ Tooltip >
216+ </ TooltipProvider >
217+ ) ;
218+ } ;
219+
195220const PolicyBlock = ( props : {
196221 title : string ;
197222 policies : FragmentType < typeof SchemaPolicyEditor_PolicyWarningsFragment > ;
198223 type : 'warning' | 'error' ;
224+ goToline ?: ( line : number | undefined ) => void ;
199225} ) => {
200226 const policies = useFragment ( SchemaPolicyEditor_PolicyWarningsFragment , props . policies ) ;
201227 return (
202228 < div >
203- < h2 className = "text-neutral-2 mb-2 text-sm font-medium" > { props . title } </ h2 >
229+ < h2 className = "text-neutral-10 mb-3 text-sm font-bold" >
230+ { props . title } < PolicyInfo />
231+ </ h2 >
204232 < ul className = "list-inside list-disc pl-3 text-sm/relaxed" >
205233 { policies . edges . map ( ( edge , key ) => (
206234 < li
207235 key = { key }
208236 className = { cn ( props . type === 'warning' ? 'text-yellow-400' : 'text-red-400' , 'my-1' ) }
209237 >
210- < span className = "text-neutral-8 text-left" > { labelize ( edge . node . message ) } </ span >
238+ < span className = "text-neutral-10 text-left" >
239+ { labelize ( edge . node . message . replace ( / \. $ / , '' ) ) } { ' ' }
240+ </ span >
241+ { edge . node . start ?. line ? (
242+ < span
243+ className = "text-neutral-9 ml-1 cursor-default text-xs hover:underline"
244+ onClick = { ( ) => props . goToline ?.( edge . node . start ?. line || undefined ) }
245+ >
246+ on line { edge . node . start . line }
247+ </ span >
248+ ) : null }
249+ < TooltipProvider delayDuration = { 0 } >
250+ < Tooltip >
251+ < TooltipTrigger >
252+ < CircleQuestionMarkIcon size = { 16 } className = "text-neutral-6 ml-2 inline-block" />
253+ </ TooltipTrigger >
254+ < TooltipContent >
255+ rule: < span className = "text-neutral-12" > { edge . node . ruleId } </ span >
256+ </ TooltipContent >
257+ </ Tooltip >
258+ </ TooltipProvider >
211259 </ li >
212260 ) ) }
213261 </ ul >
@@ -426,6 +474,7 @@ function DefaultSchemaView(props: {
426474} ) {
427475 const schemaCheck = useFragment ( DefaultSchemaView_SchemaCheckFragment , props . schemaCheck ) ;
428476 const [ selectedView , setSelectedView ] = useState < string > ( 'details' ) ;
477+ const [ scrollToLine , setScrollToLine ] = useState < number | undefined > ( ) ;
429478
430479 const items = [
431480 {
@@ -480,7 +529,13 @@ function DefaultSchemaView(props: {
480529
481530 return (
482531 < >
483- < Tabs value = { selectedView } onValueChange = { value => setSelectedView ( value ) } >
532+ < Tabs
533+ value = { selectedView }
534+ onValueChange = { value => {
535+ setScrollToLine ( undefined ) ;
536+ setSelectedView ( value ) ;
537+ } }
538+ >
484539 < TabsList className = "bg-neutral-5 dark:bg-neutral-3 border-neutral-5 dark:border-neutral-3 w-full justify-start rounded-none border-x border-b" >
485540 { items . map ( item => (
486541 < TabsTrigger key = { item . value } value = { item . value } disabled = { item . isDisabled } >
@@ -543,6 +598,10 @@ function DefaultSchemaView(props: {
543598 title = "Schema Policy Warnings"
544599 policies = { schemaCheck . schemaPolicyWarnings }
545600 type = "warning"
601+ goToline = { line => {
602+ setScrollToLine ( Math . max ( ( line ?? 0 ) - 1 , 0 ) ) ;
603+ setSelectedView ( 'policy' ) ;
604+ } }
546605 />
547606 </ div >
548607 ) : null }
@@ -581,6 +640,7 @@ function DefaultSchemaView(props: {
581640 errors = {
582641 ( 'schemaPolicyErrors' in schemaCheck && schemaCheck . schemaPolicyErrors ) || null
583642 }
643+ scrollToLine = { scrollToLine }
584644 />
585645 </ >
586646 ) }
@@ -768,6 +828,7 @@ const SchemaPolicyEditor_PolicyWarningsFragment = graphql(`
768828 edges {
769829 node {
770830 message
831+ ruleId
771832 start {
772833 line
773834 column
@@ -785,6 +846,7 @@ const SchemaPolicyEditor = (props: {
785846 compositeSchemaSDL : string ;
786847 warnings : FragmentType < typeof SchemaPolicyEditor_PolicyWarningsFragment > | null ;
787848 errors : FragmentType < typeof SchemaPolicyEditor_PolicyWarningsFragment > | null ;
849+ scrollToLine ?: number ;
788850} ) => {
789851 const warnings = useFragment ( SchemaPolicyEditor_PolicyWarningsFragment , props . warnings ) ;
790852 const errors = useFragment ( SchemaPolicyEditor_PolicyWarningsFragment , props . errors ) ;
@@ -794,10 +856,10 @@ const SchemaPolicyEditor = (props: {
794856 options = { {
795857 renderLineHighlightOnlyWhenFocus : true ,
796858 readOnly : true ,
797- lineNumbers : 'off ' ,
859+ lineNumbers : 'on ' ,
798860 renderValidationDecorations : 'on' ,
799861 } }
800- onMount = { ( _ , monaco ) => {
862+ onMount = { ( editor , monaco ) => {
801863 monaco . editor . setModelMarkers ( monaco . editor . getModels ( ) [ 0 ] , 'owner' , [
802864 ...( warnings ?. edges
803865 . map ( edge => edge . node )
@@ -822,6 +884,10 @@ const SchemaPolicyEditor = (props: {
822884 severity : monaco . MarkerSeverity . Error ,
823885 } ) ) ?? [ ] ) ,
824886 ] ) ;
887+
888+ if ( props . scrollToLine ) {
889+ editor . revealLineInCenter ( props . scrollToLine ) ;
890+ }
825891 } }
826892 schema = { props . compositeSchemaSDL }
827893 />
0 commit comments