Skip to content

Commit 54a1ef9

Browse files
Soorya Narayanhvr
Soorya Narayan
authored andcommitted
Implement new table-based browsable package index
The new index relies on jQuery DataTables to implement client-side sorting. This has been factored out of the HSoC work haskell#514 by @sooryan The code has been refactored & cleaned up by Duncan, Gershom and myself.
1 parent faba2dd commit 54a1ef9

File tree

10 files changed

+184
-87
lines changed

10 files changed

+184
-87
lines changed

Distribution/Server/Features/Html.hs

Lines changed: 66 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,12 @@ import Data.Array (Array, listArray)
7070
import qualified Data.Array as Array
7171
import qualified Data.Ix as Ix
7272
import Data.Time.Format (formatTime)
73-
import Data.Time.Clock (getCurrentTime)
74-
import qualified Data.Time.Format.Human as HumanTime
7573
import Data.Time.Locale.Compat (defaultTimeLocale)
7674
import qualified Data.ByteString.Lazy as BS (ByteString)
7775

7876
import Text.XHtml.Strict
7977
import qualified Text.XHtml.Strict as XHtml
8078
import Text.XHtml.Table (simpleTable)
81-
import Network.URI (escapeURIString, isUnreserved)
8279

8380

8481
-- TODO: move more of the below to Distribution.Server.Pages.*, it's getting
@@ -129,6 +126,7 @@ initHtmlFeature env@ServerEnv{serverTemplatesDir, serverTemplatesMode,
129126
, "distro-monitor.html"
130127
, "revisions.html"
131128
, "package-page.html"
129+
, "table-interface.html"
132130
]
133131

134132

