Skip to content

Commit

Permalink
Merge pull request #152 from notea-org/feature/debugging
Browse files Browse the repository at this point in the history
Debugging utilities + more user-friendly error listing
  • Loading branch information
tecc authored Nov 28, 2022
2 parents 2431c96 + 102a573 commit 247b209
Show file tree
Hide file tree
Showing 23 changed files with 1,225 additions and 63 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/logs

# local env files
.env
Expand All @@ -43,4 +44,4 @@ tsconfig.tsbuildinfo
# Editor files
# - IntelliJ IDEA
.idea/
*.iml
*.iml
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ COPY --from=builder /app/node_modules ./node_modules
VOLUME /app/config
# VOLUME /app/data

ENV CONFIG_FILE=/app/config/notea.yml
ENV CONFIG_FILE=/app/config/notea.yml LOG_DIRECTORY=/app/logs

EXPOSE 3000

Expand Down
74 changes: 74 additions & 0 deletions components/debug/debug-info-copy-button.tsx
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>
);
};
17 changes: 17 additions & 0 deletions components/debug/issue-list.tsx
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>
);
};
193 changes: 193 additions & 0 deletions components/debug/issue.tsx
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>
);
};
27 changes: 27 additions & 0 deletions components/debug/logs.tsx
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>
);
};
20 changes: 20 additions & 0 deletions components/settings/debugging.tsx
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>
);
};
Loading

0 comments on commit 247b209

Please sign in to comment.