From 65c956cd17d3b9062e42da3e5775b4f7bbb2f50c Mon Sep 17 00:00:00 2001
From: Soorya Narayan
Date: Mon, 13 Jun 2016 22:19:13 +0530
Subject: [PATCH 01/39] Tags for review
Cateories are parsed into tags. Helper functions to allow for tags to be proposed and accepted set up
Cleaned up the interface
---
Distribution/Server/Features/Html.hs | 34 ++++++++---
Distribution/Server/Features/Tags.hs | 61 ++++++++++++++++---
Distribution/Server/Features/Tags/State.hs | 54 +++++++++++++++-
Distribution/Server/Features/Upload.hs | 12 ++++
Distribution/Server/Features/Users.hs | 11 +++-
datafiles/templates/Html/package-page.html.st | 1 +
6 files changed, 150 insertions(+), 23 deletions(-)
diff --git a/Distribution/Server/Features/Html.hs b/Distribution/Server/Features/Html.hs
index 33ac9d86b..769049701 100644
--- a/Distribution/Server/Features/Html.hs
+++ b/Distribution/Server/Features/Html.hs
@@ -279,7 +279,7 @@ htmlFeature env@ServerEnv{..}
docsCandidates tarIndexCache
candidates templates
htmlPreferred = mkHtmlPreferred utilities core versions
- htmlTags = mkHtmlTags utilities core list tags
+ htmlTags = mkHtmlTags utilities core upload list tags
htmlSearch = mkHtmlSearch utilities core list names
htmlResources = concat [
@@ -473,7 +473,7 @@ mkHtmlCore ServerEnv{serverBaseURI}
, withPackagePreferred
}
UploadFeature{guardAuthorisedAsMaintainerOrTrustee}
- TagsFeature{queryTagsForPackage}
+ TagsFeature{queryTagsForPackage, queryReviewTagsForPackage }
documentationFeature@DocumentationFeature{documentationResource, queryDocumentation}
TarIndexCacheFeature{cachedTarIndex}
reportsFeature
@@ -1438,6 +1438,7 @@ data HtmlTags = HtmlTags {
mkHtmlTags :: HtmlUtilities
-> CoreFeature
+ -> UploadFeature
-> ListFeature
-> TagsFeature
-> HtmlTags
@@ -1447,6 +1448,7 @@ mkHtmlTags HtmlUtilities{..}
, lookupPackageName
}
}
+ UploadFeature{guardAuthorisedAsUploaderOrMaintainerOrTrustee}
ListFeature{makeItemList}
TagsFeature{..} = HtmlTags{..}
where
@@ -1527,14 +1529,28 @@ mkHtmlTags HtmlUtilities{..}
serveTagsForm dpath = do
pkgname <- packageInPath dpath
currTags <- queryTagsForPackage pkgname
+ revTags <- queryReviewTagsForPackage pkgname
let tagsStr = concat . intersperse ", " . map display . Set.toList $ currTags
- return $ toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
- [paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
- form ! [theclass "box", XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
- [ hidden "_method" "PUT"
- , dlist . ddef . toHtml $ makeInput [thetype "text", value tagsStr] "tags" "Set tags to "
- , paragraph << input ! [thetype "submit", value "Set tags"]
- ]]
+ tagForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
+ [paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
+ form ! [theclass "box", XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
+ [ hidden "_method" "PUT"
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value tagsStr] "tags" "Set tags to "
+ , paragraph << input ! [thetype "submit", value "Set tags"]
+ ]]
+ tagRForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
+ [paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
+ form ! [theclass "box", XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
+ [ hidden "_method" "PUT"
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value tagsStr] "tags" "Set tags to "
+ , paragraph << input ! [thetype "submit", value "Set tags"]
+ ], paragraph << ["Proposals" ++ (show revTags)]]
+ user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
+ case user of
+ "Uploaders" -> return tagForm
+ otherwise -> case revTags of
+ Nothing -> return tagForm
+ Just s -> return tagRForm
{-------------------------------------------------------------------------------
Search
diff --git a/Distribution/Server/Features/Tags.hs b/Distribution/Server/Features/Tags.hs
index de1f0e141..59e03f8c1 100644
--- a/Distribution/Server/Features/Tags.hs
+++ b/Distribution/Server/Features/Tags.hs
@@ -12,11 +12,13 @@ module Distribution.Server.Features.Tags (
import Control.Applicative (optional)
import Distribution.Server.Framework
+import Distribution.Server.Framework.Auth
import Distribution.Server.Framework.BackupDump
+import Debug.Trace
import Distribution.Server.Features.Tags.State
import Distribution.Server.Features.Tags.Backup
-
+import Distribution.Server.Features.Users( guardAuthorised' )
import Distribution.Server.Features.Core
import Distribution.Server.Features.Upload
@@ -47,11 +49,13 @@ data TagsFeature = TagsFeature {
queryGetTagList :: forall m. MonadIO m => m [(Tag, Set PackageName)],
queryTagsForPackage :: forall m. MonadIO m => PackageName -> m (Set Tag),
+ queryReviewTagsForPackage :: forall m. MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag)),
-- All package names that were modified, and all tags that were modified
-- In almost all cases, one of these will be a singleton. Happstack
-- functions should be used to query the resultant state.
tagsUpdated :: Hook (Set PackageName, Set Tag) (),
+
-- Calculated tags are used so that other features can reserve a
-- tag for their own use (a calculated, rather than freely
-- assignable, tag). It is a subset of the main mapping.
@@ -63,6 +67,7 @@ data TagsFeature = TagsFeature {
withTagPath :: forall a. DynamicPath -> (Tag -> Set PackageName -> ServerPartE a) -> ServerPartE a,
collectTags :: forall m. MonadIO m => Set PackageName -> m (Map PackageName (Set Tag)),
+
putTags :: PackageName -> ServerPartE ()
}
@@ -87,11 +92,12 @@ initTagsFeature :: ServerEnv
-> IO TagsFeature)
initTagsFeature ServerEnv{serverStateDir} = do
tagsState <- tagsStateComponent serverStateDir
+ tagsReview <- tagsReviewComponent serverStateDir
specials <- newMemStateWHNF emptyPackageTags
updateTag <- newHook
return $ \core@CoreFeature{..} upload -> do
- let feature = tagsFeature core upload tagsState specials updateTag
+ let feature = tagsFeature core upload tagsState tagsReview specials updateTag
registerHookJust packageChangeHook isPackageChangeAny $ \(pkgid, mpkginfo) ->
case mpkginfo of
@@ -106,7 +112,7 @@ initTagsFeature ServerEnv{serverStateDir} = do
tagsStateComponent :: FilePath -> IO (StateComponent AcidState PackageTags)
tagsStateComponent stateDir = do
- st <- openLocalStateFrom (stateDir > "db" > "Tags") initialPackageTags
+ st <- openLocalStateFrom (stateDir > "db" > "Tags" > "Existing") initialPackageTags
return StateComponent {
stateDesc = "Package tags"
, stateHandle = st
@@ -117,9 +123,25 @@ tagsStateComponent stateDir = do
, resetState = tagsStateComponent
}
+tagsReviewComponent :: FilePath -> IO (StateComponent AcidState ReviewTags)
+tagsReviewComponent stateDir = do
+ st <- openLocalStateFrom (stateDir > "db" > "Tags" > "Review") emptyReviewTags
+ return StateComponent {
+ stateDesc = "Review tags"
+ , stateHandle = st
+ , getState = query st GetReviewTags
+ , putState = update st . ReplaceReviewTags
+ -- , backupState = \_ pkgTags -> [csvToBackup ["tags.csv"] $ tagsToCSV pkgTags]
+ -- , restoreState = tagsBackup
+ -- , resetState = tagsStateComponent
+ }
+
+
+
tagsFeature :: CoreFeature
-> UploadFeature
-> StateComponent AcidState PackageTags
+ -> StateComponent AcidState ReviewTags
-> MemState PackageTags
-> Hook (Set PackageName, Set Tag) ()
-> TagsFeature
@@ -127,8 +149,9 @@ tagsFeature :: CoreFeature
tagsFeature CoreFeature{ queryGetPackageIndex
, coreResource = CoreResource { guardValidPackageName }
}
- UploadFeature{ guardAuthorisedAsMaintainerOrTrustee }
+ UploadFeature{ guardAuthorisedAsUploaderOrMaintainerOrTrustee }
tagsState
+ tagsReview
calculatedTags
tagsUpdated
= TagsFeature{..}
@@ -177,6 +200,9 @@ tagsFeature CoreFeature{ queryGetPackageIndex
queryTagsForPackage :: MonadIO m => PackageName -> m (Set Tag)
queryTagsForPackage pkgname = queryState tagsState (TagsForPackage pkgname)
+ queryReviewTagsForPackage :: MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag))
+ queryReviewTagsForPackage pkgname = queryState tagsReview (LookupReviewTags pkgname)
+
setCalculatedTag :: Tag -> Set PackageName -> IO ()
setCalculatedTag tag pkgs = do
modifyMemState calculatedTags (setTag tag pkgs)
@@ -198,17 +224,31 @@ tagsFeature CoreFeature{ queryGetPackageIndex
putTags :: PackageName -> ServerPartE ()
putTags pkgname = do
guardValidPackageName pkgname
- guardAuthorisedAsMaintainerOrTrustee pkgname
+ -- a <- guardAuthorised' [InGroup uploadersGroup]
mtags <- optional $ look "tags"
case simpleParse =<< mtags of
Just (TagList tags) -> do
- calcTags <- fmap (packageToTags pkgname) $ readMemState calculatedTags
- let tagSet = Set.fromList tags `Set.union` calcTags
- void $ updateState tagsState $ SetPackageTags pkgname tagSet
- runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
- return ()
+ user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
+ case user of
+ "Uploaders" -> do
+ calcTags <- queryTagsForPackage pkgname
+ let tagSet = Set.difference (Set.fromList tags) calcTags
+ let tagRem = Set.difference calcTags (Set.fromList tags)
+ void $ updateState tagsReview $ InsertReviewTags pkgname tagSet tagRem
+ return ()
+ otherwise -> do
+ calcTags <- fmap (packageToTags pkgname) $ readMemState calculatedTags
+ let tagSet = Set.fromList tags `Set.union` calcTags
+ add = Set.difference (Set.fromList tags) calcTags
+ del = Set.difference calcTags (Set.fromList tags)
+ void $ updateState tagsState $ SetPackageTags pkgname tagSet
+ void $ updateState tagsReview $ ClearReviewTags pkgname
+ runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
+ return ()
Nothing -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
+
+
-- initial tags, on import
constructTagIndex :: PackageIndex PkgInfo -> PackageTags
constructTagIndex = foldl' addToTags emptyPackageTags . PackageIndex.allPackagesByName
@@ -249,6 +289,7 @@ constructImmutableTags genDesc =
++ (if he then [Tag "program"] else [])
++ (if ht then [Tag "test"] else [])
++ (if hb then [Tag "benchmark"] else [])
+ ++ (constructCategoryTags desc)
where
licenseToTag :: License -> [Tag]
licenseToTag l = case l of
diff --git a/Distribution/Server/Features/Tags/State.hs b/Distribution/Server/Features/Tags/State.hs
index cbda74c30..81d30e491 100644
--- a/Distribution/Server/Features/Tags/State.hs
+++ b/Distribution/Server/Features/Tags/State.hs
@@ -1,7 +1,6 @@
{-# LANGUAGE DeriveDataTypeable, TypeFamilies, TemplateHaskell, GeneralizedNewtypeDeriving #-}
module Distribution.Server.Features.Tags.State where
-
import Distribution.Server.Framework.Instances ()
import Distribution.Server.Framework.MemSize
@@ -65,11 +64,19 @@ data PackageTags = PackageTags {
packageTags :: Map PackageName (Set Tag),
-- a secondary reverse mapping
tagPackages :: Map Tag (Set PackageName)
+ -- tags(add, remove) set for review by the maintainer
+ -- reviewTags :: Map PackageName (Set Tag, Set Tag)
} deriving (Eq, Show, Typeable)
+-- Packagename (Proposed Additions, Proposed Deletions)
+data ReviewTags = ReviewTags (Map PackageName (Set Tag, Set Tag)) deriving (Eq, Show)
+
emptyPackageTags :: PackageTags
emptyPackageTags = PackageTags Map.empty Map.empty
+emptyReviewTags :: ReviewTags
+emptyReviewTags = ReviewTags Map.empty
+
tagToPackages :: Tag -> PackageTags -> Set PackageName
tagToPackages tag = Map.findWithDefault Set.empty tag . tagPackages
@@ -142,6 +149,8 @@ renameTag tag tag' pkgTags@(PackageTags _ packages) =
$(deriveSafeCopy 0 'base ''Tag)
$(deriveSafeCopy 0 'base ''PackageTags)
+$(deriveSafeCopy 0 'base ''ReviewTags)
+
instance NFData PackageTags where
rnf (PackageTags a b) = rnf a `seq` rnf b
@@ -167,12 +176,22 @@ getPackageTags = ask
replacePackageTags :: PackageTags -> Update PackageTags ()
replacePackageTags = put
+getReviewTags :: Query ReviewTags ReviewTags
+getReviewTags = ask
+
+replaceReviewTags :: ReviewTags -> Update ReviewTags ()
+replaceReviewTags = put
+
setPackageTags :: PackageName -> Set Tag -> Update PackageTags ()
setPackageTags name tagList = modify $ setTags name tagList
setTagPackages :: Tag -> Set PackageName -> Update PackageTags ()
setTagPackages tag pkgList = modify $ setTag tag pkgList
+-- setReviewPackageTags :: PackageName -> (Set Tag, Set Tag) -> Update PackageTags ()
+-- setReviewPackageTags name (tagList1, taglist2) = modify $ setTags name reviewTags
+
+
-- | Tag a package. Returns True if the element was inserted, and False if
-- the tag as already present (same result though)
addPackageTag :: PackageName -> Tag -> Update PackageTags Bool
@@ -191,7 +210,36 @@ removePackageTag name tag = do
Nothing -> return False
Just pkgTags' -> put pkgTags' >> return True
-makeAcidic ''PackageTags ['tagsForPackage
+clearReviewTags :: PackageName -> Update ReviewTags ()
+clearReviewTags pkgname
+ = do
+ ReviewTags m <- get
+ put (ReviewTags (Map.insert pkgname (Set.empty,Set.empty) m))
+
+
+insertReviewTags :: PackageName -> Set Tag -> Set Tag -> Update ReviewTags ()
+insertReviewTags pkgname add del
+ = do
+ ReviewTags m <- get
+ put (ReviewTags (Map.insertWith (insertReviewHelper) pkgname (add,del) m))
+
+insertReviewHelper :: (Set Tag, Set Tag) -> (Set Tag, Set Tag) -> (Set Tag, Set Tag)
+insertReviewHelper (a,b) (c,d) = (Set.union a c, Set.union b d)
+
+lookupReviewTags :: PackageName -> Query ReviewTags (Maybe (Set Tag, Set Tag))
+lookupReviewTags pkgname
+ = do ReviewTags m <- ask
+ return (Map.lookup pkgname m)
+
+$(makeAcidic ''ReviewTags ['insertReviewTags
+ ,'lookupReviewTags
+ ,'getReviewTags
+ ,'clearReviewTags
+ ,'replaceReviewTags
+ ])
+
+
+$(makeAcidic ''PackageTags ['tagsForPackage
,'packagesForTag
,'getTagList
,'getPackageTags
@@ -200,5 +248,5 @@ makeAcidic ''PackageTags ['tagsForPackage
,'setTagPackages
,'addPackageTag
,'removePackageTag
- ]
+ ])
diff --git a/Distribution/Server/Features/Upload.hs b/Distribution/Server/Features/Upload.hs
index 2a8196e87..00d43b296 100644
--- a/Distribution/Server/Features/Upload.hs
+++ b/Distribution/Server/Features/Upload.hs
@@ -59,6 +59,7 @@ data UploadFeature = UploadFeature {
maintainersGroup :: PackageName -> UserGroup,
-- | Requiring being logged in as the maintainer of a package.
+ guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String,
guardAuthorisedAsMaintainer :: PackageName -> ServerPartE (),
-- | Requiring being logged in as the maintainer of a package or a trustee.
guardAuthorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE (),
@@ -298,6 +299,17 @@ uploadFeature ServerEnv{serverBlobStore = store}
guardAuthorisedAsMaintainer pkgname =
guardAuthorised_ [InGroup (maintainersGroup pkgname)]
+ guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String
+ guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname= do
+ mt <- guardAuthorised' [InGroup (maintainersGroup pkgname), InGroup trusteesGroup]
+ upl <- guardAuthorised' [AnyKnownUser]
+ if mt
+ then return "MaintainersOrTrustees"
+ else
+ if upl
+ then return "Uploaders"
+ else return ""
+
guardAuthorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE ()
guardAuthorisedAsMaintainerOrTrustee pkgname =
guardAuthorised_ [InGroup (maintainersGroup pkgname), InGroup trusteesGroup]
diff --git a/Distribution/Server/Features/Users.hs b/Distribution/Server/Features/Users.hs
index ae4431398..90205a6da 100644
--- a/Distribution/Server/Features/Users.hs
+++ b/Distribution/Server/Features/Users.hs
@@ -5,7 +5,7 @@ module Distribution.Server.Features.Users (
initUserFeature,
UserFeature(..),
UserResource(..),
-
+ -- guardAuthorised',
GroupResource(..),
) where
@@ -61,6 +61,7 @@ data UserFeature = UserFeature {
guardAuthorised_ :: [PrivilegeCondition] -> ServerPartE (),
-- | Require any of a set of privileges, giving the id of the current user.
guardAuthorised :: [PrivilegeCondition] -> ServerPartE UserId,
+ guardAuthorised' :: [PrivilegeCondition] -> ServerPartE Bool,
-- | Require being logged in, giving the id of the current user.
guardAuthenticated :: ServerPartE UserId,
-- | A hook to override the default authentication error in particular
@@ -389,6 +390,14 @@ userFeature templates usersState adminsState
Auth.guardPriviledged users uid privconds
return uid
+ guardAuthorised' :: [PrivilegeCondition] -> ServerPartE Bool
+ guardAuthorised' privconds = do
+ users <- queryGetUserDb
+ uid <- guardAuthenticatedWithErrHook users
+ a <- Auth.checkPriviledged users uid privconds
+ return a
+
+
-- Simply check if the user is authenticated as some user, without any
-- check that they have any particular priveledges. Only useful as a
-- building block.
diff --git a/datafiles/templates/Html/package-page.html.st b/datafiles/templates/Html/package-page.html.st
index 6c376346f..8dc9be033 100644
--- a/datafiles/templates/Html/package-page.html.st
+++ b/datafiles/templates/Html/package-page.html.st
@@ -19,6 +19,7 @@
The $package.name$ package
$if(isDeprecated)$
From 87db09ed2f4dafd8698207e6e404013cba32f616 Mon Sep 17 00:00:00 2001
From: Soorya Narayan
Date: Wed, 15 Jun 2016 23:17:10 +0530
Subject: [PATCH 02/39] Browsable Package Index
Added Author and Maintainer fields to PackageItem
jQuery DataTables used for the page
Added votes as a queryable parameter
UI changes to propose/accept tags
---
Distribution/Server/Features.hs | 1 +
Distribution/Server/Features/Html.hs | 94 ++++++++++++++-----
.../Server/Features/Html/HtmlUtilities.hs | 11 +++
Distribution/Server/Features/PackageList.hs | 35 +++++--
Distribution/Server/Features/Tags.hs | 46 ++++-----
Distribution/Server/Features/Votes.hs | 23 +++--
Distribution/Server/Framework/MemSize.hs | 12 ++-
Distribution/Server/Pages/Index.hs | 12 ++-
datafiles/static/hackage.css | 2 +-
.../templates/Html/tag-interface.html.st | 44 +++++++++
10 files changed, 214 insertions(+), 66 deletions(-)
create mode 100644 datafiles/templates/Html/tag-interface.html.st
diff --git a/Distribution/Server/Features.hs b/Distribution/Server/Features.hs
index fa2e13256..f5914103a 100644
--- a/Distribution/Server/Features.hs
+++ b/Distribution/Server/Features.hs
@@ -257,6 +257,7 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
coreFeature
-- [reverse index disabled] reverseFeature
downloadFeature
+ votesFeature
tagsFeature
versionsFeature
diff --git a/Distribution/Server/Features/Html.hs b/Distribution/Server/Features/Html.hs
index 769049701..1f116d94e 100644
--- a/Distribution/Server/Features/Html.hs
+++ b/Distribution/Server/Features/Html.hs
@@ -63,7 +63,7 @@ import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Vector as Vec
-import Data.Maybe (fromMaybe, isJust)
+import Data.Maybe (fromMaybe, isJust, fromJust)
import Data.Monoid ((<>))
import qualified Data.Text as T
import Data.Traversable (traverse)
@@ -131,6 +131,7 @@ initHtmlFeature env@ServerEnv{serverTemplatesDir, serverTemplatesMode,
, "distro-monitor.html"
, "revisions.html"
, "package-page.html"
+ , "tag-interface.html"
]
@@ -270,6 +271,7 @@ htmlFeature env@ServerEnv{..}
cachePackagesPage
cacheNamesPage
templates
+ list
htmlUsers = mkHtmlUsers user usersdetails
htmlUploads = mkHtmlUploads utilities upload
htmlDocUploads = mkHtmlDocUploads utilities core docsCore templates
@@ -437,7 +439,8 @@ htmlFeature env@ServerEnv{..}
{-------------------------------------------------------------------------------
Core
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlCore = HtmlCore {
htmlCoreResources :: [Resource]
@@ -462,18 +465,19 @@ mkHtmlCore :: ServerEnv
-> AsyncCache Response
-> AsyncCache Response
-> Templates
+ -> ListFeature
-> HtmlCore
mkHtmlCore ServerEnv{serverBaseURI}
utilities@HtmlUtilities{..}
UserFeature{queryGetUserDb}
- CoreFeature{coreResource}
+ CoreFeature{coreResource , queryGetPackageIndex}
VersionsFeature{ versionsResource
, queryGetDeprecatedFor
, queryGetPreferredInfo
, withPackagePreferred
}
UploadFeature{guardAuthorisedAsMaintainerOrTrustee}
- TagsFeature{queryTagsForPackage, queryReviewTagsForPackage }
+ TagsFeature{queryTagsForPackage}
documentationFeature@DocumentationFeature{documentationResource, queryDocumentation}
TarIndexCacheFeature{cachedTarIndex}
reportsFeature
@@ -486,6 +490,7 @@ mkHtmlCore ServerEnv{serverBaseURI}
cachePackagesPage
cacheNamesPage
templates
+ ListFeature{makeItemList}
= HtmlCore{..}
where
cores@CoreResource{packageInPath, lookupPackageName, lookupPackageId} = coreResource
@@ -513,6 +518,11 @@ mkHtmlCore ServerEnv{serverBaseURI}
, (resourceAt "/packages/names" ) {
resourceGet = [("html", const $ readAsyncCache cacheNamesPage)]
}
+ , (resourceAt "/packages/names/experiment" ) {
+ resourceDesc = [(GET, "Show detailed package dependency information")]
+ , resourceGet = [("html",
+ serveMaintainPage')]
+ }
, (extendResource $ corePackagesPage cores) {
resourceDesc = [(GET, "Show package index")]
, resourceGet = [("html", const $ readAsyncCache cachePackagesPage)]
@@ -610,6 +620,17 @@ mkHtmlCore ServerEnv{serverBaseURI}
, "versions" $= map packageId pkgs
]
+ serveMaintainPage' :: DynamicPath -> ServerPartE Response
+ serveMaintainPage' _ = do
+ pkgIndex <- queryGetPackageIndex
+ let packageNames = Pages.toPackageNames pkgIndex
+ pkgDetails <- liftIO $ makeItemList packageNames
+ let rowList = map (makeRow) pkgDetails
+ tabledata = "" +++ rowList +++""
+ template <- getTemplate templates "tag-interface.html"
+ return $ toResponse $ template
+ [ "tabledata" $= tabledata ]
+
serveDistroMonitorPage :: DynamicPath -> ServerPartE Response
serveDistroMonitorPage dpath = do
pkgname <- packageInPath dpath
@@ -661,7 +682,8 @@ mkHtmlCore ServerEnv{serverBaseURI}
{-------------------------------------------------------------------------------
Users
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlUsers = HtmlUsers {
htmlUsersResources :: [Resource]
@@ -775,7 +797,8 @@ mkHtmlUsers UserFeature{..} UserDetailsFeature{..} = HtmlUsers{..}
{-------------------------------------------------------------------------------
Uploads
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlUploads = HtmlUploads {
htmlUploadsResources :: [Resource]
@@ -823,7 +846,8 @@ mkHtmlUploads HtmlUtilities{..} UploadFeature{..} = HtmlUploads{..}
{-------------------------------------------------------------------------------
Documentation uploads
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlDocUploads = HtmlDocUploads {
htmlDocUploadsResources :: [Resource]
@@ -870,7 +894,8 @@ mkHtmlDocUploads HtmlUtilities{..} CoreFeature{coreResource} DocumentationFeatur
{-------------------------------------------------------------------------------
Build reports
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlReports = HtmlReports {
htmlReportsResources :: [Resource]
@@ -916,7 +941,8 @@ mkHtmlReports HtmlUtilities{..} CoreFeature{..} ReportsFeature{..} templates = H
{-------------------------------------------------------------------------------
Candidates
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlCandidates = HtmlCandidates {
htmlCandidatesResources :: [Resource]
@@ -1173,7 +1199,8 @@ dependenciesPage isCandidate render =
{-------------------------------------------------------------------------------
Preferred versions
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlPreferred = HtmlPreferred {
htmlPreferredResources :: [Resource]
@@ -1391,7 +1418,8 @@ mkHtmlPreferred HtmlUtilities{..}
{-------------------------------------------------------------------------------
Downloads
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlDownloads = HtmlDownloads {
htmlDownloadsResources :: [Resource]
@@ -1429,7 +1457,8 @@ mkHtmlDownloads HtmlUtilities{..} DownloadFeature{..} = HtmlDownloads{..}
{-------------------------------------------------------------------------------
Tags
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlTags = HtmlTags {
htmlTagsResources :: [Resource]
@@ -1522,7 +1551,7 @@ mkHtmlTags HtmlUtilities{..}
_ <- lookupPackageName pkgname -- TODO: necessary?
putTags pkgname
return $ toResponse $ Resource.XHtml $ hackagePage "Set tags"
- [toHtml "Put tags for ", packageNameLink pkgname]
+ [toHtml "Suggested tags for ", packageNameLink pkgname]
-- serve form for editing, to be received by putTags
serveTagsForm :: DynamicPath -> ServerPartE Response
@@ -1530,31 +1559,43 @@ mkHtmlTags HtmlUtilities{..}
pkgname <- packageInPath dpath
currTags <- queryTagsForPackage pkgname
revTags <- queryReviewTagsForPackage pkgname
- let tagsStr = concat . intersperse ", " . map display . Set.toList $ currTags
+ let toStr = concat . intersperse ", " . map display . Set.toList
+ tagsStr = toStr currTags
+ addns = toStr $ fst $ fromJust revTags
+ delns = toStr $ snd $ fromJust revTags
tagForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
[paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
- form ! [theclass "box", XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
+ thediv ! [theclass "box"] << [paragraph << [bold $ toHtml "Current Tags", br, toHtml tagsStr],
+ form ! [ XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
[ hidden "_method" "PUT"
- , dlist . ddef . toHtml $ makeInput [thetype "text", value tagsStr] "tags" "Set tags to "
- , paragraph << input ! [thetype "submit", value "Set tags"]
- ]]
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "addns" "Propose Additions "
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "delns" "Propose Deletions "
+ ,
+ paragraph << input ! [thetype "submit", value "Propose tags"]
+ ]]]
tagRForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
[paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
- form ! [theclass "box", XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
+ thediv ! [theclass "box"] << [paragraph << [bold $ toHtml "Current Tags", br, toHtml tagsStr],
+ form ! [ XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
[ hidden "_method" "PUT"
- , dlist . ddef . toHtml $ makeInput [thetype "text", value tagsStr] "tags" "Set tags to "
- , paragraph << input ! [thetype "submit", value "Set tags"]
- ], paragraph << ["Proposals" ++ (show revTags)]]
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "addns" "Propose Additions "
+ , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "delns" "Propose Deletions "
+ ,
+ paragraph << input ! [thetype "submit", value "Propose tags"]
+ ], paragraph << [big $ toHtml "Proposals", br, bold $ toHtml "additions: ", toHtml addns, br, bold $ toHtml "deletions: ", toHtml delns]]]
user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
case user of
"Uploaders" -> return tagForm
- otherwise -> case revTags of
+ _ -> case revTags of
Nothing -> return tagForm
- Just s -> return tagRForm
+ Just _ -> return tagRForm
+
+
{-------------------------------------------------------------------------------
Search
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
data HtmlSearch = HtmlSearch {
htmlSearchResources :: [Resource]
@@ -1813,7 +1854,8 @@ mkHtmlSearch HtmlUtilities{..}
{-------------------------------------------------------------------------------
Groups
--------------------------------------------------------------------------------}
+-----------------------------------------------------------------------------
+-}
htmlGroupResource :: UserFeature -> GroupResource -> [Resource]
htmlGroupResource UserFeature{..} r@(GroupResource groupR userR getGroup) =
diff --git a/Distribution/Server/Features/Html/HtmlUtilities.hs b/Distribution/Server/Features/Html/HtmlUtilities.hs
index cc35b96bf..da928faa1 100644
--- a/Distribution/Server/Features/Html/HtmlUtilities.hs
+++ b/Distribution/Server/Features/Html/HtmlUtilities.hs
@@ -19,6 +19,7 @@ data HtmlUtilities = HtmlUtilities {
packageLink :: PackageId -> Html
, packageNameLink :: PackageName -> Html
, renderItem :: PackageItem -> Html
+ , makeRow :: PackageItem -> Html
, renderTags :: Set Tag -> [Html]
}
@@ -32,6 +33,16 @@ htmlUtilities CoreFeature{coreResource}
packageNameLink :: PackageName -> Html
packageNameLink pkgname = anchor ! [href $ corePackageNameUri cores "" pkgname] << display pkgname
+ makeRow :: PackageItem -> Html
+ makeRow item = tr << [ td $ packageNameLink $ itemName item
+ , td $ toHtml $ show $ itemDownloads item
+ , td $ toHtml $ show $ itemVotes item
+ , td $ toHtml $ itemDesc item
+ , td $ " (" +++ renderTags (itemTags item) +++ ")"
+ , td $ toHtml $ itemMaintainer item
+ ]
+
+
renderItem :: PackageItem -> Html
renderItem item = li ! classes <<
[ packageNameLink pkgname
diff --git a/Distribution/Server/Features/PackageList.hs b/Distribution/Server/Features/PackageList.hs
index c9ac5d140..3b506516d 100644
--- a/Distribution/Server/Features/PackageList.hs
+++ b/Distribution/Server/Features/PackageList.hs
@@ -10,6 +10,7 @@ import Distribution.Server.Framework
import Distribution.Server.Features.Core
-- [reverse index disabled] import Distribution.Server.Features.ReverseDependencies
+import Distribution.Server.Features.Votes
import Distribution.Server.Features.DownloadCount
import Distribution.Server.Features.Tags
import Distribution.Server.Features.PreferredVersions
@@ -55,11 +56,15 @@ data PackageItem = PackageItem {
itemDeprecated :: !(Maybe [PackageName]),
-- The description of the package from its Cabal file
itemDesc :: !String,
+ -- Maintainer of the package
+ itemMaintainer :: !String,
-- Whether the item is in the Haskell Platform
--itemPlatform :: Bool,
+ -- Author of the package (Probably won't be used in display)
+ itemVotes :: Int,
-- The total number of downloads. (For sorting, not displaying.)
-- Updated periodically.
- itemDownloads :: !Int,
+ itemDownloads :: Int,
-- The number of direct revdeps. (Likewise.)
-- also: distinguish direct/flat?
-- [reverse index disabled] itemRevDepsCount :: !Int,
@@ -76,11 +81,11 @@ data PackageItem = PackageItem {
}
instance MemSize PackageItem where
- memSize (PackageItem a b c d e f g h i) = memSize9 a b c d e f g h i
+ memSize (PackageItem a b c d e f g h i j k) = memSize11 a b c d e f g h i j k
emptyPackageItem :: PackageName -> PackageItem
-emptyPackageItem pkg = PackageItem pkg Set.empty Nothing "" 0
+emptyPackageItem pkg = PackageItem pkg Set.empty Nothing "" "" 0 0
-- [reverse index disabled] 0
False 0 0 0
@@ -89,6 +94,7 @@ initListFeature :: ServerEnv
-> IO (CoreFeature
-- [reverse index disabled] -> ReverseFeature
-> DownloadFeature
+ -> VotesFeature
-> TagsFeature
-> VersionsFeature
-> IO ListFeature)
@@ -99,11 +105,12 @@ initListFeature _env = do
return $ \core@CoreFeature{..}
-- [reverse index disabled] revs
download
+ votesf@VotesFeature{..}
tagsf@TagsFeature{..}
versions@VersionsFeature{..} -> do
let (feature, modifyItem, updateDesc) =
- listFeature core download tagsf versions
+ listFeature core download votesf tagsf versions
itemCache itemUpdate
registerHookJust packageChangeHook isPackageChangeAny $ \(pkgid, _) ->
@@ -117,11 +124,18 @@ initListFeature _env = do
modifyItem pkgname (updateReverseItem revCount)
runHook' itemUpdate $ Set.fromDistinctAscList pkgs
-}
+
+ registerHook votesUpdated $ \(pkgname, _) -> do
+ votes <- pkgNumVotes pkgname
+ modifyItem pkgname (updateVoteItem votes)
+ runHook_ itemUpdate (Set.singleton pkgname)
+
registerHook tagsUpdated $ \(pkgs, _) -> do
forM_ (Set.toList pkgs) $ \pkgname -> do
tags <- queryTagsForPackage pkgname
modifyItem pkgname (updateTagItem tags)
runHook_ itemUpdate pkgs
+
registerHook deprecatedHook $ \(pkgname, mpkgs) -> do
modifyItem pkgname (updateDeprecation mpkgs)
runHook_ itemUpdate (Set.singleton pkgname)
@@ -131,6 +145,7 @@ initListFeature _env = do
listFeature :: CoreFeature
-> DownloadFeature
+ -> VotesFeature
-> TagsFeature
-> VersionsFeature
-> MemState (Map PackageName PackageItem)
@@ -140,7 +155,7 @@ listFeature :: CoreFeature
PackageName -> IO ())
listFeature CoreFeature{..}
- DownloadFeature{..} TagsFeature{..} VersionsFeature{..}
+ DownloadFeature{..} VotesFeature{..} TagsFeature{..} VersionsFeature{..}
itemCache itemUpdate
= (ListFeature{..}, modifyItem, updateDesc)
where
@@ -201,11 +216,13 @@ listFeature CoreFeature{..}
-- [reverse index disabled] revCount <- query . GetReverseCount $ pkgname
tags <- queryTagsForPackage pkgname
downs <- recentPackageDownloads
+ votes <- pkgNumVotes pkgname
deprs <- queryGetDeprecatedFor pkgname
return $ (,) pkgname $ (updateDescriptionItem (pkgDesc pkg) $ emptyPackageItem pkgname) {
itemTags = tags
, itemDeprecated = deprs
, itemDownloads = cmFind pkgname downs
+ , itemVotes = votes
-- [reverse index disabled] , itemRevDepsCount = directReverseCount revCount
}
@@ -234,6 +251,8 @@ updateDescriptionItem genDesc item =
-- This checks if the library is buildable. However, since
-- desc is flattened, we might miss some flags. Perhaps use the
-- CondTree instead.
+ -- itemAuthor = author desc,
+ itemMaintainer = maintainer desc,
itemHasLibrary = hasLibs desc,
itemNumExecutables = length . filter (buildable . buildInfo) $ executables desc,
itemNumTests = length . filter (buildable . testBuildInfo) $ testSuites desc,
@@ -245,7 +264,11 @@ updateTagItem tags item =
item {
itemTags = tags
}
-
+updateVoteItem :: Int -> PackageItem -> PackageItem
+updateVoteItem votes item =
+ item {
+ itemVotes = votes
+ }
updateDeprecation :: Maybe [PackageName] -> PackageItem -> PackageItem
updateDeprecation pkgs item =
item {
diff --git a/Distribution/Server/Features/Tags.hs b/Distribution/Server/Features/Tags.hs
index 59e03f8c1..f41feca75 100644
--- a/Distribution/Server/Features/Tags.hs
+++ b/Distribution/Server/Features/Tags.hs
@@ -14,7 +14,6 @@ import Control.Applicative (optional)
import Distribution.Server.Framework
import Distribution.Server.Framework.Auth
import Distribution.Server.Framework.BackupDump
-import Debug.Trace
import Distribution.Server.Features.Tags.State
import Distribution.Server.Features.Tags.Backup
@@ -224,27 +223,30 @@ tagsFeature CoreFeature{ queryGetPackageIndex
putTags :: PackageName -> ServerPartE ()
putTags pkgname = do
guardValidPackageName pkgname
- -- a <- guardAuthorised' [InGroup uploadersGroup]
- mtags <- optional $ look "tags"
- case simpleParse =<< mtags of
- Just (TagList tags) -> do
- user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
- case user of
- "Uploaders" -> do
- calcTags <- queryTagsForPackage pkgname
- let tagSet = Set.difference (Set.fromList tags) calcTags
- let tagRem = Set.difference calcTags (Set.fromList tags)
- void $ updateState tagsReview $ InsertReviewTags pkgname tagSet tagRem
- return ()
- otherwise -> do
- calcTags <- fmap (packageToTags pkgname) $ readMemState calculatedTags
- let tagSet = Set.fromList tags `Set.union` calcTags
- add = Set.difference (Set.fromList tags) calcTags
- del = Set.difference calcTags (Set.fromList tags)
- void $ updateState tagsState $ SetPackageTags pkgname tagSet
- void $ updateState tagsReview $ ClearReviewTags pkgname
- runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
- return ()
+ addns <- optional $ look "addns"
+ delns <- optional $ look "delns"
+ case simpleParse =<< addns of
+ Just (TagList add) -> do
+ case simpleParse =<< delns of
+ Just (TagList del) -> do
+ user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
+ case user of
+ "Uploaders" -> do
+ calcTags <- queryTagsForPackage pkgname
+ let addTags = Set.fromList add `Set.difference` calcTags
+ let delTags = Set.fromList del `Set.intersection` calcTags
+ void $ updateState tagsReview $ InsertReviewTags pkgname addTags delTags
+ return ()
+ _ -> do
+ calcTags <- queryTagsForPackage pkgname
+ let tagSet = (addTags `Set.union` calcTags) `Set.difference` delTags
+ addTags = Set.fromList add
+ delTags = Set.fromList del
+ void $ updateState tagsState $ SetPackageTags pkgname tagSet
+ void $ updateState tagsReview $ ClearReviewTags pkgname
+ runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
+ return ()
+ _ -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
Nothing -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
diff --git a/Distribution/Server/Features/Votes.hs b/Distribution/Server/Features/Votes.hs
index a9cdbb0a5..515ab4440 100644
--- a/Distribution/Server/Features/Votes.hs
+++ b/Distribution/Server/Features/Votes.hs
@@ -32,9 +32,10 @@ import qualified Text.XHtml.Strict as X
-- | Define the prototype for this feature
data VotesFeature = VotesFeature {
- votesFeatureInterface :: HackageFeature
+ votesFeatureInterface :: HackageFeature
, didUserVote :: forall m. MonadIO m => PackageName -> UserId -> m Bool
, pkgNumVotes :: forall m. MonadIO m => PackageName -> m Int
+ , votesUpdated :: Hook (PackageName, Int) ()
, renderVotesHtml :: PackageName -> ServerPartE X.Html
}
@@ -49,11 +50,13 @@ initVotesFeature :: ServerEnv
-> IO VotesFeature)
initVotesFeature env@ServerEnv{serverStateDir} = do
dbVotesState <- votesStateComponent serverStateDir
+ updateVotes <- newHook
return $ \coref@CoreFeature{..} userf@UserFeature{..} -> do
let feature = votesFeature env
dbVotesState
- coref userf
+ coref userf updateVotes
+
return feature
-- | Define the backing store (i.e. database component)
@@ -79,12 +82,14 @@ votesFeature :: ServerEnv
-> StateComponent AcidState VotesState
-> CoreFeature -- To get site package list
-> UserFeature -- To authenticate users
+ -> Hook (PackageName, Int) ()
-> VotesFeature
votesFeature ServerEnv{..}
votesState
CoreFeature { coreResource = CoreResource{..} }
UserFeature{..}
+ votesUpdated
= VotesFeature{..}
where
votesFeatureInterface = (emptyHackageFeature "votes") {
@@ -133,7 +138,7 @@ votesFeature ServerEnv{..}
pkgname <- packageInPath dpath
guardValidPackageName pkgname
cacheControlWithoutETag [Public, maxAgeMinutes 10]
- voteCount <- queryState votesState (GetPackageVoteCount pkgname)
+ voteCount <- pkgNumVotes pkgname
let obj = objectL
[ ("packageName", string $ display pkgname)
, ("numVotes", toJSON voteCount)
@@ -146,8 +151,12 @@ votesFeature ServerEnv{..}
uid <- guardAuthorised [AnyKnownUser]
pkgname <- packageInPath dpath
guardValidPackageName pkgname
-
+ number <- pkgNumVotes pkgname
success <- updateState votesState (AddVote pkgname uid)
+ if success
+ then runHook_ votesUpdated (pkgname, number+1)
+ else return ()
+
if success
then ok . toResponse $ Render.voteConfirmationPage pkgname
"Package voted for successfully"
@@ -160,9 +169,11 @@ votesFeature ServerEnv{..}
uid <- guardAuthorised [AnyKnownUser]
pkgname <- packageInPath dpath
guardValidPackageName pkgname
-
+ number <- pkgNumVotes pkgname
success <- updateState votesState (RemoveVote pkgname uid)
-
+ if success
+ then runHook_ votesUpdated (pkgname, number-1)
+ else return ()
let responseMsg | success = "Package vote removed successfully."
| otherwise = "User has not voted for this package."
ok . toResponse $ Render.voteConfirmationPage
diff --git a/Distribution/Server/Framework/MemSize.hs b/Distribution/Server/Framework/MemSize.hs
index 45788b71a..882b2fb38 100644
--- a/Distribution/Server/Framework/MemSize.hs
+++ b/Distribution/Server/Framework/MemSize.hs
@@ -2,8 +2,8 @@
module Distribution.Server.Framework.MemSize (
MemSize(..),
memSizeMb, memSizeKb,
- memSize0, memSize1, memSize2, memSize3, memSize4,
- memSize5, memSize6, memSize7, memSize8, memSize9, memSize10,
+ memSize0, memSize1, memSize2, memSize3, memSize4,memSize5,
+ memSize6, memSize7, memSize8, memSize9, memSize10,memSize11,
memSizeUArray, memSizeUVector
) where
@@ -60,7 +60,7 @@ memSize7 :: (MemSize a6, MemSize a5, MemSize a4, MemSize a3, MemSize a2, MemSize
memSize8 :: (MemSize a7, MemSize a6, MemSize a5, MemSize a4, MemSize a3, MemSize a2, MemSize a1, MemSize a) => a -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> Int
memSize9 :: (MemSize a8, MemSize a7, MemSize a6, MemSize a5, MemSize a4, MemSize a3, MemSize a2, MemSize a1, MemSize a) => a -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> Int
memSize10 :: (MemSize a9, MemSize a8, MemSize a7, MemSize a6, MemSize a5, MemSize a4, MemSize a3, MemSize a2, MemSize a1, MemSize a) => a -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> Int
-
+memSize11 :: (MemSize a10, MemSize a9, MemSize a8, MemSize a7, MemSize a6, MemSize a5, MemSize a4, MemSize a3, MemSize a2, MemSize a1, MemSize a) => a -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10 ->Int
memSize0 = 0
memSize1 a = 2 + memSize a
@@ -90,6 +90,12 @@ memSize10 a b c d e
+ memSize g + memSize h + memSize i
+ memSize j
+memSize11 a b c d e
+ f g h i j k= 12 + memSize a + memSize b + memSize c
+ + memSize d + memSize e + memSize f
+ + memSize g + memSize h + memSize i
+ + memSize j + memSize k
+
instance MemSize (a -> b) where
memSize _ = 0
diff --git a/Distribution/Server/Pages/Index.hs b/Distribution/Server/Pages/Index.hs
index 09ff5822b..152e8e9fa 100644
--- a/Distribution/Server/Pages/Index.hs
+++ b/Distribution/Server/Pages/Index.hs
@@ -1,6 +1,6 @@
-- Generate an HTML page listing all available packages
-module Distribution.Server.Pages.Index (packageIndex) where
+module Distribution.Server.Pages.Index where
import Distribution.Server.Pages.Template ( hackagePage )
import Distribution.Server.Pages.Util ( packageType )
@@ -21,7 +21,6 @@ import qualified Text.XHtml.Strict as XHtml ( name )
import Data.Char (toLower, toUpper, isSpace)
import Data.List (intersperse, sortBy, groupBy, nub, maximumBy)
-
packageIndex :: PackageIndex.PackageIndex PkgInfo -> Html
packageIndex = formatPkgGroups
. map (mkPackageIndexInfo
@@ -30,6 +29,15 @@ packageIndex = formatPkgGroups
. maximumBy (comparing packageVersion))
. PackageIndex.allPackagesByName
+toPackageNames :: PackageIndex.PackageIndex PkgInfo -> [PackageName]
+toPackageNames = map (pii_pkgName
+ . mkPackageIndexInfo
+ . flattenPackageDescription
+ . pkgDesc
+ . maximumBy (comparing packageVersion))
+ . PackageIndex.allPackagesByName
+
+
data PackageIndexInfo = PackageIndexInfo {
pii_pkgName :: !PackageName,
pii_categories :: ![Category],
diff --git a/datafiles/static/hackage.css b/datafiles/static/hackage.css
index b6ce49868..f0ae92792 100644
--- a/datafiles/static/hackage.css
+++ b/datafiles/static/hackage.css
@@ -620,7 +620,7 @@ p.tip {
/* Misc admin forms */
-form.box {
+.box {
background: #faf9dc;
border: 1px solid #d8d7ad;
padding: 0.5em 1em;
diff --git a/datafiles/templates/Html/tag-interface.html.st b/datafiles/templates/Html/tag-interface.html.st
new file mode 100644
index 000000000..0ceacfddb
--- /dev/null
+++ b/datafiles/templates/Html/tag-interface.html.st
@@ -0,0 +1,44 @@
+
+
+
+ $hackageCssTheme()$
+
+
+
+ All packages by name | Hackage
+
+
+
+ $hackagePageHeader()$
+
+
+
All packages by name
+
+
+
+ Name | Downloads | Votes | Description | Tags | Maintainer |
+
+
+ $tabledata$
+
+
+
+
+
+
From 6efcaa337f07aba0a48693b3e430b35bdc6f9a11 Mon Sep 17 00:00:00 2001
From: Soorya Narayan
Date: Sat, 18 Jun 2016 23:00:41 +0530
Subject: [PATCH 03/39] Smoothened rough edges in tag proposals
A clickable UI for accepting/rejecting tags
Added get method to /package/:pkgname/tags
shifted forms to a separate template
---
Distribution/Server/Features/Html.hs | 93 ++++++++-----
Distribution/Server/Features/Tags.hs | 16 ++-
Distribution/Server/Features/Tags/State.hs | 13 +-
datafiles/templates/Html/tag-edit.html.st | 150 +++++++++++++++++++++
4 files changed, 237 insertions(+), 35 deletions(-)
create mode 100644 datafiles/templates/Html/tag-edit.html.st
diff --git a/Distribution/Server/Features/Html.hs b/Distribution/Server/Features/Html.hs
index 1f116d94e..c510569b9 100644
--- a/Distribution/Server/Features/Html.hs
+++ b/Distribution/Server/Features/Html.hs
@@ -132,6 +132,7 @@ initHtmlFeature env@ServerEnv{serverTemplatesDir, serverTemplatesMode,
, "revisions.html"
, "package-page.html"
, "tag-interface.html"
+ , "tag-edit.html"
]
@@ -281,7 +282,7 @@ htmlFeature env@ServerEnv{..}
docsCandidates tarIndexCache
candidates templates
htmlPreferred = mkHtmlPreferred utilities core versions
- htmlTags = mkHtmlTags utilities core upload list tags
+ htmlTags = mkHtmlTags utilities core upload list tags templates
htmlSearch = mkHtmlSearch utilities core list names
htmlResources = concat [
@@ -1470,6 +1471,7 @@ mkHtmlTags :: HtmlUtilities
-> UploadFeature
-> ListFeature
-> TagsFeature
+ -> Templates
-> HtmlTags
mkHtmlTags HtmlUtilities{..}
CoreFeature{ coreResource = CoreResource{
@@ -1479,7 +1481,9 @@ mkHtmlTags HtmlUtilities{..}
}
UploadFeature{guardAuthorisedAsUploaderOrMaintainerOrTrustee}
ListFeature{makeItemList}
- TagsFeature{..} = HtmlTags{..}
+ TagsFeature{..}
+ templates
+ = HtmlTags{..}
where
tags = tagsResource
@@ -1495,7 +1499,7 @@ mkHtmlTags HtmlUtilities{..}
resourceGet = [("html", serveTagListing)]
}
, (extendResource $ packageTagsListing tags) {
- resourcePut = [("html", putPackageTags)], resourceGet = []
+ resourcePut = [("html", putPackageTags)], resourceGet = [("html", showPackageTags)]
}
, tagEdit -- (extendResource $ packageTagsEdit tags) { resourceGet = [("html", serveTagsForm)] }
]
@@ -1550,8 +1554,42 @@ mkHtmlTags HtmlUtilities{..}
pkgname <- packageInPath dpath
_ <- lookupPackageName pkgname -- TODO: necessary?
putTags pkgname
- return $ toResponse $ Resource.XHtml $ hackagePage "Set tags"
- [toHtml "Suggested tags for ", packageNameLink pkgname]
+ currTags <- queryTagsForPackage pkgname
+ revTags <- queryReviewTagsForPackage pkgname
+ let toStr = concat . intersperse ", " . map display . Set.toList
+ tagsStr = toStr currTags
+ addns = toStr $ fst $ fromMaybe (Set.empty, Set.empty) revTags
+ delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
+ disp = thediv ! [theclass "box"] << [ paragraph << [bold $ toHtml "Current Tags: ", toHtml tagsStr, br],
+ paragraph << [bold $ toHtml "Additions to be reviewed: ", toHtml addns, br],
+ paragraph << [bold $ toHtml "Deletions to be reviewed: ", toHtml delns, br]
+ ]
+
+ return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" [ big $ bold $ toHtml $ display pkgname
+ , disp
+ , anchor ![href $ "tags/edit" ] << "Propose a tag?", toHtml " or "
+ , toHtml "return to ", packageNameLink pkgname, br
+ ]
+
+ showPackageTags :: DynamicPath -> ServerPartE Response
+ showPackageTags dpath = do
+ pkgname <- packageInPath dpath
+ currTags <- queryTagsForPackage pkgname
+ revTags <- queryReviewTagsForPackage pkgname
+ let toStr = concat . intersperse ", " . map display . Set.toList
+ tagsStr = toStr currTags
+ addns = toStr $ fst $ fromMaybe (Set.empty, Set.empty) revTags
+ delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
+ disp = thediv ! [theclass "box"] << [ paragraph << [bold $ toHtml "Current Tags: ", toHtml tagsStr, br],
+ paragraph << [bold $ toHtml "Additions to be reviewed: ", toHtml addns, br],
+ paragraph << [bold $ toHtml "Deletions to be reviewed: ", toHtml delns, br]
+ ]
+
+ return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" [ big $ bold $ toHtml $ display pkgname
+ , disp
+ , anchor ![href $ "tags/edit" ] << "Propose a tag?", toHtml " or "
+ , toHtml "return to ", packageNameLink pkgname, br
+ ]
-- serve form for editing, to be received by putTags
serveTagsForm :: DynamicPath -> ServerPartE Response
@@ -1559,36 +1597,29 @@ mkHtmlTags HtmlUtilities{..}
pkgname <- packageInPath dpath
currTags <- queryTagsForPackage pkgname
revTags <- queryReviewTagsForPackage pkgname
+ template <- getTemplate templates "tag-edit.html"
let toStr = concat . intersperse ", " . map display . Set.toList
tagsStr = toStr currTags
- addns = toStr $ fst $ fromJust revTags
- delns = toStr $ snd $ fromJust revTags
- tagForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
- [paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
- thediv ! [theclass "box"] << [paragraph << [bold $ toHtml "Current Tags", br, toHtml tagsStr],
- form ! [ XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
- [ hidden "_method" "PUT"
- , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "addns" "Propose Additions "
- , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "delns" "Propose Deletions "
- ,
- paragraph << input ! [thetype "submit", value "Propose tags"]
- ]]]
- tagRForm = toResponse $ Resource.XHtml $ hackagePage "Edit package tags"
- [paragraph << [toHtml "Set tags for ", packageNameLink pkgname],
- thediv ! [theclass "box"] << [paragraph << [bold $ toHtml "Current Tags", br, toHtml tagsStr],
- form ! [ XHtml.method "post", action $ packageTagsUri tags "" pkgname] <<
- [ hidden "_method" "PUT"
- , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "addns" "Propose Additions "
- , dlist . ddef . toHtml $ makeInput [thetype "text", value " "] "delns" "Propose Deletions "
- ,
- paragraph << input ! [thetype "submit", value "Propose tags"]
- ], paragraph << [big $ toHtml "Proposals", br, bold $ toHtml "additions: ", toHtml addns, br, bold $ toHtml "deletions: ", toHtml delns]]]
+ addns = toStr $ fst $ fromMaybe (Set.empty,Set.empty) revTags
+ delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
+
+
user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
case user of
- "Uploaders" -> return tagForm
- _ -> case revTags of
- Nothing -> return tagForm
- Just _ -> return tagRForm
+ "Uploaders" -> return $ toResponse . template $
+ [ "pkgname" $= display pkgname
+ , "addns" $= addns
+ , "tags" $= (tagsStr)
+ , "delns" $= delns
+ , "isuser" $= "true"
+ ]
+ _ -> return $toResponse . template $
+ [ "pkgname" $= display pkgname
+ , "addns" $= addns
+ , "tags" $= (tagsStr)
+ , "delns" $= delns
+ , "istrustee" $= "false"
+ ]
diff --git a/Distribution/Server/Features/Tags.hs b/Distribution/Server/Features/Tags.hs
index f41feca75..b8b9e5bf1 100644
--- a/Distribution/Server/Features/Tags.hs
+++ b/Distribution/Server/Features/Tags.hs
@@ -32,6 +32,7 @@ import Distribution.PackageDescription
import Distribution.PackageDescription.Configuration
import Distribution.License
+import Data.Maybe(fromMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map (Map)
@@ -225,6 +226,8 @@ tagsFeature CoreFeature{ queryGetPackageIndex
guardValidPackageName pkgname
addns <- optional $ look "addns"
delns <- optional $ look "delns"
+ raddns <- optional $ look "raddns"
+ rdelns <- optional $ look "rdelns"
case simpleParse =<< addns of
Just (TagList add) -> do
case simpleParse =<< delns of
@@ -234,16 +237,25 @@ tagsFeature CoreFeature{ queryGetPackageIndex
"Uploaders" -> do
calcTags <- queryTagsForPackage pkgname
let addTags = Set.fromList add `Set.difference` calcTags
- let delTags = Set.fromList del `Set.intersection` calcTags
+ delTags = Set.fromList del `Set.intersection` calcTags
void $ updateState tagsReview $ InsertReviewTags pkgname addTags delTags
return ()
_ -> do
calcTags <- queryTagsForPackage pkgname
+ revTags <- queryReviewTagsForPackage pkgname
let tagSet = (addTags `Set.union` calcTags) `Set.difference` delTags
addTags = Set.fromList add
delTags = Set.fromList del
+ rdel = case simpleParse =<< rdelns of
+ Just (TagList rdel) -> rdel
+ Nothing -> []
+ radd = case simpleParse =<< raddns of
+ Just (TagList radd) -> radd
+ Nothing -> []
+ addRev = Set.difference (fst $ fromMaybe (Set.empty, Set.empty) revTags) (Set.fromList add `Set.union` Set.fromList radd)
+ delRev = Set.difference (snd $ fromMaybe (Set.empty, Set.empty) revTags) (Set.fromList del `Set.union` Set.fromList rdel)
void $ updateState tagsState $ SetPackageTags pkgname tagSet
- void $ updateState tagsReview $ ClearReviewTags pkgname
+ void $ updateState tagsReview $ InsertReviewTags_ pkgname addRev delRev
runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
return ()
_ -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
diff --git a/Distribution/Server/Features/Tags/State.hs b/Distribution/Server/Features/Tags/State.hs
index 81d30e491..efa702f8d 100644
--- a/Distribution/Server/Features/Tags/State.hs
+++ b/Distribution/Server/Features/Tags/State.hs
@@ -64,10 +64,9 @@ data PackageTags = PackageTags {
packageTags :: Map PackageName (Set Tag),
-- a secondary reverse mapping
tagPackages :: Map Tag (Set PackageName)
- -- tags(add, remove) set for review by the maintainer
- -- reviewTags :: Map PackageName (Set Tag, Set Tag)
} deriving (Eq, Show, Typeable)
+
-- Packagename (Proposed Additions, Proposed Deletions)
data ReviewTags = ReviewTags (Map PackageName (Set Tag, Set Tag)) deriving (Eq, Show)
@@ -96,6 +95,7 @@ alterTags name mtagList (PackageTags tags packages) =
setTags :: PackageName -> Set Tag -> PackageTags -> PackageTags
setTags pkgname tagList = alterTags pkgname (keepSet tagList)
+
deletePackageTags :: PackageName -> PackageTags -> PackageTags
deletePackageTags name = alterTags name Nothing
@@ -223,6 +223,12 @@ insertReviewTags pkgname add del
ReviewTags m <- get
put (ReviewTags (Map.insertWith (insertReviewHelper) pkgname (add,del) m))
+insertReviewTags_ :: PackageName -> Set Tag -> Set Tag -> Update ReviewTags ()
+insertReviewTags_ pkgname add del
+ = do
+ ReviewTags m <- get
+ put (ReviewTags (Map.insert pkgname (add,del) m))
+
insertReviewHelper :: (Set Tag, Set Tag) -> (Set Tag, Set Tag) -> (Set Tag, Set Tag)
insertReviewHelper (a,b) (c,d) = (Set.union a c, Set.union b d)
@@ -231,7 +237,10 @@ lookupReviewTags pkgname
= do ReviewTags m <- ask
return (Map.lookup pkgname m)
+
+
$(makeAcidic ''ReviewTags ['insertReviewTags
+ ,'insertReviewTags_
,'lookupReviewTags
,'getReviewTags
,'clearReviewTags
diff --git a/datafiles/templates/Html/tag-edit.html.st b/datafiles/templates/Html/tag-edit.html.st
new file mode 100644
index 000000000..8367fc9d9
--- /dev/null
+++ b/datafiles/templates/Html/tag-edit.html.st
@@ -0,0 +1,150 @@
+
+
+
+ $hackageCssTheme()$
+ Edit package tags | Hackage
+
+
+
+
+
+ $hackagePageHeader()$
+
+ Set tags for
$pkgname$
+
+
Current Tags
+
$tags$
+
+ $if(isuser)$
+
+ Other proposals
+
additions: $addns$
+
deletions: $delns$
+
+ $endif$
+ $if(istrustee)$
+
+
Proposals
+
additions:
+
+
deletions:
+
+
+ $endif$
+
+
+
+ $footer()$
+
+
+
From 138731ca77c3d2b497ad62085baa10a2a5965c3a Mon Sep 17 00:00:00 2001
From: Soorya Narayan
Date: Tue, 21 Jun 2016 01:18:12 +0530
Subject: [PATCH 04/39] Tag Aliasing
When a trustee aliases Tag abcd -> Tag abc, all packages that were earlier
tagged `abcd` get tagged to `abc` and any new packages tagged `abcd` get
retagged `abc` on upload
---
Distribution/Server/Features/Core.hs | 5 +-
Distribution/Server/Features/Html.hs | 38 ++++++++++-
Distribution/Server/Features/Tags.hs | 73 +++++++++++++++++++---
Distribution/Server/Features/Tags/State.hs | 44 ++++++++++++-
Distribution/Server/Features/Upload.hs | 5 ++
5 files changed, 151 insertions(+), 14 deletions(-)
diff --git a/Distribution/Server/Features/Core.hs b/Distribution/Server/Features/Core.hs
index 60fa1d581..a82c724b9 100644
--- a/Distribution/Server/Features/Core.hs
+++ b/Distribution/Server/Features/Core.hs
@@ -230,7 +230,8 @@ data CoreResource = CoreResource {
-- | Find a PackageId or PackageName inside a path.
packageInPath :: forall m a. (MonadPlus m, FromReqURI a) => DynamicPath -> m a,
-
+ -- | Find a TagName inside a path.
+ tagInPath :: forall m a. (MonadPlus m, FromReqURI a) => DynamicPath -> m a,
-- | Find a tarball's PackageId from inside a path, doing some checking
-- for consistency between the package and tarball.
--
@@ -448,6 +449,8 @@ coreFeature ServerEnv{serverBlobStore = store} UserFeature{..}
packageInPath dpath = maybe mzero return (lookup "package" dpath >>= fromReqURI)
+ tagInPath dpath = maybe mzero return (lookup "tag" dpath >>= fromReqURI)
+
packageTarballInPath dpath = do
PackageIdentifier name version <- packageInPath dpath
case lookup "tarball" dpath >>= fromReqURI of
diff --git a/Distribution/Server/Features/Html.hs b/Distribution/Server/Features/Html.hs
index c510569b9..3831820b8 100644
--- a/Distribution/Server/Features/Html.hs
+++ b/Distribution/Server/Features/Html.hs
@@ -63,7 +63,7 @@ import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Vector as Vec
-import Data.Maybe (fromMaybe, isJust, fromJust)
+import Data.Maybe (fromMaybe, isJust)
import Data.Monoid ((<>))
import qualified Data.Text as T
import Data.Traversable (traverse)
@@ -1476,10 +1476,11 @@ mkHtmlTags :: HtmlUtilities
mkHtmlTags HtmlUtilities{..}
CoreFeature{ coreResource = CoreResource{
packageInPath
+ , tagInPath
, lookupPackageName
}
}
- UploadFeature{guardAuthorisedAsUploaderOrMaintainerOrTrustee}
+ UploadFeature{guardAuthorisedAsUploaderOrMaintainerOrTrustee,guardAuthorisedAsTrustee}
ListFeature{makeItemList}
TagsFeature{..}
templates
@@ -1501,6 +1502,12 @@ mkHtmlTags HtmlUtilities{..}
, (extendResource $ packageTagsListing tags) {
resourcePut = [("html", putPackageTags)], resourceGet = [("html", showPackageTags)]
}
+ , (extendResource $ tagAliasEdit tags) {
+ resourcePut = [("html", putAliasEdit)]
+ }
+ , (extendResource $ tagAliasEditForm tags) {
+ resourceGet = [("html", serveAliasForm)]
+ }
, tagEdit -- (extendResource $ packageTagsEdit tags) { resourceGet = [("html", serveTagsForm)] }
]
@@ -1522,8 +1529,32 @@ mkHtmlTags HtmlUtilities{..}
]
tagItem tg = anchor ! [href $ tagUri tags "" tg] << display tg
+ putAliasEdit :: DynamicPath -> ServerPartE Response
+ putAliasEdit dpath = do
+ let tagname = snd (dpath !! 0)
+ mergeTags (Tag tagname)
+ return $ toResponse $ Resource.XHtml $ hackagePage ("Merged Tag " ++ tagname) $
+ [ paragraph << ["Return to"]
+ , anchor ! [href "/packages/tags"] << tagname
+ ]
+
+ serveAliasForm :: DynamicPath -> ServerPartE Response
+ serveAliasForm dpath = do
+ tagname <- tagInPath dpath
+ guardAuthorisedAsTrustee
+ let aliasForm = [ h2 << ("Merge Tag " ++ tagname)
+ , form ! [theclass "box", XHtml.method "post", action ("/packages/tag/" ++ tagname ++ "/alias")] <<
+ [ hidden "_method" "PUT"
+ , input ! [value " ", name "tags", identifier "tags"]
+ , toHtml "Tag(s) to merge with"
+ , input ! [thetype "submit", value "Merge"]
+ ]
+ ]
+ return $ toResponse $ Resource.XHtml $ hackagePage ("Merge Tag " ++ tagname) $ aliasForm
+
serveTagListing :: DynamicPath -> ServerPartE Response
- serveTagListing dpath =
+ serveTagListing dpath = do
+ tagname <- tagInPath dpath
withTagPath dpath $ \tg pkgnames -> do
let tagd = "Packages tagged " ++ display tg
pkgs = Set.toList pkgnames
@@ -1537,6 +1568,7 @@ mkHtmlTags HtmlUtilities{..}
[] -> toHtml "No packages have this tag."
_ -> toHtml
[ paragraph << [if count==1 then "1 package has" else show count ++ " packages have", " this tag."]
+ , anchor ! [href $ tagname ++ "/alias/edit"] << "[Merge tag]"
, paragraph ! [theclass "toc"] << [toHtml "Related tags: ", toHtml $ showHistogram histogram]
, ulist ! [theclass "packages"] << map renderItem items ]
]
diff --git a/Distribution/Server/Features/Tags.hs b/Distribution/Server/Features/Tags.hs
index b8b9e5bf1..5f234f23d 100644
--- a/Distribution/Server/Features/Tags.hs
+++ b/Distribution/Server/Features/Tags.hs
@@ -32,7 +32,7 @@ import Distribution.PackageDescription
import Distribution.PackageDescription.Configuration
import Distribution.License
-import Data.Maybe(fromMaybe)
+import Data.Maybe(fromMaybe, fromJust)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map (Map)
@@ -50,6 +50,7 @@ data TagsFeature = TagsFeature {
queryGetTagList :: forall m. MonadIO m => m [(Tag, Set PackageName)],
queryTagsForPackage :: forall m. MonadIO m => PackageName -> m (Set Tag),
queryReviewTagsForPackage :: forall m. MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag)),
+ queryAliasForTag :: MonadIO m => Tag -> m (Maybe Tag),
-- All package names that were modified, and all tags that were modified
-- In almost all cases, one of these will be a singleton. Happstack
@@ -68,7 +69,8 @@ data TagsFeature = TagsFeature {
withTagPath :: forall a. DynamicPath -> (Tag -> Set PackageName -> ServerPartE a) -> ServerPartE a,
collectTags :: forall m. MonadIO m => Set PackageName -> m (Map PackageName (Set Tag)),
- putTags :: PackageName -> ServerPartE ()
+ putTags :: PackageName -> ServerPartE (),
+ mergeTags :: Tag -> ServerPartE ()
}
@@ -80,6 +82,8 @@ data TagsResource = TagsResource {
tagListing :: Resource,
packageTagsListing :: Resource,
packageTagsEdit :: Resource,
+ tagAliasEdit :: Resource,
+ tagAliasEditForm :: Resource,
tagUri :: String -> Tag -> String,
tagsUri :: String -> String,
@@ -93,20 +97,23 @@ initTagsFeature :: ServerEnv
initTagsFeature ServerEnv{serverStateDir} = do
tagsState <- tagsStateComponent serverStateDir
tagsReview <- tagsReviewComponent serverStateDir
+ tagAlias <- tagsAliasComponent serverStateDir
specials <- newMemStateWHNF emptyPackageTags
updateTag <- newHook
return $ \core@CoreFeature{..} upload -> do
- let feature = tagsFeature core upload tagsState tagsReview specials updateTag
+ let feature = tagsFeature core upload tagsState tagsReview tagAlias specials updateTag
registerHookJust packageChangeHook isPackageChangeAny $ \(pkgid, mpkginfo) ->
case mpkginfo of
Nothing -> return ()
Just pkginfo -> do
let pkgname = packageName pkgid
- tags = Set.fromList . constructImmutableTags . pkgDesc $ pkginfo
- updateState tagsState . SetPackageTags pkgname $ tags
- runHook_ updateTag (Set.singleton pkgname, tags)
+ tags = constructImmutableTags . pkgDesc $ pkginfo
+ aliases <- sequence $ map (\tag -> queryState tagAlias $ GetTagAlias tag) tags
+ let newtags = Set.fromList $ map fromJust aliases
+ updateState tagsState . SetPackageTags pkgname $ newtags
+ runHook_ updateTag (Set.singleton pkgname, newtags)
return feature
@@ -123,6 +130,20 @@ tagsStateComponent stateDir = do
, resetState = tagsStateComponent
}
+tagsAliasComponent :: FilePath -> IO (StateComponent AcidState TagAlias)
+tagsAliasComponent stateDir = do
+ st <- openLocalStateFrom (stateDir > "db" > "Tags" > "Alias") emptyTagAlias
+ return StateComponent {
+ stateDesc = "Tags Alias"
+ , stateHandle = st
+ , getState = query st GetTagAliasesState
+ , putState = update st . AddTagAliasesState
+ -- , backupState = \_ pkgTags -> [csvToBackup ["tags.csv"] $ tagsToCSV pkgTags]
+ -- , restoreState = tagsBackup
+ -- , resetState = tagsStateComponent
+ }
+
+
tagsReviewComponent :: FilePath -> IO (StateComponent AcidState ReviewTags)
tagsReviewComponent stateDir = do
st <- openLocalStateFrom (stateDir > "db" > "Tags" > "Review") emptyReviewTags
@@ -142,6 +163,7 @@ tagsFeature :: CoreFeature
-> UploadFeature
-> StateComponent AcidState PackageTags
-> StateComponent AcidState ReviewTags
+ -> StateComponent AcidState TagAlias
-> MemState PackageTags
-> Hook (Set PackageName, Set Tag) ()
-> TagsFeature
@@ -152,6 +174,7 @@ tagsFeature CoreFeature{ queryGetPackageIndex
UploadFeature{ guardAuthorisedAsUploaderOrMaintainerOrTrustee }
tagsState
tagsReview
+ tagsAlias
calculatedTags
tagsUpdated
= TagsFeature{..}
@@ -159,6 +182,8 @@ tagsFeature CoreFeature{ queryGetPackageIndex
tagsResource = fix $ \r -> TagsResource
{ tagsListing = resourceAt "/packages/tags/.:format"
, tagListing = resourceAt "/packages/tag/:tag.:format"
+ , tagAliasEdit = resourceAt "/packages/tag/:tag/alias"
+ , tagAliasEditForm = resourceAt "/packages/tag/:tag/alias/edit"
, packageTagsListing = resourceAt "/package/:package/tags.:format"
, packageTagsEdit = resourceAt "/package/:package/tags/edit"
, tagUri = \format tag -> renderResource (tagListing r) [display tag, format]
@@ -200,6 +225,9 @@ tagsFeature CoreFeature{ queryGetPackageIndex
queryTagsForPackage :: MonadIO m => PackageName -> m (Set Tag)
queryTagsForPackage pkgname = queryState tagsState (TagsForPackage pkgname)
+ queryAliasForTag :: MonadIO m => Tag -> m (Maybe Tag)
+ queryAliasForTag tag = queryState tagsAlias (GetTagAlias tag)
+
queryReviewTagsForPackage :: MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag))
queryReviewTagsForPackage pkgname = queryState tagsReview (LookupReviewTags pkgname)
@@ -221,6 +249,29 @@ tagsFeature CoreFeature{ queryGetPackageIndex
pkgMap <- liftM packageTags $ queryState tagsState GetPackageTags
return $ Map.fromDistinctAscList . map (\pkg -> (pkg, Map.findWithDefault Set.empty pkg pkgMap)) $ Set.toList pkgs
+
+ mergeTags :: Tag -> ServerPartE ()
+ mergeTags deprTag = do
+ tags <- optional $ look "tags"
+ index <- queryGetPackageIndex
+ case simpleParse =<< tags of
+ Just (Tag orig) -> do
+ void $ updateState tagsAlias $ AddTagAlias (Tag orig) deprTag
+ void $ constructMergedTagIndex (Tag orig) deprTag index
+ _ -> errBadRequest "Tag not recognised" [MText "Couldn't parse tag. It should be a single tag."]
+
+ -- tags on merging
+ constructMergedTagIndex :: forall m. MonadIO m => Tag -> Tag -> PackageIndex PkgInfo -> m (PackageTags)
+ constructMergedTagIndex orig depr = foldM addToTags emptyPackageTags . PackageIndex.allPackagesByName
+ where addToTags calcTags pkgList = do
+ let info = pkgDesc $ last pkgList
+ !pn = packageName info
+ pkgTags <- queryTagsForPackage pn
+ let newTags = if (depr `elem` pkgTags) then (Set.delete depr (Set.insert orig pkgTags)) else pkgTags
+ void $ updateState tagsState $ SetPackageTags pn newTags
+ runHook_ tagsUpdated (Set.singleton pn, newTags)
+ return (setTags pn newTags calcTags)
+
putTags :: PackageName -> ServerPartE ()
putTags pkgname = do
guardValidPackageName pkgname
@@ -235,16 +286,20 @@ tagsFeature CoreFeature{ queryGetPackageIndex
user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
case user of
"Uploaders" -> do
+ aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) add
calcTags <- queryTagsForPackage pkgname
- let addTags = Set.fromList add `Set.difference` calcTags
+ let add_ = map fromJust aliases
+ addTags = Set.fromList add_ `Set.difference` calcTags
delTags = Set.fromList del `Set.intersection` calcTags
void $ updateState tagsReview $ InsertReviewTags pkgname addTags delTags
return ()
_ -> do
calcTags <- queryTagsForPackage pkgname
+ aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) add
revTags <- queryReviewTagsForPackage pkgname
let tagSet = (addTags `Set.union` calcTags) `Set.difference` delTags
- addTags = Set.fromList add
+ add_ = map fromJust aliases
+ addTags = Set.fromList add_
delTags = Set.fromList del
rdel = case simpleParse =<< rdelns of
Just (TagList rdel) -> rdel
@@ -282,6 +337,8 @@ constructImmutableTagIndex = foldl' addToTags emptyPackageTags . PackageIndex.al
!tags = constructImmutableTags info
in setTags pn (Set.fromList tags) calcTags
+
+
-- These are constructed when a package is uploaded/on startup
constructCategoryTags :: PackageDescription -> [Tag]
constructCategoryTags = map (tagify . map toLower) . fillMe . categorySplit . category
diff --git a/Distribution/Server/Features/Tags/State.hs b/Distribution/Server/Features/Tags/State.hs
index efa702f8d..af2c17250 100644
--- a/Distribution/Server/Features/Tags/State.hs
+++ b/Distribution/Server/Features/Tags/State.hs
@@ -70,12 +70,39 @@ data PackageTags = PackageTags {
-- Packagename (Proposed Additions, Proposed Deletions)
data ReviewTags = ReviewTags (Map PackageName (Set Tag, Set Tag)) deriving (Eq, Show)
+data TagAlias = TagAlias (Map Tag (Set Tag))
+
+addTagAlias :: Tag -> Tag -> Update TagAlias ()
+addTagAlias tag alias= do
+ TagAlias m <- get
+ put (TagAlias (Map.insertWith (Set.union) tag (Set.singleton alias) m))
+
+lookupTagAlias :: Tag -> Query TagAlias (Maybe (Set Tag))
+lookupTagAlias tag
+ = do TagAlias m <- ask
+ return (Map.lookup tag m)
+
+getTagAlias :: Tag -> Query TagAlias (Maybe Tag)
+getTagAlias tag
+ = do TagAlias m <- ask
+ return (canonical tag m) where
+ canonical tag m
+ | tag `elem` (Map.keys m) = Just tag
+ | tag `elem` (foldr Set.union Set.empty $ Map.elems m) = Just (lookupKey tag m)
+ | otherwise = Just tag
+ where
+ lookupKey key m = (Map.keys $ Map.filter (tag `elem` ) m) !! 0
+
emptyPackageTags :: PackageTags
emptyPackageTags = PackageTags Map.empty Map.empty
emptyReviewTags :: ReviewTags
emptyReviewTags = ReviewTags Map.empty
+emptyTagAlias :: TagAlias
+emptyTagAlias = TagAlias Map.empty
+
+
tagToPackages :: Tag -> PackageTags -> Set PackageName
tagToPackages tag = Map.findWithDefault Set.empty tag . tagPackages
@@ -95,7 +122,6 @@ alterTags name mtagList (PackageTags tags packages) =
setTags :: PackageName -> Set Tag -> PackageTags -> PackageTags
setTags pkgname tagList = alterTags pkgname (keepSet tagList)
-
deletePackageTags :: PackageName -> PackageTags -> PackageTags
deletePackageTags name = alterTags name Nothing
@@ -150,7 +176,7 @@ renameTag tag tag' pkgTags@(PackageTags _ packages) =
$(deriveSafeCopy 0 'base ''Tag)
$(deriveSafeCopy 0 'base ''PackageTags)
$(deriveSafeCopy 0 'base ''ReviewTags)
-
+$(deriveSafeCopy 0 'base ''TagAlias)
instance NFData PackageTags where
rnf (PackageTags a b) = rnf a `seq` rnf b
@@ -182,6 +208,13 @@ getReviewTags = ask
replaceReviewTags :: ReviewTags -> Update ReviewTags ()
replaceReviewTags = put
+getTagAliasesState :: Query TagAlias TagAlias
+getTagAliasesState = ask
+
+addTagAliasesState :: TagAlias -> Update TagAlias ()
+addTagAliasesState = put
+
+
setPackageTags :: PackageName -> Set Tag -> Update PackageTags ()
setPackageTags name tagList = modify $ setTags name tagList
@@ -247,6 +280,12 @@ $(makeAcidic ''ReviewTags ['insertReviewTags
,'replaceReviewTags
])
+$(makeAcidic ''TagAlias ['addTagAlias
+ ,'getTagAlias
+ ,'lookupTagAlias
+ ,'addTagAliasesState
+ ,'getTagAliasesState
+ ])
$(makeAcidic ''PackageTags ['tagsForPackage
,'packagesForTag
@@ -259,3 +298,4 @@ $(makeAcidic ''PackageTags ['tagsForPackage
,'removePackageTag
])
+
diff --git a/Distribution/Server/Features/Upload.hs b/Distribution/Server/Features/Upload.hs
index 00d43b296..ab9e38ec4 100644
--- a/Distribution/Server/Features/Upload.hs
+++ b/Distribution/Server/Features/Upload.hs
@@ -61,6 +61,7 @@ data UploadFeature = UploadFeature {
-- | Requiring being logged in as the maintainer of a package.
guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String,
guardAuthorisedAsMaintainer :: PackageName -> ServerPartE (),
+ guardAuthorisedAsTrustee :: ServerPartE (),
-- | Requiring being logged in as the maintainer of a package or a trustee.
guardAuthorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE (),
@@ -299,6 +300,10 @@ uploadFeature ServerEnv{serverBlobStore = store}
guardAuthorisedAsMaintainer pkgname =
guardAuthorised_ [InGroup (maintainersGroup pkgname)]
+ guardAuthorisedAsTrustee :: ServerPartE ()
+ guardAuthorisedAsTrustee =
+ guardAuthorised_ [InGroup trusteesGroup]
+
guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String
guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname= do
mt <- guardAuthorised' [InGroup (maintainersGroup pkgname), InGroup trusteesGroup]
From 5c132ebe0bc29b28e6632f696dbcf7e77cbbbf16 Mon Sep 17 00:00:00 2001
From: Soorya Narayan
Date: Tue, 21 Jun 2016 13:00:21 +0530
Subject: [PATCH 05/39] Cleaning up earlier code
Added backup functions for tag alias
Fixed tag aliasing bug on startup
---
Distribution/Server/Features/Html.hs | 125 ++++++------------
.../Server/Features/Html/HtmlUtilities.hs | 20 +++
Distribution/Server/Features/PackageList.hs | 7 +-
Distribution/Server/Features/Tags.hs | 100 ++++++--------
Distribution/Server/Features/Tags/Backup.hs | 33 ++++-
Distribution/Server/Features/Tags/State.hs | 113 +++++++---------
Distribution/Server/Features/Upload.hs | 24 ++--
Distribution/Server/Features/Users.hs | 6 +-
Distribution/Server/Features/Votes.hs | 2 +-
datafiles/static/hackage.css | 2 +
datafiles/templates/Html/tag-edit.html.st | 30 +++--
datafiles/templates/index.html.st | 1 +
hackage-server.cabal | 18 ++-
13 files changed, 235 insertions(+), 246 deletions(-)
diff --git a/Distribution/Server/Features/Html.hs b/Distribution/Server/Features/Html.hs
index 3831820b8..45501ed8b 100644
--- a/Distribution/Server/Features/Html.hs
+++ b/Distribution/Server/Features/Html.hs
@@ -440,8 +440,7 @@ htmlFeature env@ServerEnv{..}
{-------------------------------------------------------------------------------
Core
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlCore = HtmlCore {
htmlCoreResources :: [Resource]
@@ -519,10 +518,10 @@ mkHtmlCore ServerEnv{serverBaseURI}
, (resourceAt "/packages/names" ) {
resourceGet = [("html", const $ readAsyncCache cacheNamesPage)]
}
- , (resourceAt "/packages/names/experiment" ) {
+ , (resourceAt "/packages/names/tags" ) {
resourceDesc = [(GET, "Show detailed package dependency information")]
, resourceGet = [("html",
- serveMaintainPage')]
+ serveTagIndex)]
}
, (extendResource $ corePackagesPage cores) {
resourceDesc = [(GET, "Show package index")]
@@ -621,13 +620,13 @@ mkHtmlCore ServerEnv{serverBaseURI}
, "versions" $= map packageId pkgs
]
- serveMaintainPage' :: DynamicPath -> ServerPartE Response
- serveMaintainPage' _ = do
+ serveTagIndex :: DynamicPath -> ServerPartE Response
+ serveTagIndex _ = do
pkgIndex <- queryGetPackageIndex
let packageNames = Pages.toPackageNames pkgIndex
pkgDetails <- liftIO $ makeItemList packageNames
let rowList = map (makeRow) pkgDetails
- tabledata = "" +++ rowList +++""
+ tabledata = "" +++ rowList +++ ""
template <- getTemplate templates "tag-interface.html"
return $ toResponse $ template
[ "tabledata" $= tabledata ]
@@ -683,8 +682,7 @@ mkHtmlCore ServerEnv{serverBaseURI}
{-------------------------------------------------------------------------------
Users
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlUsers = HtmlUsers {
htmlUsersResources :: [Resource]
@@ -798,8 +796,7 @@ mkHtmlUsers UserFeature{..} UserDetailsFeature{..} = HtmlUsers{..}
{-------------------------------------------------------------------------------
Uploads
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlUploads = HtmlUploads {
htmlUploadsResources :: [Resource]
@@ -847,8 +844,7 @@ mkHtmlUploads HtmlUtilities{..} UploadFeature{..} = HtmlUploads{..}
{-------------------------------------------------------------------------------
Documentation uploads
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlDocUploads = HtmlDocUploads {
htmlDocUploadsResources :: [Resource]
@@ -895,8 +891,7 @@ mkHtmlDocUploads HtmlUtilities{..} CoreFeature{coreResource} DocumentationFeatur
{-------------------------------------------------------------------------------
Build reports
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlReports = HtmlReports {
htmlReportsResources :: [Resource]
@@ -942,8 +937,7 @@ mkHtmlReports HtmlUtilities{..} CoreFeature{..} ReportsFeature{..} templates = H
{-------------------------------------------------------------------------------
Candidates
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlCandidates = HtmlCandidates {
htmlCandidatesResources :: [Resource]
@@ -1200,8 +1194,7 @@ dependenciesPage isCandidate render =
{-------------------------------------------------------------------------------
Preferred versions
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlPreferred = HtmlPreferred {
htmlPreferredResources :: [Resource]
@@ -1419,8 +1412,7 @@ mkHtmlPreferred HtmlUtilities{..}
{-------------------------------------------------------------------------------
Downloads
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlDownloads = HtmlDownloads {
htmlDownloadsResources :: [Resource]
@@ -1458,8 +1450,7 @@ mkHtmlDownloads HtmlUtilities{..} DownloadFeature{..} = HtmlDownloads{..}
{-------------------------------------------------------------------------------
Tags
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlTags = HtmlTags {
htmlTagsResources :: [Resource]
@@ -1480,7 +1471,7 @@ mkHtmlTags HtmlUtilities{..}
, lookupPackageName
}
}
- UploadFeature{guardAuthorisedAsUploaderOrMaintainerOrTrustee,guardAuthorisedAsTrustee}
+ UploadFeature{authorisedAsAnyUser, authorisedAsMaintainerOrTrustee, guardAuthorisedAsTrustee}
ListFeature{makeItemList}
TagsFeature{..}
templates
@@ -1533,21 +1524,24 @@ mkHtmlTags HtmlUtilities{..}
putAliasEdit dpath = do
let tagname = snd (dpath !! 0)
mergeTags (Tag tagname)
- return $ toResponse $ Resource.XHtml $ hackagePage ("Merged Tag " ++ tagname) $
- [ paragraph << ["Return to"]
- , anchor ! [href "/packages/tags"] << tagname
+ return $ toResponse $ Resource.XHtml $ hackagePage "Merged Tag" $
+ [ h2 << "Merged tag"
+ , toHtml "Return to "
+ , anchor ! [href $ "/packages/tags"] << "tag listings"
]
serveAliasForm :: DynamicPath -> ServerPartE Response
serveAliasForm dpath = do
tagname <- tagInPath dpath
guardAuthorisedAsTrustee
- let aliasForm = [ h2 << ("Merge Tag " ++ tagname)
- , form ! [theclass "box", XHtml.method "post", action ("/packages/tag/" ++ tagname ++ "/alias")] <<
- [ hidden "_method" "PUT"
- , input ! [value " ", name "tags", identifier "tags"]
- , toHtml "Tag(s) to merge with"
- , input ! [thetype "submit", value "Merge"]
+ let aliasForm = [ thediv ! [theclass "box"] <<
+ [h2 << ("Merge Tag " ++ tagname)
+ , form ! [XHtml.method "post", action ("/packages/tag/" ++ tagname ++ "/alias")] <<
+ [ hidden "_method" "PUT"
+ , input ! [value "", name "tags", identifier "tags"]
+ , toHtml " (Tag to merge with) ", br
+ , input ! [thetype "submit", value "Merge"]
+ ]
]
]
return $ toResponse $ Resource.XHtml $ hackagePage ("Merge Tag " ++ tagname) $ aliasForm
@@ -1588,40 +1582,16 @@ mkHtmlTags HtmlUtilities{..}
putTags pkgname
currTags <- queryTagsForPackage pkgname
revTags <- queryReviewTagsForPackage pkgname
- let toStr = concat . intersperse ", " . map display . Set.toList
- tagsStr = toStr currTags
- addns = toStr $ fst $ fromMaybe (Set.empty, Set.empty) revTags
- delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
- disp = thediv ! [theclass "box"] << [ paragraph << [bold $ toHtml "Current Tags: ", toHtml tagsStr, br],
- paragraph << [bold $ toHtml "Additions to be reviewed: ", toHtml addns, br],
- paragraph << [bold $ toHtml "Deletions to be reviewed: ", toHtml delns, br]
- ]
-
- return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" [ big $ bold $ toHtml $ display pkgname
- , disp
- , anchor ![href $ "tags/edit" ] << "Propose a tag?", toHtml " or "
- , toHtml "return to ", packageNameLink pkgname, br
- ]
+ let disp = renderReviewTags currTags revTags pkgname
+ return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" disp
showPackageTags :: DynamicPath -> ServerPartE Response
showPackageTags dpath = do
pkgname <- packageInPath dpath
currTags <- queryTagsForPackage pkgname
revTags <- queryReviewTagsForPackage pkgname
- let toStr = concat . intersperse ", " . map display . Set.toList
- tagsStr = toStr currTags
- addns = toStr $ fst $ fromMaybe (Set.empty, Set.empty) revTags
- delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
- disp = thediv ! [theclass "box"] << [ paragraph << [bold $ toHtml "Current Tags: ", toHtml tagsStr, br],
- paragraph << [bold $ toHtml "Additions to be reviewed: ", toHtml addns, br],
- paragraph << [bold $ toHtml "Deletions to be reviewed: ", toHtml delns, br]
- ]
-
- return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" [ big $ bold $ toHtml $ display pkgname
- , disp
- , anchor ![href $ "tags/edit" ] << "Propose a tag?", toHtml " or "
- , toHtml "return to ", packageNameLink pkgname, br
- ]
+ let disp = renderReviewTags currTags revTags pkgname
+ return $ toResponse $ Resource.XHtml $ hackagePage "Package Tags" disp
-- serve form for editing, to be received by putTags
serveTagsForm :: DynamicPath -> ServerPartE Response
@@ -1632,33 +1602,27 @@ mkHtmlTags HtmlUtilities{..}
template <- getTemplate templates "tag-edit.html"
let toStr = concat . intersperse ", " . map display . Set.toList
tagsStr = toStr currTags
- addns = toStr $ fst $ fromMaybe (Set.empty,Set.empty) revTags
- delns = toStr $ snd $ fromMaybe (Set.empty, Set.empty) revTags
-
-
- user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
- case user of
- "Uploaders" -> return $ toResponse . template $
- [ "pkgname" $= display pkgname
- , "addns" $= addns
- , "tags" $= (tagsStr)
- , "delns" $= delns
- , "isuser" $= "true"
- ]
- _ -> return $toResponse . template $
+ addns = toStr $ fst revTags
+ delns = toStr $ snd revTags
+ trustainer <- authorisedAsMaintainerOrTrustee pkgname
+ user <- authorisedAsAnyUser
+ if trustainer || user
+ then return $ toResponse . template $
[ "pkgname" $= display pkgname
, "addns" $= addns
- , "tags" $= (tagsStr)
+ , "tags" $= tagsStr
, "delns" $= delns
- , "istrustee" $= "false"
+ , "istrustee" $= trustainer
+ , "isuser" $= if trustainer then False else True
]
+ else return $ toResponse $ Resource.XHtml $ hackagePage "Error" $ [h2 << "Authorization Error"
+ , paragraph << "You need to be logged in to propose tags"]
{-------------------------------------------------------------------------------
Search
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
data HtmlSearch = HtmlSearch {
htmlSearchResources :: [Resource]
@@ -1917,8 +1881,7 @@ mkHtmlSearch HtmlUtilities{..}
{-------------------------------------------------------------------------------
Groups
------------------------------------------------------------------------------
--}
+-------------------------------------------------------------------------------}
htmlGroupResource :: UserFeature -> GroupResource -> [Resource]
htmlGroupResource UserFeature{..} r@(GroupResource groupR userR getGroup) =
diff --git a/Distribution/Server/Features/Html/HtmlUtilities.hs b/Distribution/Server/Features/Html/HtmlUtilities.hs
index da928faa1..ae0aa619e 100644
--- a/Distribution/Server/Features/Html/HtmlUtilities.hs
+++ b/Distribution/Server/Features/Html/HtmlUtilities.hs
@@ -11,6 +11,7 @@ import Distribution.Server.Features.Core
import Distribution.Text (display)
import Data.List (intersperse)
import Data.Set (Set)
+import Data.Maybe (fromMaybe)
import Distribution.Server.Features.PackageList
import Distribution.Server.Pages.Util (packageType)
import Distribution.Package
@@ -21,6 +22,7 @@ data HtmlUtilities = HtmlUtilities {
, renderItem :: PackageItem -> Html
, makeRow :: PackageItem -> Html
, renderTags :: Set Tag -> [Html]
+ , renderReviewTags :: Set Tag -> (Set Tag, Set Tag) -> PackageName -> [Html]
}
htmlUtilities :: CoreFeature -> TagsFeature -> HtmlUtilities
@@ -61,4 +63,22 @@ htmlUtilities CoreFeature{coreResource}
(map (\tg -> anchor ! [href $ tagUri tagsResource "" tg] << display tg)
$ Set.toList tags)
+ -- The page displayed at /package/:package/tags
+ renderReviewTags :: Set Tag -> (Set Tag, Set Tag) -> PackageName -> [Html]
+ renderReviewTags currTags revTags pkgname=
+ let toStr = concat . intersperse ", " . map display . Set.toList
+ tagsStr = toStr currTags
+ addns = toStr $ fst revTags
+ delns = toStr $ snd revTags
+ disp = thediv ! [theclass "box"] << [ paragraph << [bold $ toHtml "Current Tags: ", toHtml tagsStr, br]
+ , paragraph << [bold $ toHtml "Additions to be reviewed: ", toHtml $ if (addns /= "") then addns else "None", br]
+ , paragraph << [bold $ toHtml "Deletions to be reviewed: ", toHtml $ if (delns /= "") then delns else "None", br]
+ ]
+ in
+ [ big $ bold $ toHtml $ display pkgname
+ , disp
+ , anchor ![href $ "tags/edit" ] << "Propose a tag?", toHtml " or "
+ , toHtml "return to ", packageNameLink pkgname, br
+ ]
+
cores = coreResource
diff --git a/Distribution/Server/Features/PackageList.hs b/Distribution/Server/Features/PackageList.hs
index 3b506516d..8ecc71f2c 100644
--- a/Distribution/Server/Features/PackageList.hs
+++ b/Distribution/Server/Features/PackageList.hs
@@ -60,11 +60,11 @@ data PackageItem = PackageItem {
itemMaintainer :: !String,
-- Whether the item is in the Haskell Platform
--itemPlatform :: Bool,
- -- Author of the package (Probably won't be used in display)
- itemVotes :: Int,
+ -- Number of votes for the package
+ itemVotes :: !Int,
-- The total number of downloads. (For sorting, not displaying.)
-- Updated periodically.
- itemDownloads :: Int,
+ itemDownloads :: !Int,
-- The number of direct revdeps. (Likewise.)
-- also: distinguish direct/flat?
-- [reverse index disabled] itemRevDepsCount :: !Int,
@@ -251,7 +251,6 @@ updateDescriptionItem genDesc item =
-- This checks if the library is buildable. However, since
-- desc is flattened, we might miss some flags. Perhaps use the
-- CondTree instead.
- -- itemAuthor = author desc,
itemMaintainer = maintainer desc,
itemHasLibrary = hasLibs desc,
itemNumExecutables = length . filter (buildable . buildInfo) $ executables desc,
diff --git a/Distribution/Server/Features/Tags.hs b/Distribution/Server/Features/Tags.hs
index 5f234f23d..7341eea03 100644
--- a/Distribution/Server/Features/Tags.hs
+++ b/Distribution/Server/Features/Tags.hs
@@ -17,9 +17,9 @@ import Distribution.Server.Framework.BackupDump
import Distribution.Server.Features.Tags.State
import Distribution.Server.Features.Tags.Backup
-import Distribution.Server.Features.Users( guardAuthorised' )
import Distribution.Server.Features.Core
import Distribution.Server.Features.Upload
+import Distribution.Server.Framework.BackupRestore
import qualified Distribution.Server.Packages.PackageIndex as PackageIndex
import Distribution.Server.Packages.PackageIndex (PackageIndex)
@@ -32,7 +32,6 @@ import Distribution.PackageDescription
import Distribution.PackageDescription.Configuration
import Distribution.License
-import Data.Maybe(fromMaybe, fromJust)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Map (Map)
@@ -49,8 +48,8 @@ data TagsFeature = TagsFeature {
queryGetTagList :: forall m. MonadIO m => m [(Tag, Set PackageName)],
queryTagsForPackage :: forall m. MonadIO m => PackageName -> m (Set Tag),
- queryReviewTagsForPackage :: forall m. MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag)),
- queryAliasForTag :: MonadIO m => Tag -> m (Maybe Tag),
+ queryReviewTagsForPackage :: forall m. MonadIO m => PackageName -> m (Set Tag,Set Tag),
+ queryAliasForTag :: forall m. MonadIO m => Tag -> m Tag,
-- All package names that were modified, and all tags that were modified
-- In almost all cases, one of these will be a singleton. Happstack
@@ -68,7 +67,6 @@ data TagsFeature = TagsFeature {
withTagPath :: forall a. DynamicPath -> (Tag -> Set PackageName -> ServerPartE a) -> ServerPartE a,
collectTags :: forall m. MonadIO m => Set PackageName -> m (Map PackageName (Set Tag)),
-
putTags :: PackageName -> ServerPartE (),
mergeTags :: Tag -> ServerPartE ()
@@ -96,13 +94,12 @@ initTagsFeature :: ServerEnv
-> IO TagsFeature)
initTagsFeature ServerEnv{serverStateDir} = do
tagsState <- tagsStateComponent serverStateDir
- tagsReview <- tagsReviewComponent serverStateDir
tagAlias <- tagsAliasComponent serverStateDir
specials <- newMemStateWHNF emptyPackageTags
updateTag <- newHook
return $ \core@CoreFeature{..} upload -> do
- let feature = tagsFeature core upload tagsState tagsReview tagAlias specials updateTag
+ let feature = tagsFeature core upload tagsState tagAlias specials updateTag
registerHookJust packageChangeHook isPackageChangeAny $ \(pkgid, mpkginfo) ->
case mpkginfo of
@@ -111,7 +108,7 @@ initTagsFeature ServerEnv{serverStateDir} = do
let pkgname = packageName pkgid
tags = constructImmutableTags . pkgDesc $ pkginfo
aliases <- sequence $ map (\tag -> queryState tagAlias $ GetTagAlias tag) tags
- let newtags = Set.fromList $ map fromJust aliases
+ let newtags = Set.fromList aliases
updateState tagsState . SetPackageTags pkgname $ newtags
runHook_ updateTag (Set.singleton pkgname, newtags)
@@ -138,31 +135,14 @@ tagsAliasComponent stateDir = do
, stateHandle = st
, getState = query st GetTagAliasesState
, putState = update st . AddTagAliasesState
- -- , backupState = \_ pkgTags -> [csvToBackup ["tags.csv"] $ tagsToCSV pkgTags]
- -- , restoreState = tagsBackup
- -- , resetState = tagsStateComponent
- }
-
-
-tagsReviewComponent :: FilePath -> IO (StateComponent AcidState ReviewTags)
-tagsReviewComponent stateDir = do
- st <- openLocalStateFrom (stateDir > "db" > "Tags" > "Review") emptyReviewTags
- return StateComponent {
- stateDesc = "Review tags"
- , stateHandle = st
- , getState = query st GetReviewTags
- , putState = update st . ReplaceReviewTags
- -- , backupState = \_ pkgTags -> [csvToBackup ["tags.csv"] $ tagsToCSV pkgTags]
- -- , restoreState = tagsBackup
- -- , resetState = tagsStateComponent
+ , backupState = \_ aliases -> [csvToBackup ["aliases.csv"] $ aliasToCSV aliases]
+ , restoreState = aliasBackup
+ , resetState = tagsAliasComponent
}
-
-
tagsFeature :: CoreFeature
-> UploadFeature
-> StateComponent AcidState PackageTags
- -> StateComponent AcidState ReviewTags
-> StateComponent AcidState TagAlias
-> MemState PackageTags
-> Hook (Set PackageName, Set Tag) ()
@@ -171,9 +151,8 @@ tagsFeature :: CoreFeature
tagsFeature CoreFeature{ queryGetPackageIndex
, coreResource = CoreResource { guardValidPackageName }
}
- UploadFeature{ guardAuthorisedAsUploaderOrMaintainerOrTrustee }
+ UploadFeature{authorisedAsAnyUser, authorisedAsMaintainerOrTrustee}
tagsState
- tagsReview
tagsAlias
calculatedTags
tagsUpdated
@@ -217,7 +196,9 @@ tagsFeature CoreFeature{ queryGetPackageIndex
initImmutableTags = do
index <- queryGetPackageIndex
let calcTags = tagPackages $ constructImmutableTagIndex index
- forM_ (Map.toList calcTags) $ uncurry setCalculatedTag
+ aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) $ Map.keys calcTags
+ let calcTags' = Map.toList . Map.fromListWith (Set.union) $ zip aliases (Map.elems calcTags)
+ forM_ calcTags' $ uncurry setCalculatedTag
queryGetTagList :: MonadIO m => m [(Tag, Set PackageName)]
queryGetTagList = queryState tagsState GetTagList
@@ -225,11 +206,11 @@ tagsFeature CoreFeature{ queryGetPackageIndex
queryTagsForPackage :: MonadIO m => PackageName -> m (Set Tag)
queryTagsForPackage pkgname = queryState tagsState (TagsForPackage pkgname)
- queryAliasForTag :: MonadIO m => Tag -> m (Maybe Tag)
+ queryAliasForTag :: MonadIO m => Tag -> m Tag
queryAliasForTag tag = queryState tagsAlias (GetTagAlias tag)
- queryReviewTagsForPackage :: MonadIO m => PackageName -> m (Maybe (Set Tag,Set Tag))
- queryReviewTagsForPackage pkgname = queryState tagsReview (LookupReviewTags pkgname)
+ queryReviewTagsForPackage :: MonadIO m => PackageName -> m (Set Tag,Set Tag)
+ queryReviewTagsForPackage pkgname = queryState tagsState (LookupReviewTags pkgname)
setCalculatedTag :: Tag -> Set PackageName -> IO ()
setCalculatedTag tag pkgs = do
@@ -249,7 +230,6 @@ tagsFeature CoreFeature{ queryGetPackageIndex
pkgMap <- liftM packageTags $ queryState tagsState GetPackageTags
return $ Map.fromDistinctAscList . map (\pkg -> (pkg, Map.findWithDefault Set.empty pkg pkgMap)) $ Set.toList pkgs
-
mergeTags :: Tag -> ServerPartE ()
mergeTags deprTag = do
tags <- optional $ look "tags"
@@ -261,16 +241,19 @@ tagsFeature CoreFeature{ queryGetPackageIndex
_ -> errBadRequest "Tag not recognised" [MText "Couldn't parse tag. It should be a single tag."]
-- tags on merging
- constructMergedTagIndex :: forall m. MonadIO m => Tag -> Tag -> PackageIndex PkgInfo -> m (PackageTags)
+ constructMergedTagIndex :: forall m. (Functor m, MonadIO m) => Tag -> Tag -> PackageIndex PkgInfo -> m (PackageTags)
constructMergedTagIndex orig depr = foldM addToTags emptyPackageTags . PackageIndex.allPackagesByName
where addToTags calcTags pkgList = do
let info = pkgDesc $ last pkgList
!pn = packageName info
pkgTags <- queryTagsForPackage pn
- let newTags = if (depr `elem` pkgTags) then (Set.delete depr (Set.insert orig pkgTags)) else pkgTags
- void $ updateState tagsState $ SetPackageTags pn newTags
- runHook_ tagsUpdated (Set.singleton pn, newTags)
- return (setTags pn newTags calcTags)
+ if (Set.member depr pkgTags)
+ then do
+ let newTags = Set.delete depr (Set.insert orig pkgTags)
+ void $ updateState tagsState $ SetPackageTags pn newTags
+ runHook_ tagsUpdated (Set.singleton pn, newTags)
+ return $ setTags pn newTags calcTags
+ else return $ setTags pn pkgTags calcTags
putTags :: PackageName -> ServerPartE ()
putTags pkgname = do
@@ -283,36 +266,37 @@ tagsFeature CoreFeature{ queryGetPackageIndex
Just (TagList add) -> do
case simpleParse =<< delns of
Just (TagList del) -> do
- user <- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname
- case user of
- "Uploaders" -> do
- aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) add
- calcTags <- queryTagsForPackage pkgname
- let add_ = map fromJust aliases
- addTags = Set.fromList add_ `Set.difference` calcTags
- delTags = Set.fromList del `Set.intersection` calcTags
- void $ updateState tagsReview $ InsertReviewTags pkgname addTags delTags
- return ()
- _ -> do
+ trustainer <- authorisedAsMaintainerOrTrustee pkgname
+ user <- authorisedAsAnyUser
+ if trustainer
+ then do
calcTags <- queryTagsForPackage pkgname
aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) add
revTags <- queryReviewTagsForPackage pkgname
let tagSet = (addTags `Set.union` calcTags) `Set.difference` delTags
- add_ = map fromJust aliases
- addTags = Set.fromList add_
+ addTags = Set.fromList aliases
delTags = Set.fromList del
- rdel = case simpleParse =<< rdelns of
+ rdel' = case simpleParse =<< rdelns of
Just (TagList rdel) -> rdel
Nothing -> []
- radd = case simpleParse =<< raddns of
+ radd' = case simpleParse =<< raddns of
Just (TagList radd) -> radd
Nothing -> []
- addRev = Set.difference (fst $ fromMaybe (Set.empty, Set.empty) revTags) (Set.fromList add `Set.union` Set.fromList radd)
- delRev = Set.difference (snd $ fromMaybe (Set.empty, Set.empty) revTags) (Set.fromList del `Set.union` Set.fromList rdel)
+ addRev = Set.difference (fst revTags) (Set.fromList add `Set.union` Set.fromList radd')
+ delRev = Set.difference (snd revTags) (Set.fromList del `Set.union` Set.fromList rdel')
void $ updateState tagsState $ SetPackageTags pkgname tagSet
- void $ updateState tagsReview $ InsertReviewTags_ pkgname addRev delRev
+ void $ updateState tagsState $ InsertReviewTags' pkgname addRev delRev
runHook_ tagsUpdated (Set.singleton pkgname, tagSet)
return ()
+ else if user
+ then do
+ aliases <- sequence $ map (\tag -> queryState tagsAlias $ GetTagAlias tag) add
+ calcTags <- queryTagsForPackage pkgname
+ let addTags = Set.fromList aliases `Set.difference` calcTags
+ delTags = Set.fromList del `Set.intersection` calcTags
+ void $ updateState tagsState $ InsertReviewTags pkgname addTags delTags
+ return ()
+ else errBadRequest "Authorization Error" [MText "You need to be logged in to propose tags"]
_ -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
Nothing -> errBadRequest "Tags not recognized" [MText "Couldn't parse your tag list. It should be comma separated with any number of alphanumerical tags. Tags can also also have -+#*."]
diff --git a/Distribution/Server/Features/Tags/Backup.hs b/Distribution/Server/Features/Tags/Backup.hs
index e9fdac5e1..2e4c0d2e1 100644
--- a/Distribution/Server/Features/Tags/Backup.hs
+++ b/Distribution/Server/Features/Tags/Backup.hs
@@ -1,7 +1,10 @@
module Distribution.Server.Features.Tags.Backup (
tagsBackup,
+ aliasBackup,
tagsToCSV,
- tagsToRecord
+ aliasToCSV,
+ tagsToRecord,
+ aliasToRecord,
) where
import Distribution.Server.Features.Tags.State
@@ -28,6 +31,19 @@ updateTags tagsState = RestoreBackup {
else return (updateTags tagsState)
, restoreFinalize = return tagsState
}
+aliasBackup :: RestoreBackup TagAlias
+aliasBackup = updateAlias emptyTagAlias
+
+updateAlias :: TagAlias -> RestoreBackup TagAlias
+updateAlias tagAliases = RestoreBackup {
+ restoreEntry = \(BackupByteString entry bs) ->
+ if entry == ["tagAlias.csv"]
+ then do csv <- importCSV "tagAlias.csv" bs
+ tagAliases' <- updateFromCSVA csv tagAliases
+ return (updateAlias tagAliases')
+ else return (updateAlias tagAliases)
+ , restoreFinalize = return tagAliases
+ }
updateFromCSV :: CSV -> PackageTags -> Restore PackageTags
updateFromCSV = concatM . map fromRecord
@@ -39,11 +55,26 @@ updateFromCSV = concatM . map fromRecord
return (setTags pkgname (Set.fromList tags) tagsState)
fromRecord x _ = fail $ "Invalid tags record: " ++ show x
+updateFromCSVA :: CSV -> TagAlias -> Restore TagAlias
+updateFromCSVA = concatM . map fromRecord
+ where
+ fromRecord :: Record -> TagAlias -> Restore TagAlias
+ fromRecord (canonical:aliases) tagsAlias | not (null aliases) = do
+ tag <- parseText "tag" canonical
+ alias <- mapM (parseText "tag") aliases
+ return (setAliases tag (Set.fromList alias) tagsAlias)
+ fromRecord x _ = fail $ "Invalid tags record: " ++ show x
+
------------------------------------------------------------------------------
tagsToCSV :: PackageTags -> CSV
tagsToCSV = map (\(p, t) -> tagsToRecord p $ Set.toList t)
. Map.toList . packageTags
+aliasToCSV :: TagAlias -> CSV
+aliasToCSV (TagAlias ta) = map (\(t, a) -> aliasToRecord t $ Set.toList a) . Map.toList $ ta
+
tagsToRecord :: PackageName -> [Tag] -> Record -- [String]
tagsToRecord pkgname tags = display pkgname:map display tags
+aliasToRecord :: Tag -> [Tag] -> Record -- [String]
+aliasToRecord canonical aliases = display canonical:map display aliases
diff --git a/Distribution/Server/Features/Tags/State.hs b/Distribution/Server/Features/Tags/State.hs
index af2c17250..0bbaca568 100644
--- a/Distribution/Server/Features/Tags/State.hs
+++ b/Distribution/Server/Features/Tags/State.hs
@@ -63,17 +63,16 @@ data PackageTags = PackageTags {
-- the primary index
packageTags :: Map PackageName (Set Tag),
-- a secondary reverse mapping
- tagPackages :: Map Tag (Set PackageName)
+ tagPackages :: Map Tag (Set PackageName),
+ -- Packagename (Proposed Additions, Proposed Deletions)
+ reviewTags :: Map PackageName (Set Tag, Set Tag)
} deriving (Eq, Show, Typeable)
--- Packagename (Proposed Additions, Proposed Deletions)
-data ReviewTags = ReviewTags (Map PackageName (Set Tag, Set Tag)) deriving (Eq, Show)
-
-data TagAlias = TagAlias (Map Tag (Set Tag))
+data TagAlias = TagAlias (Map Tag (Set Tag)) deriving (Eq, Show, Typeable)
addTagAlias :: Tag -> Tag -> Update TagAlias ()
-addTagAlias tag alias= do
+addTagAlias tag alias = do
TagAlias m <- get
put (TagAlias (Map.insertWith (Set.union) tag (Set.singleton alias) m))
@@ -82,27 +81,21 @@ lookupTagAlias tag
= do TagAlias m <- ask
return (Map.lookup tag m)
-getTagAlias :: Tag -> Query TagAlias (Maybe Tag)
+getTagAlias :: Tag -> Query TagAlias Tag
getTagAlias tag
= do TagAlias m <- ask
- return (canonical tag m) where
- canonical tag m
- | tag `elem` (Map.keys m) = Just tag
- | tag `elem` (foldr Set.union Set.empty $ Map.elems m) = Just (lookupKey tag m)
- | otherwise = Just tag
- where
- lookupKey key m = (Map.keys $ Map.filter (tag `elem` ) m) !! 0
+ if tag `elem` (Map.keys m)
+ then return tag
+ else if tag `Set.member` (foldr Set.union Set.empty $ Map.elems m)
+ then return $ head (Map.keys $ Map.filter (tag `Set.member`) m)
+ else return tag
emptyPackageTags :: PackageTags
-emptyPackageTags = PackageTags Map.empty Map.empty
-
-emptyReviewTags :: ReviewTags
-emptyReviewTags = ReviewTags Map.empty
+emptyPackageTags = PackageTags Map.empty Map.empty Map.empty
emptyTagAlias :: TagAlias
emptyTagAlias = TagAlias Map.empty
-
tagToPackages :: Tag -> PackageTags -> Set PackageName
tagToPackages tag = Map.findWithDefault Set.empty tag . tagPackages
@@ -110,35 +103,43 @@ packageToTags :: PackageName -> PackageTags -> Set Tag
packageToTags pkg = Map.findWithDefault Set.empty pkg . packageTags
alterTags :: PackageName -> Maybe (Set Tag) -> PackageTags -> PackageTags
-alterTags name mtagList (PackageTags tags packages) =
+alterTags name mtagList pt@(PackageTags tags packages _) =
let tagList = fromMaybe Set.empty mtagList
oldTags = Map.findWithDefault Set.empty name tags
adjustPlusTags pkgMap tag' = addSetMap tag' name pkgMap
adjustMinusTags pkgMap tag' = removeSetMap tag' name pkgMap
packages' = flip (foldl' adjustPlusTags) (Set.toList $ Set.difference tagList oldTags)
$ foldl' adjustMinusTags packages (Set.toList $ Set.difference oldTags tagList)
- in PackageTags (Map.alter (const mtagList) name tags) packages'
+ in pt{
+ packageTags = Map.alter (const mtagList) name tags,
+ tagPackages = packages'
+ }
setTags :: PackageName -> Set Tag -> PackageTags -> PackageTags
setTags pkgname tagList = alterTags pkgname (keepSet tagList)
+setAliases :: Tag -> Set Tag -> TagAlias -> TagAlias
+setAliases tag aliases (TagAlias ta) = TagAlias (Map.insertWith (Set.union) tag aliases ta)
+
deletePackageTags :: PackageName -> PackageTags -> PackageTags
deletePackageTags name = alterTags name Nothing
addTag :: PackageName -> Tag -> PackageTags -> Maybe PackageTags
-addTag name tag (PackageTags tags packages) =
+addTag name tag (PackageTags tags packages review) =
let existing = Map.findWithDefault Set.empty name tags
in case tag `Set.member` existing of
True -> Nothing
False -> Just $ PackageTags (addSetMap name tag tags)
(addSetMap tag name packages)
+ review
removeTag :: PackageName -> Tag -> PackageTags -> Maybe PackageTags
-removeTag name tag (PackageTags tags packages) =
+removeTag name tag (PackageTags tags packages review) =
let existing = Map.findWithDefault Set.empty name tags
in case tag `Set.member` existing of
True -> Just $ PackageTags (removeSetMap name tag tags)
(removeSetMap tag name packages)
+ review
False -> Nothing
addSetMap :: (Ord k, Ord a) => k -> a -> Map k (Set a) -> Map k (Set a)
@@ -148,14 +149,14 @@ removeSetMap :: (Ord k, Ord a) => k -> a -> Map k (Set a) -> Map k (Set a)
removeSetMap key val = Map.update (keepSet . Set.delete val) key
alterTag :: Tag -> Maybe (Set PackageName) -> PackageTags -> PackageTags
-alterTag tag mpkgList (PackageTags tags packages) =
+alterTag tag mpkgList (PackageTags tags packages review) =
let pkgList = fromMaybe Set.empty mpkgList
oldPkgs = Map.findWithDefault Set.empty tag packages
adjustPlusPkgs tagMap name' = addSetMap name' tag tagMap
adjustMinusPkgs tagMap name' = removeSetMap name' tag tagMap
tags' = flip (foldl' adjustPlusPkgs) (Set.toList $ Set.difference pkgList oldPkgs)
$ foldl' adjustMinusPkgs tags (Set.toList $ Set.difference oldPkgs pkgList)
- in PackageTags tags' (Map.alter (const mpkgList) tag packages)
+ in PackageTags tags' (Map.alter (const mpkgList) tag packages) review
keepSet :: Ord a => Set a -> Maybe (Set a)
keepSet s = if Set.null s then Nothing else Just s
@@ -168,21 +169,20 @@ deleteTag :: Tag -> PackageTags -> PackageTags
deleteTag tag = alterTag tag Nothing
renameTag :: Tag -> Tag -> PackageTags -> PackageTags
-renameTag tag tag' pkgTags@(PackageTags _ packages) =
+renameTag tag tag' pkgTags@(PackageTags _ packages _) =
let oldPkgs = Map.findWithDefault Set.empty tag packages
in setTag tag' oldPkgs . deleteTag tag $ pkgTags
-------------------------------------------------------------------------------
$(deriveSafeCopy 0 'base ''Tag)
$(deriveSafeCopy 0 'base ''PackageTags)
-$(deriveSafeCopy 0 'base ''ReviewTags)
$(deriveSafeCopy 0 'base ''TagAlias)
instance NFData PackageTags where
- rnf (PackageTags a b) = rnf a `seq` rnf b
+ rnf (PackageTags a b c) = rnf a `seq` rnf b `seq` rnf c
instance MemSize PackageTags where
- memSize (PackageTags a b) = memSize2 a b
+ memSize (PackageTags a b c) = memSize3 a b c
initialPackageTags :: PackageTags
initialPackageTags = emptyPackageTags
@@ -202,12 +202,6 @@ getPackageTags = ask
replacePackageTags :: PackageTags -> Update PackageTags ()
replacePackageTags = put
-getReviewTags :: Query ReviewTags ReviewTags
-getReviewTags = ask
-
-replaceReviewTags :: ReviewTags -> Update ReviewTags ()
-replaceReviewTags = put
-
getTagAliasesState :: Query TagAlias TagAlias
getTagAliasesState = ask
@@ -221,9 +215,6 @@ setPackageTags name tagList = modify $ setTags name tagList
setTagPackages :: Tag -> Set PackageName -> Update PackageTags ()
setTagPackages tag pkgList = modify $ setTag tag pkgList
--- setReviewPackageTags :: PackageName -> (Set Tag, Set Tag) -> Update PackageTags ()
--- setReviewPackageTags name (tagList1, taglist2) = modify $ setTags name reviewTags
-
-- | Tag a package. Returns True if the element was inserted, and False if
-- the tag as already present (same result though)
@@ -243,42 +234,28 @@ removePackageTag name tag = do
Nothing -> return False
Just pkgTags' -> put pkgTags' >> return True
-clearReviewTags :: PackageName -> Update ReviewTags ()
-clearReviewTags pkgname
- = do
- ReviewTags m <- get
- put (ReviewTags (Map.insert pkgname (Set.empty,Set.empty) m))
+clearReviewTags :: PackageName -> Update PackageTags ()
+clearReviewTags pkgname = do
+ PackageTags p t r <- get
+ put (PackageTags p t (Map.insert pkgname (Set.empty,Set.empty) r))
+insertReviewTags :: PackageName -> Set Tag -> Set Tag -> Update PackageTags ()
+insertReviewTags pkgname add del = do
+ PackageTags p t r <- get
+ put (PackageTags p t (Map.insertWith insertReviewHelper pkgname (add,del) r))
-insertReviewTags :: PackageName -> Set Tag -> Set Tag -> Update ReviewTags ()
-insertReviewTags pkgname add del
- = do
- ReviewTags m <- get
- put (ReviewTags (Map.insertWith (insertReviewHelper) pkgname (add,del) m))
+insertReviewTags' :: PackageName -> Set Tag -> Set Tag -> Update PackageTags ()
+insertReviewTags' pkgname add del = do
+ PackageTags p t r <- get
+ put (PackageTags p t (Map.insert pkgname (add,del) r))
-insertReviewTags_ :: PackageName -> Set Tag -> Set Tag -> Update ReviewTags ()
-insertReviewTags_ pkgname add del
- = do
- ReviewTags m <- get
- put (ReviewTags (Map.insert pkgname (add,del) m))
insertReviewHelper :: (Set Tag, Set Tag) -> (Set Tag, Set Tag) -> (Set Tag, Set Tag)
insertReviewHelper (a,b) (c,d) = (Set.union a c, Set.union b d)
-lookupReviewTags :: PackageName -> Query ReviewTags (Maybe (Set Tag, Set Tag))
-lookupReviewTags pkgname
- = do ReviewTags m <- ask
- return (Map.lookup pkgname m)
-
-
+lookupReviewTags :: PackageName -> Query PackageTags (Set Tag, Set Tag)
+lookupReviewTags pkgname = asks $ Map.findWithDefault (Set.empty, Set.empty) pkgname . reviewTags
-$(makeAcidic ''ReviewTags ['insertReviewTags
- ,'insertReviewTags_
- ,'lookupReviewTags
- ,'getReviewTags
- ,'clearReviewTags
- ,'replaceReviewTags
- ])
$(makeAcidic ''TagAlias ['addTagAlias
,'getTagAlias
@@ -296,6 +273,10 @@ $(makeAcidic ''PackageTags ['tagsForPackage
,'setTagPackages
,'addPackageTag
,'removePackageTag
+ ,'insertReviewTags
+ ,'insertReviewTags'
+ ,'lookupReviewTags
+ ,'clearReviewTags
])
diff --git a/Distribution/Server/Features/Upload.hs b/Distribution/Server/Features/Upload.hs
index ab9e38ec4..7b47f5f34 100644
--- a/Distribution/Server/Features/Upload.hs
+++ b/Distribution/Server/Features/Upload.hs
@@ -59,7 +59,8 @@ data UploadFeature = UploadFeature {
maintainersGroup :: PackageName -> UserGroup,
-- | Requiring being logged in as the maintainer of a package.
- guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String,
+ authorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE Bool,
+ authorisedAsAnyUser :: ServerPartE Bool,
guardAuthorisedAsMaintainer :: PackageName -> ServerPartE (),
guardAuthorisedAsTrustee :: ServerPartE (),
-- | Requiring being logged in as the maintainer of a package or a trustee.
@@ -302,18 +303,15 @@ uploadFeature ServerEnv{serverBlobStore = store}
guardAuthorisedAsTrustee :: ServerPartE ()
guardAuthorisedAsTrustee =
- guardAuthorised_ [InGroup trusteesGroup]
-
- guardAuthorisedAsUploaderOrMaintainerOrTrustee :: PackageName -> ServerPartE String
- guardAuthorisedAsUploaderOrMaintainerOrTrustee pkgname= do
- mt <- guardAuthorised' [InGroup (maintainersGroup pkgname), InGroup trusteesGroup]
- upl <- guardAuthorised' [AnyKnownUser]
- if mt
- then return "MaintainersOrTrustees"
- else
- if upl
- then return "Uploaders"
- else return ""
+ guardAuthorised_ [InGroup trusteesGroup]
+
+ authorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE Bool
+ authorisedAsMaintainerOrTrustee pkgname=
+ guardAuthorised' [InGroup (maintainersGroup pkgname), InGroup trusteesGroup]
+
+ authorisedAsAnyUser :: ServerPartE Bool
+ authorisedAsAnyUser =
+ guardAuthorised' [AnyKnownUser]
guardAuthorisedAsMaintainerOrTrustee :: PackageName -> ServerPartE ()
guardAuthorisedAsMaintainerOrTrustee pkgname =
diff --git a/Distribution/Server/Features/Users.hs b/Distribution/Server/Features/Users.hs
index 90205a6da..badbb6d93 100644
--- a/Distribution/Server/Features/Users.hs
+++ b/Distribution/Server/Features/Users.hs
@@ -5,7 +5,6 @@ module Distribution.Server.Features.Users (
initUserFeature,
UserFeature(..),
UserResource(..),
- -- guardAuthorised',
GroupResource(..),
) where
@@ -394,9 +393,8 @@ userFeature templates usersState adminsState
guardAuthorised' privconds = do
users <- queryGetUserDb
uid <- guardAuthenticatedWithErrHook users
- a <- Auth.checkPriviledged users uid privconds
- return a
-
+ valid <- Auth.checkPriviledged users uid privconds
+ return valid
-- Simply check if the user is authenticated as some user, without any
-- check that they have any particular priveledges. Only useful as a
diff --git a/Distribution/Server/Features/Votes.hs b/Distribution/Server/Features/Votes.hs
index 515ab4440..dcb2badd3 100644
--- a/Distribution/Server/Features/Votes.hs
+++ b/Distribution/Server/Features/Votes.hs
@@ -35,7 +35,7 @@ data VotesFeature = VotesFeature {
votesFeatureInterface :: HackageFeature
, didUserVote :: forall m. MonadIO m => PackageName -> UserId -> m Bool
, pkgNumVotes :: forall m. MonadIO m => PackageName -> m Int
- , votesUpdated :: Hook (PackageName, Int) ()
+ , votesUpdated :: Hook (PackageName, Int) ()
, renderVotesHtml :: PackageName -> ServerPartE X.Html
}
diff --git a/datafiles/static/hackage.css b/datafiles/static/hackage.css
index f0ae92792..8fff2d126 100644
--- a/datafiles/static/hackage.css
+++ b/datafiles/static/hackage.css
@@ -64,6 +64,8 @@ h5 { font-size: 100%; /* 13pt */ }
select, input, button, textarea {
font:99% sans-serif;
+ margin: 0.5em;
+ padding: 0.1em;
}
table {
diff --git a/datafiles/templates/Html/tag-edit.html.st b/datafiles/templates/Html/tag-edit.html.st
index 8367fc9d9..c7b9bc3b7 100644
--- a/datafiles/templates/Html/tag-edit.html.st
+++ b/datafiles/templates/Html/tag-edit.html.st
@@ -11,6 +11,9 @@
.reject{
color:red;
}
+ .eadd, .edel{
+ clear:both;
+ }
#additions, #deletions{
list-style: none;
margin: 0px;
@@ -22,7 +25,7 @@
display: inline-block;
border: 1px solid black;
}
- #additions li :hover, #deletions li :hover {
+ #additions :hover, #deletions :hover {
background: white;
}
@@ -58,19 +61,22 @@
$endif$
$if(istrustee)$
-
-
Proposals
-
additions:
-
+
+
Proposals
+
additions:
+
+
+
deletions:
-
-
- $endif$
-
+
+
-
- $footer()$
-
@@ -141,10 +165,19 @@
$endif$
+ $if(hasrdeps)$
+
+ ReverseDependencies |
+ $rdeps$ |
+
+ $endif$
+
+ $if(hasexecs)$
Executables |
$executables$ |
+ $endif$
Downloads |
@@ -153,7 +186,16 @@
Votes |
- $votesSection$ |
+ $score$ ($votes$ Votes)
+
+ Clear Rating
+ |
Status |
@@ -201,4 +243,47 @@
$footer()$