@@ -158,7 +156,7 @@ initHtmlFeature env@ServerEnv{serverTemplatesDir, serverTemplatesMode,
158156
tarIndexCache
159157
reportsCore
160158
usersdetails
161-
(htmlUtilities core tags)
159+
(htmlUtilities core tags user)
162160
mainCache namesCache
163161
templates
164162

@@ -268,6 +266,8 @@ htmlFeature env@ServerEnv{..}
268266
cachePackagesPage
269267
cacheNamesPage
270268
templates
269+
list
270+
names
271271
htmlUsers = mkHtmlUsers user usersdetails
272272
htmlUploads = mkHtmlUploads utilities upload
273273
htmlDocUploads = mkHtmlDocUploads utilities core docsCore templates
@@ -277,8 +277,8 @@ htmlFeature env@ServerEnv{..}
277277
docsCandidates tarIndexCache
278278
candidates templates
279279
htmlPreferred = mkHtmlPreferred utilities core versions
280-
htmlTags = mkHtmlTags utilities core list tags
281-
htmlSearch = mkHtmlSearch utilities core list names
280+
htmlTags = mkHtmlTags utilities core list tags templates
281+
htmlSearch = mkHtmlSearch utilities core list names templates
282282

283283
htmlResources = concat [
284284
htmlCoreResources htmlCore
@@ -460,11 +460,13 @@ mkHtmlCore :: ServerEnv
460460
-> AsyncCache Response
461461
-> AsyncCache Response
462462
-> Templates
463+
-> ListFeature
464+
-> SearchFeature
463465
-> HtmlCore
464466
mkHtmlCore ServerEnv{serverBaseURI}
465467
utilities@HtmlUtilities{..}
466468
UserFeature{queryGetUserDb, checkAuthenticated}
467-
CoreFeature{coreResource}
469+
CoreFeature{coreResource, queryGetPackageIndex}
468470
VersionsFeature{ versionsResource
469471
, queryGetDeprecatedFor
470472
, queryGetPreferredInfo
@@ -484,6 +486,8 @@ mkHtmlCore ServerEnv{serverBaseURI}
484486
cachePackagesPage
485487
cacheNamesPage
486488
templates
489+
ListFeature{makeItemList}
490+
SearchFeature{..}
487491
= HtmlCore{..}
488492
where
489493
cores@CoreResource{packageInPath, lookupPackageName, lookupPackageId} = coreResource
@@ -511,6 +515,10 @@ mkHtmlCore ServerEnv{serverBaseURI}
511515
, (resourceAt "/packages/names" ) {
512516
resourceGet = [("html", const $ readAsyncCache cacheNamesPage)]
513517
}
518+
, (resourceAt "/packages/browse" ) {
519+
resourceDesc = [(GET, "Show browsable list of all packages")]
520+
, resourceGet = [("html", serveBrowsePage)]
521+
}
514522
, (extendResource $ corePackagesPage cores) {
515523
resourceDesc = [(GET, "Show package index")]
516524
, resourceGet = [("html", const $ readAsyncCache cachePackagesPage)]
@@ -608,6 +616,21 @@ mkHtmlCore ServerEnv{serverBaseURI}
608616
, "versions" $= map packageId pkgs
609617
]
610618

619+
serveBrowsePage :: DynamicPath -> ServerPartE Response
620+
serveBrowsePage _ = do
621+
pkgIndex <- queryGetPackageIndex
622+
let packageNames = Pages.toPackageNames pkgIndex
623+
pkgDetails <- liftIO $ makeItemList packageNames
624+
let rowList = map makeRow pkgDetails
625+
tabledata = "" +++ rowList +++ ""
626+
cacheControl [Public, maxAgeHours 1]
627+
(etagFromHash (PackageIndex.indexSize pkgIndex))
628+
template <- getTemplate templates "table-interface.html"
629+
return $ toResponse $ template
630+
[ "heading" $= "All packages"
631+
, "content" $= "A browsable index of all the packages"
632+
, "tabledata" $= tabledata ]
633+
611634
serveDistroMonitorPage :: DynamicPath -> ServerPartE Response
612635
serveDistroMonitorPage dpath = do
613636
pkgname <- packageInPath dpath
@@ -1446,6 +1469,7 @@ mkHtmlTags :: HtmlUtilities
14461469
-> CoreFeature
14471470
-> ListFeature
14481471
-> TagsFeature
1472+
-> Templates
14491473
-> HtmlTags
14501474
mkHtmlTags HtmlUtilities{..}
14511475
CoreFeature{ coreResource = CoreResource{
@@ -1454,7 +1478,9 @@ mkHtmlTags HtmlUtilities{..}
14541478
}
14551479
}
14561480
ListFeature{makeItemList}
1457-
TagsFeature{..} = HtmlTags{..}
1481+
TagsFeature{..}
1482+
templates
1483+
= HtmlTags{..}
14581484
where
14591485
tags = tagsResource
14601486

@@ -1500,17 +1526,21 @@ mkHtmlTags HtmlUtilities{..}
15001526
pkgs = Set.toList pkgnames
15011527
items <- liftIO $ makeItemList pkgs
15021528
let (mtag, histogram) = Map.updateLookupWithKey (\_ _ -> Nothing) tg $ tagHistogram items
1529+
rowList = map makeRow items
15031530
-- make a 'related tags' section, so exclude this tag from the histogram
15041531
count = fromMaybe 0 mtag
1505-
return $ toResponse $ Resource.XHtml $ hackagePage tagd $
1506-
[ h2 << tagd
1507-
, case items of
1532+
template <- getTemplate templates "table-interface.html"
1533+
return $ toResponse $ template
1534+
[ "heading" $= tagd
1535+
, "content" $= case items of
15081536
[] -> toHtml "No packages have this tag."
15091537
_ -> toHtml
15101538
[ paragraph << [if count==1 then "1 package has" else show count ++ " packages have", " this tag."]
15111539
, paragraph ! [theclass "toc"] << [toHtml "Related tags: ", toHtml $ showHistogram histogram]
1512-
, ulist ! [theclass "packages"] << map renderItem items ]
1540+
]
1541+
, "tabledata" $= rowList
15131542
]
1543+
15141544
where
15151545
showHistogram hist = (++takeHtml) . intersperse (toHtml ", ") $
15161546
map histogramEntry $ take takeAmount sortHist
@@ -1554,11 +1584,13 @@ mkHtmlSearch :: HtmlUtilities
15541584
-> CoreFeature
15551585
-> ListFeature
15561586
-> SearchFeature
1587+
-> Templates
15571588
-> HtmlSearch
15581589
mkHtmlSearch HtmlUtilities{..}
15591590
CoreFeature{..}
15601591
ListFeature{makeItemList}
1561-
SearchFeature{..} =
1592+
SearchFeature{..}
1593+
templates =
15621594
HtmlSearch{..}
15631595
where
15641596
htmlSearchResources = [
@@ -1569,10 +1601,10 @@ mkHtmlSearch HtmlUtilities{..}
15691601

15701602
servePackageFind :: DynamicPath -> ServerPartE Response
15711603
servePackageFind _ = do
1572-
(mtermsStr, offset, limit, mexplain) <-
1573-
queryString $ (,,,) <$> optional (look "terms")
1574-
<*> mplus (lookRead "offset") (pure 0)
1575-
<*> mplus (lookRead "limit") (pure 100)
1604+
(mtermsStr, mexplain) <-
1605+
queryString $ (,) <$> optional (look "terms")
1606+
-- <*> mplus (lookRead "offset") (pure 0)
1607+
-- <*> mplus (lookRead "limit") (pure 100)
15761608
<*> optional (look "explain")
15771609
let explain = isJust mexplain
15781610
case mtermsStr of
@@ -1587,18 +1619,23 @@ mkHtmlSearch HtmlUtilities{..}
15871619
, toHtml $ explainResults results
15881620
]
15891621

1590-
Just termsStr | terms <- words termsStr, not (null terms) -> do
1591-
pkgIndex <- liftIO $ queryGetPackageIndex
1592-
currentTime <- liftIO $ getCurrentTime
1593-
pkgnames <- searchPackages terms
1594-
let (pageResults, moreResults) = splitAt limit (drop offset pkgnames)
1595-
pkgDetails <- liftIO $ makeItemList pageResults
1596-
return $ toResponse $ Resource.XHtml $
1597-
hackagePage "Package search" $
1598-
[ toHtml $ searchForm termsStr False
1599-
, toHtml $ resultsArea pkgIndex currentTime pkgDetails offset limit moreResults termsStr
1600-
, alternativeSearchTerms termsStr
1601-
]
1622+
Just termsStr | terms <- words termsStr -> do
1623+
-- pkgIndex <- liftIO $ queryGetPackageIndex
1624+
-- currentTime <- liftIO $ getCurrentTime
1625+
pkgnames <- if null terms
1626+
then fmap Pages.toPackageNames queryGetPackageIndex
1627+
else searchPackages terms
1628+
-- let (pageResults, moreResults) = splitAt limit (drop offset pkgnames)
1629+
pkgDetails <- liftIO $ makeItemList pkgnames
1630+
1631+
let rowList = map makeRow pkgDetails
1632+
tabledata = "" +++ rowList
1633+
template <- getTemplate templates "table-interface.html"
1634+
return $ toResponse $ template
1635+
[ "heading" $= toHtml (searchForm termsStr False)
1636+
, "content" $= "A browsable index of all the packages"
1637+
, "tabledata" $= tabledata
1638+
, "footer" $= alternativeSearchTerms termsStr]
16021639

16031640
_ ->
16041641
return $ toResponse $ Resource.XHtml $
@@ -1607,55 +1644,6 @@ mkHtmlSearch HtmlUtilities{..}
16071644
, alternativeSearch
16081645
]
16091646
where
1610-
resultsArea pkgIndex currentTime pkgDetails offset limit moreResults termsStr =
1611-
[ h2 << "Results"
1612-
, if offset == 0
1613-
then noHtml
1614-
else paragraph << ("(" ++ show (fst range + 1) ++ " to "
1615-
++ show (snd range) ++ ")")
1616-
, case pkgDetails of
1617-
[] | offset == 0 -> toHtml "None"
1618-
| otherwise -> toHtml "No more results"
1619-
_ -> toHtml
1620-
[ ulist ! [theclass "searchresults"]
1621-
<< map renderSearchResult pkgDetails
1622-
, if null moreResults
1623-
then noHtml
1624-
else anchor ! [href moreResultsLink]
1625-
<< "More results..."
1626-
]
1627-
]
1628-
where
1629-
renderSearchResult :: PackageItem -> Html
1630-
renderSearchResult item = li ! classes <<
1631-
[ packageNameLink pkgname
1632-
, toHtml $ " " ++ ptype
1633-
, br
1634-
, toHtml (itemDesc item)
1635-
, br
1636-
, small ! [ theclass "info" ] <<
1637-
[ toHtml (renderTags (itemTags item))
1638-
, " Last uploaded " +++ humanTime ]
1639-
]
1640-
where
1641-
pkgname = itemName item
1642-
timestamp = maximum
1643-
$ map pkgOriginalUploadTime
1644-
$ PackageIndex.lookupPackageName pkgIndex pkgname
1645-
-- takes current time as argument so it can say how many $X ago something was
1646-
humanTime = HumanTime.humanReadableTime' currentTime timestamp
1647-
ptype = packageType (itemHasLibrary item) (itemNumExecutables item)
1648-
(itemNumTests item) (itemNumBenchmarks item)
1649-
classes = case classList of [] -> []; _ -> [theclass $ unwords classList]
1650-
classList = (case itemDeprecated item of Nothing -> []; _ -> ["deprecated"])
1651-
1652-
range = (offset, offset + length pkgDetails)
1653-
moreResultsLink =
1654-
"/packages/search?"
1655-
++ "terms=" ++ escapeURIString isUnreserved termsStr
1656-
++ "&offset=" ++ show (offset + limit)
1657-
++ "&limit=" ++ show limit
1658-
16591647
searchForm termsStr explain =
16601648
[ h2 << "Package search"
16611649
, form ! [XHtml.method "GET", action "/packages/search"] <<

Distribution/Server/Features/Html/HtmlUtilities.hs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,42 @@ import Data.Set (Set)
1414
import Distribution.Server.Features.PackageList
1515
import Distribution.Server.Pages.Util (packageType)
1616
import Distribution.Package
17+
import Distribution.Server.Features.Users
1718

1819
data HtmlUtilities = HtmlUtilities {
1920
packageLink :: PackageId -> Html
2021
, packageNameLink :: PackageName -> Html
2122
, renderItem :: PackageItem -> Html
23+
, makeRow :: PackageItem -> Html
2224
, renderTags :: Set Tag -> [Html]
2325
}
2426

25-
htmlUtilities :: CoreFeature -> TagsFeature -> HtmlUtilities
27+
htmlUtilities :: CoreFeature -> TagsFeature -> UserFeature -> HtmlUtilities
2628
htmlUtilities CoreFeature{coreResource}
27-
TagsFeature{tagsResource} = HtmlUtilities{..}
29+
TagsFeature{tagsResource} UserFeature{userResource} = HtmlUtilities{..}
2830
where
2931
packageLink :: PackageId -> Html
3032
packageLink pkgid = anchor ! [href $ corePackageIdUri cores "" pkgid] << display pkgid
3133

3234
packageNameLink :: PackageName -> Html
3335
packageNameLink pkgname = anchor ! [href $ corePackageNameUri cores "" pkgname] << display pkgname
3436

37+
makeRow :: PackageItem -> Html
38+
makeRow item = tr << [ td $ itemNameHtml
39+
, td $ toHtml $ show $ itemDownloads item
40+
, td $ toHtml $ show $ itemVotes item / 2
41+
, td $ toHtml $ "" -- FIXME/TODO: show $ itemRevDepsCount item
42+
, td $ toHtml $ itemDesc item
43+
, td $ " (" +++ renderTags (itemTags item) +++ ")"
44+
, td $ "" +++ intersperse (toHtml ", ") (map renderUser (itemMaintainer item))
45+
]
46+
where
47+
renderUser user = anchor ! [href $ userPageUri userResource "" user] << display user
48+
itemNameHtml = packageNameLink (itemName item) +++
49+
case itemDeprecated item of
50+
Just pkgs -> " (deprecated in favor of " +++ intersperse (toHtml ", ") (map packageNameLink pkgs) +++ ")"
51+
Nothing -> toHtml ""
52+
3553
renderItem :: PackageItem -> Html
3654
renderItem item = li ! classes <<
3755
[ packageNameLink pkgname

Distribution/Server/Pages/Index.hs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Generate an HTML page listing all available packages
22

3-
module Distribution.Server.Pages.Index (packageIndex) where
3+
module Distribution.Server.Pages.Index (packageIndex, toPackageNames) where
44

55
import Distribution.Server.Pages.Template ( hackagePage )
66
import Distribution.Server.Pages.Util ( packageType )
@@ -30,6 +30,15 @@ packageIndex = formatPkgGroups
3030
. maximumBy (comparing packageVersion))
3131
. PackageIndex.allPackagesByName
3232

33+
toPackageNames :: PackageIndex.PackageIndex PkgInfo -> [PackageName]
34+
toPackageNames = map (pii_pkgName
35+
. mkPackageIndexInfo
36+
. flattenPackageDescription
37+
. pkgDesc
38+
. maximumBy (comparing packageVersion))
39+
. PackageIndex.allPackagesByName
40+
41+
3342
data PackageIndexInfo = PackageIndexInfo {
3443
pii_pkgName :: !PackageName,
3544
pii_categories :: ![Category],

Distribution/Server/Pages/Template.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ stylesheetURL = "/static/hackage.css"
6060

6161
-- URL of the package list
6262
pkgListURL :: URL
63-
pkgListURL = "/packages/"
63+
pkgListURL = "/packages/browse"
6464

6565
-- URL of the upload form
6666
introductionURL :: URL

datafiles/static/hackage.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ h6 { font-size: 100%; /* 13px */ }
6565

6666
select, input, button, textarea {
6767
font:99% sans-serif;
68+
margin: 0.5em;
69+
padding: 0.1em;
6870
}
6971

7072
table {
@@ -204,7 +206,7 @@ pre {
204206
background: rgb(41,56,69);
205207
border-top: 5px solid rgb(78,98,114);
206208
color: #ddd;
207-
padding: 0.2em;
209+
padding: 0.2em 0.2em 0.5em;
208210
position: relative;
209211
text-align: left;
210212
font-size: 125%;
@@ -641,7 +643,7 @@ p.tip {
641643

642644
/* Misc admin forms */
643645

644-
form.box {
646+
.box {
645647
background: #faf9dc;
646648
border: 1px solid #d8d7ad;
647649
padding: 0.5em 1em;

0 commit comments

Comments
 (0)