1+ import { FC } from 'react' ;
2+ import {
3+ getNameFromRecommendation ,
4+ getNameFromSeverity ,
5+ Issue as IssueInfo ,
6+ IssueFix ,
7+ IssueSeverity
8+ } from 'libs/shared/debugging' ;
9+ import {
10+ Accordion as MuiAccordion ,
11+ AccordionDetails as MuiAccordionDetails ,
12+ AccordionSummary as MuiAccordionSummary ,
13+ withStyles
14+ } from '@material-ui/core' ;
15+ import { ChevronDownIcon } from '@heroicons/react/outline' ;
16+ import useI18n from 'libs/web/hooks/use-i18n' ;
17+ import { errorToString , isProbablyError } from 'libs/shared/util' ;
18+
19+ const Accordion = withStyles ( {
20+ root : {
21+ boxShadow : 'none' ,
22+ '&:not(:last-child)' : {
23+ borderBottom : 0 ,
24+ } ,
25+ '&:before' : {
26+ display : 'none' ,
27+ } ,
28+ '&$expanded' : {
29+ margin : 'auto 0' ,
30+ } ,
31+ } ,
32+ expanded : {
33+ } ,
34+ } ) ( MuiAccordion ) ;
35+
36+ const AccordionSummary = withStyles ( {
37+ root : {
38+ backgroundColor : 'rgba(0, 0, 0, .03)' ,
39+ borderBottom : '1px solid rgba(0, 0, 0, .125)' ,
40+ borderTopRightRadius : 'inherit' ,
41+ borderBottomRightRadius : 'inherit' ,
42+ marginBottom : - 1 ,
43+ minHeight : 56 ,
44+ '&$expanded' : {
45+ minHeight : 56 ,
46+ borderBottomRightRadius : '0'
47+ } ,
48+ } ,
49+ content : {
50+ '&$expanded' : {
51+ margin : '12px 0' ,
52+ }
53+ } ,
54+ expanded : { } ,
55+ } ) ( MuiAccordionSummary ) ;
56+
57+ const AccordionDetails = withStyles ( ( theme ) => ( {
58+ root : {
59+ padding : theme . spacing ( 2 ) ,
60+ } ,
61+ } ) ) ( MuiAccordionDetails ) ;
62+
63+ interface FixProps {
64+ id : string ;
65+ fix : IssueFix ;
66+ }
67+ const Fix : FC < FixProps > = ( { id, fix } ) => {
68+ const i18n = useI18n ( ) ;
69+ const { t } = i18n ;
70+ const steps = fix . steps ?? [ ] ;
71+ return (
72+ < Accordion key = { id } className = { "bg-gray-300" } >
73+ < AccordionSummary
74+ expandIcon = { < ChevronDownIcon width = ".8em" /> }
75+ aria-controls = { `${ id } -details` }
76+ id = { `${ id } -summary` }
77+ >
78+ < div className = { "flex flex-col" } >
79+ { fix . recommendation !== 0 && (
80+ < span className = { "text-xs uppercase" } > { getNameFromRecommendation ( fix . recommendation , i18n ) } </ span >
81+ ) }
82+ < span className = { "font-bold" } > { fix . description } </ span >
83+ </ div >
84+ </ AccordionSummary >
85+ < AccordionDetails className = { "rounded-[inherit]" } >
86+ { steps . length > 0 ? (
87+ < ol className = "list-decimal list-inside" >
88+ { steps . map ( ( step , i ) => {
89+ const stepId = `${ id } -step-${ i } ` ;
90+ return (
91+ < li key = { stepId } > { step } </ li >
92+ ) ;
93+ } ) }
94+ </ ol >
95+ ) : (
96+ < span > { t ( 'No steps were provided by Notea to perform this fix.' ) } </ span >
97+ ) }
98+ </ AccordionDetails >
99+ </ Accordion >
100+ ) ;
101+ } ;
102+
103+ interface IssueProps {
104+ issue : IssueInfo ;
105+ id : string ;
106+ }
107+
108+ export const Issue : FC < IssueProps > = function ( props ) {
109+ const { issue, id } = props ;
110+ const i18n = useI18n ( ) ;
111+ const { t } = i18n ;
112+
113+ let borderColour : string ;
114+ switch ( issue . severity ) {
115+ case IssueSeverity . SUGGESTION :
116+ borderColour = "border-gray-500" ;
117+ break ;
118+ case IssueSeverity . WARNING :
119+ borderColour = "border-yellow-100" ;
120+ break ;
121+ case IssueSeverity . ERROR :
122+ borderColour = "border-red-500" ;
123+ break ;
124+ case IssueSeverity . FATAL_ERROR :
125+ borderColour = "border-red-300" ;
126+ break ;
127+ }
128+
129+ const Cause : FC < { value : IssueInfo [ 'cause' ] } > = ( { value } ) => {
130+ if ( typeof value === 'string' ) {
131+ return (
132+ < div className = { "flex flex-row my-1" } >
133+ < span className = { "font-bold" } > { t ( 'Cause' ) } </ span >
134+ < span className = { "font-mono ml-1" } > { value } </ span >
135+ </ div >
136+ ) ;
137+ }
138+
139+ if ( isProbablyError ( value ) ) {
140+ return (
141+ < div className = { "flex flex-col my-1" } >
142+ < span className = { "font-bold" } > { t ( 'Cause' ) } </ span >
143+ < pre className = { "font-mono whitespace-pre" } > { errorToString ( value ) } </ pre >
144+ </ div >
145+ ) ;
146+ }
147+
148+ throw new Error ( "Invalid value type" ) ;
149+ } ;
150+
151+ return (
152+ < Accordion className = { `border-l-4 ${ borderColour } bg-gray-200` } >
153+ < AccordionSummary
154+ className = { "bg-gray-100" }
155+ expandIcon = { < ChevronDownIcon width = ".8em" /> }
156+ aria-controls = { `${ id } -details` }
157+ id = { `${ id } -summary` }
158+ >
159+ < div className = { "flex flex-col bg-transparent" } >
160+ < span className = { "text-xs uppercase" } >
161+ { issue . isRuntime === true ? 'Runtime ' : '' }
162+ { getNameFromSeverity ( issue . severity , i18n ) }
163+ </ span >
164+ < span className = { "font-bold" } > { issue . name } </ span >
165+ </ div >
166+ </ AccordionSummary >
167+ < AccordionDetails className = { "flex flex-col" } >
168+ < span > { issue . description ?? t ( 'No description was provided for this issue.' ) } </ span >
169+ { issue . cause && < Cause value = { issue . cause } /> }
170+
171+ { issue . fixes . length > 0 ? (
172+ < div className = { "mt-1 flex flex-col" } >
173+ < span className = { "font-bold" } > { t ( 'Potential fixes' ) } </ span >
174+ < div >
175+ { issue . fixes . map ( ( fix , i ) => {
176+ const fixId = `${ id } -fix-${ i } ` ;
177+ return (
178+ < Fix
179+ key = { fixId }
180+ id = { fixId }
181+ fix = { fix }
182+ />
183+ ) ;
184+ } ) }
185+ </ div >
186+ </ div >
187+ ) : (
188+ < span > { t ( 'No fixes are known by Notea for this issue.' ) } </ span >
189+ ) }
190+ </ AccordionDetails >
191+ </ Accordion >
192+ ) ;
193+ } ;
0 commit comments