packages feed

gerrit-0.1.6.1: src/Gerrit/Data/Change.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}

module Gerrit.Data.Change (
    GerritQuery (..),
    GerritChangeStatus (..),
    GerritRevisionKind (..),
    GerritFile (..),
    GerritCommit (..),
    GerritRevision (..),
    GerritDetailedLabelVote (..),
    GerritDetailedLabel (..),
    GerritAuthor (..),
    GerritCommitAuthor (..),
    GerritChangeMessage (..),
    GerritChange (..),
    GerritTime (..),
    changeQS,
    queryText,
    defaultQueryChangeOptions,
    hasLabel,
)
where

import Data.Aeson
import Data.Aeson.Casing (aesonPrefix, snakeCase)
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock
import Data.Time.Format (defaultTimeLocale, formatTime, parseTimeM)
import GHC.Generics (Generic)

aesonOptions :: Options
aesonOptions = defaultOptions{fieldLabelModifier = recordToJson}
  where
    recordToJson "number" = "_number"
    recordToJson "account_id" = "_account_id"
    recordToJson "aAccountId" = "_account_id"
    recordToJson "aName" = "name"
    recordToJson "aEmail" = "email"
    recordToJson "aUsername" = "username"
    recordToJson "more_changes" = "_more_changes"
    recordToJson n = n

-- https://gerrit-review.googlesource.com/Documentation/user-search.html
data GerritQuery
    = Status GerritChangeStatus
    | Owner Text
    | CommitMessage Text
    | Project Text
    | ChangeId Text
    | After UTCTime
    deriving (Eq, Show)

-- | Convert a GerritQuery object to the search terms
queryText :: GerritQuery -> Text
queryText (Status stat) = "status:" <> T.toLower (T.pack $ show stat)
queryText (Owner owner') = "owner:" <> owner'
queryText (CommitMessage message) = "message:" <> message
queryText (Project project') = "project:" <> project'
queryText (ChangeId changeId) = "change:" <> changeId
queryText (After date') = "after:" <> T.pack formatedDate
  where
    formatedDate = formatTime defaultTimeLocale "%F" date'

defaultQueryChangeOptions :: Text
defaultQueryChangeOptions =
    "o="
        <> T.intercalate
            "&o="
            [ "MESSAGES"
            , "DETAILED_ACCOUNTS"
            , "DETAILED_LABELS"
            , "CURRENT_REVISION"
            , "CURRENT_FILES"
            , "CURRENT_COMMIT"
            ]

{- | Build the Query String for the changes endpoint
>>> changeQS 10 [Project "software-factory/gerrit-haskell"] Nothing
"q=project:software-factory/gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT"
>>> changeQS 10 [Project "software-factory/gerrit-haskell"] $ Just 100
"q=project:software-factory/gerrit-haskell&n=10&o=MESSAGES&o=DETAILED_ACCOUNTS&o=DETAILED_LABELS&o=CURRENT_REVISION&o=CURRENT_FILES&o=CURRENT_COMMIT&start=100"
-}
changeQS :: Int -> [GerritQuery] -> Maybe Int -> Text
changeQS count queries startM =
    let base =
            T.intercalate
                "&"
                [ changeString
                , countString
                , defaultQueryChangeOptions
                ]
     in base <> startString
  where
    changeString = "q=" <> T.intercalate "+" (map queryText queries)
    countString = "n=" <> T.pack (show count)
    startString = maybe mempty (\s -> "&start=" <> T.pack (show s)) startM

-- | Check if a gerrit change as a label
hasLabel :: T.Text -> Int -> GerritChange -> Bool
hasLabel label labelValue change = case M.lookup label (labels change) of
    Just gerritLabel ->
        (> 0) $
            length $
                filter
                    (\vote -> fromMaybe 0 (value vote) == labelValue)
                    (fromMaybe [] (Gerrit.Data.Change.all gerritLabel))
    _ -> False

data GerritChangeStatus = NEW | MERGED | ABANDONED
    deriving (Eq, Show, Generic, FromJSON)

-- https://gerrit-review.googlesource.com/Documentation/json.html
data GerritRevisionKind = REWORK | TRIVIAL_REBASE | MERGE_FIRST_PARENT_UPDATE | NO_CODE_CHANGE | NO_CHANGE
    deriving (Eq, Show, Generic, FromJSON)

data GerritFile = GerritFile
    { gfStatus :: Maybe Text
    , gfLinesInserted :: Maybe Int
    , gfLinesDeleted :: Maybe Int
    , gfSizeDelta :: Maybe Int
    , gfSize :: Maybe Int
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritFile where
    parseJSON = genericParseJSON $ aesonPrefix snakeCase

data GerritCommit = GerritCommit
    { cAuthor :: GerritCommitAuthor
    , cCommitter :: GerritCommitAuthor
    , cSubject :: Text
    , cMessage :: Text
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritCommit where
    parseJSON = genericParseJSON $ aesonPrefix snakeCase

data GerritRevision = GerritRevision
    { grRef :: Text
    , grKind :: GerritRevisionKind
    , grFiles :: M.Map Text GerritFile
    , grCommit :: GerritCommit
    , grUploader :: GerritAuthor
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritRevision where
    parseJSON = genericParseJSON $ aesonPrefix snakeCase

data GerritDetailedLabelVote = GerritDetailedLabelVote
    { value :: Maybe Int
    , account_id :: Int
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritDetailedLabelVote where
    parseJSON = genericParseJSON aesonOptions

data GerritDetailedLabel = GerritDetailedLabel
    { all :: Maybe [GerritDetailedLabelVote]
    , default_value :: Int
    }
    deriving (Eq, Show, Generic, FromJSON)

data GerritAuthor = GerritAuthor
    { aAccountId :: Int
    , aName :: Maybe Text
    , aEmail :: Maybe Text
    , aUsername :: Maybe Text
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritAuthor where
    parseJSON = genericParseJSON aesonOptions

data GerritCommitAuthor = GerritCommitAuthor
    { caName :: Text
    , caEmail :: Maybe Text
    , caDate :: GerritTime
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritCommitAuthor where
    parseJSON = genericParseJSON $ aesonPrefix snakeCase

newtype GerritTime = GerritTime {unGerritTime :: UTCTime}
    deriving (Eq, Show)

instance FromJSON GerritTime where
    parseJSON = withText "UTCTimePlus" (parse . T.unpack)
      where
        format = "%F %T.000000000"
        tryParse = parseTimeM False defaultTimeLocale
        parse s = GerritTime <$> tryParse format s

data GerritChangeMessage = GerritChangeMessage
    { mId :: Text
    , mAuthor :: Maybe GerritAuthor
    , mDate :: GerritTime
    , mMessage :: Text
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritChangeMessage where
    parseJSON = genericParseJSON $ aesonPrefix snakeCase

data GerritChange = GerritChange
    { id :: Text
    , project :: Text
    , branch :: Text
    , hashtags :: [Text]
    , subject :: Text
    , status :: GerritChangeStatus
    , mergeable :: Maybe Bool
    , revisions :: M.Map Text (Maybe GerritRevision)
    , current_revision :: Maybe Text
    , number :: Int
    , labels :: M.Map Text GerritDetailedLabel
    , messages :: [GerritChangeMessage]
    , owner :: GerritAuthor
    , created :: GerritTime
    , updated :: GerritTime
    , submitted :: Maybe GerritTime
    , submitter :: Maybe GerritAuthor
    , topic :: Maybe Text
    , insertions :: Int
    , deletions :: Int
    , more_changes :: Maybe Bool
    , work_in_progress :: Maybe Bool
    }
    deriving (Eq, Show, Generic)

instance FromJSON GerritChange where
    parseJSON = genericParseJSON aesonOptions