-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ff63963
commit 336b917
Showing
7 changed files
with
177 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module OctGraph.Cache where | ||
|
||
import RIO | ||
import RIO.Directory (createDirectoryIfMissing, doesFileExist) | ||
import RIO.FilePath (dropFileName) | ||
|
||
import qualified Data.Aeson as J | ||
|
||
writeCache :: (MonadIO m, J.ToJSON a) => FilePath -> a -> m () | ||
writeCache path target = do | ||
createDirectoryIfMissing True (dropFileName path) | ||
liftIO $ J.encodeFile path target | ||
|
||
readCache :: (MonadIO m, J.FromJSON a, Monoid a) => FilePath -> m a | ||
readCache path = do | ||
isExist <- doesFileExist path | ||
if isExist then | ||
fromMaybe mempty <$> liftIO (J.decodeFileStrict path) | ||
else | ||
pure mempty |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,93 @@ | ||
module OctGraph.Cmd where | ||
|
||
import RIO | ||
import qualified RIO.List as L | ||
import qualified RIO.Map as Map | ||
|
||
import Data.Fallible | ||
import qualified Mix.Plugin.Logger as MixLogger | ||
import qualified Mix.Plugin.Logger as MixLogger | ||
import OctGraph.Cache (readCache, writeCache) | ||
import OctGraph.Cmd.Chart | ||
import OctGraph.Config | ||
import OctGraph.Env | ||
import OctGraph.Pulls as Pulls | ||
import OctGraph.Pulls as Pulls | ||
import OctGraph.Pulls.Review as Review | ||
|
||
cmd :: RIO Env () | ||
cmd = do | ||
repos <- asks (view #repositories . view #config) | ||
ps <- forM repos $ \repo -> do | ||
data Cmd | ||
= PullRequestFreqency | ||
| ReviewFrequency | ||
deriving (Show, Eq) | ||
|
||
run :: Cmd -> RIO Env () | ||
run cmd = do | ||
ps <- fetchPulls =<< asks (view #repositories . view #config) | ||
case cmd of | ||
PullRequestFreqency -> | ||
createChartFile (concat $ Map.elems ps) | ||
ReviewFrequency -> do | ||
_ <- fetchReviews ps | ||
pure () | ||
|
||
showNotImpl :: MonadIO m => m () | ||
showNotImpl = hPutBuilder stdout "not yet implement command.\n" | ||
|
||
fetchPulls :: [RepositoryPath] -> RIO Env PullRequests | ||
fetchPulls repos = fmap Map.fromList . forM repos $ \repo -> do | ||
pulls <- fetchPullsWithCache repo | ||
MixLogger.logInfo (display repo) | ||
MixLogger.logInfo (display $ " all pulls: " <> tshow (length pulls)) | ||
MixLogger.logInfo (display $ " closed pulls: " <> tshow (length $ filter isClosed pulls)) | ||
pure pulls | ||
createChartFile (concat ps) | ||
|
||
showNotImpl :: MonadIO m => m () | ||
showNotImpl = hPutBuilder stdout "not yet implement command.\n" | ||
pure (repo, pulls) | ||
|
||
fetchPullsWithCache :: RepositoryPath -> RIO Env [PullRequest] | ||
fetchPullsWithCache repo = evalContT $ do | ||
cachedPulls <- lift $ Pulls.readCache repo | ||
path <- lift $ Pulls.cachePath repo | ||
MixLogger.logDebug (fromString $ "read cache: " <> path) | ||
cachedPulls <- lift $ readCache path | ||
pulls <- lift (fetchPullsWith cachedPulls) !?= err | ||
when (length pulls /= length cachedPulls) $ | ||
lift (Pulls.writeCache repo pulls) | ||
when (length pulls /= length cachedPulls) $ do | ||
MixLogger.logDebug (fromString $ "write cache: " <> path) | ||
lift (writeCache path pulls) | ||
pure pulls | ||
where | ||
err txt = exit $ MixLogger.logError (display $ repo <> txt) >> pure [] | ||
|
||
fetchPullsWith [] = fetchAllPulls repo | ||
fetchPullsWith cached = fmap (`mergePulls` cached) <$> fetchLatestPulls repo | ||
|
||
fetchReviews :: PullRequests -> RIO Env (Map RepositoryPath Reviews) | ||
fetchReviews ps = fmap Map.fromList . forM (Map.toList ps) $ \(repo, pulls) ->do | ||
reviews <- fetchReviewsWithCache repo pulls | ||
MixLogger.logInfo (display repo) | ||
MixLogger.logInfo (display $ " all reviews: " <> tshow (sum $ fmap length reviews)) | ||
pure (repo, reviews) | ||
|
||
fetchReviewsWithCache :: RepositoryPath -> [PullRequest] -> RIO Env Reviews | ||
fetchReviewsWithCache repo pulls = evalContT $ do | ||
path <- lift $ Review.cachePath repo | ||
MixLogger.logDebug (fromString $ "read cache: " <> path) | ||
cachedReviews <- lift $ readCache path | ||
rs <- fmap Map.fromList $ forM (take 100 $ reverse pulls) $ \pull -> do | ||
threadDelay 1_000_000 | ||
lift (fetchReviewsWith cachedReviews pull) !?= err | ||
MixLogger.logDebug (fromString $ "write cache: " <> path) | ||
lift (writeCache path rs) | ||
pure rs | ||
where | ||
err txt = exit $ MixLogger.logError (display $ repo <> txt) >> pure mempty | ||
|
||
fetchReviewsWith :: Map Int [Review] -> PullRequest -> RIO Env (Either Text (Int, [Review])) | ||
fetchReviewsWith cache pull = fmap (pull ^. #id,) <$> | ||
case Map.lookup (pull ^. #id) cache of | ||
Nothing -> Review.fetchAllReviews repo pull | ||
Just cached -> do | ||
if Pulls.isClosed pull then | ||
pure $ Right cached | ||
else | ||
case L.maximumByMaybe (compare `on` view #created_at) cached of | ||
Nothing -> Review.fetchAllReviews repo pull | ||
Just latest -> | ||
if pull ^. #updated_at > latest ^. #created_at then | ||
fmap (`mergeReviews` cached) <$> Review.fetchLatestReviews repo pull | ||
else | ||
pure $ Right cached |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
module OctGraph.Pulls.Review where | ||
|
||
import RIO | ||
import RIO.FilePath ((<.>), (</>)) | ||
import qualified RIO.List as L | ||
import qualified RIO.Text as T | ||
import RIO.Time | ||
import qualified RIO.Vector as V | ||
|
||
import Data.Extensible | ||
import qualified GitHub.Data.Definitions as GitHub | ||
import qualified GitHub.Data.Name as GitHub | ||
import qualified GitHub.Data.Reviews as GitHub | ||
import qualified GitHub.Endpoints.PullRequests.Reviews as GitHub | ||
import qualified Mix.Plugin.GitHub as MixGitHub | ||
import qualified Mix.Plugin.Logger as MixLogger | ||
import OctGraph.Config (RepositoryPath, | ||
splitRepoName) | ||
import OctGraph.Env | ||
import OctGraph.Pulls (PullRequest) | ||
|
||
type Review = Record | ||
'[ "id" >: Int | ||
, "pull" >: Int | ||
, "user" >: Text | ||
, "created_at" >: UTCTime | ||
] | ||
|
||
type Reviews = Map Int [Review] | ||
|
||
fetchLatestReviews, fetchAllReviews :: RepositoryPath -> PullRequest -> RIO Env (Either Text [Review]) | ||
fetchLatestReviews = fetchReviews' (GitHub.FetchAtLeast 100) | ||
fetchAllReviews = fetchReviews' GitHub.FetchAll | ||
|
||
fetchReviews' :: GitHub.FetchCount -> RepositoryPath -> PullRequest -> RIO Env (Either Text [Review]) | ||
fetchReviews' count repo pr = do | ||
MixLogger.logDebug (display $ "fetch reviews: " <> repo <> "#" <> tshow (pr ^. #id) <> " (" <> tshow count <> ")") | ||
resp <- MixGitHub.fetch $ GitHub.pullRequestReviewsR | ||
(GitHub.mkName Proxy org) | ||
(GitHub.mkName Proxy name) | ||
(GitHub.IssueNumber $ pr ^. #id) | ||
count | ||
pure $ case resp of | ||
Left _ -> Left "cannot fetch reviews" | ||
Right reviews -> Right (toReview pr <$> V.toList reviews) | ||
where | ||
(org, name) = splitRepoName repo | ||
|
||
toReview :: PullRequest -> GitHub.Review -> Review | ||
toReview pr review | ||
= #id @= GitHub.untagId (GitHub.reviewId review) | ||
<: #pull @= (pr ^. #id) | ||
<: #user @= GitHub.untagName (GitHub.simpleUserLogin $ GitHub.reviewUser review) | ||
<: #created_at @= GitHub.reviewSubmittedAt review | ||
<: nil | ||
|
||
mergeReviews :: [Review] -> [Review] -> [Review] | ||
mergeReviews reviews = | ||
L.sortBy (\x y -> (x ^. #id) `compare` (y ^. #id)) | ||
. L.nubBy (\x y -> x ^. #id == y ^. #id) | ||
. (reviews ++) | ||
|
||
cachePath :: RepositoryPath -> RIO Env FilePath | ||
cachePath repo = (</> "reviews" </> T.unpack repo <.> "json") <$> asks (view #cache) |