From eec06d1e966d327dbd5c893224dfc5a826ea6b41 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Fri, 22 Dec 2023 20:49:30 +0000 Subject: [PATCH] web: display crawler errors --- CHANGELOG.md | 2 ++ web/src/App.res | 39 +++++++++++++++++++++++++++++++++++ web/src/components/Search.res | 14 +++++++++++++ web/src/components/Store.res | 4 ++++ 4 files changed, 59 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ae6a128..14c1c55d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. - [crawler] Proxy can be configured with `HTTP_PROXY` and `HTTPS_PROXY` environment. To proxy http requests between crawlers and the api, use the `API_PROXY` environment. - [crawler] A new `groups` sub-field in all Author fields (`on_author` and `author`) for `Change` and `Events`. Groups memberships are reflected from the config file to the database. +- [crawler] Processing errors are no longer fatal and they are now stored in the index. +- [web] A red bell is added to the UI when crawler errors exists for the given query to display the missing data. ### Changed diff --git a/web/src/App.res b/web/src/App.res index 0f730dca5..11822d9e1 100644 --- a/web/src/App.res +++ b/web/src/App.res @@ -290,6 +290,34 @@ module About = { } } +module Errors = { + module CrawlerError = { + @react.component + let make = (~err: CrawlerTypes.crawler_error) => { + let entity: option = err.entity->Belt.Option.flatMap(Js.Json.stringifyAny) +
+ getDate} /> +
{("entity: " ++ entity->Belt.Option.getWithDefault(""))->str}
+
{("message: " ++ err.message)->str}
+
{("body: " ++ err.body)->str}
+
+
+ } + } + + @react.component + let make = (~store: Store.t) => { + let (state, _) = store + +

{"Crawler Errors"->str}

+

+ {"The following errors happened when updating the index. This is likely causing some data to be missing."->str} +

+ {state.errors->Belt.List.map(e => )->Belt.List.toArray->React.array} +
+ } +} + module App = { @react.component let make = (~about: ConfigTypes.about) => { @@ -350,6 +378,16 @@ module App = { + {state.errors->Belt.List.head->Belt.Option.isNone + ? React.null + : +
("/" ++ state.index ++ "/errors")->RescriptReactRouter.push} + style={ReactDOM.Style.make(~cursor="pointer", ~paddingLeft="5px", ())}> + +
+
} + // //
| list{_, "metrics"} => | list{_, "metric", name} => + | list{_, "errors"} => | _ =>

{"Not found"->str}

}} diff --git a/web/src/components/Search.res b/web/src/components/Search.res index b14cb4855..5e29d2ed7 100644 --- a/web/src/components/Search.res +++ b/web/src/components/Search.res @@ -399,6 +399,20 @@ module Top = { None }, [state.query]) + // Update crawler error + let handleErrors = (resp: WebApi.axiosResponse) => + switch resp.data { + | CrawlerTypes.Success(errors_list) => + SetErrors(errors_list.errors)->dispatch->Js.Promise.resolve + | CrawlerTypes.Error(err) => Js.Console.error(err)->Js.Promise.resolve + } + + React.useEffect1(() => { + ({index: state.index, query: state.query}->WebApi.Crawler.errors + |> Js.Promise.then_(handleErrors))->ignore + None + }, [state.query]) + // Dispatch the value upstream let handleCheck = (newValue, res: WebApi.axiosResponse) => { switch res.data { diff --git a/web/src/components/Store.res b/web/src/components/Store.res index 87afbe388..606a5f81e 100644 --- a/web/src/components/Store.res +++ b/web/src/components/Store.res @@ -66,6 +66,7 @@ module Store = { about: ConfigTypes.about, dexie: Dexie.Database.t, toasts: list, + errors: list, } type action = | ChangeIndex(string) @@ -74,6 +75,7 @@ module Store = { | SetLimit(int) | SetOrder(option) | SetAuthorScopedTab(authorScopedTab) + | SetErrors(list) | FetchFields(fieldsRespR) | FetchSuggestions(suggestionsR) | FetchProjects(projectsR) @@ -121,6 +123,7 @@ module Store = { changes_pies_panel: false, dexie: MonoIndexedDB.mkDexie(), toasts: list{}, + errors: list{}, } let reducer = (state: t, action: action) => @@ -151,6 +154,7 @@ module Store = { Prelude.setLocationSearch("l", limit->string_of_int)->ignore {...state, limit: limit} } + | SetErrors(errors) => {...state, errors: errors} | FetchFields(res) => {...state, fields: res->RemoteData.fmap(resp => resp.fields)} | FetchSuggestions(res) => {...state, suggestions: res} | FetchProjects(res) => {...state, projects: res}