Skip to content
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

Merged
merged 25 commits into from
Oct 7, 2020

Conversation

abhiaiyer91
Copy link
Contributor

@abhiaiyer91 abhiaiyer91 commented Sep 29, 2020

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 in onPreInit by calling reporter.setErrorMap

@gatsbot gatsbot bot added the status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer label Sep 29, 2020
@abhiaiyer91 abhiaiyer91 marked this pull request as draft September 29, 2020 17:58
@abhiaiyer91 abhiaiyer91 changed the title <!-- Have any questions? Check out the contributing docs at https://gatsby.dev/contribute, or ask in this Pull Request and a Gatsby maintainer will be happy to help :) --> Pass an errorMap to reporter.error Sep 29, 2020
@gatsby-cloud
Copy link

gatsby-cloud bot commented Sep 29, 2020

Gatsby Cloud Build Report

gatsby

🎉 Your build was successful! See the Deploy preview here.

Build Details

View the build logs here.

🕐 Build time: 20m

@LekoArts LekoArts added topic: cli Related to the Gatsby CLI and removed status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer labels Sep 30, 2020
@abhiaiyer91 abhiaiyer91 marked this pull request as ready for review October 2, 2020 12:55
Copy link
Contributor

@wardpeet wardpeet left a 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.

Comment on lines 43 to 56
expect(events).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: `LOG_ACTION`,
action: expect.objectContaining({
type: `LOG`,
payload: expect.objectContaining({
text: "setErrorMap",
level: "INFO",
}),
}),
}),
])
)
Copy link
Contributor

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!` } })
Copy link
Contributor

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.

Suggested change
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 })
Copy link
Contributor

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"

Suggested change
const localReporter = getLocalReporter({ activity, reporter })

Comment on lines +330 to +334
const extendedLocalReporter = extendLocalReporterToCatchPluginErrors({
reporter: localReporter,
pluginName: plugin.name,
runningActivities,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const extendedLocalReporter = extendLocalReporterToCatchPluginErrors({
reporter: localReporter,
pluginName: plugin.name,
runningActivities,
})
const extendedLocalReporter = extendReporterWithCatchPluginErrorAndErrorMap({
reporter: localReporter,
pluginName: plugin.name,
activity,
runningActivities,
})

Comment on lines 58 to 70
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)
}
Copy link
Contributor

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

packages/gatsby-cli/src/reporter/reporter.ts Outdated Show resolved Hide resolved
Comment on lines 116 to 132
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)
}
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utility is good

Copy link
Contributor

@wardpeet wardpeet left a 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

Comment on lines +44 to +58
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`)
})
Copy link
Contributor

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.

Comment on lines 14 to 23
if (id) {
// Look at original errorMap, ids cannot be overwritten
if (errorMap[id]) {
errorMapEntry = errorMap[id]
}

if (suppliedErrorMap[id]) {
errorMapEntry = suppliedErrorMap[id]
}
}
Copy link
Contributor

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

Suggested change
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]
}
}

Comment on lines 132 to 140
function extendErrorIdWithPluginName(pluginName, errorMeta) {
if (typeof errorMeta === `object`) {
const id = errorMeta && errorMeta[`id`]

if (id) {
errorMeta[`id`] = `${pluginName}_${id}`
}
}
}
Copy link
Contributor

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.

Suggested change
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;
}

Comment on lines 170 to 186
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)
}
Copy link
Contributor

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

Suggested change
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)
}

Comment on lines 57 to 59
getErrorMap = (): Record<ErrorId, IErrorMapEntry> =>
// TODO: We always spread the internal error map to ensure our keys do not get overwritten
this.errorMap
Copy link
Contributor

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.

Suggested change
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const structuredError = constructError({ details }, this.getErrorMap())
const structuredError = constructError({ details }, this.errorMap)

@wardpeet wardpeet changed the title Pass an errorMap to reporter.error feat(gatsby,gatsby-cli): Pass an errorMap to reporter.error Oct 4, 2020
@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys
})
)
.allow(null),
errorCategory: Joi.string().valid([`USER`, `SYSTEM`]),
Copy link
Contributor

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?

Suggested change
errorCategory: Joi.string().valid([`USER`, `SYSTEM`]),
category: Joi.string().valid([`USER`, `SYSTEM`]),

@LekoArts
Copy link
Contributor

LekoArts commented Oct 5, 2020

Codewise this LGTM overall (Ward left some good comments), but got some questions around this (don't know if it was discussed already):

  1. We use 5-digit numbers for the errors so that those are better searchable in Google than e.g. 1 as an ID. Can/Should we enforce the same format for plugin errors?
  2. Since plugins can't override already existing errors from our error map we should at least show a warning somewhere I guess? Otherwise the error reporting silently fails/displays the wrong error message.
  3. Our error map has a loose format, e.g. all GraphQL errors are prefixed with 859 and we're currently at 85927.
    • What happens when a plugin developer decides to claim 85928 and we add the same ID sometime later to our own errorMap?
    • What happens when two plugins have the same ID?
    • Should we enforce certain ranges for our internal errorMap and the external?

@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys
})
)
.allow(null),
category: Joi.string().valid([`USER`, `SYSTEM`]),
Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THIRD_PARTY added

@abhiaiyer91
Copy link
Contributor Author

Codewise this LGTM overall (Ward left some good comments), but got some questions around this (don't know if it was discussed already):

  1. We use 5-digit numbers for the errors so that those are better searchable in Google than e.g. 1 as an ID. Can/Should we enforce the same format for plugin errors?

  2. Since plugins can't override already existing errors from our error map we should at least show a warning somewhere I guess? Otherwise the error reporting silently fails/displays the wrong error message.

  3. Our error map has a loose format, e.g. all GraphQL errors are prefixed with 859 and we're currently at 85927.

    • What happens when a plugin developer decides to claim 85928 and we add the same ID sometime later to our own errorMap?
    • What happens when two plugins have the same ID?
    • Should we enforce certain ranges for our internal errorMap and the external?
  1. Are people actually searching these? And do we have error explainers like react does? If not and if we're not planning on doing so, whats the point of the ids

  2. We can log something

  • Internal Error map wins
  • The error maps are prefixed with plugin name so they wont clash like that
  • We can but to me the current structure doesnt have rhyme or reason, atleast not ones i see clearly

@LekoArts
Copy link
Contributor

LekoArts commented Oct 6, 2020

  1. People are using those IDs for their descriptions in all places and yeah, they also use it for searching for things. We haven't gotten dedicated docs pages for each error but that's something we have as an item on the list for potential next projects

  2. 👍

    • Okay, as long as we have it documented and optionally log something it should be discover-able enough
    • 👍
    • While the numbers were certainly chosen randomly in the start, the system behind it is something like: For a dedicated area of errors choose the same 4-digits and then count up. So e.g. HTML/SSR errors are 9531x, Webpack errors are 9812x etc. If we would split up the error 98123 into more errors it would be 98125, 98126 etc.

So overall it's quite arbitrarily chosen and I don't have a strong opinion on the Should we enforce certain ranges for our internal errorMap and the external? question, just an idea to minimize the clashing of internal <-> external errorMap :)

@@ -20,6 +20,7 @@ export const errorSchema: Joi.ObjectSchema<IStructuredError> = Joi.object().keys
})
)
.allow(null),
category: Joi.string().valid([`USER`, `SYSTEM`]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
category: Joi.string().valid([`USER`, `SYSTEM`]),
category: Joi.string().valid([`USER`, `SYSTEM`, `THIRD_PARTY`]),

Copy link
Contributor

@wardpeet wardpeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wardpeet wardpeet merged commit 56402db into master Oct 7, 2020
@wardpeet wardpeet deleted the registerErrorMap branch October 7, 2020 17:05
@wesrice
Copy link
Contributor

wesrice commented Oct 8, 2020

👋 Hey. This doesn't play well with Gatsby + TypeScript. I'll open a new issue once I understand the problem better.

3:31 Cannot find module 'gatsby-cli/src/structured-errors/error-map' or its corresponding type declarations.
    1 | import { Actions, ActivityStatuses, ActivityTypes } from "../constants";
    2 | import { IStructuredError } from "../../structured-errors/types";
  > 3 | import { ErrorCategory } from "gatsby-cli/src/structured-errors/error-map";
      |                               ^
    4 | export interface IGatsbyCLIState {
    5 |     messages: Array<ILog>;
    6 |     activities: {

@abhiaiyer91
Copy link
Contributor Author

@wesrice ahhh auto import in vscode. my bad, i have this fixed in this PR #27340

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: cli Related to the Gatsby CLI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants