packages feed

bugzilla-redhat 0.3.1 → 0.3.2

raw patch · 11 files changed

+226/−53 lines, 11 filesdep +hspecdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: hspec

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Web.Bugzilla.RedHat: ApikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession
+ Web.Bugzilla.RedHat: BugzillaApikey :: Text -> BugzillaApikey
+ Web.Bugzilla.RedHat: ExternalBug :: Text -> Int -> Text -> Text -> Text -> Int -> ExternalType -> ExternalBug
+ Web.Bugzilla.RedHat: ExternalType :: Text -> Text -> Int -> Text -> Text -> ExternalType
+ Web.Bugzilla.RedHat: [bugExternalBugs] :: Bug -> Maybe [ExternalBug]
+ Web.Bugzilla.RedHat: [externalBugId] :: ExternalBug -> Text
+ Web.Bugzilla.RedHat: [externalBzId] :: ExternalBug -> Int
+ Web.Bugzilla.RedHat: [externalDescription] :: ExternalBug -> Text
+ Web.Bugzilla.RedHat: [externalId] :: ExternalBug -> Int
+ Web.Bugzilla.RedHat: [externalPriority] :: ExternalBug -> Text
+ Web.Bugzilla.RedHat: [externalStatus] :: ExternalBug -> Text
+ Web.Bugzilla.RedHat: [externalTypeDescription] :: ExternalType -> Text
+ Web.Bugzilla.RedHat: [externalTypeFullUrl] :: ExternalType -> Text
+ Web.Bugzilla.RedHat: [externalTypeId] :: ExternalType -> Int
+ Web.Bugzilla.RedHat: [externalTypeType] :: ExternalType -> Text
+ Web.Bugzilla.RedHat: [externalTypeUrl] :: ExternalType -> Text
+ Web.Bugzilla.RedHat: [externalType] :: ExternalBug -> ExternalType
+ Web.Bugzilla.RedHat: apikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession
+ Web.Bugzilla.RedHat: data ExternalBug
+ Web.Bugzilla.RedHat: data ExternalType
+ Web.Bugzilla.RedHat: data Request
+ Web.Bugzilla.RedHat: getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)
+ Web.Bugzilla.RedHat: newtype BugzillaApikey
+ Web.Bugzilla.RedHat: searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]
+ Web.Bugzilla.RedHat: searchBugsAllWithLimit :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [Bug]
+ Web.Bugzilla.RedHat.Search: changedRange :: UTCTime -> UTCTime -> SearchExpression
+ Web.Bugzilla.RedHat.Search: changedSince :: UTCTime -> SearchExpression
+ Web.Bugzilla.RedHat.Search: changedUntil :: UTCTime -> SearchExpression
+ Web.Bugzilla.RedHat.Search: isNotEmpty :: FieldType a => Field a -> SearchExpression
- Web.Bugzilla.RedHat: Bug :: !BugId -> Maybe [Text] -> UserEmail -> User -> [BugId] -> [UserEmail] -> [User] -> Text -> [Text] -> UTCTime -> UserEmail -> User -> [BugId] -> Maybe BugId -> Maybe [Flag] -> [Text] -> Bool -> Bool -> Bool -> Bool -> [Text] -> UTCTime -> Text -> Text -> Text -> Text -> UserEmail -> Text -> [Text] -> Text -> Text -> Text -> Text -> Text -> [Text] -> Text -> HashMap Text Text -> Bug
+ Web.Bugzilla.RedHat: Bug :: !BugId -> Maybe [Text] -> UserEmail -> User -> [BugId] -> [UserEmail] -> [User] -> Text -> [Text] -> UTCTime -> UserEmail -> User -> [BugId] -> Maybe BugId -> Maybe [Flag] -> [Text] -> Bool -> Bool -> Bool -> Bool -> [Text] -> UTCTime -> Text -> Text -> Text -> Text -> UserEmail -> Text -> [Text] -> Text -> Text -> Text -> Text -> Text -> [Text] -> Text -> HashMap Text Text -> Maybe [ExternalBug] -> Bug

Files

ChangeLog.md view
@@ -1,5 +1,16 @@ # bugzilla-redhat version history +## 0.3.2 (2021-06-19)+- BugillaServer can now be fully qualified (@TristanCacqueray)+- Add apikeySession to support api_key auth (@TristanCacqueray)+- Add isNotEmpty search expression operator (@TristanCacqueray)+- Add changedSince and changedUntil search expression (@TristanCacqueray)+- Add searchBugsAllWithLimit, searchBugsAll and getBugAll+  to get all the bug fields (@TristanCacqueray)+- Change Bug to include ExternalBugs information (@TristanCacqueray)+- Export Request+- support ghc-9.0 (@juhp)+ ## 0.3.1 (2021-02-07) - export sendBzRequest 
README.md view
@@ -10,3 +10,12 @@  Relevant links: - https://bugzilla.redhat.com/docs/en/html/api/index.html++## Contributing+Bug reports, feedback, and fixes are welcome.++Thanks to the contributors!++<a href="https://github.com/juhp/hsbugzilla/graphs/contributors">+  <img src="https://contributors-img.web.app/image?repo=juhp/hsbugzilla" />+</a>
+ TODO view
@@ -0,0 +1,7 @@+- support getting bug's requested fields++- instance Monoid SearchExpression?++- rename Bugzilla.Redhat to RedHatBugzilla for 0.4++- use http-query (http-conduit Simple)
bugzilla-redhat.cabal view
@@ -1,8 +1,8 @@ name:                bugzilla-redhat-version:             0.3.1+version:             0.3.2 synopsis:            A Haskell interface to the Bugzilla native REST API-description:         This package is designed to provide an easy-to-use, typesafe-                     interface to querying Bugzilla from Haskell.+description:         This package is designed to provide an easy-to-use,+                     type-safe interface to querying Bugzilla from Haskell.                      .                      This is a friendly fork of Seth Fowler's library,                      with minor updates and API tweaks needed for@@ -13,12 +13,12 @@ author:              Seth Fowler <mark.seth.fowler@gmail.com> maintainer:          Jens Petersen <juhpetersen@gmail.com> copyright:           2014 Seth Fowler,-                     2020 Jens Petersen+                     2020-2021 Jens Petersen category:            Web build-type:          Simple extra-source-files:  README.md                      ChangeLog.md-                     changelog.orig+                     TODO cabal-version:       >=1.10  source-repository head@@ -36,7 +36,7 @@   other-modules:       Web.Bugzilla.RedHat.Internal.Network,                        Web.Bugzilla.RedHat.Internal.Search,                        Web.Bugzilla.RedHat.Internal.Types-  build-depends:       base >=4.6 && <4.15,+  build-depends:       base >=4.6 && <5,                        aeson >=0.7 && <1.6,                        blaze-builder >=0.3 && <0.5,                        bytestring >=0.10 && <0.11,@@ -62,10 +62,22 @@    ghc-options:         -Wall   main-is:             BugzillaDemo.hs-  build-depends:       base >=4.6 && <4.15,+  build-depends:       base >=4.6 && <5,                        bugzilla-redhat,                        containers >=0.5 && <0.7,                        text >=0.11 && <1.3,                        time >=1.4 && <1.10   hs-source-dirs:      demo+  default-language:    Haskell2010++test-suite bugzilla-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base >=4.6 && <5,+                       aeson >=0.7 && <1.6,+                       bugzilla-redhat,+                       hspec >=2.0 && <3.0,+                       time >=1.4 && <1.10+  ghc-options:         -Wall   default-language:    Haskell2010
− changelog.orig
@@ -1,18 +0,0 @@-0.2.1.1--	* Tightened the version bounds for data-default. The old bounds weren't-	accurate.--0.2.1.0--	* Added searchBugs' and searchBugsWithLimit', variations which return-	only a list of BugIds. These are more suitable for polling than the-	existing functions.--0.2.0.0--	* History events now have a sequential id, just like comments.--	* When using an anonymous session, Bugzilla does not return email-	addresses for users.  To accomodate this, the User type now stores email-	addresses as type 'Maybe UserEmail'.
src/Web/Bugzilla/RedHat.hs view
@@ -10,7 +10,7 @@ -- --   A very simple program using this package might look like this: ----- >   ctx <- newBugzillaContext "bugzilla.example.org"+-- >   ctx <- newBugzillaContext "bugzilla.redhat.com" -- >   let session = anonymousSession ctx -- >       user = "me@example.org" -- >       query = AssignedToField .==. user .&&.@@ -21,23 +21,29 @@ -- --   There's a somewhat more in-depth demo program included with the --   source code to this package.+ module Web.Bugzilla.RedHat ( -- * Connecting to Bugzilla   newBugzillaContext , loginSession+, apikeySession , anonymousSession  , BugzillaServer , BugzillaContext , BugzillaSession (..) , BugzillaToken (..)+, BugzillaApikey (..)    -- * Querying Bugzilla , searchBugs+, searchBugsAll , searchBugs' , searchBugsWithLimit+, searchBugsAllWithLimit , searchBugsWithLimit' , getBug+, getBugAll , getAttachment , getAttachments , getComments@@ -49,6 +55,7 @@ , sendBzRequest , intAsText +, Request , BugId , AttachmentId , CommentId@@ -61,6 +68,8 @@ , User (..) , Flag (..) , Bug (..)+, ExternalBug (..)+, ExternalType (..) , Attachment (..) , Comment (..) , History (..)@@ -74,6 +83,7 @@  import Control.Exception (throw, try) import Control.Monad.IO.Class (liftIO)+import Data.Aeson (FromJSON) import qualified Data.Text as T import Network.Connection (TLSSettings(..)) import Network.HTTP.Conduit (mkManagerSettings, newManager)@@ -106,6 +116,10 @@              Left e                        -> throw e              Right token                   -> Just $ LoginSession ctx token +-- | Creates a 'BugzillaSession' using the provided api key.+apikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession+apikeySession = ApikeySession+ -- | Creates an anonymous 'BugzillaSession'. Note that some content --   will be hidden by Bugzilla when you make queries in this state. anonymousSession :: BugzillaContext -> BugzillaSession@@ -118,11 +132,15 @@ -- can be constructed conveniently using the operators in "Web.Bugzilla.Search". searchBugs :: BugzillaSession -> SearchExpression -> IO [Bug] searchBugs session search = do-  let searchQuery = evalSearchExpr search-      req = newBzRequest session ["bug"] searchQuery-  (BugList bugs) <- sendBzRequest session req+  BugList bugs <- doSearchBugs session search Nothing Nothing   return bugs +-- | Similar to 'searchBugs', but return _all fields.+searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]+searchBugsAll session search = do+  BugList bugs <- doSearchBugs session search (Just "_all") Nothing+  return bugs+ -- | Like 'searchBugs', but returns a list of 'BugId's. You can -- retrieve the 'Bug' for each 'BugId' using 'getBug'. The combination -- of 'searchBugs'' and 'getBug' is much less efficient than@@ -131,12 +149,22 @@ -- set of bugs returned by a query has changed. searchBugs' :: BugzillaSession -> SearchExpression -> IO [BugId] searchBugs' session search = do-  let fieldsQuery = [("include_fields", Just "id")]-      searchQuery = evalSearchExpr search-      req = newBzRequest session ["bug"] (fieldsQuery ++ searchQuery)-  (BugIdList bugs) <- sendBzRequest session req+  BugIdList bugs <- doSearchBugs session search (Just "id") Nothing   return bugs +doSearchBugs :: FromJSON a => BugzillaSession -> SearchExpression -> Maybe T.Text -> Maybe (Int, Int) -> IO a+doSearchBugs session search includeField limits = do+  let fieldsQuery = case includeField of+        Nothing -> []+        Just field -> [("include_fields", Just field)]+      limitQuery = case limits of+        Nothing -> []+        Just (limit, offset) -> [("limit", Just $ intAsText limit),+                                 ("offset", Just $ intAsText offset)]+      searchQuery = evalSearchExpr search+      req = newBzRequest session ["bug"] (limitQuery ++ fieldsQuery ++ searchQuery)+  sendBzRequest session req+ -- | Search Bugzilla and returns a limited number of results. You can --   call this repeatedly and use 'offset' to retrieve the results of --   a large query incrementally. Note that most Bugzillas won't@@ -149,13 +177,19 @@                     -> SearchExpression                     -> IO [Bug] searchBugsWithLimit session limit offset search = do-  let limitQuery = [("limit", Just $ intAsText limit),-                    ("offset", Just $ intAsText offset)]-      searchQuery = evalSearchExpr search-      req = newBzRequest session ["bug"] (limitQuery ++ searchQuery)-  (BugList bugs) <- sendBzRequest session req+  BugList bugs <- doSearchBugs session search Nothing (Just (limit, offset))   return bugs +-- | Similar to 'searchBugsWithLimit', but return _all fields.+searchBugsAllWithLimit :: BugzillaSession+                       -> Int  -- ^ The maximum number of results to return.+                       -> Int  -- ^ The offset from the first result to start from.+                       -> SearchExpression+                       -> IO [Bug]+searchBugsAllWithLimit session limit offset search = do+  BugList bugs <- doSearchBugs session search (Just "_all") (Just (limit, offset))+  return bugs+ -- | Like 'searchBugsWithLimit', but returns a list of 'BugId's. See -- 'searchBugs'' for more discussion. searchBugsWithLimit' :: BugzillaSession@@ -164,24 +198,29 @@                      -> SearchExpression                      -> IO [BugId] searchBugsWithLimit' session limit offset search = do-  let fieldsQuery = [("include_fields", Just "id")]-      limitQuery = [("limit", Just $ intAsText limit),-                    ("offset", Just $ intAsText offset)]-      searchQuery = evalSearchExpr search-      req = newBzRequest session ["bug"] (fieldsQuery ++ limitQuery ++ searchQuery)-  (BugIdList bugs) <- sendBzRequest session req+  BugIdList bugs <- doSearchBugs session search (Just "id") (Just (limit, offset))   return bugs  -- | Retrieve a bug by bug number. getBug :: BugzillaSession -> BugId -> IO (Maybe Bug)-getBug session bid = do-  let req = newBzRequest session ["bug", intAsText bid] []+getBug session bid = getBugIncludeFields session bid []++-- | Retrieve all bug field by bug number+getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)+getBugAll session bid = getBugIncludeFields session bid ["_all"]++-- | Retrieve a bug by bug number with fields+getBugIncludeFields :: BugzillaSession -> BugId -> [T.Text] -> IO (Maybe Bug)+getBugIncludeFields session bid includeFields = do+  let req = newBzRequest session ["bug", intAsText bid] query   (BugList bugs) <- sendBzRequest session req   case bugs of     [bug] -> return $ Just bug     []    -> return Nothing     _     -> throw $ BugzillaUnexpectedValue                      "Request for a single bug returned multiple bugs"+  where+    query = map (\f -> ("include_fields", Just f)) includeFields  -- | Retrieve a bug by attachment number. getAttachment :: BugzillaSession -> AttachmentId -> IO (Maybe Attachment)
src/Web/Bugzilla/RedHat/Internal/Network.hs view
@@ -5,6 +5,7 @@ module Web.Bugzilla.RedHat.Internal.Network ( BugzillaServer , BugzillaContext (..)+, BugzillaApikey (..) , BugzillaToken (..) , BugzillaSession (..) , BugzillaException (..)@@ -24,6 +25,7 @@ import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (runResourceT) import Data.Aeson+import Data.Maybe (fromMaybe) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL #if !MIN_VERSION_base(4,11,0)@@ -32,14 +34,13 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Data.Typeable-import Network.HTTP.Conduit (Manager, Request(..), Response(..), defaultRequest, host, httpLbs, path, queryString, secure)+import Network.HTTP.Conduit (Manager, Request(..), Response(..), defaultRequest, host, httpLbs, path, queryString, secure, parseRequest) import Network.HTTP.Types.URI (QueryText, encodePathSegments, renderQueryText)  type BugzillaServer  = T.Text  -- | Holds information about a 'BugzillaServer' and manages outgoing--- connections. You can use 'newBugzillaContext' or--- 'withBugzillaContext' to create one.+-- connections. You can use 'newBugzillaContext' to create one. data BugzillaContext = BugzillaContext   { bzServer  :: BugzillaServer   , bzManager :: Manager@@ -47,6 +48,8 @@  newtype BugzillaToken = BugzillaToken T.Text +newtype BugzillaApikey = BugzillaApikey T.Text+ instance FromJSON BugzillaToken where   parseJSON (Object v) = BugzillaToken <$> v .: "token"   parseJSON _          = mzero@@ -55,10 +58,12 @@ -- 'loginSession', as appropriate, to create one. data BugzillaSession = AnonymousSession BugzillaContext                      | LoginSession BugzillaContext BugzillaToken+                     | ApikeySession BugzillaContext BugzillaApikey  bzContext :: BugzillaSession -> BugzillaContext bzContext (AnonymousSession ctx) = ctx bzContext (LoginSession ctx _)   = ctx+bzContext (ApikeySession ctx _)   = ctx  data BugzillaException   = BugzillaJSONParseError String@@ -81,15 +86,22 @@  newBzRequest :: BugzillaSession -> [T.Text] -> QueryText -> Request newBzRequest session methodParts query =-    sslRequest {-      host        = TE.encodeUtf8 . bzServer . bzContext $ session,+    baseRequest {       path        = toByteString $ encodePathSegments $ "rest" : methodParts,       queryString = toByteString $ renderQueryText True queryWithToken     }   where+    -- Try to parse the bzServer first, if it has a scheme then use it as the base request,+    -- otherwise force a secure ssl request.+    baseRequest :: Request+    baseRequest = fromMaybe (sslRequest { host = serverBytes }) (parseRequest serverStr)+    serverBytes = TE.encodeUtf8 serverTxt+    serverStr = T.unpack serverTxt+    serverTxt = bzServer . bzContext $ session     queryWithToken = case session of-                       AnonymousSession _                   -> query-                       LoginSession _ (BugzillaToken token) -> ("token", Just token) : query+                       AnonymousSession _                     -> query+                       LoginSession _ (BugzillaToken token)   -> ("token", Just token) : query+                       ApikeySession _ (BugzillaApikey token) -> ("api_key", Just token) : query  data BzError = BzError Int String                deriving (Eq, Show)
src/Web/Bugzilla/RedHat/Internal/Search.hs view
@@ -37,6 +37,7 @@ data SearchTerm where   UnaryOp  :: FieldType a => T.Text -> Field a -> SearchTerm   BinaryOp :: (FieldType a, FieldType b) => T.Text -> Field a -> b -> SearchTerm+  EqTerm   :: (FieldType a, FieldType b) => Field a -> b -> SearchTerm  -- | A Boolean expression which can be used to query Bugzilla. data SearchExpression@@ -56,6 +57,7 @@ evalSearchTerm :: Int -> SearchTerm -> [QueryPart] evalSearchTerm t (UnaryOp op field)          = termQuery t field op ("" :: T.Text) evalSearchTerm t (BinaryOp op field val)     = termQuery t field op val+evalSearchTerm _ (EqTerm field val)          = [(searchFieldName field, Just . fvAsText $ val)]  evalSearchExpr :: SearchExpression -> [QueryPart] evalSearchExpr e = snd $ evalSearchExpr' 1 e
src/Web/Bugzilla/RedHat/Internal/Types.hs view
@@ -18,6 +18,8 @@ , User (..) , UserList (..) , Flag (..)+, ExternalType (..)+, ExternalBug (..) , Bug (..) , BugList (..) , BugIdList (..)@@ -338,6 +340,46 @@          <*> v .:? "requestee"   parseJSON _ = mzero +-- | An external bug type+data ExternalType = ExternalType+  { externalTypeDescription :: T.Text+  , externalTypeUrl         :: T.Text+  , externalTypeId          :: Int+  , externalTypeType        :: T.Text+  , externalTypeFullUrl     :: T.Text+  } deriving (Eq, Ord, Show)++instance FromJSON ExternalType where+  parseJSON (Object v) =+    ExternalType <$> v .: "description"+                 <*> v .: "url"+                 <*> v .: "id"+                 <*> v .: "type"+                 <*> v .: "full_url"+  parseJSON _ = mzero++-- | An external bug.+data ExternalBug = ExternalBug+  { externalDescription    :: T.Text+  , externalBzId           :: Int+  , externalPriority       :: T.Text+  , externalBugId          :: T.Text+  , externalStatus         :: T.Text+  , externalId             :: Int+  , externalType           :: ExternalType+  } deriving (Eq, Ord, Show)++instance FromJSON ExternalBug where+  parseJSON (Object v) =+    ExternalBug <$> v .: "ext_description"+                <*> v .: "ext_bz_id"+                <*> v .: "ext_priority"+                <*> v .: "ext_bz_bug_id"+                <*> v .: "ext_status"+                <*> v .: "id"+                <*> v .: "type"+  parseJSON _ = mzero+ -- | A Bugzilla bug. data Bug = Bug   { bugId                  :: !BugId@@ -377,6 +419,7 @@   , bugVersion             :: [T.Text]   , bugWhiteboard          :: T.Text   , bugCustomFields        :: H.HashMap T.Text T.Text+  , bugExternalBugs        :: Maybe [ExternalBug]   } deriving (Eq, Show)  instance FromJSON Bug where@@ -418,6 +461,7 @@           <*> v .: "version"           <*> v .: "whiteboard"           <*> pure (customFields v)+          <*> v .:? "external_bugs"   parseJSON _ = mzero  customFields :: Object -> H.HashMap T.Text T.Text
src/Web/Bugzilla/RedHat/Search.hs view
@@ -22,11 +22,15 @@ , containsAll , changedBefore , changedAfter+, changedSince+, changedUntil+, changedRange , changedFrom , changedTo , changedBy , contentMatches , isEmpty+, isNotEmpty , (.&&.) , (.||.) , not'@@ -94,6 +98,18 @@ changedAfter :: FieldType a => Field a -> UTCTime -> SearchExpression changedAfter = (Term .) . BinaryOp "changedafter" +-- | Filter bug changed since UTCTime+changedSince :: UTCTime -> SearchExpression+changedSince ts = Term $ EqTerm (CustomField "chfieldfrom") ts++-- | Filter bug changed until UTCTime+changedUntil :: UTCTime -> SearchExpression+changedUntil ts = Term $ EqTerm (CustomField "chfieldto") ts++-- | Filter bug changed in range+changedRange :: UTCTime -> UTCTime -> SearchExpression+changedRange from to = changedSince from .&&. changedUntil to+ changedFrom :: FieldType a => Field a -> a -> SearchExpression changedFrom = (Term .) . BinaryOp "changedfrom" @@ -108,6 +124,9 @@  isEmpty :: FieldType a => Field a -> SearchExpression isEmpty = Term . UnaryOp "isempty"++isNotEmpty :: FieldType a => Field a -> SearchExpression+isNotEmpty = Term . UnaryOp "isnotempty"  (.&&.) :: SearchExpression -> SearchExpression -> SearchExpression (.&&.) (And as) (And bs) = And (as ++ bs)
+ test/Spec.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+module Main (main) where++import Test.Hspec+import Web.Bugzilla.RedHat.Search++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "unit tests" $ do+  describe "search" $ do+    it "eval isNotEmpty" $ do+      evalSearchExpr (isNotEmpty (CustomField "my_field"))+        `shouldBe` [ ("f1", Just "my_field"),+                     ("o1", Just "isnotempty"),+                     ("v1", Just "")+                   ]+    it "eval changedSince" $ do+      evalSearchExpr (changedSince (read "2021-04-01 00:00:00 UTC"))+        `shouldBe` [("chfieldfrom", Just "2021-04-01T00:00:00Z")]++    it "eval changedRange" $ do+      evalSearchExpr+        ( changedRange+            (read "2021-04-01 00:00:00 UTC")+            (read "2021-04-13 00:00:00 UTC")+        )+        `shouldBe` [ -- TODO: fix unused f1 and f4 query args+                     ("f1", Just "OP"),+                     ("f4", Just "CP"),+                     ("chfieldfrom", Just "2021-04-01T00:00:00Z"),+                     ("chfieldto", Just "2021-04-13T00:00:00Z")+                   ]