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

Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] #9233

Merged
merged 7 commits into from Jun 13, 2023

Conversation

chris48s
Copy link
Member

@chris48s chris48s commented Jun 2, 2023

Closes #9174
Refs #7727
Refs #9055

This is one of those issues where I thought it probably wouldn't be that hard but it turned into a bit of a beast.

At the moment, if we throw a ShieldsRuntimeError, the error badge uses the cacheLength of the service class throwing the error to set the response maxAge. This means if a build badge throws an error, the response has a maxAge of 30 seconds. If its a license badge then it is an hour, etc.
This PR allows us to set a cacheSeconds property on the exception object when we throw it which we can use to set a maxAge on certain error responses independently of the service class default.
It also adds a systemErrors param to the service base class fetch methods which we can use to pass extra params to the exception object when specific got errors are encountered.

Taking all that together, this would allow a contributor to write something like

async fetch({ packageName }) {
  return this._requestJson({
    schema,
    url: `https://example.com/${packageName}`,

    // https://github.com/sindresorhus/got/blob/main/documentation/6-timeout.md
    // this is in milliseconds
    options: { timeout: { request: 3000 } }, 

    systemErrors: {
      ETIMEDOUT: { prettyMessage: 'scanning', cacheSeconds: 10 },
    },
  })
}

at the service layer to implement the behaviour described in #9174.

I've tried to split this into smaller commits that explain what is going on at each stage in the process.

@calebcartwright - Although it would be nice to get this merged, if you've got time to review stuff, I am more keen to get #9014 over the line tbh.

By default error responses use the cacheLength of
the service class throwing the error.

This allows error to tell the handling layer the maxAge
that should be set on the error badge response.
This

1. allows us to specify custom properties to pass to the exception
   constructor if we throw any of the standard got errors
   e.g: `ETIMEDOUT`, `ECONNRESET`, etc
2. uses a custom `cacheSeconds` property (if set on the exception)
   to set the response maxAge
@chris48s chris48s added the core Server, BaseService, GitHub auth label Jun 2, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Jun 2, 2023

Warnings
⚠️ Lots 'o changes. Skipping diff-based checks.
⚠️ This PR modified service code for azure-devops but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for bit but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for bitbucket but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for bitrise but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for bundlephobia but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for cii-best-practices but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for circleci but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for clearlydefined but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for codacy but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for codecov but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for codefactor but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for coveralls but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for coverity but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for discord but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for docker but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for drone but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for dynamic but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for ecologi but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for elm-package but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for endpoint but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for f-droid but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for factorio-mod-portal but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for fedora but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for feedz but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for gem but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for gerrit but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for github/gist but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for github but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for gitlab but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for hackernews but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for homebrew but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for jenkins but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for jetbrains but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for jira but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for jitpack but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for librariesio but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for localizely but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for luarocks but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for matrix but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for nexus but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for npm but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for npms-io but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for opm but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for ossf-scorecard but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for piwheels but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for pypi but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for reddit but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for reuse but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for snyk but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for sourceforge but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for spack but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for stackexchange but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for tas but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for tokei but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for ubuntu but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for weblate but not its test code.
That's okay so long as it's refactoring existing code.
⚠️ This PR modified service code for wheelmap but not its test code.
That's okay so long as it's refactoring existing code.
Messages
📖 ✨ Thanks for your contribution to Shields, @chris48s!
📖

Thanks for contributing to our documentation. We ❤️ our documentarians!

Generated by 🚫 dangerJS against b8a184b

@chris48s chris48s changed the title WIP Implement a pattern for dealing with upstream APIs which are slow on the first hit; run [*****] WIP Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] Jun 2, 2023
url,
options = {},
errorMessages = {},
customExceptions = {},
Copy link
Member Author

Choose a reason for hiding this comment

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

errorMessages and customExceptions do somewhat similar but importantly different things. Naming things is hard.

Copy link
Member

Choose a reason for hiding this comment

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

ack yeah I see that being tricky down the road since both of these are (ultimately) used in the setting of error messages.

I feel like in a perfect world (one where no refactoring would be required), I'd be inclined to keep a single top level object for errors and maybe have it have two properties, one that would point to an object that's our standard http response status code to error message dictionary we have today, and the other would be the new I feel like a single top level object that had two properties, one being new dictionary that maps nodejs error codes to a desired error message.

Thinking on a bit more, I suppose having those two mappings separate as you do here is fine; I think it's just the first having squatted on the highly generic errorMessages name that feels problematic.

What if we come up with a more descriptive name for this one for now, e.g. (overly verbosely) nodeErrorCodeToShieldsExceptions and then we could potentially consider renaming errorMessages to something similarly more descriptive down the road?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah broadly agreed. Just thinking about the ergonomics of consuming this at the service layer...

Mapping HTTP error codes to error messages is really common - we do stuff like

errorMessages: { 404: 'package not found' }

all over the place, so lets keep that easy to do in a convenient way.

I've designed this feature (provisionally lets call it customExceptions) to be relatively generic, but I'd expect us to actually use this quite rarely.

I think given that, I'd prefer to keep a flatter structure and avoid making errorMessages a nested object. In the vast majority of cases we would only care about one of the keys. If we can come up with 2 good names we would need to change the code in loads of places anyway, so lets make those two good names the top-level names rather than introduce another layer of nesting.

i.e: If we need to go and change a load of existing service classes, I'd rather do a big find and replace changing

errorMessages: { 404: 'package not found' }

to

httpErrors: { 404: 'package not found' }

than

errorMessages: { httpErrors: { 404: 'package not found' } }

or whatever.

If we ignore the fact that there is loads of existing code using errorMessages and we were naming these two concepts from scratch today, what do you think would be the ideal names/API for these things? I guess the things we currently call errorMessages are really HTTP Status Codes. The thing I've called customExceptions are really System Error Codes. How about httpErrors and systemErrors ?

Copy link
Member

Choose a reason for hiding this comment

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

How about httpErrors and systemErrors ?

Yup, love it. Definitely clears up the naming overlap and is much more succinct than the ones I threw out there

@@ -42,6 +42,7 @@ class ShieldsRuntimeError extends Error {
if (props.underlyingError) {
this.stack = props.underlyingError.stack
}
this.cacheSeconds = props.cacheSeconds
Copy link
Member Author

Choose a reason for hiding this comment

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

I've done this here so any ShieldsRuntimeError can have a custom cacheSeconds but at the moment we're only using it for Inaccessible

Comment on lines 31 to 34
throw new Inaccessible({
...customExceptions[err.code],
underlyingError: err,
})
Copy link
Member Author

Choose a reason for hiding this comment

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

Spreading in this order means you can't manually override underlyingError. I don't think it would ever make sense to do that.

Comment on lines +181 to +184
// don't allow the user to set cacheSeconds any shorter than this._cacheLength
cacheSeconds: Math.max(
...[this._cacheLength, cacheSeconds].filter(x => x !== undefined)
),
Copy link
Member Author

@chris48s chris48s Jun 2, 2023

Choose a reason for hiding this comment

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

I can't leave a line comment on it because I've not changed it in this PR, but there is an existing endpoint service test covering this.

Comment on lines 41 to 50
const cacheLength = coalesce(
serviceOverrideCacheLengthSeconds,
serviceDefaultCacheLengthSeconds,
defaultCacheLengthSeconds
)

// Overrides can apply _more_ caching, but not less. Query param overriding
// can request more overriding than service override, but not less.
const candidateOverrides = [
serviceOverrideCacheLengthSeconds,
overrideCacheLengthFromQueryParams(queryParams),
Copy link
Member Author

@chris48s chris48s Jun 2, 2023

Choose a reason for hiding this comment

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

For the history on this, see #2755

Basically, the only thing relying on the logic as it stood here was the endpoint badge (we don't want endpoint badge users to be able to set a lower cacheSeconds value than the service default). I've moved this logic so it gets applied in endpoint.service.js so that we can now lower cacheSeconds with a custom exception property if we want. If we left this as it was, we'd only be able to raise cacheSeconds with a custom exception property (but not lower it).

@chris48s chris48s changed the title WIP Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] Jun 2, 2023
@chris48s chris48s marked this pull request as ready for review June 2, 2023 19:45
calebcartwright
calebcartwright previously approved these changes Jun 5, 2023
Copy link
Member

@calebcartwright calebcartwright left a comment

Choose a reason for hiding this comment

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

One thought left inline below wrt the names with one suggested change that could accompany this PR and another (potential) follow up. Don't feel terribly strongly though and wouldn't object to this moving ahead as-is with both of those items punted till later, or even if we never do either

@chris48s chris48s changed the title Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] Implement a pattern for dealing with upstream APIs which are slow on the first hit; run [*****] Jun 12, 2023
@chris48s chris48s changed the title Implement a pattern for dealing with upstream APIs which are slow on the first hit; run [*****] Implement a pattern for dealing with upstream APIs which are slow on the first hit; affects [endpoint] Jun 12, 2023
@chris48s
Copy link
Member Author

If we merge anything else that uses errorMessages before we merge this, we'll need to update it.

If there are any open PRs that use errorMessages after we merge this, we'll need to update them.

Copy link
Member

@calebcartwright calebcartwright left a comment

Choose a reason for hiding this comment

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

fwiw this is a case where I'm glad we took a moment to bikeshed on names; I really like the new names and think they provide an additional level of clarity that's useful above and beyond this particular new pattern

@chris48s
Copy link
Member Author

Agreed 👍

@chris48s chris48s merged commit 14892e3 into badges:master Jun 13, 2023
23 checks passed
jNullj added a commit to jNullj/shields-fun-fork that referenced this pull request Jun 14, 2023
PR badges#9233 replaced errorsMessages with httpErrors.
This commit updates the new changes to stay up to date with that PR
chris48s pushed a commit that referenced this pull request Jun 15, 2023
* feat: Add author filter option for CommitActivity

Add a new filter option to [GithubCommitActivity], allowing users to filter the commit activity by a specific author.

To make the filter more explicit, The label display "commits by [author]" for the total amount of commits and "commit activity by [author]" for other intervals when an author filter is selected.

To maintain a clear and organized code structure, The filtered author is added as an argument and not to the shield path.

The request to find the number of commits by the author is made using the REST api rather then the GraphQL api to make it in 1 request rather then 2.

Resolves #9215

* fix: solve eslint errors

* Add tests for [GithubCommitActivity] filter by author

Add tests for the new filter by author feature.

* update [GithubCommitActivity] spec file for new author feat

Add test for new transformAuthorFilter function of GithubCommitActivity added for the author filter feature.

* Fix null string for label of GithubCommitActivity

* Update GithubCommitActivity example

* improve error handeling for GithubCommitActivity

The author filter error handling removed was redundent as it would never execute, there is no way to seperate branch not found from repo not found.

* update depricated functions

PR #9233 replaced errorsMessages with httpErrors.
This commit updates the new changes to stay up to date with that PR

* remove test for nonexisting error

this exception was removed in commit 9e358c8 and is not needed anymore

* Fixed test for commit activity unexisting repo

* Update example for GithubCommitActivity

Picked a user with commits in the repo as an example that would work

* Add test for invalid commit activity branch

Add test for REST API calls in commit activity branch

---------

Co-authored-by: jNullj <jNullj@users.noreply.github.com>
mbtools added a commit to mbtools/shields that referenced this pull request Jun 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Server, BaseService, GitHub auth
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a pattern for dealing with upstream APIs which are slow on the first hit
2 participants