on master help sign in
implemented pagination
commit889aee0692ff68e9438271451eabb7b7c71b35a0 authorSaurabh Nanda <saurabh@vacationlabs.com> dateMon 5 Aug 2019, 22:10 (7 years ago) parent7840254
5 files changed+102−34
assets/css/styles.css+2−1 src/PGQueue/Endpoints.hs+45−28 src/PGQueue/Job.hs+4−4 src/PGQueue/Links.hs+14 src/PGQueue/Web.hs+37−1
assets/css/styles.css+2−1
@@ -73,7 +73,8 @@ ul.nav.nav-pills.job-type-nav > li > a {
7373}
7474
7575ul.search-bar > li.search-filter {
76− padding:0px;
76+ padding:0px;
77+ margin-right: 0.5em;
7778}
7879
7980.filter-value i.glyphicon {
src/PGQueue/Endpoints.hs+45−28
@@ -1,4 +1,4 @@
1−{-# LANGUAGE TypeOperators, DeriveGeneric, NamedFieldPuns, DataKinds #-}
1+{-# LANGUAGE TypeOperators, DeriveGeneric, NamedFieldPuns, DataKinds, StandaloneDeriving, FlexibleContexts #-}
22
33module PGQueue.Endpoints where
44
@@ -29,11 +29,11 @@ import Data.Time.Format.Human as Time
2929import Data.Aeson as Aeson
3030import qualified Data.HashMap.Strict as HM
3131import GHC.Exts (toList)
32−
33−data Routes route = Routes
34− { rFilterResults :: route :- Get '[HTML] (Html ())
35− , rStaticAssets :: route :- "assets" :> Raw
36− } deriving (Generic)
32+import Data.Maybe (fromMaybe)
33+import Data.Text.Conversions (fromText, toText)
34+import Control.Applicative ((<|>))
35+import Data.Time.Convenience as Time
36+import qualified PGQueue.Links as Links
3737
3838startApp :: IO ()
3939startApp = do
@@ -73,19 +73,22 @@ server = Routes
7373 , rStaticAssets = serveDirectoryFileServer "assets"
7474 }
7575
76+
7677withDbConnection :: HasJobMonitor m
7778 => (Connection -> m a)
7879 -> m a
7980withDbConnection fn = getDbPool >>= \pool -> Pool.withResource pool fn
8081
8182filterResults :: HasJobMonitor m
82− => m (Html ())
83−filterResults = do
84− jobs <- withDbConnection $ \conn -> filterJobs blankFilter
83+ => Maybe Filter
84+ -> m (Html ())
85+filterResults mFilter = do
86+ let filters = fromMaybe mempty mFilter
87+ jobs <- withDbConnection $ \conn -> filterJobs filters
8588 t <- liftIO getCurrentTime
8689 pure $ pageLayout $ do
87− searchBar
88− resultsPanel t jobs
90+ searchBar t filters
91+ resultsPanel t filters jobs
8992
9093
9194pageNav :: Html ()
@@ -133,28 +136,37 @@ pageLayout inner = do
133136 script_ [ src_ "https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.js" ] $ ("" :: Text)
134137 script_ [ src_ "assets/js/logo-slider.js" ] $ ("" :: Text)
135138
136−searchBar :: Html ()
137−searchBar = do
139+searchBar :: UTCTime -> Filter -> Html ()
140+searchBar t filter@Filter{filterStatuses, filterCreatedAfter, filterCreatedBefore, filterUpdatedAfter, filterUpdatedBefore, filterJobTypes} = do
138141 form_ [ style_ "padding-top: 2em;" ] $ do
139142 div_ [ class_ "form-group" ] $ do
140143 div_ [ class_ "search-container" ] $ do
141144 ul_ [ class_ "list-inline search-bar" ] $ do
142− -- li_ [ class_ "search-filter" ] $ do
143− -- span_ [ class_ "filter-name" ] $ "Status"
144− -- span_ [ class_ "filter-value" ] $ do
145− -- "Currently running"
146− -- a_ [ href_ "#", class_ "text-danger" ] $ i_ [ class_ "glyphicon glyphicon-remove" ] $ ""
147− li_ [ class_ "search-filter" ] $ ""
145+ forM_ filterStatuses $ \s -> renderFilter "Status" (toText s) "#"
146+ maybe mempty (\x -> renderFilter "Created after" (showText x) "#") filterCreatedAfter
147+ maybe mempty (\x -> renderFilter "Created before" (showText x) "#") filterCreatedBefore
148+ maybe mempty (\x -> renderFilter "Updated after" (showText x) "#") filterUpdatedAfter
149+ maybe mempty (\x -> renderFilter "Updated before" (showText x) "#") filterUpdatedBefore
150+ forM_ filterJobTypes $ \x -> renderFilter "Job type" x "#"
151+
148152 button_ [ class_ "btn btn-default search-button", type_ "button" ] $ "Search"
149153 ul_ [ class_ "list-inline" ] $ do
150154 li_ $ span_ $ strong_ "Common searches:"
151− li_ $ a_ [ href_ "#" ] $ "All jobs"
155+ li_ $ a_ [ href_ (Links.rFilterResults $ Just mempty) ] $ "All jobs"
152156 li_ $ a_ [ href_ "#" ] $ "Currently running"
153− li_ $ a_ [ href_ "#" ] $ "Successful"
154− li_ $ a_ [ href_ "#" ] $ "Failed"
155− li_ $ a_ [ href_ "#" ] $ "Future"
157+ li_ $ a_ [ href_ (Links.rFilterResults $ Just $ filter <> mempty{ filterStatuses = [Job.Success] }) ] $ "Successful"
158+ li_ $ a_ [ href_ (Links.rFilterResults $ Just $ filter <> mempty{ filterStatuses = [Job.Failed] }) ] $ "Failed"
159+ li_ $ a_ [ href_ (Links.rFilterResults $ Just $ filter <> mempty{ filterStatuses = [Job.Queued] }) ] $ "Future"
156160 li_ $ a_ [ href_ "#" ] $ "Retried"
157− li_ $ a_ [ href_ "#" ] $ "Last 10 mins"
161+ li_ $ a_ [ href_ (Links.rFilterResults $ Just $ filter <> mempty{ filterUpdatedAfter = Just $ timeSince t 10 Minutes Ago }) ] $ "Last 10 mins"
162+ where
163+ renderFilter :: Text -> Text -> Text -> Html ()
164+ renderFilter k v u = do
165+ li_ [ class_ "search-filter" ] $ do
166+ span_ [ class_ "filter-name" ] $ toHtml k
167+ span_ [ class_ "filter-value" ] $ do
168+ toHtml v
169+ a_ [ href_ u, class_ "text-danger" ] $ i_ [ class_ "glyphicon glyphicon-remove" ] $ ""
158170
159171
160172timeDuration :: UTCTime -> UTCTime -> (Int, String)
@@ -376,8 +388,8 @@ rowLocked = do
376388 td_ "Text"
377389 td_ $ button_ [ class_ "btn btn-default", type_ "button" ] $ "Unlock"
378390
379−resultsPanel :: UTCTime -> [Job] -> Html ()
380−resultsPanel t jobs = do
391+resultsPanel :: UTCTime -> Filter -> [Job] -> Html ()
392+resultsPanel t filter@Filter{filterPage} jobs = do
381393 div_ [ class_ "panel panel-default" ] $ do
382394 div_ [ class_ "panel-heading" ] $ h3_ [ class_ "panel-title" ] $ do
383395 "Currently running"
@@ -399,8 +411,13 @@ resultsPanel t jobs = do
399411 -- rowRetry
400412 -- rowFailed
401413 div_ [ class_ "panel-footer" ] $ nav_ $ ul_ [ class_ "pager", style_ "margin:0px;" ] $ do
402− li_ [ class_ "previous" ] $ a_ [ href_ "#" ] $ "Prev"
403− li_ [ class_ "next" ] $ a_ [ href_ "#" ] $ "Next"
414+ li_ [ class_ "previous" ] $ case filterPage of
415+ Nothing -> a_ [ disabled_ "disabled" ] $ "Prev"
416+ Just (l, 0) -> a_ [ disabled_ "disabled" ] $ "Prev"
417+ Just (l, o) -> a_ [ href_ (Links.rFilterResults $ Just $ filter {filterPage = Just (l, max 0 $ o - l)}) ] $ "Prev"
418+ li_ [ class_ "next" ] $ case filterPage of
419+ Nothing -> a_ [ href_ (Links.rFilterResults $ Just $ filter {filterPage = Just (10, 10)}) ] $ "Next"
420+ Just (l, o) -> a_ [ href_ (Links.rFilterResults $ Just $ filter {filterPage = Just (l, o + l)}) ] $ "Next"
404421
405422
406423ariaExpanded_ :: Text -> Attribute
src/PGQueue/Job.hs+4−4
@@ -1,4 +1,4 @@
1−{-# LANGUAGE RankNTypes, FlexibleInstances, FlexibleContexts, PartialTypeSignatures, TupleSections, DeriveGeneric #-}
1+{-# LANGUAGE RankNTypes, FlexibleInstances, FlexibleContexts, PartialTypeSignatures, TupleSections, DeriveGeneric, UndecidableInstances #-}
22module PGQueue.Job
33 ( jobMonitor
44 , jobEventListener
@@ -50,7 +50,7 @@ import Data.Time
5050import Data.Aeson hiding (Success)
5151import qualified Data.Aeson as Aeson
5252import qualified Data.Aeson.Types as Aeson (Parser, parseMaybe)
53−import Data.String.Conv (toS)
53+import Data.String.Conv (StringConv(..), toS)
5454import Data.Functor (void)
5555import Control.Monad (forever)
5656import Data.Maybe (isNothing)
@@ -181,13 +181,13 @@ instance ToText Status where
181181 Retry -> "retry"
182182 Failed -> "failed"
183183
184−instance FromText (Either String Status) where
184+instance (StringConv Text a) => FromText (Either a Status) where
185185 fromText t = case t of
186186 "success" -> Right Success
187187 "queued" -> Right Queued
188188 "failed" -> Right Failed
189189 "retry" -> Right Retry
190− x -> Left $ "Unknown job status: " <> toS x
190+ x -> Left $ toS $ "Unknown job status: " <> x
191191
192192instance FromField Status where
193193 fromField f mBS = (fromText <$> (fromField f mBS)) >>= \case
src/PGQueue/Links.hsnew file+14
@@ -0,0 +1,14 @@
1+{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
2+
3+module PGQueue.Links where
4+
5+import Servant
6+import Servant.API.Generic
7+import PGQueue.Web (Routes(..))
8+import Data.String.Conv
9+import Data.Text
10+
11+Routes{..} = allFieldLinks' absText :: Routes (AsLink Text)
12+
13+absText :: Link -> Text
14+absText l = "/" <> (toS $ show $ linkURI l)
src/PGQueue/Web.hs+37−1
@@ -1,4 +1,4 @@
1−{-# LANGUAGE DeriveGeneric, NamedFieldPuns #-}
1+{-# LANGUAGE DeriveGeneric, NamedFieldPuns, TypeOperators, DataKinds #-}
22module PGQueue.Web where
33
44import PGQueue.Types
@@ -14,6 +14,13 @@ import Data.Pool as Pool
1414import UnliftIO
1515import Data.Maybe
1616import Data.String (fromString)
17+import Control.Applicative ((<|>))
18+import Data.List (nub)
19+import Servant
20+import Servant.API.Generic
21+import Servant.HTML.Lucid
22+import Lucid
23+import Data.String.Conv
1724
1825data OrderDirection = Asc | Desc deriving (Eq, Show, Generic, Enum)
1926
@@ -35,6 +42,21 @@ data Filter = Filter
3542 , filterPage :: Maybe (Int, Int)
3643 } deriving (Eq, Show, Generic)
3744
45+instance Semigroup Filter where
46+ (<>) a b = Filter
47+ { filterStatuses = nub (filterStatuses b <> filterStatuses a)
48+ , filterCreatedAfter = filterCreatedAfter b <|> filterCreatedAfter a
49+ , filterCreatedBefore = filterCreatedBefore b <|> filterCreatedBefore a
50+ , filterUpdatedAfter = filterUpdatedAfter b <|> filterUpdatedBefore a
51+ , filterUpdatedBefore = filterUpdatedBefore b <|> filterUpdatedBefore a
52+ , filterJobTypes = nub (filterJobTypes b <> filterJobTypes a)
53+ , filterOrder = filterOrder b <|> filterOrder a
54+ , filterPage = filterPage b <|> filterPage a
55+ }
56+
57+instance Monoid Filter where
58+ mempty = blankFilter
59+
3860blankFilter :: Filter
3961blankFilter = Filter
4062 { filterStatuses = []
@@ -62,6 +84,20 @@ instance FromJSON Filter where
6284 parseJSON = Aeson.genericParseJSON Aeson.defaultOptions{omitNothingFields = True}
6385
6486
87+instance FromHttpApiData Filter where
88+ parseQueryParam x = case eitherDecode (toS x) of
89+ Left e -> Left $ toS e
90+ Right r -> Right r
91+
92+instance ToHttpApiData Filter where
93+ toQueryParam x = toS $ Aeson.encode x
94+
95+data Routes route = Routes
96+ { rFilterResults :: route :- QueryParam "filters" Filter :> Get '[HTML] (Html ())
97+ , rStaticAssets :: route :- "assets" :> Raw
98+ } deriving (Generic)
99+
100+
65101filterJobsQuery :: TableName -> Filter -> (PGS.Query, [Action])
66102filterJobsQuery tname Filter{filterStatuses, filterCreatedBefore, filterCreatedAfter, filterUpdatedBefore, filterUpdatedAfter, filterJobTypes, filterOrder, filterPage} =
67103 ( "SELECT " <> Job.concatJobDbColumns <> " FROM " <> tname <> whereClause <> " " <> (orderClause $ fromMaybe (OrdUpdatedAt, Desc) filterOrder) <> " " <> limitOffsetClause