From b94e60f4daf6755a2e8d8ee7358f68aab75ae1b1 Mon Sep 17 00:00:00 2001 From: Olga Bulat Date: Mon, 2 Oct 2023 18:24:44 +0300 Subject: [PATCH] Show timeout errors on the frontend (#2838) * Show timeout errors on the frontend * Use FetchingError in all stores * Fix error * Show client-side errors on single result pages * Set 500 as a non-retriable error * Add changes from code review * Use local base64 image for thumbnail * Fix footer * Fix image-cell test * Extract common error checking functionality * Update unit tests --- .../VErrorSection/VErrorSection.vue | 22 ++-- .../meta/VErrorSection.stories.mdx | 37 +++--- .../VExternalSearch/VExternalSearchForm.vue | 2 +- frontend/src/components/VFooter/VFooter.vue | 2 +- .../VSearchResultsGrid/VAudioCollection.vue | 4 +- .../VSearchResultsGrid/VImageGrid.vue | 10 +- frontend/src/constants/errors.ts | 40 ++++++- frontend/src/middleware/search.ts | 11 +- frontend/src/middleware/single-result.ts | 9 +- frontend/src/pages/audio/_id/index.vue | 13 ++- frontend/src/pages/image/_id/index.vue | 16 ++- frontend/src/pages/search.vue | 32 +++--- frontend/src/pages/search/audio.vue | 4 +- frontend/src/pages/search/image.vue | 4 +- frontend/src/stores/media/index.ts | 105 ++++++------------ frontend/src/stores/media/related-media.ts | 24 ++-- frontend/src/stores/media/single-result.ts | 32 ++---- frontend/src/stores/provider.ts | 21 ++-- frontend/src/types/fetch-state.ts | 35 +++++- frontend/src/utils/errors.ts | 88 +++++++++++++++ .../unit/specs/stores/media-store.spec.js | 71 +++--------- .../test/unit/specs/stores/provider.spec.js | 36 ++++-- .../specs/stores/single-result-store.spec.js | 29 ++++- 23 files changed, 373 insertions(+), 274 deletions(-) create mode 100644 frontend/src/utils/errors.ts diff --git a/frontend/src/components/VErrorSection/VErrorSection.vue b/frontend/src/components/VErrorSection/VErrorSection.vue index 42363862e6a..51edfcf524e 100644 --- a/frontend/src/components/VErrorSection/VErrorSection.vue +++ b/frontend/src/components/VErrorSection/VErrorSection.vue @@ -18,10 +18,9 @@ diff --git a/frontend/src/constants/errors.ts b/frontend/src/constants/errors.ts index 9b48069ac10..7cf4057954e 100644 --- a/frontend/src/constants/errors.ts +++ b/frontend/src/constants/errors.ts @@ -5,7 +5,45 @@ export const NO_RESULT = "NO_RESULT" export const SERVER_TIMEOUT = "SERVER_TIMEOUT" +export const ECONNABORTED = "ECONNABORTED" -export const errorCodes = [NO_RESULT, SERVER_TIMEOUT] as const +export const ERR_UNKNOWN = "ERR_UNKNOWN" + +export const customErrorCodes = [ + NO_RESULT, + SERVER_TIMEOUT, + ECONNABORTED, + ERR_UNKNOWN, +] as const + +/** + * The error codes Axios uses. + * @see https://github.com/axios/axios/blob/9588fcdec8aca45c3ba2f7968988a5d03f23168c/lib/core/AxiosError.js#L57C2-L71 + */ +const axiosErrorCodes = [ + "ERR_BAD_OPTION_VALUE", + "ERR_BAD_OPTION", + "ECONNABORTED", + "ETIMEDOUT", + "ERR_NETWORK", + "ERR_FR_TOO_MANY_REDIRECTS", + "ERR_DEPRECATED", + "ERR_BAD_RESPONSE", + "ERR_BAD_REQUEST", + "ERR_CANCELED", + "ERR_NOT_SUPPORT", + "ERR_INVALID_URL", +] as const + +export const errorCodes = [...customErrorCodes, ...axiosErrorCodes] as const + +export const clientSideErrorCodes: readonly ErrorCode[] = [ + ECONNABORTED, + SERVER_TIMEOUT, + NO_RESULT, + ERR_UNKNOWN, + "ERR_NETWORK", + "ETIMEDOUT", +] as const export type ErrorCode = (typeof errorCodes)[number] diff --git a/frontend/src/middleware/search.ts b/frontend/src/middleware/search.ts index f0f2cd78c87..93a19c545b6 100644 --- a/frontend/src/middleware/search.ts +++ b/frontend/src/middleware/search.ts @@ -1,6 +1,7 @@ import { useSearchStore } from "~/stores/search" import { useMediaStore } from "~/stores/media" -import { NO_RESULT } from "~/constants/errors" + +import { handledClientSide } from "~/utils/errors" import type { Middleware } from "@nuxt/types" @@ -44,13 +45,7 @@ export const searchMiddleware: Middleware = async ({ const results = await mediaStore.fetchMedia() const fetchingError = mediaStore.fetchState.fetchingError - // NO_RESULTS and timeout are handled client-side, for other errors show server error page - if ( - !results && - fetchingError && - !fetchingError?.message?.includes(NO_RESULT) && - !fetchingError?.message?.includes("timeout") - ) { + if (!results && fetchingError && !handledClientSide(fetchingError)) { nuxtError(fetchingError) } } diff --git a/frontend/src/middleware/single-result.ts b/frontend/src/middleware/single-result.ts index ca9f2f81b65..83babf0218e 100644 --- a/frontend/src/middleware/single-result.ts +++ b/frontend/src/middleware/single-result.ts @@ -1,6 +1,7 @@ import { useSingleResultStore } from "~/stores/media/single-result" import { useSearchStore } from "~/stores/search" import { useRelatedMediaStore } from "~/stores/media/related-media" +import { isRetriable } from "~/utils/errors" import { AUDIO, IMAGE } from "~/constants/media" @@ -17,11 +18,15 @@ export const singleResultMiddleware: Middleware = async ({ if (process.server) { const media = await singleResultStore.fetch(mediaType, route.params.id) - await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id) if (!media) { - error(singleResultStore.fetchState.fetchingError ?? {}) + const fetchingError = singleResultStore.fetchState.fetchingError + + if (fetchingError && !isRetriable(fetchingError)) { + error(fetchingError ?? {}) + } } + await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id) } else { // Client-side rendering singleResultStore.setMediaById(mediaType, route.params.id) diff --git a/frontend/src/pages/audio/_id/index.vue b/frontend/src/pages/audio/_id/index.vue index 28afd835bd7..8091a2b9dfc 100644 --- a/frontend/src/pages/audio/_id/index.vue +++ b/frontend/src/pages/audio/_id/index.vue @@ -1,6 +1,11 @@