Currently Site Kit data stores simply store the error in an error property per store which has a few drawbacks:
In components, we currently use an ErrorNotice component which simply takes that single error and displays it. While this works _fairly_ well for module setup flows and settings being edited, it will show more obvious disadvantages as we're migrating dashboard widgets (e.g. PageSpeed Insights widget right now doesn't display any alternative content on error, it just keeps loading). The goal here should be that every widget knows whether (one of) its API queries resulted in a failure and that it then returns appropriate error content. Eventually such errors should use predefined components so that e.g. the widget area renderer can detect that and aggregate certain types of errors together - this way users would see a single error message instead of e.g. 4 times the same one.
Components in general also need to have awareness of errors. For example, loading indicators shouldn't keep loading forever when there was actually an error that will prevent the loading process from being completed. Related example: https://github.com/google/site-kit-wp/commit/d256c1065d91172bd9615682936804e5ddd61f88
Let's think about how we can make errors available to components in a more granular way.
_Do not alter or remove anything below. The following sections will be managed by moderators only._
createErrorStore() function which creates a partial store for errors. It should have:receiveError( error, baseName, args ) (should be marked as private)error should be required.baseName should be documented with something like "This should be a selector or action name."baseName and args (only one error per combination, i.e. an error for the exact same request would override the previous one)error is provided, just store it in error state property like today; this part of the reducer should be annotated as legacy behavior with a TODO comment to remove it once all instances of that behavior have been removederror onlygetError( baseName, args ) (should be marked as private)baseName should be required, but due to legacy support for now no parameters should be required.baseName should be documented with something like "This should be a selector or action name."baseName is provided, return the first error for that baseName (i.e. for any args)error state property like today; this part of the selector should be annotated as legacy behavior with a TODO comment to remove it once all instances of that behavior have been removedgetErrorForSelector( selectorName, args )getError, the only difference is that here selectorName should actually be required (no legacy support needed), and this selector should be publicgetErrorForAction( actionName, args )getError, the only difference is that here actionName should actually be required (no legacy support needed), and this selector should be publicgetErrors( baseName ) (should be marked as private)baseName, or empty arraygetErrorsForSelector( selectorName )getErrorsgetErrorsForAction( actionName )getErrorsgetError or receiveError functions should have that part removed, the new store includes support for that.createFetchStore should dispatch API errors via the new receiveError, if available on the store (in our case it will be for every store, see above).receiveError call with _only_ the error itself, to replicate what is currently hard-coded in the CATCH_FETCH reducer.modules/analytics getAccounts (fetch store uses different name and arguments)modules/analytics getProperties (fetch store uses different name)createSettingsStore saveSettings (fetch store uses different arguments)In essence, the aim of this issue is to map errors to baseName + arguments. This is similar to how resolvers (and their "resolution" state) are mapped to selector name + arguments (see: https://github.com/google/site-kit-wp/blob/64b9b2b04349641f6b8b452267b4d4d59bfa3280/assets/js/googlesitekit/modules/datastore/modules.js#L251).
Currently we store errors encountered on a per-store basis, which means they aren't specific to particular actions/selectors. Dispatching errors for a particular set of arguments solves this problem.
One thing to consider is that we should _clear any errors_ for a given baseName + args combo whenever it is invoked again. Forcing the developer to do this manually is silly, so the aim should be that any action/resolver ahould clear its error before making a new "async" part of the request.
This means, in addition to the ACs mentioned (which act as IB), we should add:
clearError( baseName, args ) (should be marked as private)baseName + args combo so it no longer is returned by getErrorFor* selectors and doesn't appear in content.clearError( baseName, args ) to https://github.com/google/site-kit-wp/blob/ddde4fa52bcdebd93a9dd11784a7a6972144928d/assets/js/googlesitekit/data/create-fetch-store.js#L148 (after START_FETCH)createErrorStore actions, but use the private, basic getError and receieveError for now, with TODO: comments explaining the intent to convert them fully in a future issue. This cuts down on the amount of code to review.Because the ACs here are largely something to follow for IB, I referenced them in the IB but added some context and also an extra clearError action to the IB. I think it's a good idea to integrate error clearing into our actions that dispatch errors so we don't wind up in a state where the error isn't cleared or needs to be cleared elsewhere.
IB ✅
@tofumatt / @felixarntz IB says to add clearError action to the createFetchStore factory but how can i get to the clear function itself without knowning the current datastore name? Without it I can't call registry.dispatch( STORE_NAME ).clearError(...) sequence. Do I need to addd an additional parameter to the createFetchStore function to receive the current store name?
@eugene-manuilov Good point, yes createFetchStore should receive storeName then. Also, createFetchStore should check whether clearError (as well as the other error selectors/actions) exist on the store before calling them, since that is not a given.
Created the #1864 PR with initial changes that include the createErrorStore partial store, changes in the createFetchStore, updates to existing stores and modification of the PSI dashboard widget to use the new approach to track and display errors. The PR itself seems to be pretty big, so I have decided to send it for review and process remaining tasks (if i miss anything) in separate PRs.
@eugene-manuilov I think you already covered everything in the PR overall, there are however a few things to fix. I think the only part that might have made sense to do separately is the PSI component changes, but since you have them there already, let's continue to cover everything in that PR.
@felixarntz I have a question about Analytics' datastores. Do we really need to manually trigger receiveError actions? Error should be already processed by fetch stores and manually triggering receiveError means that we just generate duplicate actions. Am I right or do I miss something? :thinking:
@eugene-manuilov Yes we do. It might feel a bit odd from an engineering perspective, but the reason we need to do that in certain cases:
baseName and args which are passed to the public action or selector.baseName and args that the fetch store receives. These usually match the public action/selector, but in few cases _may_ be different.baseName and args than the public action or selector that is using it. And the consuming code (e.g. the components) shouldn't need to know any internals about the action or selector's fetch mechanism to know where to look for the error.baseName and args differ from the action/selector name and the action/selector args. Essentially, we're patching over these few inconsistencies.baseName and args that we'd manually specify.The ACs have 3 of these cases (which probably is already most of such cases that we have at all), so you can look at those for examples: In each of them, something about the selector/action differs from the respective internal fetch action's signature.
@felixarntz I have addressed your feedback in PR and I think it's ready for another review, but there are two components that rely on the legacy getError selector which I am not sure how to update:
Do you know how to update it?
@eugene-manuilov I don't think we should update these components here? Also not mentioned in the ACs. This PR should only be about the datastore, except for the PSI module widget for which we should add error handling (like you've already done).
@felixarntz you asked me to remove error from the CATCH_FETCH action and from the appropriate reducer: here and here. By doing it, we are losing the default getError() selector (without arguments) because error hanlder in the fetch store itself always sets errors using baseName and arguments list. So, we need to rework all legacy getError occurrences to use the new approach (with selector/action name and arguments list).
@eugene-manuilov I mentioned not only removing error from CATCH_FETCH, but also handling it via the receiveError action, see https://github.com/google/site-kit-wp/pull/1864#discussion_r465956464: I believe you didn't implement the second bullet point, that's how we'd have parameter-less getError working still.
Thanks @felixarntz. I missed that part :doh:. Ok, updated it and it's ready for review. I have updated js tests to use new errors for selectors and actions, plus found some other places where we need to manually call receiveError. Also, added hasErrors selector as it was needed in one of tests.
Tested by simulating an error for the mobile selector and the desktop selector, and only the appropriate section had an error based on the error triggered. Here's a screencast of the mobile-only error being simulated, but both were tested:

QA, very nice ✅