feat(users/Profpatsch/whatcd-resolver): add autorefresh
Adds a little polling mechanism that compares against an ID that is generated anew every time the server is restarted. Works well together with shortcuttable. Change-Id: Icc6745b599e43881c14349794feaf5794cfe6777 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11172 Autosubmit: Profpatsch <mail@profpatsch.de> Reviewed-by: Profpatsch <mail@profpatsch.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
efa5fe1239
commit
3b9fb1aa60
4 changed files with 82 additions and 9 deletions
|
@ -125,11 +125,11 @@
|
||||||
message: "`void` leads to bugs. Use an explicit `_ <- …` instead"
|
message: "`void` leads to bugs. Use an explicit `_ <- …` instead"
|
||||||
|
|
||||||
- name: Data.Foldable.length
|
- name: Data.Foldable.length
|
||||||
within: []
|
within: ["MyPrelude"]
|
||||||
message: "`Data.Foldable.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`."
|
message: "`Data.Foldable.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`."
|
||||||
|
|
||||||
- name: Prelude.length
|
- name: Prelude.length
|
||||||
within: [MyPrelude]
|
within: ["MyPrelude"]
|
||||||
message: "`Prelude.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`."
|
message: "`Prelude.length` is dangerous to use, because it also works on types you wouldn’t expect, like `length (3,4) == 1` and `length (Just 2) == 1`. Use the `length` function for your specific type instead, for example `List.length` or `Map.length`."
|
||||||
|
|
||||||
# Using an explicit lambda with its argument “underscored”
|
# Using an explicit lambda with its argument “underscored”
|
||||||
|
|
|
@ -164,6 +164,21 @@ querySingleRow ::
|
||||||
querySingleRow qry params = do
|
querySingleRow qry params = do
|
||||||
query qry params >>= ensureSingleRow
|
query qry params >>= ensureSingleRow
|
||||||
|
|
||||||
|
-- TODO: implement via fold, so that the result doesn’t have to be realized in memory
|
||||||
|
querySingleRowWith ::
|
||||||
|
( MonadPostgres m,
|
||||||
|
ToRow qParams,
|
||||||
|
Typeable qParams,
|
||||||
|
Typeable a,
|
||||||
|
MonadThrow m
|
||||||
|
) =>
|
||||||
|
Query ->
|
||||||
|
qParams ->
|
||||||
|
Decoder a ->
|
||||||
|
Transaction m a
|
||||||
|
querySingleRowWith qry params decoder = do
|
||||||
|
queryWith qry params decoder >>= ensureSingleRow
|
||||||
|
|
||||||
-- TODO: implement via fold, so that the result doesn’t have to be realized in memory
|
-- TODO: implement via fold, so that the result doesn’t have to be realized in memory
|
||||||
querySingleRowMaybe ::
|
querySingleRowMaybe ::
|
||||||
( MonadPostgres m,
|
( MonadPostgres m,
|
||||||
|
|
|
@ -66,6 +66,17 @@ addAttribute span key a = Otel.addAttribute span ("_." <> key) a
|
||||||
addAttributes :: (MonadIO m) => Otel.Span -> HashMap Text Otel.Attribute -> m ()
|
addAttributes :: (MonadIO m) => Otel.Span -> HashMap Text Otel.Attribute -> m ()
|
||||||
addAttributes span attrs = Otel.addAttributes span $ attrs & HashMap.mapKeys ("_." <>)
|
addAttributes span attrs = Otel.addAttributes span $ attrs & HashMap.mapKeys ("_." <>)
|
||||||
|
|
||||||
|
appThrowTreeNewSpan :: (MonadThrow m, MonadOtel m) => Text -> ErrorTree -> m a
|
||||||
|
appThrowTreeNewSpan spanName exc = inSpan' spanName $ \span -> do
|
||||||
|
let msg = prettyErrorTree exc
|
||||||
|
recordException
|
||||||
|
span
|
||||||
|
( T2
|
||||||
|
(label @"type_" "AppException")
|
||||||
|
(label @"message" msg)
|
||||||
|
)
|
||||||
|
throwM $ AppException msg
|
||||||
|
|
||||||
appThrowTree :: (MonadThrow m, MonadIO m) => Otel.Span -> ErrorTree -> m a
|
appThrowTree :: (MonadThrow m, MonadIO m) => Otel.Span -> ErrorTree -> m a
|
||||||
appThrowTree span exc = do
|
appThrowTree span exc = do
|
||||||
let msg = prettyErrorTree exc
|
let msg = prettyErrorTree exc
|
||||||
|
@ -87,6 +98,11 @@ assertM span f v = case f v of
|
||||||
Right a -> pure a
|
Right a -> pure a
|
||||||
Left err -> appThrowTree span err
|
Left err -> appThrowTree span err
|
||||||
|
|
||||||
|
assertMNewSpan :: (MonadThrow f, MonadOtel f) => Text -> (t -> Either ErrorTree a) -> t -> f a
|
||||||
|
assertMNewSpan spanName f v = case f v of
|
||||||
|
Right a -> pure a
|
||||||
|
Left err -> appThrowTreeNewSpan spanName err
|
||||||
|
|
||||||
-- | A specialized variant of @addEvent@ that records attributes conforming to
|
-- | A specialized variant of @addEvent@ that records attributes conforming to
|
||||||
-- the OpenTelemetry specification's
|
-- the OpenTelemetry specification's
|
||||||
-- <https://github.com/open-telemetry/opentelemetry-specification/blob/49c2f56f3c0468ceb2b69518bcadadd96e0a5a8b/specification/trace/semantic_conventions/exceptions.md semantic conventions>
|
-- <https://github.com/open-telemetry/opentelemetry-specification/blob/49c2f56f3c0468ceb2b69518bcadadd96e0a5a8b/specification/trace/semantic_conventions/exceptions.md semantic conventions>
|
||||||
|
|
|
@ -82,7 +82,16 @@ main =
|
||||||
htmlUi :: AppT IO ()
|
htmlUi :: AppT IO ()
|
||||||
htmlUi = do
|
htmlUi = do
|
||||||
let debug = True
|
let debug = True
|
||||||
withRunInIO $ \runInIO -> Warp.run 9092 $ \req respond -> do
|
uniqueRunId <-
|
||||||
|
runTransaction $
|
||||||
|
querySingleRowWith
|
||||||
|
[sql|
|
||||||
|
SELECT gen_random_uuid()::text
|
||||||
|
|]
|
||||||
|
()
|
||||||
|
(Dec.fromField @Text)
|
||||||
|
|
||||||
|
withRunInIO $ \runInIO -> Warp.run 9093 $ \req respond -> do
|
||||||
let catchAppException act =
|
let catchAppException act =
|
||||||
try act >>= \case
|
try act >>= \case
|
||||||
Right a -> pure a
|
Right a -> pure a
|
||||||
|
@ -95,7 +104,7 @@ htmlUi = do
|
||||||
if debug
|
if debug
|
||||||
then Html.Pretty.renderHtml >>> stringToText >>> textToBytesUtf8 >>> toLazyBytes
|
then Html.Pretty.renderHtml >>> stringToText >>> textToBytesUtf8 >>> toLazyBytes
|
||||||
else Html.renderHtml
|
else Html.renderHtml
|
||||||
let h route act =
|
let hh route act =
|
||||||
runInIO $
|
runInIO $
|
||||||
Otel.inSpan'
|
Otel.inSpan'
|
||||||
[fmt|Route {route }|]
|
[fmt|Route {route }|]
|
||||||
|
@ -108,9 +117,9 @@ htmlUi = do
|
||||||
)
|
)
|
||||||
( \span -> withRunInIO $ \runInIO' -> do
|
( \span -> withRunInIO $ \runInIO' -> do
|
||||||
res <- runInIO' $ act span
|
res <- runInIO' $ act span
|
||||||
respond . Wai.responseLBS Http.ok200 [("Content-Type", "text/html")] . renderHtml $ res
|
respond . Wai.responseLBS Http.ok200 ([("Content-Type", "text/html")] <> res.extraHeaders) . renderHtml $ res.html
|
||||||
)
|
)
|
||||||
|
let h route act = hh route (\span -> act span <&> (\html -> T2 (label @"html" html) (label @"extraHeaders" [])))
|
||||||
let mp span parser =
|
let mp span parser =
|
||||||
Multipart.parseMultipartOrThrow
|
Multipart.parseMultipartOrThrow
|
||||||
(appThrowTree span)
|
(appThrowTree span)
|
||||||
|
@ -127,8 +136,12 @@ htmlUi = do
|
||||||
Parse.runParse "Unable to find the right request query arguments" (lmap Wai.queryString parser) req
|
Parse.runParse "Unable to find the right request query arguments" (lmap Wai.queryString parser) req
|
||||||
& assertM span id
|
& assertM span id
|
||||||
|
|
||||||
|
let parseQueryArgsNewSpan spanName parser =
|
||||||
|
Parse.runParse "Unable to find the right request query arguments" (lmap Wai.queryString parser) req
|
||||||
|
& assertMNewSpan spanName id
|
||||||
|
|
||||||
case req & Wai.pathInfo & Text.intercalate "/" of
|
case req & Wai.pathInfo & Text.intercalate "/" of
|
||||||
"" -> h "/" mainHtml
|
"" -> h "/" (mainHtml uniqueRunId)
|
||||||
"snips/redacted/search" -> do
|
"snips/redacted/search" -> do
|
||||||
h "/snips/redacted/search" $ \span -> do
|
h "/snips/redacted/search" $ \span -> do
|
||||||
dat <-
|
dat <-
|
||||||
|
@ -209,12 +222,30 @@ htmlUi = do
|
||||||
)
|
)
|
||||||
jsonld <- httpGetJsonLd (qry.target)
|
jsonld <- httpGetJsonLd (qry.target)
|
||||||
pure $ renderJsonld jsonld
|
pure $ renderJsonld jsonld
|
||||||
otherRoute -> h [fmt|/{otherRoute}|] mainHtml
|
"autorefresh" -> do
|
||||||
|
qry <-
|
||||||
|
runInIO $
|
||||||
|
parseQueryArgsNewSpan
|
||||||
|
"Autorefresh Query Parse"
|
||||||
|
( label @"hasItBeenRestarted"
|
||||||
|
<$> singleQueryArgument "hasItBeenRestarted" Field.utf8
|
||||||
|
)
|
||||||
|
respond $
|
||||||
|
Wai.responseLBS
|
||||||
|
Http.ok200
|
||||||
|
( [("Content-Type", "text/html")]
|
||||||
|
<> if uniqueRunId /= qry.hasItBeenRestarted
|
||||||
|
then -- cause the client side to refresh
|
||||||
|
[("HX-Refresh", "true")]
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
""
|
||||||
|
otherRoute -> h [fmt|/{otherRoute}|] (mainHtml uniqueRunId)
|
||||||
where
|
where
|
||||||
everySecond :: Text -> Enc -> Html -> Html
|
everySecond :: Text -> Enc -> Html -> Html
|
||||||
everySecond call extraData innerHtml = [hsx|<div hx-trigger="every 1s" hx-swap="outerHTML" hx-post={call} hx-vals={Enc.encToBytesUtf8 extraData}>{innerHtml}</div>|]
|
everySecond call extraData innerHtml = [hsx|<div hx-trigger="every 1s" hx-swap="outerHTML" hx-post={call} hx-vals={Enc.encToBytesUtf8 extraData}>{innerHtml}</div>|]
|
||||||
|
|
||||||
mainHtml _span = runTransaction $ do
|
mainHtml uniqueRunId _span = runTransaction $ do
|
||||||
jsonld <-
|
jsonld <-
|
||||||
httpGetJsonLd
|
httpGetJsonLd
|
||||||
( URI.parseURI "https://musicbrainz.org/work/92000fd4-d304-406d-aeb4-6bdbeed318ec" & annotate "not an URI" & unwrapError,
|
( URI.parseURI "https://musicbrainz.org/work/92000fd4-d304-406d-aeb4-6bdbeed318ec" & annotate "not an URI" & unwrapError,
|
||||||
|
@ -257,6 +288,17 @@ htmlUi = do
|
||||||
<div id="redacted-search-results">
|
<div id="redacted-search-results">
|
||||||
{bestTorrentsTable}
|
{bestTorrentsTable}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- refresh the page if the uniqueRunId is different -->
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
type="text"
|
||||||
|
id="autorefresh"
|
||||||
|
name="hasItBeenRestarted"
|
||||||
|
value={uniqueRunId}
|
||||||
|
hx-get="/autorefresh"
|
||||||
|
hx-trigger="every 5s"
|
||||||
|
hx-swap="none"
|
||||||
|
/>
|
||||||
</body>
|
</body>
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue