-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
feat(gatsby,gatsby-cli): Pass an errorMap to reporter.error #27176
Conversation
Gatsby Cloud Build Reportgatsby 🎉 Your build was successful! See the Deploy preview here. Build Details🕐 Build time: 20m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I'm mostly wondering about if we want to make the reporter public API broader.
expect(events).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
type: `LOG_ACTION`, | ||
action: expect.objectContaining({ | ||
type: `LOG`, | ||
payload: expect.objectContaining({ | ||
text: "setErrorMap", | ||
level: "INFO", | ||
}), | ||
}), | ||
}), | ||
]) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have this in our test? if we want to test if setErrorMap is tested we should use jest.spyOn on the reporter instead. I'm not sure we have to test it cause the below code will validate that the custom error is set.
reporter.info("setErrorMap") | ||
|
||
if (process.env.PANIC_IN_PLUGIN) { | ||
reporter.panic({ id: "TEST_ERROR", context: { someProp: `PANIC!` } }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we move to a number based one as that's more or less the convention we have. It's probably something we need to force.
reporter.panic({ id: "TEST_ERROR", context: { someProp: `PANIC!` } }) | |
reporter.panic({ id: "666", context: { someProp: `PANIC!` } }) |
originalEnd.apply(activity) | ||
runningActivities.delete(activity) | ||
} | ||
const localReporter = getLocalReporter({ activity, reporter }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move everything inside "extendLocalReporterToCatchPluginErrors"
const localReporter = getLocalReporter({ activity, reporter }) |
const extendedLocalReporter = extendLocalReporterToCatchPluginErrors({ | ||
reporter: localReporter, | ||
pluginName: plugin.name, | ||
runningActivities, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const extendedLocalReporter = extendLocalReporterToCatchPluginErrors({ | |
reporter: localReporter, | |
pluginName: plugin.name, | |
runningActivities, | |
}) | |
const extendedLocalReporter = extendReporterWithCatchPluginErrorAndErrorMap({ | |
reporter: localReporter, | |
pluginName: plugin.name, | |
activity, | |
runningActivities, | |
}) |
setErrorMapWithPluginName = (name: string) => ( | ||
entry: Record<ErrorId, IErrorMapEntry> | ||
): void => { | ||
const entries = Object.entries(entry) | ||
|
||
const newErrorMap = entries.reduce((memo, [key, val]) => { | ||
memo[`${name}_${key}`] = val | ||
|
||
return memo | ||
}, {}) | ||
|
||
this.setErrorMap(newErrorMap) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to expose it on the reporter? Maybe it's better to export a utility that we use in api-runner. It feels like a private function
logFailureWithPluginName = ({ | ||
methodName, | ||
pluginName, | ||
}: { | ||
methodName: string | ||
pluginName: string | ||
}) => (errorMeta: ErrorMeta, error?: Error | Array<Error>): void => { | ||
if (typeof errorMeta === `object`) { | ||
const id = errorMeta && errorMeta[`id`] | ||
|
||
if (id) { | ||
errorMeta[`id`] = `${pluginName}_${id}` | ||
} | ||
} | ||
|
||
this[methodName](errorMeta, error) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same her do we want it on the reporter and not as a utility? I want to limit the public api from reporter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utility is good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some nitpicks to make code side-effect free
test(`it constructs an error from the supplied errorMap`, () => { | ||
const error = constructError( | ||
{ details: { id: `1337`, context: { someProp: `Error!` } } }, | ||
{ | ||
"1337": { | ||
text: (context): string => `Error text is ${context.someProp} `, | ||
level: Level.ERROR, | ||
docsUrl: `https://www.gatsbyjs.org/docs/gatsby-cli/#new`, | ||
}, | ||
} | ||
) | ||
|
||
expect(error.code).toBe(`1337`) | ||
expect(error.docsUrl).toBe(`https://www.gatsbyjs.org/docs/gatsby-cli/#new`) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test so we check if original errorMap cannot be overridden.
if (id) { | ||
// Look at original errorMap, ids cannot be overwritten | ||
if (errorMap[id]) { | ||
errorMapEntry = errorMap[id] | ||
} | ||
|
||
if (suppliedErrorMap[id]) { | ||
errorMapEntry = suppliedErrorMap[id] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably my mistake in a previous comment but it should be
if (id) { | |
// Look at original errorMap, ids cannot be overwritten | |
if (errorMap[id]) { | |
errorMapEntry = errorMap[id] | |
} | |
if (suppliedErrorMap[id]) { | |
errorMapEntry = suppliedErrorMap[id] | |
} | |
} | |
if (id) { | |
// Look at original errorMap, ids cannot be overwritten | |
if (errorMap[id]) { | |
errorMapEntry = errorMap[id] | |
} else if (suppliedErrorMap[id]) { | |
errorMapEntry = suppliedErrorMap[id] | |
} | |
} |
function extendErrorIdWithPluginName(pluginName, errorMeta) { | ||
if (typeof errorMeta === `object`) { | ||
const id = errorMeta && errorMeta[`id`] | ||
|
||
if (id) { | ||
errorMeta[`id`] = `${pluginName}_${id}` | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep this side-effect free so we don't augment the object but instead create a new one.
function extendErrorIdWithPluginName(pluginName, errorMeta) { | |
if (typeof errorMeta === `object`) { | |
const id = errorMeta && errorMeta[`id`] | |
if (id) { | |
errorMeta[`id`] = `${pluginName}_${id}` | |
} | |
} | |
} | |
function extendErrorIdWithPluginName(pluginName, errorMeta) { | |
if (typeof errorMeta === `object`) { | |
const id = errorMeta && errorMeta[`id`] | |
if (id) { | |
return { | |
...errorMeta, | |
id: `${pluginName}_${id}` | |
} | |
} | |
} | |
return errorMeta; | |
} |
error = (errorMeta, error) => { | ||
extendErrorIdWithPluginName(pluginName, errorMeta) | ||
|
||
return reporter.error(errorMeta, error) | ||
} | ||
|
||
panic = (errorMeta, error) => { | ||
extendErrorIdWithPluginName(pluginName, errorMeta) | ||
|
||
return reporter.panic(errorMeta, error) | ||
} | ||
|
||
panicOnBuild = (errorMeta, error) => { | ||
extendErrorIdWithPluginName(pluginName, errorMeta) | ||
|
||
return reporter.panicOnBuild(errorMeta, error) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the above change we should be able to move to
error = (errorMeta, error) => { | |
extendErrorIdWithPluginName(pluginName, errorMeta) | |
return reporter.error(errorMeta, error) | |
} | |
panic = (errorMeta, error) => { | |
extendErrorIdWithPluginName(pluginName, errorMeta) | |
return reporter.panic(errorMeta, error) | |
} | |
panicOnBuild = (errorMeta, error) => { | |
extendErrorIdWithPluginName(pluginName, errorMeta) | |
return reporter.panicOnBuild(errorMeta, error) | |
} | |
error = (errorMeta, error) => { | |
return reporter.error(extendErrorIdWithPluginName(pluginName, errorMeta), error) | |
} | |
panic = (errorMeta, error) => { | |
return reporter.panic(extendErrorIdWithPluginName(pluginName, errorMeta), error) | |
} | |
panicOnBuild = (errorMeta, error) => { | |
return reporter.panicOnBuild(extendErrorIdWithPluginName(pluginName, errorMeta), error) | |
} |
getErrorMap = (): Record<ErrorId, IErrorMapEntry> => | ||
// TODO: We always spread the internal error map to ensure our keys do not get overwritten | ||
this.errorMap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not expose this function, so users can't augment it themselves.
getErrorMap = (): Record<ErrorId, IErrorMapEntry> => | |
// TODO: We always spread the internal error map to ensure our keys do not get overwritten | |
this.errorMap |
@@ -136,7 +158,8 @@ class Reporter { | |||
} | |||
} | |||
|
|||
const structuredError = constructError({ details }) | |||
const structuredError = constructError({ details }, this.getErrorMap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const structuredError = constructError({ details }, this.getErrorMap()) | |
const structuredError = constructError({ details }, this.errorMap) |
@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys | |||
}) | |||
) | |||
.allow(null), | |||
errorCategory: Joi.string().valid([`USER`, `SYSTEM`]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's already part of an error, should we prefix it with error?
errorCategory: Joi.string().valid([`USER`, `SYSTEM`]), | |
category: Joi.string().valid([`USER`, `SYSTEM`]), |
Codewise this LGTM overall (Ward left some good comments), but got some questions around this (don't know if it was discussed already):
|
@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys | |||
}) | |||
) | |||
.allow(null), | |||
category: Joi.string().valid([`USER`, `SYSTEM`]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would we chose as category for "Wordpress host is down right now and we're getting errors retrieving data" ? Maybe we can have a THIRD_PARTY
category for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, I would have marked that as user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THIRD_PARTY added
|
So overall it's quite arbitrarily chosen and I don't have a strong opinion on the |
@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys | |||
}) | |||
) | |||
.allow(null), | |||
category: Joi.string().valid([`USER`, `SYSTEM`]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
category: Joi.string().valid([`USER`, `SYSTEM`]), | |
category: Joi.string().valid([`USER`, `SYSTEM`, `THIRD_PARTY`]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
👋 Hey. This doesn't play well with Gatsby + TypeScript. I'll open a new issue once I understand the problem better.
|
This PR has enhancements to the reporter (in gatsby cli) and "local reporter" (in gatsby).
The Reporter Class has added
errorMap
-> Plugins can register new structured errors inonPreInit
by callingreporter.setErrorMap