-
-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #152 from notea-org/feature/debugging
Debugging utilities + more user-friendly error listing
- Loading branch information
Showing
23 changed files
with
1,225 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Button } from '@material-ui/core'; | ||
import useI18n from 'libs/web/hooks/use-i18n'; | ||
import { FC } from 'react'; | ||
import { logLevelToString, DebugInformation } from 'libs/shared/debugging'; | ||
|
||
export const DebugInfoCopyButton: FC<{ | ||
debugInfo: DebugInformation; | ||
}> = ({ debugInfo }) => { | ||
const { t } = useI18n(); | ||
|
||
function generateDebugInfo(): string { | ||
let data = | ||
'Notea debug information' + | ||
'\nTime ' + | ||
new Date(Date.now()).toISOString() + | ||
'\n\n'; | ||
function ensureNewline() { | ||
if (!data.endsWith('\n') && data.length >= 1) { | ||
data += '\n'; | ||
} | ||
} | ||
|
||
if (debugInfo.issues.length > 0) { | ||
ensureNewline(); | ||
data += 'Configuration errors: '; | ||
let i = 1; | ||
const prefixLength = debugInfo.issues.length.toString().length; | ||
for (const issue of debugInfo.issues) { | ||
const prefix = i.toString().padStart(prefixLength, ' ') + ': '; | ||
const empty = ' '.repeat(prefixLength + 2); | ||
|
||
data += prefix + issue.name; | ||
data += empty + '// ' + issue.cause; | ||
data += empty + issue.description; | ||
i++; | ||
} | ||
} else { | ||
data += 'No detected configuration errors.'; | ||
} | ||
|
||
if (debugInfo.logs.length > 0) { | ||
ensureNewline(); | ||
for (const log of debugInfo.logs) { | ||
data += `[${new Date(log.time).toISOString()} ${log.name}] ${logLevelToString(log.level)}: ${log.msg}`; | ||
} | ||
} | ||
|
||
|
||
|
||
return data; | ||
} | ||
|
||
function copyDebugInfo() { | ||
const text = generateDebugInfo(); | ||
|
||
navigator.clipboard | ||
.writeText(text) | ||
.then(() => { | ||
// nothing | ||
}) | ||
.catch((e) => { | ||
console.error( | ||
'Error when trying to copy debugging information to clipboard: %O', | ||
e | ||
); | ||
}); | ||
} | ||
|
||
return ( | ||
<Button variant="contained" onClick={copyDebugInfo}> | ||
{t('Copy debugging information')} | ||
</Button> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { FC } from 'react'; | ||
import { Issue } from 'libs/shared/debugging'; | ||
import { Issue as IssueDisplay } from './issue'; | ||
|
||
export const IssueList: FC<{ | ||
issues: Array<Issue>; | ||
}> = ({ issues }) => { | ||
return ( | ||
<div className="flex flex-col"> | ||
{issues.map((v, i) => { | ||
return ( | ||
<IssueDisplay key={i} issue={v} id={`issue-${i}`}/> | ||
); | ||
})} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import { FC } from 'react'; | ||
import { | ||
getNameFromRecommendation, | ||
getNameFromSeverity, | ||
Issue as IssueInfo, | ||
IssueFix, | ||
IssueSeverity | ||
} from 'libs/shared/debugging'; | ||
import { | ||
Accordion as MuiAccordion, | ||
AccordionDetails as MuiAccordionDetails, | ||
AccordionSummary as MuiAccordionSummary, | ||
withStyles | ||
} from '@material-ui/core'; | ||
import { ChevronDownIcon } from '@heroicons/react/outline'; | ||
import useI18n from 'libs/web/hooks/use-i18n'; | ||
import { errorToString, isProbablyError } from 'libs/shared/util'; | ||
|
||
const Accordion = withStyles({ | ||
root: { | ||
boxShadow: 'none', | ||
'&:not(:last-child)': { | ||
borderBottom: 0, | ||
}, | ||
'&:before': { | ||
display: 'none', | ||
}, | ||
'&$expanded': { | ||
margin: 'auto 0', | ||
}, | ||
}, | ||
expanded: { | ||
}, | ||
})(MuiAccordion); | ||
|
||
const AccordionSummary = withStyles({ | ||
root: { | ||
backgroundColor: 'rgba(0, 0, 0, .03)', | ||
borderBottom: '1px solid rgba(0, 0, 0, .125)', | ||
borderTopRightRadius: 'inherit', | ||
borderBottomRightRadius: 'inherit', | ||
marginBottom: -1, | ||
minHeight: 56, | ||
'&$expanded': { | ||
minHeight: 56, | ||
borderBottomRightRadius: '0' | ||
}, | ||
}, | ||
content: { | ||
'&$expanded': { | ||
margin: '12px 0', | ||
} | ||
}, | ||
expanded: {}, | ||
})(MuiAccordionSummary); | ||
|
||
const AccordionDetails = withStyles((theme) => ({ | ||
root: { | ||
padding: theme.spacing(2), | ||
}, | ||
}))(MuiAccordionDetails); | ||
|
||
interface FixProps { | ||
id: string; | ||
fix: IssueFix; | ||
} | ||
const Fix: FC<FixProps> = ({ id, fix }) => { | ||
const i18n = useI18n(); | ||
const { t } = i18n; | ||
const steps = fix.steps ?? []; | ||
return ( | ||
<Accordion key={id} className={"bg-gray-300"}> | ||
<AccordionSummary | ||
expandIcon={<ChevronDownIcon width=".8em"/>} | ||
aria-controls={`${id}-details`} | ||
id={`${id}-summary`} | ||
> | ||
<div className={"flex flex-col"}> | ||
{fix.recommendation !== 0 && ( | ||
<span className={"text-xs uppercase"}>{getNameFromRecommendation(fix.recommendation, i18n)}</span> | ||
)} | ||
<span className={"font-bold"}>{fix.description}</span> | ||
</div> | ||
</AccordionSummary> | ||
<AccordionDetails className={"rounded-[inherit]"}> | ||
{steps.length > 0 ? ( | ||
<ol className="list-decimal list-inside"> | ||
{steps.map((step, i) => { | ||
const stepId = `${id}-step-${i}`; | ||
return ( | ||
<li key={stepId}>{step}</li> | ||
); | ||
})} | ||
</ol> | ||
) : ( | ||
<span>{t('No steps were provided by Notea to perform this fix.')}</span> | ||
)} | ||
</AccordionDetails> | ||
</Accordion> | ||
); | ||
}; | ||
|
||
interface IssueProps { | ||
issue: IssueInfo; | ||
id: string; | ||
} | ||
|
||
export const Issue: FC<IssueProps> = function (props) { | ||
const { issue, id } = props; | ||
const i18n = useI18n(); | ||
const { t } = i18n; | ||
|
||
let borderColour: string; | ||
switch (issue.severity) { | ||
case IssueSeverity.SUGGESTION: | ||
borderColour = "border-gray-500"; | ||
break; | ||
case IssueSeverity.WARNING: | ||
borderColour = "border-yellow-100"; | ||
break; | ||
case IssueSeverity.ERROR: | ||
borderColour = "border-red-500"; | ||
break; | ||
case IssueSeverity.FATAL_ERROR: | ||
borderColour = "border-red-300"; | ||
break; | ||
} | ||
|
||
const Cause: FC<{ value: IssueInfo['cause'] }> = ({ value }) => { | ||
if (typeof value === 'string') { | ||
return ( | ||
<div className={"flex flex-row my-1"}> | ||
<span className={"font-bold"}>{t('Cause')}</span> | ||
<span className={"font-mono ml-1"}>{value}</span> | ||
</div> | ||
); | ||
} | ||
|
||
if (isProbablyError(value)) { | ||
return ( | ||
<div className={"flex flex-col my-1"}> | ||
<span className={"font-bold"}>{t('Cause')}</span> | ||
<pre className={"font-mono whitespace-pre"}>{errorToString(value)}</pre> | ||
</div> | ||
); | ||
} | ||
|
||
throw new Error("Invalid value type"); | ||
}; | ||
|
||
return ( | ||
<Accordion className={`border-l-4 ${borderColour} bg-gray-200`}> | ||
<AccordionSummary | ||
className={"bg-gray-100"} | ||
expandIcon={<ChevronDownIcon width=".8em"/>} | ||
aria-controls={`${id}-details`} | ||
id={`${id}-summary`} | ||
> | ||
<div className={"flex flex-col bg-transparent"}> | ||
<span className={"text-xs uppercase"}> | ||
{issue.isRuntime === true ? 'Runtime ' : ''} | ||
{getNameFromSeverity(issue.severity, i18n)} | ||
</span> | ||
<span className={"font-bold"}>{issue.name}</span> | ||
</div> | ||
</AccordionSummary> | ||
<AccordionDetails className={"flex flex-col"}> | ||
<span>{issue.description ?? t('No description was provided for this issue.')}</span> | ||
{issue.cause && <Cause value={issue.cause}/>} | ||
|
||
{issue.fixes.length > 0 ? ( | ||
<div className={"mt-1 flex flex-col"}> | ||
<span className={"font-bold"}>{t('Potential fixes')}</span> | ||
<div> | ||
{issue.fixes.map((fix, i) => { | ||
const fixId = `${id}-fix-${i}`; | ||
return ( | ||
<Fix | ||
key={fixId} | ||
id={fixId} | ||
fix={fix} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
) : ( | ||
<span>{t('No fixes are known by Notea for this issue.')}</span> | ||
)} | ||
</AccordionDetails> | ||
</Accordion> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { FC } from 'react'; | ||
import { logLevelToString, LogLike } from 'libs/shared/debugging'; | ||
|
||
interface LogsProps { | ||
logs: Array<LogLike> | ||
} | ||
|
||
export const Logs: FC<LogsProps> = (props) => { | ||
return ( | ||
<div className={"flex flex-col space-y-1"}> | ||
{props.logs.length > 0 ? props.logs.map((log, i) => { | ||
return ( | ||
<div className={"flex flex-col border-l pl-2"} key={i}> | ||
<span className={"text-sm uppercase"}> | ||
{logLevelToString(log.level)} at {new Date(log.time ?? 0).toLocaleString()} from <b>{log.name}</b> | ||
</span> | ||
<span className={"font-mono"}> | ||
{log.msg} | ||
</span> | ||
</div> | ||
); | ||
}) : ( | ||
<span>No logs.</span> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { IssueList } from 'components/debug/issue-list'; | ||
import type { FC } from 'react'; | ||
import { DebugInfoCopyButton } from 'components/debug/debug-info-copy-button'; | ||
import { DebugInformation, Issue } from 'libs/shared/debugging'; | ||
|
||
export const Debugging: FC<{ | ||
debugInfo: DebugInformation; | ||
}> = (props) => { | ||
const issues: Array<Issue> = [...props.debugInfo.issues].sort((a, b) => b.severity - a.severity); | ||
return ( | ||
<div className="my-2"> | ||
<IssueList | ||
issues={issues} | ||
/> | ||
<div className={'flex flex-row my-2'}> | ||
<DebugInfoCopyButton debugInfo={props.debugInfo} /> | ||
</div> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.