diff --git a/app/cli/DesignSystem.hs b/app/cli/DesignSystem.hs index c1c80f39..c31459a5 100644 --- a/app/cli/DesignSystem.hs +++ b/app/cli/DesignSystem.hs @@ -9,6 +9,8 @@ import Data.Foldable (forM_) import Data.Functor.Identity (runIdentity) import Data.Text (Text) import Data.Text.Lazy qualified as TL +import Data.Time.Calendar.OrdinalDate as Time +import Data.Time.Clock (UTCTime (..)) import Data.UUID qualified as UUID import Data.Vector (Vector) import Data.Vector qualified as Vector @@ -100,6 +102,8 @@ packageListItemExample = , "Basic libraries" , mkVersion [4, 16, 0, 0] , License (simpleLicenseExpression BSD_3_Clause) + , Just $ UTCTime (Time.fromMondayStartWeek 2024 32 1) 0 + , Just $ UTCTime (Time.fromMondayStartWeek 2024 60 15) 0 ) categoryCardExample :: FloraHTML diff --git a/assets/css/3-screens/1-package/1-package.css b/assets/css/3-screens/1-package/1-package.css index b190da14..3851c4da 100644 --- a/assets/css/3-screens/1-package/1-package.css +++ b/assets/css/3-screens/1-package/1-package.css @@ -174,12 +174,12 @@ span.revised-date::before { color: #fff; padding: 5px; text-align: center; - width: 200px; display: none; /* hide by default */ } span.revised-date:hover::before { display: block; + width: 200px; } .revised-date { diff --git a/cabal.project b/cabal.project index 8ca9908d..51f921c0 100644 --- a/cabal.project +++ b/cabal.project @@ -21,7 +21,7 @@ allow-older: pg-entity:time test-show-details: direct package * - ghc-options: "-L /usr/pgsql-14/lib" +RTS -A32m -RTS -j -haddock + ghc-options: "-L /usr/pgsql-14/lib" +RTS -A32m -RTS -j package warp flags: -x509 @@ -32,7 +32,7 @@ package zlib source-repository-package type: git location: https://github.com/scrive/tracing - tag: e49720d + tag: 9c2baa1 subdir: . ./tracing-effectful diff --git a/flora.cabal b/flora.cabal index f7022ef5..a1d75fc2 100644 --- a/flora.cabal +++ b/flora.cabal @@ -448,6 +448,7 @@ executable flora-cli , sel , text , text-display + , time , transformers , uuid , vector diff --git a/migrations/20240906101200_add_upload_and_revision_times_to_latest_versions.sql b/migrations/20240906101200_add_upload_and_revision_times_to_latest_versions.sql new file mode 100644 index 00000000..a2fd2731 --- /dev/null +++ b/migrations/20240906101200_add_upload_and_revision_times_to_latest_versions.sql @@ -0,0 +1,30 @@ +DROP MATERIALIZED VIEW latest_versions; + +CREATE MATERIALIZED VIEW latest_versions (namespace, name, synopsis, package_id, version, license, uploaded_at, revised_at) + AS SELECT DISTINCT ON (p.package_id) p.namespace + , p.name + , r.synopsis + , p.package_id + , r.version + , r.license + , r.uploaded_at + , r.revised_at + FROM packages AS p + INNER JOIN releases AS r ON p.package_id = r.package_id + WHERE p.status = 'fully-imported' + AND r.deprecated IS DISTINCT FROM TRUE + GROUP BY p.namespace, p.name, synopsis, p.package_id, r.version, license, r.uploaded_at, r.revised_at + ORDER BY p.package_id + , version DESC; + +CREATE INDEX + ON latest_versions (name); + +CREATE INDEX + ON latest_versions (namespace + , name); + +CREATE UNIQUE INDEX + ON latest_versions (name + , namespace + , version) diff --git a/src/core/Flora/Model/Package/Query.hs b/src/core/Flora/Model/Package/Query.hs index a8f4b7a2..ac77335f 100644 --- a/src/core/Flora/Model/Package/Query.hs +++ b/src/core/Flora/Model/Package/Query.hs @@ -238,6 +238,8 @@ WITH dependents AS ( , r.version , r.synopsis , r.license + , r.uploaded_at + , r.revised_at FROM packages AS p INNER JOIN dependents AS dep ON p.package_id = dep.dependent_id INNER JOIN releases AS r ON r.package_id = p.package_id @@ -252,6 +254,8 @@ SELECT d.namespace , d.version , d.synopsis , d.license + , d.uploaded_at + , d.revised_at FROM dependents AS d WHERE rank = 1 |] @@ -268,6 +272,8 @@ WITH dependents AS ( , r.version , r.synopsis , r.license + , r.uploaded_at + , r.revised_at FROM packages AS p INNER JOIN dependents AS dep ON p.package_id = dep.dependent_id INNER JOIN releases AS r ON r.package_id = p.package_id @@ -281,6 +287,8 @@ SELECT d.namespace , d.version , d.synopsis , d.license + , d.uploaded_at + , d.revised_at FROM dependents AS d WHERE rank = 1 |] @@ -367,6 +375,8 @@ WITH requirements AS (SELECT DISTINCT p1.component_type , r3.version AS dependency_latest_version , r3.synopsis AS dependency_latest_synopsis , r3.license AS dependency_latest_license + , r3.uploaded_at + , r3.revised_at FROM requirements AS req INNER JOIN packages AS p2 ON p2.namespace = req.namespace AND p2.name = req.name @@ -374,7 +384,7 @@ WITH requirements AS (SELECT DISTINCT p1.component_type WHERE r3.version = (SELECT max(version) FROM releases WHERE package_id = p2.package_id) - GROUP BY req.component_type, req.component_name, req.namespace, req.name, req.requirement, req.components, r3.version, r3.synopsis, r3.license + GROUP BY req.component_type, req.component_name, req.namespace, req.name, req.requirement, req.components, r3.version, r3.synopsis, r3.license, r3.uploaded_at, r3.revised_at ORDER BY req.component_type , req.component_name DESC |] @@ -471,6 +481,8 @@ getPackagesFromCategoryWithLatestVersion categoryId = dbtToEff $ query Select q , lv.version , lv.license , 1 + , lv.uploaded_at + , lv.revised_at from latest_versions as lv inner join package_categories as p1 on p1.package_id = lv.package_id inner join categories as c2 on c2.category_id = p1.category_id @@ -493,6 +505,8 @@ searchPackage (offset, limit) searchString = , lv."version" , lv."license" , word_similarity(lv.name, ?) as rating + , lv."uploaded_at" + , lv."revised_at" FROM latest_versions as lv WHERE ? <% lv.name GROUP BY @@ -501,6 +515,8 @@ searchPackage (offset, limit) searchString = , lv."synopsis" , lv."version" , lv."license" + , lv."uploaded_at" + , lv."revised_at" ORDER BY rating desc, count(lv."namespace") desc, lv.name asc OFFSET ? LIMIT ? @@ -525,6 +541,8 @@ searchPackageByNamespace (offset, limit) namespace searchString = , lv."version" , lv."license" , word_similarity(lv.name, ?) as rating + , lv."uploaded_at" + , lv."revised_at" FROM latest_versions as lv WHERE ? <% lv."name" @@ -535,6 +553,8 @@ searchPackageByNamespace (offset, limit) namespace searchString = , lv."synopsis" , lv."version" , lv."license" + , lv."uploaded_at" + , lv."revised_at" ORDER BY rating desc, count(lv."namespace") desc, lv.name asc LIMIT ? OFFSET ? @@ -629,6 +649,8 @@ listAllPackages (offset, limit) = , lv."version" , lv."license" , (1.0::real) as rating + , lv."uploaded_at" + , lv."revised_at" FROM latest_versions as lv GROUP BY lv."namespace" @@ -636,6 +658,8 @@ listAllPackages (offset, limit) = , lv."synopsis" , lv."version" , lv."license" + , lv."uploaded_at" + , lv."revised_at" ORDER BY rating DESC , COUNT(lv."namespace") DESC , lv.name ASC @@ -661,6 +685,8 @@ listAllPackagesInNamespace (offset, limit) namespace = , lv."version" , lv."license" , (1.0::real) as rating + , lv."uploaded_at" + , lv."revised_at" FROM latest_versions as lv WHERE lv."namespace" = ? GROUP BY @@ -669,6 +695,8 @@ listAllPackagesInNamespace (offset, limit) namespace = , lv."synopsis" , lv."version" , lv."license" + , lv."uploaded_at" + , lv."revised_at" ORDER BY rating desc, lv."name" asc OFFSET ? LIMIT ? diff --git a/src/core/Flora/Model/Package/Types.hs b/src/core/Flora/Model/Package/Types.hs index 647eb6e7..cf9365a4 100644 --- a/src/core/Flora/Model/Package/Types.hs +++ b/src/core/Flora/Model/Package/Types.hs @@ -246,6 +246,8 @@ data PackageInfo = PackageInfo , version :: Version , license :: SPDX.License , rating :: Maybe Double + , uploadedAt :: Maybe UTCTime + , revisedAt :: Maybe UTCTime } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (FromRow, NFData) diff --git a/src/core/Flora/Model/Requirement.hs b/src/core/Flora/Model/Requirement.hs index 8eea8ddd..eab03b1d 100644 --- a/src/core/Flora/Model/Requirement.hs +++ b/src/core/Flora/Model/Requirement.hs @@ -20,6 +20,7 @@ import Control.DeepSeq import Data.ByteString.Lazy (fromStrict) import Data.Maybe (fromJust) import Data.Text.Encoding (encodeUtf8) +import Data.Time (UTCTime) import Deriving.Aeson import Distribution.SPDX.License qualified as SPDX import Distribution.Types.Version (Version) @@ -68,6 +69,8 @@ data DependencyInfo = DependencyInfo , latestVersion :: Version , latestSynopsis :: Text , latestLicense :: SPDX.License + , uploadedAt :: Maybe UTCTime + , revisedAt :: Maybe UTCTime } deriving stock (Eq, Show, Generic) deriving anyclass (FromRow, NFData) @@ -83,6 +86,8 @@ data ComponentDependency' = ComponentDependency' , latestVersion :: Version , latestSynopsis :: Text , latestLicense :: SPDX.License + , uploadedAt :: Maybe UTCTime + , revisedAt :: Maybe UTCTime } deriving stock (Eq, Show, Generic) deriving anyclass (FromRow, NFData) diff --git a/src/core/Flora/Search.hs b/src/core/Flora/Search.hs index 8a71253b..35412d6a 100644 --- a/src/core/Flora/Search.hs +++ b/src/core/Flora/Search.hs @@ -167,6 +167,8 @@ dependencyInfoToPackageInfo dep = dep.latestVersion dep.latestLicense Nothing + dep.uploadedAt + dep.revisedAt listAllPackagesInNamespace :: (DB :> es, Time :> es, Log :> es) diff --git a/src/web/FloraWeb/Components/PackageListItem.hs b/src/web/FloraWeb/Components/PackageListItem.hs index 7381524b..ec32cda4 100644 --- a/src/web/FloraWeb/Components/PackageListItem.hs +++ b/src/web/FloraWeb/Components/PackageListItem.hs @@ -12,12 +12,15 @@ import Data.List (intersperse, sortOn) import Data.Map qualified as Map import Data.Text (Text) import Data.Text.Display (display) +import Data.Time (UTCTime, defaultTimeLocale) +import Data.Time qualified as Time import Data.Vector qualified as Vector +import Distribution.SPDX.License qualified as SPDX +import Distribution.Types.Version (Version) import FloraWeb.Pages.Templates (FloraHTML) import Lucid +import Lucid.Orphans () -import Distribution.SPDX.License qualified as SPDX -import Distribution.Types.Version (Version) import Flora.Model.Component.Types (CanonicalComponent (..)) import Flora.Model.Package (ElemRating (..), Namespace, PackageInfoWithExecutables (..), PackageName (..)) import Flora.Model.Requirement @@ -25,10 +28,19 @@ import Flora.Model.Requirement , DependencyInfo (..) ) import FloraWeb.Components.Icons qualified as Icon -import Lucid.Orphans () +import FloraWeb.Components.Utils -packageListItem :: (Namespace, PackageName, Text, Version, SPDX.License) -> FloraHTML -packageListItem (namespace, packageName, synopsis, version, license) = do +packageListItem + :: ( Namespace + , PackageName + , Text + , Version + , SPDX.License + , Maybe UTCTime + , Maybe UTCTime + ) + -> FloraHTML +packageListItem (namespace, packageName, synopsis, version, license, mUploadedAt, mRevisedAt) = do let href = href_ ("/packages/" <> display namespace <> "/" <> display packageName) li_ [class_ "package-list-item"] $ a_ [href, class_ ""] $ do @@ -41,6 +53,20 @@ packageListItem (namespace, packageName, synopsis, version, license) = do Icon.license toHtml license span_ [class_ "package-list-item__version"] $ "v" <> toHtml version + case mUploadedAt of + Nothing -> "" + Just ts -> + span_ [] $ do + toHtml $ Time.formatTime defaultTimeLocale "%a, %_d %b %Y" ts + case mRevisedAt of + Nothing -> span_ [] "" + Just revisionDate -> + span_ + [ dataText_ + ("Revised on " <> display (Time.formatTime defaultTimeLocale "%a, %_d %b %Y, %R %EZ" revisionDate)) + , class_ "revised-date" + ] + Icon.pen packageWithExecutableListItem :: PackageInfoWithExecutables -> FloraHTML packageWithExecutableListItem PackageInfoWithExecutables{namespace, name, synopsis, version, license, executables} = do diff --git a/src/web/FloraWeb/Pages/Server/Admin.hs b/src/web/FloraWeb/Pages/Server/Admin.hs index 1a80b20e..f5baacdf 100644 --- a/src/web/FloraWeb/Pages/Server/Admin.hs +++ b/src/web/FloraWeb/Pages/Server/Admin.hs @@ -57,8 +57,7 @@ fetchMetadataHandler (Headers session _) = do forkIO $ Async.forConcurrently_ releasesWithoutReadme - ( \(releaseId, version, packagename) -> scheduleReadmeJob jobsPool releaseId packagename version - ) + (\(releaseId, version, packagename) -> scheduleReadmeJob jobsPool releaseId packagename version) releasesWithoutUploadTime <- Query.getHackagePackageReleasesWithoutUploadTimestamp liftIO $ @@ -66,8 +65,7 @@ fetchMetadataHandler (Headers session _) = do forkIO $ Async.forConcurrently_ releasesWithoutUploadTime - ( \(releaseId, version, packagename) -> scheduleUploadTimeJob jobsPool releaseId packagename version - ) + (\(releaseId, version, packagename) -> scheduleUploadTimeJob jobsPool releaseId packagename version) releasesWithoutChangelog <- Query.getHackagePackageReleasesWithoutChangelog liftIO $ @@ -75,8 +73,7 @@ fetchMetadataHandler (Headers session _) = do forkIO $ Async.forConcurrently_ releasesWithoutChangelog - ( \(releaseId, version, packagename) -> scheduleChangelogJob jobsPool releaseId packagename version - ) + (\(releaseId, version, packagename) -> scheduleChangelogJob jobsPool releaseId packagename version) features <- ask @FeatureEnv Log.logAttention "features" features @@ -97,8 +94,7 @@ fetchMetadataHandler (Headers session _) = do forkIO $ do Async.forConcurrently_ packagesWithoutDeprecationInformation - ( \a -> scheduleReleaseDeprecationListJob jobsPool a - ) + (\a -> scheduleReleaseDeprecationListJob jobsPool a) void $ scheduleRefreshLatestVersions jobsPool pure $ redirect "/admin" diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs index 8369b831..89fa5f7d 100644 --- a/src/web/FloraWeb/Pages/Templates/Packages.hs +++ b/src/web/FloraWeb/Pages/Templates/Packages.hs @@ -144,6 +144,8 @@ showDependents namespace packageName release count packagesInfo currentPage = , dep.latestSynopsis , dep.latestVersion , dep.latestLicense + , Nothing + , Nothing ) ) when (count > 30) $ @@ -163,8 +165,7 @@ listVersions namespace packageName releases = ul_ [class_ "package-list"] $ Vector.forM_ releases - ( versionListItem namespace packageName - ) + (versionListItem namespace packageName) versionListItem :: Namespace -> PackageName -> Release -> FloraHTML versionListItem namespace packageName release = do @@ -209,11 +210,10 @@ packageListing mExactMatchItems packages = whenJust mExactMatchItems $ \exactMatchItems -> forM_ exactMatchItems $ \em -> div_ [class_ "exact-match"] $ - packageListItem (em.namespace, em.name, em.synopsis, em.version, em.license) + packageListItem (em.namespace, em.name, em.synopsis, em.version, em.license, em.uploadedAt, em.revisedAt) Vector.forM_ packages - ( \PackageInfo{..} -> packageListItem (namespace, name, synopsis, version, license) - ) + (\PackageInfo{..} -> packageListItem (namespace, name, synopsis, version, license, uploadedAt, revisedAt)) packageWithExecutableListing :: Vector PackageInfoWithExecutables @@ -430,8 +430,7 @@ displayTestedWith compilersVersions' ul_ [class_ "compiler-badges"] $ Vector.forM_ compilersVersions - ( li_ [] . a_ [class_ "compiler-badge"] . toHtml @Text . display - ) + (li_ [] . a_ [class_ "compiler-badge"] . toHtml @Text . display) displayMaintainer :: Text -> FloraHTML displayMaintainer maintainerInfo = diff --git a/test/Main.hs b/test/Main.hs index b3b06624..2a6a3838 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -8,7 +8,6 @@ import Sel.Hashing.Password qualified as Sel import System.IO import Test.Tasty (defaultMain, testGroup) -import Control.Concurrent qualified as Concurrent import Flora.BlobSpec qualified as BlobSpec import Flora.CabalSpec qualified as CabalSpec import Flora.CategorySpec qualified as CategorySpec @@ -32,8 +31,8 @@ main = do fixtures <- runTestEff ( do - testMigrations cleanUp + testMigrations importCategories Update.createPackageIndex "hackage" "" "" Nothing Update.createPackageIndex "cardano" "" "" Nothing @@ -46,7 +45,6 @@ main = do ) env.pool env.dbConfig - Concurrent.threadDelay 20000 spec <- traverse (\comp -> runTestEff comp env.pool env.dbConfig) (specs fixtures) defaultMain . testGroup "Flora Tests" $ OddJobSpec.spec : spec