bugzilla-redhat 0.3.3 → 1.0.0
raw patch · 15 files changed
+1382/−1438 lines, 15 filesdep ~aesondep ~bytestringdep ~resourcetPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: aeson, bytestring, resourcet, text, time
API changes (from Hackage documentation)
- Web.Bugzilla.RedHat: AnonymousSession :: BugzillaContext -> BugzillaSession
- Web.Bugzilla.RedHat: ApikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession
- Web.Bugzilla.RedHat: Attachment :: !AttachmentId -> !BugId -> Text -> Text -> UserEmail -> Bool -> Bool -> Bool -> [Flag] -> UTCTime -> UTCTime -> Text -> !Int -> Text -> Attachment
- Web.Bugzilla.RedHat: BoolFieldChange :: Field Bool -> Modification Bool -> Change
- Web.Bugzilla.RedHat: Bug :: !BugId -> Maybe [Text] -> UserEmail -> User -> [BugId] -> [UserEmail] -> [User] -> Text -> [Text] -> UTCTime -> UserEmail -> User -> [BugId] -> Maybe BugId -> Maybe [Flag] -> [Text] -> Bool -> Bool -> Bool -> Bool -> [Text] -> UTCTime -> Text -> Text -> Text -> Text -> UserEmail -> Text -> [Text] -> Text -> Text -> Text -> Text -> Text -> [Text] -> Text -> KeyMap Text -> Maybe [ExternalBug] -> Bug
- Web.Bugzilla.RedHat: BugzillaAPIError :: Int -> String -> BugzillaException
- Web.Bugzilla.RedHat: BugzillaApikey :: Text -> BugzillaApikey
- Web.Bugzilla.RedHat: BugzillaJSONParseError :: String -> BugzillaException
- Web.Bugzilla.RedHat: BugzillaToken :: Text -> BugzillaToken
- Web.Bugzilla.RedHat: BugzillaUnexpectedValue :: String -> BugzillaException
- Web.Bugzilla.RedHat: Comment :: !CommentId -> !BugId -> Maybe AttachmentId -> !Int -> Text -> UserEmail -> UTCTime -> Bool -> Comment
- Web.Bugzilla.RedHat: ExternalBug :: Text -> Int -> Text -> Text -> Text -> Int -> ExternalType -> ExternalBug
- Web.Bugzilla.RedHat: ExternalType :: Text -> Text -> Int -> Text -> Text -> ExternalType
- Web.Bugzilla.RedHat: Flag :: !FlagId -> !FlagType -> Text -> UserEmail -> Text -> UTCTime -> UTCTime -> Maybe UserEmail -> Flag
- Web.Bugzilla.RedHat: History :: !BugId -> [HistoryEvent] -> History
- Web.Bugzilla.RedHat: HistoryEvent :: EventId -> UTCTime -> UserEmail -> [Change] -> HistoryEvent
- Web.Bugzilla.RedHat: IntFieldChange :: Field Int -> Modification Int -> Change
- Web.Bugzilla.RedHat: ListFieldChange :: Field [Text] -> Modification [Text] -> Change
- Web.Bugzilla.RedHat: LoginSession :: BugzillaContext -> BugzillaToken -> BugzillaSession
- Web.Bugzilla.RedHat: Modification :: Maybe a -> Maybe a -> Maybe AttachmentId -> Modification a
- Web.Bugzilla.RedHat: TextFieldChange :: Field Text -> Modification Text -> Change
- Web.Bugzilla.RedHat: TimeFieldChange :: Field UTCTime -> Modification UTCTime -> Change
- Web.Bugzilla.RedHat: User :: !UserId -> Maybe UserEmail -> Text -> Text -> User
- Web.Bugzilla.RedHat: [AliasField] :: Field [Text]
- Web.Bugzilla.RedHat: [AssignedToField] :: Field UserEmail
- Web.Bugzilla.RedHat: [AttachmentCreatorField] :: Field UserEmail
- Web.Bugzilla.RedHat: [AttachmentDataField] :: Field Text
- Web.Bugzilla.RedHat: [AttachmentDescriptionField] :: Field Text
- Web.Bugzilla.RedHat: [AttachmentFilenameField] :: Field Text
- Web.Bugzilla.RedHat: [AttachmentIsObsoleteField] :: Field Bool
- Web.Bugzilla.RedHat: [AttachmentIsPatchField] :: Field Bool
- Web.Bugzilla.RedHat: [AttachmentIsPrivateField] :: Field Bool
- Web.Bugzilla.RedHat: [AttachmentMimetypeField] :: Field Text
- Web.Bugzilla.RedHat: [BlocksField] :: Field Int
- Web.Bugzilla.RedHat: [BugIdField] :: Field Int
- Web.Bugzilla.RedHat: [BugURLField] :: Field Text
- Web.Bugzilla.RedHat: [CcField] :: Field UserEmail
- Web.Bugzilla.RedHat: [CcListAccessibleField] :: Field Bool
- Web.Bugzilla.RedHat: [ChangedField] :: Field UTCTime
- Web.Bugzilla.RedHat: [ClassificationField] :: Field Text
- Web.Bugzilla.RedHat: [CommentCountField] :: Field Int
- Web.Bugzilla.RedHat: [CommentField] :: Field Text
- Web.Bugzilla.RedHat: [CommentIsPrivateField] :: Field Text
- Web.Bugzilla.RedHat: [CommentTagsField] :: Field Text
- Web.Bugzilla.RedHat: [CommenterField] :: Field UserEmail
- Web.Bugzilla.RedHat: [ComponentField] :: Field [Text]
- Web.Bugzilla.RedHat: [ContentField] :: Field Text
- Web.Bugzilla.RedHat: [CreationDateField] :: Field UTCTime
- Web.Bugzilla.RedHat: [CustomField] :: Text -> Field Text
- Web.Bugzilla.RedHat: [DaysElapsedField] :: Field Int
- Web.Bugzilla.RedHat: [DependsOnField] :: Field Int
- Web.Bugzilla.RedHat: [EverConfirmedField] :: Field Bool
- Web.Bugzilla.RedHat: [FlagRequesteeField] :: Field UserEmail
- Web.Bugzilla.RedHat: [FlagSetterField] :: Field UserEmail
- Web.Bugzilla.RedHat: [FlagsField] :: Field Text
- Web.Bugzilla.RedHat: [GroupField] :: Field Text
- Web.Bugzilla.RedHat: [HardwareField] :: Field Text
- Web.Bugzilla.RedHat: [KeywordsField] :: Field [Text]
- Web.Bugzilla.RedHat: [OperatingSystemField] :: Field Text
- Web.Bugzilla.RedHat: [PriorityField] :: Field Text
- Web.Bugzilla.RedHat: [ProductField] :: Field Text
- Web.Bugzilla.RedHat: [QaContactField] :: Field UserEmail
- Web.Bugzilla.RedHat: [ReporterAccessibleField] :: Field Bool
- Web.Bugzilla.RedHat: [ReporterField] :: Field UserEmail
- Web.Bugzilla.RedHat: [ResolutionField] :: Field Text
- Web.Bugzilla.RedHat: [RestrictCommentsField] :: Field Bool
- Web.Bugzilla.RedHat: [SeeAlsoField] :: Field Text
- Web.Bugzilla.RedHat: [SeverityField] :: Field Text
- Web.Bugzilla.RedHat: [StatusField] :: Field Text
- Web.Bugzilla.RedHat: [SummaryField] :: Field Text
- Web.Bugzilla.RedHat: [TagsField] :: Field Text
- Web.Bugzilla.RedHat: [TargetMilestoneField] :: Field Text
- Web.Bugzilla.RedHat: [TimeSinceAssigneeTouchedField] :: Field Int
- Web.Bugzilla.RedHat: [VersionField] :: Field Text
- Web.Bugzilla.RedHat: [VotesField] :: Field Text
- Web.Bugzilla.RedHat: [WhiteboardField] :: Field Text
- Web.Bugzilla.RedHat: [attachmentBugId] :: Attachment -> !BugId
- Web.Bugzilla.RedHat: [attachmentContentType] :: Attachment -> Text
- Web.Bugzilla.RedHat: [attachmentCreationTime] :: Attachment -> UTCTime
- Web.Bugzilla.RedHat: [attachmentCreator] :: Attachment -> UserEmail
- Web.Bugzilla.RedHat: [attachmentData] :: Attachment -> Text
- Web.Bugzilla.RedHat: [attachmentFileName] :: Attachment -> Text
- Web.Bugzilla.RedHat: [attachmentFlags] :: Attachment -> [Flag]
- Web.Bugzilla.RedHat: [attachmentId] :: Attachment -> !AttachmentId
- Web.Bugzilla.RedHat: [attachmentIsObsolete] :: Attachment -> Bool
- Web.Bugzilla.RedHat: [attachmentIsPatch] :: Attachment -> Bool
- Web.Bugzilla.RedHat: [attachmentIsPrivate] :: Attachment -> Bool
- Web.Bugzilla.RedHat: [attachmentLastChangeTime] :: Attachment -> UTCTime
- Web.Bugzilla.RedHat: [attachmentSize] :: Attachment -> !Int
- Web.Bugzilla.RedHat: [attachmentSummary] :: Attachment -> Text
- Web.Bugzilla.RedHat: [bugAlias] :: Bug -> Maybe [Text]
- Web.Bugzilla.RedHat: [bugAssignedToDetail] :: Bug -> User
- Web.Bugzilla.RedHat: [bugAssignedTo] :: Bug -> UserEmail
- Web.Bugzilla.RedHat: [bugBlocks] :: Bug -> [BugId]
- Web.Bugzilla.RedHat: [bugCcDetail] :: Bug -> [User]
- Web.Bugzilla.RedHat: [bugCc] :: Bug -> [UserEmail]
- Web.Bugzilla.RedHat: [bugClassification] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugComponent] :: Bug -> [Text]
- Web.Bugzilla.RedHat: [bugCreationTime] :: Bug -> UTCTime
- Web.Bugzilla.RedHat: [bugCreatorDetail] :: Bug -> User
- Web.Bugzilla.RedHat: [bugCreator] :: Bug -> UserEmail
- Web.Bugzilla.RedHat: [bugCustomFields] :: Bug -> KeyMap Text
- Web.Bugzilla.RedHat: [bugDependsOn] :: Bug -> [BugId]
- Web.Bugzilla.RedHat: [bugDupeOf] :: Bug -> Maybe BugId
- Web.Bugzilla.RedHat: [bugExternalBugs] :: Bug -> Maybe [ExternalBug]
- Web.Bugzilla.RedHat: [bugFlags] :: Bug -> Maybe [Flag]
- Web.Bugzilla.RedHat: [bugGroups] :: Bug -> [Text]
- Web.Bugzilla.RedHat: [bugId] :: Bug -> !BugId
- Web.Bugzilla.RedHat: [bugIsCcAccessible] :: Bug -> Bool
- Web.Bugzilla.RedHat: [bugIsConfirmed] :: Bug -> Bool
- Web.Bugzilla.RedHat: [bugIsCreatorAccessible] :: Bug -> Bool
- Web.Bugzilla.RedHat: [bugIsOpen] :: Bug -> Bool
- Web.Bugzilla.RedHat: [bugKeywords] :: Bug -> [Text]
- Web.Bugzilla.RedHat: [bugLastChangeTime] :: Bug -> UTCTime
- Web.Bugzilla.RedHat: [bugOpSys] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugPlatform] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugPriority] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugProduct] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugQaContact] :: Bug -> UserEmail
- Web.Bugzilla.RedHat: [bugResolution] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugSeeAlso] :: Bug -> [Text]
- Web.Bugzilla.RedHat: [bugSeverity] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugStatus] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugSummary] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugTargetMilestone] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugUrl] :: Bug -> Text
- Web.Bugzilla.RedHat: [bugVersion] :: Bug -> [Text]
- Web.Bugzilla.RedHat: [bugWhiteboard] :: Bug -> Text
- Web.Bugzilla.RedHat: [commentAttachmentId] :: Comment -> Maybe AttachmentId
- Web.Bugzilla.RedHat: [commentBugId] :: Comment -> !BugId
- Web.Bugzilla.RedHat: [commentCount] :: Comment -> !Int
- Web.Bugzilla.RedHat: [commentCreationTime] :: Comment -> UTCTime
- Web.Bugzilla.RedHat: [commentCreator] :: Comment -> UserEmail
- Web.Bugzilla.RedHat: [commentId] :: Comment -> !CommentId
- Web.Bugzilla.RedHat: [commentIsPrivate] :: Comment -> Bool
- Web.Bugzilla.RedHat: [commentText] :: Comment -> Text
- Web.Bugzilla.RedHat: [externalBugId] :: ExternalBug -> Text
- Web.Bugzilla.RedHat: [externalBzId] :: ExternalBug -> Int
- Web.Bugzilla.RedHat: [externalDescription] :: ExternalBug -> Text
- Web.Bugzilla.RedHat: [externalId] :: ExternalBug -> Int
- Web.Bugzilla.RedHat: [externalPriority] :: ExternalBug -> Text
- Web.Bugzilla.RedHat: [externalStatus] :: ExternalBug -> Text
- Web.Bugzilla.RedHat: [externalTypeDescription] :: ExternalType -> Text
- Web.Bugzilla.RedHat: [externalTypeFullUrl] :: ExternalType -> Text
- Web.Bugzilla.RedHat: [externalTypeId] :: ExternalType -> Int
- Web.Bugzilla.RedHat: [externalTypeType] :: ExternalType -> Text
- Web.Bugzilla.RedHat: [externalTypeUrl] :: ExternalType -> Text
- Web.Bugzilla.RedHat: [externalType] :: ExternalBug -> ExternalType
- Web.Bugzilla.RedHat: [flagCreationDate] :: Flag -> UTCTime
- Web.Bugzilla.RedHat: [flagId] :: Flag -> !FlagId
- Web.Bugzilla.RedHat: [flagModificationDate] :: Flag -> UTCTime
- Web.Bugzilla.RedHat: [flagName] :: Flag -> Text
- Web.Bugzilla.RedHat: [flagRequestee] :: Flag -> Maybe UserEmail
- Web.Bugzilla.RedHat: [flagSetter] :: Flag -> UserEmail
- Web.Bugzilla.RedHat: [flagStatus] :: Flag -> Text
- Web.Bugzilla.RedHat: [flagTypeId] :: Flag -> !FlagType
- Web.Bugzilla.RedHat: [historyBugId] :: History -> !BugId
- Web.Bugzilla.RedHat: [historyEventChanges] :: HistoryEvent -> [Change]
- Web.Bugzilla.RedHat: [historyEventId] :: HistoryEvent -> EventId
- Web.Bugzilla.RedHat: [historyEventTime] :: HistoryEvent -> UTCTime
- Web.Bugzilla.RedHat: [historyEventUser] :: HistoryEvent -> UserEmail
- Web.Bugzilla.RedHat: [historyEvents] :: History -> [HistoryEvent]
- Web.Bugzilla.RedHat: [modAdded] :: Modification a -> Maybe a
- Web.Bugzilla.RedHat: [modAttachmentId] :: Modification a -> Maybe AttachmentId
- Web.Bugzilla.RedHat: [modRemoved] :: Modification a -> Maybe a
- Web.Bugzilla.RedHat: [userEmail] :: User -> Maybe UserEmail
- Web.Bugzilla.RedHat: [userId] :: User -> !UserId
- Web.Bugzilla.RedHat: [userName] :: User -> Text
- Web.Bugzilla.RedHat: [userRealName] :: User -> Text
- Web.Bugzilla.RedHat: anonymousSession :: BugzillaContext -> BugzillaSession
- Web.Bugzilla.RedHat: apikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession
- Web.Bugzilla.RedHat: data Attachment
- Web.Bugzilla.RedHat: data Bug
- Web.Bugzilla.RedHat: data BugzillaContext
- Web.Bugzilla.RedHat: data BugzillaException
- Web.Bugzilla.RedHat: data BugzillaSession
- Web.Bugzilla.RedHat: data Change
- Web.Bugzilla.RedHat: data Comment
- Web.Bugzilla.RedHat: data ExternalBug
- Web.Bugzilla.RedHat: data ExternalType
- Web.Bugzilla.RedHat: data Field a
- Web.Bugzilla.RedHat: data Flag
- Web.Bugzilla.RedHat: data History
- Web.Bugzilla.RedHat: data HistoryEvent
- Web.Bugzilla.RedHat: data (Eq a, Show a) => Modification a
- Web.Bugzilla.RedHat: data Request
- Web.Bugzilla.RedHat: data User
- Web.Bugzilla.RedHat: fieldName :: Field a -> Text
- Web.Bugzilla.RedHat: getAttachment :: BugzillaSession -> AttachmentId -> IO (Maybe Attachment)
- Web.Bugzilla.RedHat: getAttachments :: BugzillaSession -> BugId -> IO [Attachment]
- Web.Bugzilla.RedHat: getBug :: BugzillaSession -> BugId -> IO (Maybe Bug)
- Web.Bugzilla.RedHat: getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)
- Web.Bugzilla.RedHat: getComments :: BugzillaSession -> BugId -> IO [Comment]
- Web.Bugzilla.RedHat: getHistory :: BugzillaSession -> BugId -> IO History
- Web.Bugzilla.RedHat: getUser :: BugzillaSession -> UserEmail -> IO (Maybe User)
- Web.Bugzilla.RedHat: getUserById :: BugzillaSession -> UserId -> IO (Maybe User)
- Web.Bugzilla.RedHat: intAsText :: Int -> Text
- Web.Bugzilla.RedHat: loginSession :: BugzillaContext -> UserEmail -> Text -> IO (Maybe BugzillaSession)
- Web.Bugzilla.RedHat: newBugzillaContext :: BugzillaServer -> IO BugzillaContext
- Web.Bugzilla.RedHat: newBzRequest :: BugzillaSession -> [Text] -> QueryText -> Request
- Web.Bugzilla.RedHat: newtype BugzillaApikey
- Web.Bugzilla.RedHat: newtype BugzillaToken
- Web.Bugzilla.RedHat: searchBugs :: BugzillaSession -> SearchExpression -> IO [Bug]
- Web.Bugzilla.RedHat: searchBugs' :: BugzillaSession -> SearchExpression -> IO [BugId]
- Web.Bugzilla.RedHat: searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]
- Web.Bugzilla.RedHat: searchBugsAllWithLimit :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [Bug]
- Web.Bugzilla.RedHat: searchBugsWithLimit :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [Bug]
- Web.Bugzilla.RedHat: searchBugsWithLimit' :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [BugId]
- Web.Bugzilla.RedHat: searchUsers :: BugzillaSession -> Text -> IO [User]
- Web.Bugzilla.RedHat: sendBzRequest :: FromJSON a => BugzillaSession -> Request -> IO a
- Web.Bugzilla.RedHat: type AttachmentId = Int
- Web.Bugzilla.RedHat: type BugId = Int
- Web.Bugzilla.RedHat: type BugzillaServer = Text
- Web.Bugzilla.RedHat: type CommentId = Int
- Web.Bugzilla.RedHat: type EventId = Int
- Web.Bugzilla.RedHat: type FlagId = Int
- Web.Bugzilla.RedHat: type FlagType = Int
- Web.Bugzilla.RedHat: type UserEmail = Text
- Web.Bugzilla.RedHat: type UserId = Int
- Web.Bugzilla.RedHat.Search: (.&&.) :: SearchExpression -> SearchExpression -> SearchExpression
- Web.Bugzilla.RedHat.Search: (./=.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (./=~.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.<.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.<=.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.==.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.=~.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.>.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.>=.) :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: (.||.) :: SearchExpression -> SearchExpression -> SearchExpression
- Web.Bugzilla.RedHat.Search: [AliasField] :: Field [Text]
- Web.Bugzilla.RedHat.Search: [AssignedToField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [AttachmentCreatorField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [AttachmentDataField] :: Field Text
- Web.Bugzilla.RedHat.Search: [AttachmentDescriptionField] :: Field Text
- Web.Bugzilla.RedHat.Search: [AttachmentFilenameField] :: Field Text
- Web.Bugzilla.RedHat.Search: [AttachmentIsObsoleteField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [AttachmentIsPatchField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [AttachmentIsPrivateField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [AttachmentMimetypeField] :: Field Text
- Web.Bugzilla.RedHat.Search: [BlocksField] :: Field Int
- Web.Bugzilla.RedHat.Search: [BugIdField] :: Field Int
- Web.Bugzilla.RedHat.Search: [BugURLField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CcField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [CcListAccessibleField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [ChangedField] :: Field UTCTime
- Web.Bugzilla.RedHat.Search: [ClassificationField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CommentCountField] :: Field Int
- Web.Bugzilla.RedHat.Search: [CommentField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CommentIsPrivateField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CommentTagsField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CommenterField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [ComponentField] :: Field [Text]
- Web.Bugzilla.RedHat.Search: [ContentField] :: Field Text
- Web.Bugzilla.RedHat.Search: [CreationDateField] :: Field UTCTime
- Web.Bugzilla.RedHat.Search: [CustomField] :: Text -> Field Text
- Web.Bugzilla.RedHat.Search: [DaysElapsedField] :: Field Int
- Web.Bugzilla.RedHat.Search: [DependsOnField] :: Field Int
- Web.Bugzilla.RedHat.Search: [EverConfirmedField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [FlagRequesteeField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [FlagSetterField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [FlagsField] :: Field Text
- Web.Bugzilla.RedHat.Search: [GroupField] :: Field Text
- Web.Bugzilla.RedHat.Search: [HardwareField] :: Field Text
- Web.Bugzilla.RedHat.Search: [KeywordsField] :: Field [Text]
- Web.Bugzilla.RedHat.Search: [OperatingSystemField] :: Field Text
- Web.Bugzilla.RedHat.Search: [PriorityField] :: Field Text
- Web.Bugzilla.RedHat.Search: [ProductField] :: Field Text
- Web.Bugzilla.RedHat.Search: [QaContactField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [ReporterAccessibleField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [ReporterField] :: Field UserEmail
- Web.Bugzilla.RedHat.Search: [ResolutionField] :: Field Text
- Web.Bugzilla.RedHat.Search: [RestrictCommentsField] :: Field Bool
- Web.Bugzilla.RedHat.Search: [SeeAlsoField] :: Field Text
- Web.Bugzilla.RedHat.Search: [SeverityField] :: Field Text
- Web.Bugzilla.RedHat.Search: [StatusField] :: Field Text
- Web.Bugzilla.RedHat.Search: [SummaryField] :: Field Text
- Web.Bugzilla.RedHat.Search: [TagsField] :: Field Text
- Web.Bugzilla.RedHat.Search: [TargetMilestoneField] :: Field Text
- Web.Bugzilla.RedHat.Search: [TimeSinceAssigneeTouchedField] :: Field Int
- Web.Bugzilla.RedHat.Search: [VersionField] :: Field Text
- Web.Bugzilla.RedHat.Search: [VotesField] :: Field Text
- Web.Bugzilla.RedHat.Search: [WhiteboardField] :: Field Text
- Web.Bugzilla.RedHat.Search: changedAfter :: FieldType a => Field a -> UTCTime -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedBefore :: FieldType a => Field a -> UTCTime -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedBy :: FieldType a => Field a -> UserEmail -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedFrom :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedRange :: UTCTime -> UTCTime -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedSince :: UTCTime -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedTo :: FieldType a => Field a -> a -> SearchExpression
- Web.Bugzilla.RedHat.Search: changedUntil :: UTCTime -> SearchExpression
- Web.Bugzilla.RedHat.Search: contains :: Field Text -> Text -> SearchExpression
- Web.Bugzilla.RedHat.Search: containsAll :: Field Text -> [Text] -> SearchExpression
- Web.Bugzilla.RedHat.Search: containsAny :: Field Text -> [Text] -> SearchExpression
- Web.Bugzilla.RedHat.Search: containsCase :: Field Text -> Text -> SearchExpression
- Web.Bugzilla.RedHat.Search: contentMatches :: Text -> SearchExpression
- Web.Bugzilla.RedHat.Search: data Field a
- Web.Bugzilla.RedHat.Search: data SearchExpression
- Web.Bugzilla.RedHat.Search: equalsAny :: FieldType a => Field a -> [a] -> SearchExpression
- Web.Bugzilla.RedHat.Search: evalSearchExpr :: SearchExpression -> [QueryPart]
- Web.Bugzilla.RedHat.Search: infix 4 .>=.
- Web.Bugzilla.RedHat.Search: infixr 2 .||.
- Web.Bugzilla.RedHat.Search: infixr 3 .&&.
- Web.Bugzilla.RedHat.Search: isEmpty :: FieldType a => Field a -> SearchExpression
- Web.Bugzilla.RedHat.Search: isNotEmpty :: FieldType a => Field a -> SearchExpression
- Web.Bugzilla.RedHat.Search: not' :: SearchExpression -> SearchExpression
+ Web.RedHatBugzilla: AnonymousSession :: BugzillaServer -> BugzillaSession
+ Web.RedHatBugzilla: ApiKeySession :: BugzillaServer -> BugzillaApiKey -> BugzillaSession
+ Web.RedHatBugzilla: Attachment :: !AttachmentId -> !BugId -> Text -> Text -> UserEmail -> Bool -> Bool -> Bool -> [Flag] -> UTCTime -> UTCTime -> Text -> !Int -> Text -> Attachment
+ Web.RedHatBugzilla: BoolFieldChange :: Field Bool -> Modification Bool -> Change
+ Web.RedHatBugzilla: Bug :: !BugId -> Maybe [Text] -> UserEmail -> User -> [BugId] -> [UserEmail] -> [User] -> Text -> [Text] -> UTCTime -> UserEmail -> User -> [BugId] -> Maybe BugId -> Maybe [Flag] -> [Text] -> Bool -> Bool -> Bool -> Bool -> [Text] -> UTCTime -> Text -> Text -> Text -> Text -> UserEmail -> Text -> [Text] -> Text -> Text -> Text -> Text -> Text -> [Text] -> Text -> KeyMap Text -> Maybe [ExternalBug] -> Bug
+ Web.RedHatBugzilla: BugzillaAPIError :: Int -> String -> BugzillaException
+ Web.RedHatBugzilla: BugzillaApiKey :: Text -> BugzillaApiKey
+ Web.RedHatBugzilla: BugzillaJSONParseError :: String -> BugzillaException
+ Web.RedHatBugzilla: BugzillaUnexpectedValue :: String -> BugzillaException
+ Web.RedHatBugzilla: Comment :: !CommentId -> !BugId -> Maybe AttachmentId -> !Int -> Text -> UserEmail -> UTCTime -> Bool -> Comment
+ Web.RedHatBugzilla: ExternalBug :: Text -> Int -> Text -> Text -> Text -> Int -> ExternalType -> ExternalBug
+ Web.RedHatBugzilla: ExternalType :: Text -> Text -> Int -> Text -> Text -> ExternalType
+ Web.RedHatBugzilla: Flag :: !FlagId -> !FlagType -> Text -> UserEmail -> Text -> UTCTime -> UTCTime -> Maybe UserEmail -> Flag
+ Web.RedHatBugzilla: History :: !BugId -> [HistoryEvent] -> History
+ Web.RedHatBugzilla: HistoryEvent :: EventId -> UTCTime -> UserEmail -> [Change] -> HistoryEvent
+ Web.RedHatBugzilla: IntFieldChange :: Field Int -> Modification Int -> Change
+ Web.RedHatBugzilla: ListFieldChange :: Field [Text] -> Modification [Text] -> Change
+ Web.RedHatBugzilla: Modification :: Maybe a -> Maybe a -> Maybe AttachmentId -> Modification a
+ Web.RedHatBugzilla: TextFieldChange :: Field Text -> Modification Text -> Change
+ Web.RedHatBugzilla: TimeFieldChange :: Field UTCTime -> Modification UTCTime -> Change
+ Web.RedHatBugzilla: User :: !UserId -> Maybe UserEmail -> Text -> Text -> User
+ Web.RedHatBugzilla: [AliasField] :: Field [Text]
+ Web.RedHatBugzilla: [AssignedToField] :: Field UserEmail
+ Web.RedHatBugzilla: [AttachmentCreatorField] :: Field UserEmail
+ Web.RedHatBugzilla: [AttachmentDataField] :: Field Text
+ Web.RedHatBugzilla: [AttachmentDescriptionField] :: Field Text
+ Web.RedHatBugzilla: [AttachmentFilenameField] :: Field Text
+ Web.RedHatBugzilla: [AttachmentIsObsoleteField] :: Field Bool
+ Web.RedHatBugzilla: [AttachmentIsPatchField] :: Field Bool
+ Web.RedHatBugzilla: [AttachmentIsPrivateField] :: Field Bool
+ Web.RedHatBugzilla: [AttachmentMimetypeField] :: Field Text
+ Web.RedHatBugzilla: [BlocksField] :: Field Int
+ Web.RedHatBugzilla: [BugIdField] :: Field Int
+ Web.RedHatBugzilla: [BugURLField] :: Field Text
+ Web.RedHatBugzilla: [CcField] :: Field UserEmail
+ Web.RedHatBugzilla: [CcListAccessibleField] :: Field Bool
+ Web.RedHatBugzilla: [ChangedField] :: Field UTCTime
+ Web.RedHatBugzilla: [ClassificationField] :: Field Text
+ Web.RedHatBugzilla: [CommentCountField] :: Field Int
+ Web.RedHatBugzilla: [CommentField] :: Field Text
+ Web.RedHatBugzilla: [CommentIsPrivateField] :: Field Text
+ Web.RedHatBugzilla: [CommentTagsField] :: Field Text
+ Web.RedHatBugzilla: [CommenterField] :: Field UserEmail
+ Web.RedHatBugzilla: [ComponentField] :: Field [Text]
+ Web.RedHatBugzilla: [ContentField] :: Field Text
+ Web.RedHatBugzilla: [CreationDateField] :: Field UTCTime
+ Web.RedHatBugzilla: [CustomField] :: Text -> Field Text
+ Web.RedHatBugzilla: [DaysElapsedField] :: Field Int
+ Web.RedHatBugzilla: [DependsOnField] :: Field Int
+ Web.RedHatBugzilla: [EverConfirmedField] :: Field Bool
+ Web.RedHatBugzilla: [FlagRequesteeField] :: Field UserEmail
+ Web.RedHatBugzilla: [FlagSetterField] :: Field UserEmail
+ Web.RedHatBugzilla: [FlagsField] :: Field Text
+ Web.RedHatBugzilla: [GroupField] :: Field Text
+ Web.RedHatBugzilla: [HardwareField] :: Field Text
+ Web.RedHatBugzilla: [KeywordsField] :: Field [Text]
+ Web.RedHatBugzilla: [OperatingSystemField] :: Field Text
+ Web.RedHatBugzilla: [PriorityField] :: Field Text
+ Web.RedHatBugzilla: [ProductField] :: Field Text
+ Web.RedHatBugzilla: [QaContactField] :: Field UserEmail
+ Web.RedHatBugzilla: [ReporterAccessibleField] :: Field Bool
+ Web.RedHatBugzilla: [ReporterField] :: Field UserEmail
+ Web.RedHatBugzilla: [ResolutionField] :: Field Text
+ Web.RedHatBugzilla: [RestrictCommentsField] :: Field Bool
+ Web.RedHatBugzilla: [SeeAlsoField] :: Field Text
+ Web.RedHatBugzilla: [SeverityField] :: Field Text
+ Web.RedHatBugzilla: [StatusField] :: Field Text
+ Web.RedHatBugzilla: [SummaryField] :: Field Text
+ Web.RedHatBugzilla: [TagsField] :: Field Text
+ Web.RedHatBugzilla: [TargetMilestoneField] :: Field Text
+ Web.RedHatBugzilla: [TimeSinceAssigneeTouchedField] :: Field Int
+ Web.RedHatBugzilla: [VersionField] :: Field Text
+ Web.RedHatBugzilla: [VotesField] :: Field Text
+ Web.RedHatBugzilla: [WhiteboardField] :: Field Text
+ Web.RedHatBugzilla: [attachmentBugId] :: Attachment -> !BugId
+ Web.RedHatBugzilla: [attachmentContentType] :: Attachment -> Text
+ Web.RedHatBugzilla: [attachmentCreationTime] :: Attachment -> UTCTime
+ Web.RedHatBugzilla: [attachmentCreator] :: Attachment -> UserEmail
+ Web.RedHatBugzilla: [attachmentData] :: Attachment -> Text
+ Web.RedHatBugzilla: [attachmentFileName] :: Attachment -> Text
+ Web.RedHatBugzilla: [attachmentFlags] :: Attachment -> [Flag]
+ Web.RedHatBugzilla: [attachmentId] :: Attachment -> !AttachmentId
+ Web.RedHatBugzilla: [attachmentIsObsolete] :: Attachment -> Bool
+ Web.RedHatBugzilla: [attachmentIsPatch] :: Attachment -> Bool
+ Web.RedHatBugzilla: [attachmentIsPrivate] :: Attachment -> Bool
+ Web.RedHatBugzilla: [attachmentLastChangeTime] :: Attachment -> UTCTime
+ Web.RedHatBugzilla: [attachmentSize] :: Attachment -> !Int
+ Web.RedHatBugzilla: [attachmentSummary] :: Attachment -> Text
+ Web.RedHatBugzilla: [bugAlias] :: Bug -> Maybe [Text]
+ Web.RedHatBugzilla: [bugAssignedToDetail] :: Bug -> User
+ Web.RedHatBugzilla: [bugAssignedTo] :: Bug -> UserEmail
+ Web.RedHatBugzilla: [bugBlocks] :: Bug -> [BugId]
+ Web.RedHatBugzilla: [bugCcDetail] :: Bug -> [User]
+ Web.RedHatBugzilla: [bugCc] :: Bug -> [UserEmail]
+ Web.RedHatBugzilla: [bugClassification] :: Bug -> Text
+ Web.RedHatBugzilla: [bugComponent] :: Bug -> [Text]
+ Web.RedHatBugzilla: [bugCreationTime] :: Bug -> UTCTime
+ Web.RedHatBugzilla: [bugCreatorDetail] :: Bug -> User
+ Web.RedHatBugzilla: [bugCreator] :: Bug -> UserEmail
+ Web.RedHatBugzilla: [bugCustomFields] :: Bug -> KeyMap Text
+ Web.RedHatBugzilla: [bugDependsOn] :: Bug -> [BugId]
+ Web.RedHatBugzilla: [bugDupeOf] :: Bug -> Maybe BugId
+ Web.RedHatBugzilla: [bugExternalBugs] :: Bug -> Maybe [ExternalBug]
+ Web.RedHatBugzilla: [bugFlags] :: Bug -> Maybe [Flag]
+ Web.RedHatBugzilla: [bugGroups] :: Bug -> [Text]
+ Web.RedHatBugzilla: [bugId] :: Bug -> !BugId
+ Web.RedHatBugzilla: [bugIsCcAccessible] :: Bug -> Bool
+ Web.RedHatBugzilla: [bugIsConfirmed] :: Bug -> Bool
+ Web.RedHatBugzilla: [bugIsCreatorAccessible] :: Bug -> Bool
+ Web.RedHatBugzilla: [bugIsOpen] :: Bug -> Bool
+ Web.RedHatBugzilla: [bugKeywords] :: Bug -> [Text]
+ Web.RedHatBugzilla: [bugLastChangeTime] :: Bug -> UTCTime
+ Web.RedHatBugzilla: [bugOpSys] :: Bug -> Text
+ Web.RedHatBugzilla: [bugPlatform] :: Bug -> Text
+ Web.RedHatBugzilla: [bugPriority] :: Bug -> Text
+ Web.RedHatBugzilla: [bugProduct] :: Bug -> Text
+ Web.RedHatBugzilla: [bugQaContact] :: Bug -> UserEmail
+ Web.RedHatBugzilla: [bugResolution] :: Bug -> Text
+ Web.RedHatBugzilla: [bugSeeAlso] :: Bug -> [Text]
+ Web.RedHatBugzilla: [bugSeverity] :: Bug -> Text
+ Web.RedHatBugzilla: [bugStatus] :: Bug -> Text
+ Web.RedHatBugzilla: [bugSummary] :: Bug -> Text
+ Web.RedHatBugzilla: [bugTargetMilestone] :: Bug -> Text
+ Web.RedHatBugzilla: [bugUrl] :: Bug -> Text
+ Web.RedHatBugzilla: [bugVersion] :: Bug -> [Text]
+ Web.RedHatBugzilla: [bugWhiteboard] :: Bug -> Text
+ Web.RedHatBugzilla: [commentAttachmentId] :: Comment -> Maybe AttachmentId
+ Web.RedHatBugzilla: [commentBugId] :: Comment -> !BugId
+ Web.RedHatBugzilla: [commentCount] :: Comment -> !Int
+ Web.RedHatBugzilla: [commentCreationTime] :: Comment -> UTCTime
+ Web.RedHatBugzilla: [commentCreator] :: Comment -> UserEmail
+ Web.RedHatBugzilla: [commentId] :: Comment -> !CommentId
+ Web.RedHatBugzilla: [commentIsPrivate] :: Comment -> Bool
+ Web.RedHatBugzilla: [commentText] :: Comment -> Text
+ Web.RedHatBugzilla: [externalBugId] :: ExternalBug -> Text
+ Web.RedHatBugzilla: [externalBzId] :: ExternalBug -> Int
+ Web.RedHatBugzilla: [externalDescription] :: ExternalBug -> Text
+ Web.RedHatBugzilla: [externalId] :: ExternalBug -> Int
+ Web.RedHatBugzilla: [externalPriority] :: ExternalBug -> Text
+ Web.RedHatBugzilla: [externalStatus] :: ExternalBug -> Text
+ Web.RedHatBugzilla: [externalTypeDescription] :: ExternalType -> Text
+ Web.RedHatBugzilla: [externalTypeFullUrl] :: ExternalType -> Text
+ Web.RedHatBugzilla: [externalTypeId] :: ExternalType -> Int
+ Web.RedHatBugzilla: [externalTypeType] :: ExternalType -> Text
+ Web.RedHatBugzilla: [externalTypeUrl] :: ExternalType -> Text
+ Web.RedHatBugzilla: [externalType] :: ExternalBug -> ExternalType
+ Web.RedHatBugzilla: [flagCreationDate] :: Flag -> UTCTime
+ Web.RedHatBugzilla: [flagId] :: Flag -> !FlagId
+ Web.RedHatBugzilla: [flagModificationDate] :: Flag -> UTCTime
+ Web.RedHatBugzilla: [flagName] :: Flag -> Text
+ Web.RedHatBugzilla: [flagRequestee] :: Flag -> Maybe UserEmail
+ Web.RedHatBugzilla: [flagSetter] :: Flag -> UserEmail
+ Web.RedHatBugzilla: [flagStatus] :: Flag -> Text
+ Web.RedHatBugzilla: [flagTypeId] :: Flag -> !FlagType
+ Web.RedHatBugzilla: [historyBugId] :: History -> !BugId
+ Web.RedHatBugzilla: [historyEventChanges] :: HistoryEvent -> [Change]
+ Web.RedHatBugzilla: [historyEventId] :: HistoryEvent -> EventId
+ Web.RedHatBugzilla: [historyEventTime] :: HistoryEvent -> UTCTime
+ Web.RedHatBugzilla: [historyEventUser] :: HistoryEvent -> UserEmail
+ Web.RedHatBugzilla: [historyEvents] :: History -> [HistoryEvent]
+ Web.RedHatBugzilla: [modAdded] :: Modification a -> Maybe a
+ Web.RedHatBugzilla: [modAttachmentId] :: Modification a -> Maybe AttachmentId
+ Web.RedHatBugzilla: [modRemoved] :: Modification a -> Maybe a
+ Web.RedHatBugzilla: [userEmail] :: User -> Maybe UserEmail
+ Web.RedHatBugzilla: [userId] :: User -> !UserId
+ Web.RedHatBugzilla: [userName] :: User -> Text
+ Web.RedHatBugzilla: [userRealName] :: User -> Text
+ Web.RedHatBugzilla: anonymousSession :: BugzillaServer -> BugzillaSession
+ Web.RedHatBugzilla: apikeySession :: BugzillaServer -> BugzillaApiKey -> BugzillaSession
+ Web.RedHatBugzilla: data Attachment
+ Web.RedHatBugzilla: data Bug
+ Web.RedHatBugzilla: data BugzillaException
+ Web.RedHatBugzilla: data BugzillaSession
+ Web.RedHatBugzilla: data Change
+ Web.RedHatBugzilla: data Comment
+ Web.RedHatBugzilla: data ExternalBug
+ Web.RedHatBugzilla: data ExternalType
+ Web.RedHatBugzilla: data Field a
+ Web.RedHatBugzilla: data Flag
+ Web.RedHatBugzilla: data History
+ Web.RedHatBugzilla: data HistoryEvent
+ Web.RedHatBugzilla: data (Eq a, Show a) => Modification a
+ Web.RedHatBugzilla: data Request
+ Web.RedHatBugzilla: data User
+ Web.RedHatBugzilla: fieldName :: Field a -> Text
+ Web.RedHatBugzilla: getAttachment :: BugzillaSession -> AttachmentId -> IO (Maybe Attachment)
+ Web.RedHatBugzilla: getAttachments :: BugzillaSession -> BugId -> IO [Attachment]
+ Web.RedHatBugzilla: getBug :: BugzillaSession -> BugId -> IO (Maybe Bug)
+ Web.RedHatBugzilla: getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)
+ Web.RedHatBugzilla: getComments :: BugzillaSession -> BugId -> IO [Comment]
+ Web.RedHatBugzilla: getHistory :: BugzillaSession -> BugId -> IO History
+ Web.RedHatBugzilla: getUser :: BugzillaSession -> UserEmail -> IO (Maybe User)
+ Web.RedHatBugzilla: getUserById :: BugzillaSession -> UserId -> IO (Maybe User)
+ Web.RedHatBugzilla: intAsText :: Int -> Text
+ Web.RedHatBugzilla: newBzRequest :: BugzillaSession -> [Text] -> QueryText -> Request
+ Web.RedHatBugzilla: newtype BugzillaApiKey
+ Web.RedHatBugzilla: searchBugs :: BugzillaSession -> SearchExpression -> IO [Bug]
+ Web.RedHatBugzilla: searchBugs' :: BugzillaSession -> SearchExpression -> IO [BugId]
+ Web.RedHatBugzilla: searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]
+ Web.RedHatBugzilla: searchBugsAllWithLimit :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [Bug]
+ Web.RedHatBugzilla: searchBugsWithLimit :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [Bug]
+ Web.RedHatBugzilla: searchBugsWithLimit' :: BugzillaSession -> Int -> Int -> SearchExpression -> IO [BugId]
+ Web.RedHatBugzilla: searchUsers :: BugzillaSession -> Text -> IO [User]
+ Web.RedHatBugzilla: sendBzRequest :: FromJSON a => Request -> IO a
+ Web.RedHatBugzilla: type AttachmentId = Int
+ Web.RedHatBugzilla: type BugId = Int
+ Web.RedHatBugzilla: type BugzillaServer = Text
+ Web.RedHatBugzilla: type CommentId = Int
+ Web.RedHatBugzilla: type EventId = Int
+ Web.RedHatBugzilla: type FlagId = Int
+ Web.RedHatBugzilla: type FlagType = Int
+ Web.RedHatBugzilla: type UserEmail = Text
+ Web.RedHatBugzilla: type UserId = Int
+ Web.RedHatBugzilla.Search: (.&&.) :: SearchExpression -> SearchExpression -> SearchExpression
+ Web.RedHatBugzilla.Search: (./=.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (./=~.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.<.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.<=.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.==.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.=~.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.>.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.>=.) :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: (.||.) :: SearchExpression -> SearchExpression -> SearchExpression
+ Web.RedHatBugzilla.Search: [AliasField] :: Field [Text]
+ Web.RedHatBugzilla.Search: [AssignedToField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [AttachmentCreatorField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [AttachmentDataField] :: Field Text
+ Web.RedHatBugzilla.Search: [AttachmentDescriptionField] :: Field Text
+ Web.RedHatBugzilla.Search: [AttachmentFilenameField] :: Field Text
+ Web.RedHatBugzilla.Search: [AttachmentIsObsoleteField] :: Field Bool
+ Web.RedHatBugzilla.Search: [AttachmentIsPatchField] :: Field Bool
+ Web.RedHatBugzilla.Search: [AttachmentIsPrivateField] :: Field Bool
+ Web.RedHatBugzilla.Search: [AttachmentMimetypeField] :: Field Text
+ Web.RedHatBugzilla.Search: [BlocksField] :: Field Int
+ Web.RedHatBugzilla.Search: [BugIdField] :: Field Int
+ Web.RedHatBugzilla.Search: [BugURLField] :: Field Text
+ Web.RedHatBugzilla.Search: [CcField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [CcListAccessibleField] :: Field Bool
+ Web.RedHatBugzilla.Search: [ChangedField] :: Field UTCTime
+ Web.RedHatBugzilla.Search: [ClassificationField] :: Field Text
+ Web.RedHatBugzilla.Search: [CommentCountField] :: Field Int
+ Web.RedHatBugzilla.Search: [CommentField] :: Field Text
+ Web.RedHatBugzilla.Search: [CommentIsPrivateField] :: Field Text
+ Web.RedHatBugzilla.Search: [CommentTagsField] :: Field Text
+ Web.RedHatBugzilla.Search: [CommenterField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [ComponentField] :: Field [Text]
+ Web.RedHatBugzilla.Search: [ContentField] :: Field Text
+ Web.RedHatBugzilla.Search: [CreationDateField] :: Field UTCTime
+ Web.RedHatBugzilla.Search: [CustomField] :: Text -> Field Text
+ Web.RedHatBugzilla.Search: [DaysElapsedField] :: Field Int
+ Web.RedHatBugzilla.Search: [DependsOnField] :: Field Int
+ Web.RedHatBugzilla.Search: [EverConfirmedField] :: Field Bool
+ Web.RedHatBugzilla.Search: [FlagRequesteeField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [FlagSetterField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [FlagsField] :: Field Text
+ Web.RedHatBugzilla.Search: [GroupField] :: Field Text
+ Web.RedHatBugzilla.Search: [HardwareField] :: Field Text
+ Web.RedHatBugzilla.Search: [KeywordsField] :: Field [Text]
+ Web.RedHatBugzilla.Search: [OperatingSystemField] :: Field Text
+ Web.RedHatBugzilla.Search: [PriorityField] :: Field Text
+ Web.RedHatBugzilla.Search: [ProductField] :: Field Text
+ Web.RedHatBugzilla.Search: [QaContactField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [ReporterAccessibleField] :: Field Bool
+ Web.RedHatBugzilla.Search: [ReporterField] :: Field UserEmail
+ Web.RedHatBugzilla.Search: [ResolutionField] :: Field Text
+ Web.RedHatBugzilla.Search: [RestrictCommentsField] :: Field Bool
+ Web.RedHatBugzilla.Search: [SeeAlsoField] :: Field Text
+ Web.RedHatBugzilla.Search: [SeverityField] :: Field Text
+ Web.RedHatBugzilla.Search: [StatusField] :: Field Text
+ Web.RedHatBugzilla.Search: [SummaryField] :: Field Text
+ Web.RedHatBugzilla.Search: [TagsField] :: Field Text
+ Web.RedHatBugzilla.Search: [TargetMilestoneField] :: Field Text
+ Web.RedHatBugzilla.Search: [TimeSinceAssigneeTouchedField] :: Field Int
+ Web.RedHatBugzilla.Search: [VersionField] :: Field Text
+ Web.RedHatBugzilla.Search: [VotesField] :: Field Text
+ Web.RedHatBugzilla.Search: [WhiteboardField] :: Field Text
+ Web.RedHatBugzilla.Search: changedAfter :: FieldType a => Field a -> UTCTime -> SearchExpression
+ Web.RedHatBugzilla.Search: changedBefore :: FieldType a => Field a -> UTCTime -> SearchExpression
+ Web.RedHatBugzilla.Search: changedBy :: FieldType a => Field a -> UserEmail -> SearchExpression
+ Web.RedHatBugzilla.Search: changedFrom :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: changedRange :: UTCTime -> UTCTime -> SearchExpression
+ Web.RedHatBugzilla.Search: changedSince :: UTCTime -> SearchExpression
+ Web.RedHatBugzilla.Search: changedTo :: FieldType a => Field a -> a -> SearchExpression
+ Web.RedHatBugzilla.Search: changedUntil :: UTCTime -> SearchExpression
+ Web.RedHatBugzilla.Search: contains :: Field Text -> Text -> SearchExpression
+ Web.RedHatBugzilla.Search: containsAll :: Field Text -> [Text] -> SearchExpression
+ Web.RedHatBugzilla.Search: containsAny :: Field Text -> [Text] -> SearchExpression
+ Web.RedHatBugzilla.Search: containsCase :: Field Text -> Text -> SearchExpression
+ Web.RedHatBugzilla.Search: contentMatches :: Text -> SearchExpression
+ Web.RedHatBugzilla.Search: data Field a
+ Web.RedHatBugzilla.Search: data SearchExpression
+ Web.RedHatBugzilla.Search: equalsAny :: FieldType a => Field a -> [a] -> SearchExpression
+ Web.RedHatBugzilla.Search: evalSearchExpr :: SearchExpression -> [QueryPart]
+ Web.RedHatBugzilla.Search: infix 4 .>=.
+ Web.RedHatBugzilla.Search: infixr 2 .||.
+ Web.RedHatBugzilla.Search: infixr 3 .&&.
+ Web.RedHatBugzilla.Search: isEmpty :: FieldType a => Field a -> SearchExpression
+ Web.RedHatBugzilla.Search: isNotEmpty :: FieldType a => Field a -> SearchExpression
+ Web.RedHatBugzilla.Search: not' :: SearchExpression -> SearchExpression
Files
- ChangeLog.md +12/−0
- TODO +1/−3
- bugzilla-redhat.cabal +8/−8
- demo/BugzillaDemo.hs +14/−32
- src/Web/Bugzilla/RedHat.hs +0/−283
- src/Web/Bugzilla/RedHat/Internal/Network.hs +0/−127
- src/Web/Bugzilla/RedHat/Internal/Search.hs +0/−90
- src/Web/Bugzilla/RedHat/Internal/Types.hs +0/−747
- src/Web/Bugzilla/RedHat/Search.hs +0/−147
- src/Web/RedHatBugzilla.hs +249/−0
- src/Web/RedHatBugzilla/Internal/Network.hs +114/−0
- src/Web/RedHatBugzilla/Internal/Search.hs +90/−0
- src/Web/RedHatBugzilla/Internal/Types.hs +747/−0
- src/Web/RedHatBugzilla/Search.hs +146/−0
- test/Spec.hs +1/−1
ChangeLog.md view
@@ -1,5 +1,17 @@ # bugzilla-redhat version history +## 1.0.0 (2022-02-20)+- rename Web.Bugzilla.RedHat to Web.RedHatBugzilla+- use Network.HTTP.Simple global session Manager+- from 28 Feb 2022 Red Hat Bugzilla only allows API key authentication:+- remove newBugzillaContext and BugzillaContext+- drop loginSession and also BugzillaToken+- bzServer replaces bzContext+- newBzRequest now puts API key in Authorization header+- sendBzRequest no longer takes a session argument+- rename BugzillaApikey to BugzillaApiKey+- rename ApikeySession to ApiKeySession+ ## 0.3.3 (2021-10-14) - support building with aeson-2.0
TODO view
@@ -2,6 +2,4 @@ - instance Monoid SearchExpression? -- rename Bugzilla.Redhat to RedHatBugzilla for 0.4--- use http-query (http-conduit Simple)+* use http-query
bugzilla-redhat.cabal view
@@ -1,11 +1,11 @@ name: bugzilla-redhat-version: 0.3.3+version: 1.0.0 synopsis: A Haskell interface to the Bugzilla native REST API description: This package is designed to provide an easy-to-use, type-safe interface to querying Bugzilla from Haskell. . This is a friendly fork of Seth Fowler's library,- with minor updates and API tweaks needed for+ with updates and API tweaks needed for bugzilla.redhat.com. homepage: https://github.com/juhp/hsbugzilla license: BSD3@@ -13,7 +13,7 @@ author: Seth Fowler <mark.seth.fowler@gmail.com> maintainer: Jens Petersen <juhpetersen@gmail.com> copyright: 2014 Seth Fowler,- 2020-2021 Jens Petersen+ 2020-2022 Jens Petersen category: Web build-type: Simple extra-source-files: README.md@@ -31,11 +31,11 @@ library ghc-options: -Wall- exposed-modules: Web.Bugzilla.RedHat,- Web.Bugzilla.RedHat.Search- other-modules: Web.Bugzilla.RedHat.Internal.Network,- Web.Bugzilla.RedHat.Internal.Search,- Web.Bugzilla.RedHat.Internal.Types+ exposed-modules: Web.RedHatBugzilla,+ Web.RedHatBugzilla.Search+ other-modules: Web.RedHatBugzilla.Internal.Network,+ Web.RedHatBugzilla.Internal.Search,+ Web.RedHatBugzilla.Internal.Types build-depends: base >=4.6 && <5, aeson >=0.7 && <2.1, blaze-builder >=0.3 && <0.5,
demo/BugzillaDemo.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -import Control.Exception (bracket) import Control.Monad import Data.Maybe import qualified Data.Text as T@@ -9,25 +8,23 @@ import System.Environment (getArgs) import System.IO -import Web.Bugzilla.RedHat-import Web.Bugzilla.RedHat.Search+import Web.RedHatBugzilla+import Web.RedHatBugzilla.Search main :: IO ()-main = dispatch Nothing Nothing =<< getArgs+main = dispatch Nothing =<< getArgs -dispatch :: Maybe UserEmail -> Maybe BugzillaServer -> [String] -> IO ()-dispatch Nothing s ("--login" : user : as) = dispatch (Just $ T.pack user) s as-dispatch l Nothing ("--server" : server : as) = dispatch l (Just $ T.pack server) as-dispatch l s ["--assigned-to", user] = withBz l s $ doAssignedTo (T.pack user)-dispatch l s ["--assigned-to-brief", user] = withBz l s $ doAssignedToBrief (T.pack user)-dispatch l s ["--requests", user] = withBz l s $ doRequests (T.pack user)-dispatch l s ["--history", bug, n] = withBz l s $ doHistory (read bug) (read n)-dispatch _ _ _ = usage+dispatch :: Maybe BugzillaServer -> [String] -> IO ()+dispatch Nothing ("--server" : server : as) = dispatch (Just $ T.pack server) as+dispatch s ["--assigned-to", user] = withBz s $ doAssignedTo (T.pack user)+dispatch s ["--assigned-to-brief", user] = withBz s $ doAssignedToBrief (T.pack user)+dispatch s ["--requests", user] = withBz s $ doRequests (T.pack user)+dispatch s ["--history", bug, n] = withBz s $ doHistory (read bug) (read n)+dispatch _ _ = usage usage :: IO () usage = hPutStrLn stderr "Connection options:" >> hPutStrLn stderr " --server [domain name] - REQUIRED. The Bugzilla server to access."- >> hPutStrLn stderr " --login [user email] - The user to log in with." >> hPutStrLn stderr "" >> hPutStrLn stderr "Bugzilla queries:" >> hPutStrLn stderr " --assigned-to [user email] - List bugs assigned to the user."@@ -35,22 +32,13 @@ >> hPutStrLn stderr " --requests [user email] - List requests for the user." >> hPutStrLn stderr " --history [bug number] [n] - List the most recent 'n' changes to the bug." -withBz :: Maybe UserEmail -> Maybe BugzillaServer -> (BugzillaSession -> IO ()) -> IO ()-withBz mLogin mServer f = do+withBz :: Maybe BugzillaServer -> (BugzillaSession -> IO ())+ -> IO ()+withBz mServer f = do let server = case mServer of Just s -> s Nothing -> error "Please specify a server with '--server'"- ctx <- newBugzillaContext server- case mLogin of- Just login -> do hPutStrLn stderr "Enter password: "- password <- T.pack <$> withEcho False getLine- mSession <- loginSession ctx login password- case mSession of- Just session -> do hPutStrLn stderr "Login successful."- f session- Nothing -> do hPutStrLn stderr "Login failed. Falling back to anonymous session."- f $ anonymousSession ctx- Nothing -> f $ anonymousSession ctx+ f $ anonymousSession server doAssignedTo :: UserEmail -> BugzillaSession -> IO ()@@ -158,9 +146,3 @@ showAid :: Maybe AttachmentId -> String showAid (Just aid) = " (Attachment " ++ show aid ++ ")" showAid Nothing = ""--withEcho :: Bool -> IO a -> IO a-withEcho echo action =- bracket (hGetEcho stdin)- (hSetEcho stdin)- (const $ hSetEcho stdin echo >> action)
− src/Web/Bugzilla/RedHat.hs
@@ -1,283 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}---- | This package is designed to provide an easy-to-use, typesafe--- interface to querying Bugzilla from Haskell.------ A modified version of Web.Bugzilla to support--- the list fields in Red Hat's modified bugzilla API.------ A very simple program using this package might look like this:------ > ctx <- newBugzillaContext "https://bugzilla.redhat.com"--- > let session = anonymousSession ctx--- > user = "me@example.org"--- > query = AssignedToField .==. user .&&.--- > FlagRequesteeField .==. user .&&.--- > (FlagsField `contains` "review" .||. FlagsField `contains` "feedback")--- > bugs <- searchBugs session query--- > mapM_ (putStrLn . show . bugSummary) bugs------ There's a somewhat more in-depth demo program included with the--- source code to this package.--module Web.Bugzilla.RedHat-( -- * Connecting to Bugzilla- newBugzillaContext-, loginSession-, apikeySession-, anonymousSession--, BugzillaServer-, BugzillaContext-, BugzillaSession (..)-, BugzillaToken (..)-, BugzillaApikey (..)-- -- * Querying Bugzilla-, searchBugs-, searchBugsAll-, searchBugs'-, searchBugsWithLimit-, searchBugsAllWithLimit-, searchBugsWithLimit'-, getBug-, getBugAll-, getAttachment-, getAttachments-, getComments-, getHistory-, searchUsers-, getUser-, getUserById-, newBzRequest-, sendBzRequest-, intAsText--, Request-, BugId-, AttachmentId-, CommentId-, UserId-, EventId-, FlagId-, FlagType-, UserEmail-, Field (..)-, User (..)-, Flag (..)-, Bug (..)-, ExternalBug (..)-, ExternalType (..)-, Attachment (..)-, Comment (..)-, History (..)-, HistoryEvent (..)-, Change (..)-, Modification (..)-, fieldName--, BugzillaException (..)-) where--import Control.Exception (throw, try)-import Control.Monad.IO.Class (liftIO)-import Data.Aeson (FromJSON)-import qualified Data.Text as T-import Network.Connection (TLSSettings(..))-import Network.HTTP.Conduit (mkManagerSettings, newManager)--import Web.Bugzilla.RedHat.Internal.Network-import Web.Bugzilla.RedHat.Internal.Search-import Web.Bugzilla.RedHat.Internal.Types---- | Creates a new 'BugzillaContext', suitable for connecting to the--- provided server. You should try to reuse 'BugzillaContext's--- whenever possible, because creating them is expensive.-newBugzillaContext :: BugzillaServer -> IO BugzillaContext-newBugzillaContext server = do- let settings = mkManagerSettings (TLSSettingsSimple True False False) Nothing- manager <- liftIO $ newManager settings- return $ BugzillaContext server manager---- | Attempts to create a logged-in 'BugzillaSession' using the--- provided username and password. Returns 'Nothing' if login--- fails.-loginSession :: BugzillaContext -> UserEmail -> T.Text -> IO (Maybe BugzillaSession)-loginSession ctx user password = do- let loginQuery = [("login", Just user),- ("password", Just password)]- session = anonymousSession ctx- req = newBzRequest session ["login"] loginQuery- eToken <- try $ sendBzRequest session req- return $ case eToken of- Left (BugzillaAPIError 300 _) -> Nothing- Left e -> throw e- Right token -> Just $ LoginSession ctx token---- | Creates a 'BugzillaSession' using the provided api key.-apikeySession :: BugzillaContext -> BugzillaApikey -> BugzillaSession-apikeySession = ApikeySession---- | Creates an anonymous 'BugzillaSession'. Note that some content--- will be hidden by Bugzilla when you make queries in this state.-anonymousSession :: BugzillaContext -> BugzillaSession-anonymousSession = AnonymousSession--intAsText :: Int -> T.Text-intAsText = T.pack . show---- | Searches Bugzilla and returns a list of 'Bug's. The 'SearchExpression'--- can be constructed conveniently using the operators in "Web.Bugzilla.Search".-searchBugs :: BugzillaSession -> SearchExpression -> IO [Bug]-searchBugs session search = do- BugList bugs <- doSearchBugs session search Nothing Nothing- return bugs---- | Similar to 'searchBugs', but return _all fields.-searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]-searchBugsAll session search = do- BugList bugs <- doSearchBugs session search (Just "_all") Nothing- return bugs---- | Like 'searchBugs', but returns a list of 'BugId's. You can--- retrieve the 'Bug' for each 'BugId' using 'getBug'. The combination--- of 'searchBugs'' and 'getBug' is much less efficient than--- 'searchBugs'. 'searchBugs'' is suitable for cases where you won't need to call--- 'getBug' most of the time - for example, polling to determine whether the--- set of bugs returned by a query has changed.-searchBugs' :: BugzillaSession -> SearchExpression -> IO [BugId]-searchBugs' session search = do- BugIdList bugs <- doSearchBugs session search (Just "id") Nothing- return bugs--doSearchBugs :: FromJSON a => BugzillaSession -> SearchExpression -> Maybe T.Text -> Maybe (Int, Int) -> IO a-doSearchBugs session search includeField limits = do- let fieldsQuery = case includeField of- Nothing -> []- Just field -> [("include_fields", Just field)]- limitQuery = case limits of- Nothing -> []- Just (limit, offset) -> [("limit", Just $ intAsText limit),- ("offset", Just $ intAsText offset)]- searchQuery = evalSearchExpr search- req = newBzRequest session ["bug"] (limitQuery ++ fieldsQuery ++ searchQuery)- sendBzRequest session req---- | Search Bugzilla and returns a limited number of results. You can--- call this repeatedly and use 'offset' to retrieve the results of--- a large query incrementally. Note that most Bugzillas won't--- return all of the results for a very large query by default, but--- you can request this by calling 'searchBugsWithLimit' with 0 for--- the limit.-searchBugsWithLimit :: BugzillaSession- -> Int -- ^ The maximum number of results to return.- -> Int -- ^ The offset from the first result to start from.- -> SearchExpression- -> IO [Bug]-searchBugsWithLimit session limit offset search = do- BugList bugs <- doSearchBugs session search Nothing (Just (limit, offset))- return bugs---- | Similar to 'searchBugsWithLimit', but return _all fields.-searchBugsAllWithLimit :: BugzillaSession- -> Int -- ^ The maximum number of results to return.- -> Int -- ^ The offset from the first result to start from.- -> SearchExpression- -> IO [Bug]-searchBugsAllWithLimit session limit offset search = do- BugList bugs <- doSearchBugs session search (Just "_all") (Just (limit, offset))- return bugs---- | Like 'searchBugsWithLimit', but returns a list of 'BugId's. See--- 'searchBugs'' for more discussion.-searchBugsWithLimit' :: BugzillaSession- -> Int -- ^ The maximum number of results to return.- -> Int -- ^ The offset from the first result to start from.- -> SearchExpression- -> IO [BugId]-searchBugsWithLimit' session limit offset search = do- BugIdList bugs <- doSearchBugs session search (Just "id") (Just (limit, offset))- return bugs---- | Retrieve a bug by bug number.-getBug :: BugzillaSession -> BugId -> IO (Maybe Bug)-getBug session bid = getBugIncludeFields session bid []---- | Retrieve all bug field by bug number-getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)-getBugAll session bid = getBugIncludeFields session bid ["_all"]---- | Retrieve a bug by bug number with fields-getBugIncludeFields :: BugzillaSession -> BugId -> [T.Text] -> IO (Maybe Bug)-getBugIncludeFields session bid includeFields = do- let req = newBzRequest session ["bug", intAsText bid] query- (BugList bugs) <- sendBzRequest session req- case bugs of- [bug] -> return $ Just bug- [] -> return Nothing- _ -> throw $ BugzillaUnexpectedValue- "Request for a single bug returned multiple bugs"- where- query = map (\f -> ("include_fields", Just f)) includeFields---- | Retrieve a bug by attachment number.-getAttachment :: BugzillaSession -> AttachmentId -> IO (Maybe Attachment)-getAttachment session aid = do- let req = newBzRequest session ["bug", "attachment", intAsText aid] []- (AttachmentList as) <- sendBzRequest session req- case as of- [a] -> return $ Just a- [] -> return Nothing- _ -> throw $ BugzillaUnexpectedValue- "Request for a single attachment returned multiple attachments"---- | Get all attachments for a bug.-getAttachments :: BugzillaSession -> BugId -> IO [Attachment]-getAttachments session bid = do- let req = newBzRequest session ["bug", intAsText bid, "attachment"] []- (AttachmentList as) <- sendBzRequest session req- return as---- | Get all comments for a bug.-getComments :: BugzillaSession -> BugId -> IO [Comment]-getComments session bid = do- let req = newBzRequest session ["bug", intAsText bid, "comment"] []- (CommentList as) <- sendBzRequest session req- return as---- | Get the history for a bug.-getHistory :: BugzillaSession -> BugId -> IO History-getHistory session bid = do- let req = newBzRequest session ["bug", intAsText bid, "history"] []- sendBzRequest session req---- | Search user names and emails using a substring search.-searchUsers :: BugzillaSession -> T.Text -> IO [User]-searchUsers session text = do- let req = newBzRequest session ["user"] [("match", Just text)]- (UserList users) <- sendBzRequest session req- return users---- | Get a user by email.-getUser :: BugzillaSession -> UserEmail -> IO (Maybe User)-getUser session user = do- let req = newBzRequest session ["user", user] []- (UserList users) <- sendBzRequest session req- case users of- [u] -> return $ Just u- [] -> return Nothing- _ -> throw $ BugzillaUnexpectedValue- "Request for a single user returned multiple users"---- | Get a user by user ID.-getUserById :: BugzillaSession -> UserId -> IO (Maybe User)-getUserById session uid = do- let req = newBzRequest session ["user", intAsText uid] []- (UserList users) <- sendBzRequest session req- case users of- [u] -> return $ Just u- [] -> return Nothing- _ -> throw $ BugzillaUnexpectedValue- "Request for a single user returned multiple users"
− src/Web/Bugzilla/RedHat/Internal/Network.hs
@@ -1,127 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE OverloadedStrings #-}--module Web.Bugzilla.RedHat.Internal.Network-( BugzillaServer-, BugzillaContext (..)-, BugzillaApikey (..)-, BugzillaToken (..)-, BugzillaSession (..)-, BugzillaException (..)-, QueryPart-, Request-, requestUrl-, newBzRequest-, sendBzRequest-) where--import Blaze.ByteString.Builder (toByteString)-#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>))-#endif-import Control.Exception (Exception, throw)-import Control.Monad (mzero)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Resource (runResourceT)-import Data.Aeson-import Data.Maybe (fromMaybe)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL-#if !MIN_VERSION_base(4,11,0)-import Data.Monoid ((<>))-#endif-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import Data.Typeable-import Network.HTTP.Conduit (Manager, Request(..), Response(..), defaultRequest, host, httpLbs, path, queryString, secure, parseRequest)-import Network.HTTP.Types.URI (QueryText, encodePathSegments, renderQueryText)--type BugzillaServer = T.Text---- | Holds information about a 'BugzillaServer' and manages outgoing--- connections. You can use 'newBugzillaContext' to create one.-data BugzillaContext = BugzillaContext- { bzServer :: BugzillaServer- , bzManager :: Manager- }--newtype BugzillaToken = BugzillaToken T.Text--newtype BugzillaApikey = BugzillaApikey T.Text--instance FromJSON BugzillaToken where- parseJSON (Object v) = BugzillaToken <$> v .: "token"- parseJSON _ = mzero---- | A session for Bugzilla queries. Use 'anonymousSession' and--- 'loginSession', as appropriate, to create one.-data BugzillaSession = AnonymousSession BugzillaContext- | LoginSession BugzillaContext BugzillaToken- | ApikeySession BugzillaContext BugzillaApikey--bzContext :: BugzillaSession -> BugzillaContext-bzContext (AnonymousSession ctx) = ctx-bzContext (LoginSession ctx _) = ctx-bzContext (ApikeySession ctx _) = ctx--data BugzillaException- = BugzillaJSONParseError String- | BugzillaAPIError Int String- | BugzillaUnexpectedValue String- deriving (Show, Typeable)-instance Exception BugzillaException--type QueryPart = (T.Text, Maybe T.Text)--requestUrl :: Request -> B.ByteString-requestUrl req = "https://" <> host req <> path req <> queryString req--sslRequest :: Request-sslRequest =- defaultRequest {- secure = True,- port = 443- }--newBzRequest :: BugzillaSession -> [T.Text] -> QueryText -> Request-newBzRequest session methodParts query =- baseRequest {- path = toByteString $ encodePathSegments $ "rest" : methodParts,- queryString = toByteString $ renderQueryText True queryWithToken- }- where- -- Try to parse the bzServer first, if it has a scheme then use it as the base request,- -- otherwise force a secure ssl request.- baseRequest :: Request- baseRequest = fromMaybe (sslRequest { host = serverBytes }) (parseRequest serverStr)- serverBytes = TE.encodeUtf8 serverTxt- serverStr = T.unpack serverTxt- serverTxt = bzServer . bzContext $ session- queryWithToken = case session of- AnonymousSession _ -> query- LoginSession _ (BugzillaToken token) -> ("token", Just token) : query- ApikeySession _ (BugzillaApikey token) -> ("api_key", Just token) : query--data BzError = BzError Int String- deriving (Eq, Show)--instance FromJSON BzError where- parseJSON (Object v) = BzError <$> v .: "code"- <*> v .: "message"- parseJSON _ = mzero--handleError :: String -> BL.ByteString -> IO b-handleError parseError body = do- let mError = eitherDecode body- case mError of- Left _ -> throw $ BugzillaJSONParseError parseError- Right (BzError code msg) -> throw $ BugzillaAPIError code msg--sendBzRequest :: FromJSON a => BugzillaSession -> Request -> IO a-sendBzRequest session req = runResourceT $ do- response <- liftIO $ httpLbs req . bzManager . bzContext $ session- let mResult = eitherDecode $ responseBody response- case mResult of- Left msg -> liftIO $ handleError msg (responseBody response)- Right decoded -> return decoded
− src/Web/Bugzilla/RedHat/Internal/Search.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}---- | Operators which can be used to construct queries for Bugzilla.--- These operators are intended to be typesafe: you should not be--- able to construct a query that causes Bugzilla to return an--- error. If you *are* able to construct an erroneous query, please--- report a bug.-module Web.Bugzilla.RedHat.Internal.Search-( FieldType-, SearchTerm (..)-, SearchExpression (..)-, evalSearchExpr-) where--import Data.List-import qualified Data.Text as T-import Data.Time.Clock (UTCTime(..))-import Data.Time.ISO8601 (formatISO8601)--import Web.Bugzilla.RedHat.Internal.Network-import Web.Bugzilla.RedHat.Internal.Types--class FieldType a where fvAsText :: a -> T.Text--instance FieldType T.Text where fvAsText = id-instance FieldType Int where fvAsText = T.pack . show-instance FieldType UTCTime where fvAsText = T.pack . formatISO8601--instance FieldType Bool where- fvAsText True = "true"- fvAsText False = "false"--instance FieldType a => FieldType [a] where- fvAsText = T.intercalate "," . map fvAsText--data SearchTerm where- UnaryOp :: FieldType a => T.Text -> Field a -> SearchTerm- BinaryOp :: (FieldType a, FieldType b) => T.Text -> Field a -> b -> SearchTerm- EqTerm :: (FieldType a, FieldType b) => Field a -> b -> SearchTerm---- | A Boolean expression which can be used to query Bugzilla.-data SearchExpression- = And [SearchExpression]- | Or [SearchExpression]- | Not SearchExpression- | Term SearchTerm--taggedQueryPart :: Int -> Char -> T.Text -> QueryPart-taggedQueryPart t k v = (T.cons k . T.pack . show $ t, Just v)--termQuery :: FieldType b => Int -> Field a -> T.Text -> b -> [QueryPart]-termQuery t f o v = [taggedQueryPart t 'f' (searchFieldName f),- taggedQueryPart t 'o' o,- taggedQueryPart t 'v' (fvAsText v)]--evalSearchTerm :: Int -> SearchTerm -> [QueryPart]-evalSearchTerm t (UnaryOp op field) = termQuery t field op ("" :: T.Text)-evalSearchTerm t (BinaryOp op field val) = termQuery t field op val-evalSearchTerm _ (EqTerm field val) = [(searchFieldName field, Just . fvAsText $ val)]--evalSearchExpr :: SearchExpression -> [QueryPart]-evalSearchExpr e = snd $ evalSearchExpr' 1 e- where- evalExprGroup :: Int -> [SearchExpression] -> (Int, [QueryPart])- evalExprGroup t es =- let (subExprT, subExprQs) = foldl' evalSubExpr (t + 1, []) es- qs = taggedQueryPart t 'f' "OP" :- taggedQueryPart subExprT 'f' "CP" :- subExprQs- in (subExprT + 1, qs)-- evalSubExpr :: (Int, [QueryPart]) -> SearchExpression -> (Int, [QueryPart])- evalSubExpr (t, qs) expr = let (nextT, qs') = evalSearchExpr' t expr- in (nextT, qs ++ qs')-- evalSearchExpr' :: Int -> SearchExpression -> (Int, [QueryPart])- evalSearchExpr' t (And es) = evalExprGroup t es-- evalSearchExpr' t (Or es) =- let (groupT, groupQs) = evalExprGroup t es- qs = taggedQueryPart t 'j' "OR" : groupQs- in (groupT + 1, qs)-- evalSearchExpr' t (Not es) =- let (groupT, groupQs) = evalSearchExpr' t es- qs = taggedQueryPart t 'n' "1" : groupQs- in (groupT + 1, qs)-- evalSearchExpr' t (Term term) = (t + 1, evalSearchTerm t term)
− src/Web/Bugzilla/RedHat/Internal/Types.hs
@@ -1,747 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-}--module Web.Bugzilla.RedHat.Internal.Types-( BugId-, AttachmentId-, CommentId-, UserId-, EventId-, FlagId-, FlagType-, UserEmail-, Field (..)-, User (..)-, UserList (..)-, Flag (..)-, ExternalType (..)-, ExternalBug (..)-, Bug (..)-, BugList (..)-, BugIdList (..)-, Attachment (..)-, AttachmentList (..)-, Comment (..)-, CommentList (..)-, History (..)-, HistoryEvent (..)-, Change (..)-, Modification (..)-, fieldName-, searchFieldName-) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative (pure, (<$>), (<*>))-#endif-import Control.Monad (mzero)-import Data.Aeson-#if MIN_VERSION_aeson(1,0,0)-import Data.Aeson.Text-#else-import Data.Aeson.Encode-#endif-import Data.Aeson.Types-#if MIN_VERSION_aeson(2,0,0)-import Data.Aeson.Key-import qualified Data.Aeson.KeyMap as M-#else-import qualified Data.HashMap.Strict as M-#endif-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TLB-import qualified Data.Text.Read as TR-import qualified Data.Vector as V-import Data.Time.Clock (UTCTime(..))--type BugId = Int-type AttachmentId = Int-type CommentId = Int-type UserId = Int-type EventId = Int-type FlagId = Int-type FlagType = Int-type UserEmail = T.Text---- | A field which you can search by using 'Web.Bugzilla.searchBugs' or track--- changes to using 'Web.Bugzilla.getHistory'. To get a human-readable name for--- a field, use 'fieldName'.-data Field a where- AliasField :: Field [T.Text] -- Alias- AssignedToField :: Field UserEmail -- Assignee- AttachmentCreatorField :: Field UserEmail -- Attachment creator- AttachmentDataField :: Field T.Text -- Attachment data- AttachmentDescriptionField :: Field T.Text -- Attachment description- AttachmentFilenameField :: Field T.Text -- Attachment filename- AttachmentIsObsoleteField :: Field Bool -- Attachment is obsolete- AttachmentIsPatchField :: Field Bool -- Attachment is patch- AttachmentIsPrivateField :: Field Bool -- Attachment is private- AttachmentMimetypeField :: Field T.Text -- Attachment mime type- BlocksField :: Field Int -- Blocks- BugIdField :: Field Int -- Bug ID- CcField :: Field UserEmail -- CC- CcListAccessibleField :: Field Bool -- CC list accessible- ClassificationField :: Field T.Text -- Classification- CommentField :: Field T.Text -- Comment- CommentIsPrivateField :: Field T.Text -- Comment is private- CommentTagsField :: Field T.Text -- Comment Tags- CommenterField :: Field UserEmail -- Commenter- ComponentField :: Field [T.Text] -- Component- ContentField :: Field T.Text -- Content- CreationDateField :: Field UTCTime -- Creation date- DaysElapsedField :: Field Int -- Days since bug changed- DependsOnField :: Field Int -- Depends on- EverConfirmedField :: Field Bool -- Ever confirmed- FlagRequesteeField :: Field UserEmail -- Flag Requestee- FlagSetterField :: Field UserEmail -- Flag Setter- FlagsField :: Field T.Text -- Flags- GroupField :: Field T.Text -- Group- KeywordsField :: Field [T.Text] -- Keywords- ChangedField :: Field UTCTime -- Changed- CommentCountField :: Field Int -- Number of Comments- OperatingSystemField :: Field T.Text -- OS- HardwareField :: Field T.Text -- Hardware- PriorityField :: Field T.Text -- Priority- ProductField :: Field T.Text -- Product- QaContactField :: Field UserEmail -- QA Contact- ReporterField :: Field UserEmail -- Reporter- ReporterAccessibleField :: Field Bool -- Reporter accessible- ResolutionField :: Field T.Text -- Resolution- RestrictCommentsField :: Field Bool -- Restrict Comments- SeeAlsoField :: Field T.Text -- See Also- SeverityField :: Field T.Text -- Severity- StatusField :: Field T.Text -- Status- WhiteboardField :: Field T.Text -- Whiteboard- SummaryField :: Field T.Text -- Summary- TagsField :: Field T.Text -- Tags- TargetMilestoneField :: Field T.Text -- Target Milestone- TimeSinceAssigneeTouchedField :: Field Int -- Time Since Assignee Touched- BugURLField :: Field T.Text -- URL- VersionField :: Field T.Text -- Version- VotesField :: Field T.Text -- Votes- CustomField :: T.Text -> Field T.Text -- (Custom fields)--instance Eq (Field a) where- (CustomField a) == (CustomField b) = a == b- (CustomField _) == _ = False- _ == (CustomField _) = False- a == b = searchFieldName a == searchFieldName b--instance Show (Field a) where- show AliasField = "AliasField"- show AssignedToField = "AssignedToField"- show AttachmentCreatorField = "AttachmentCreatorField"- show AttachmentDataField = "AttachmentDataField"- show AttachmentDescriptionField = "AttachmentDescriptionField"- show AttachmentFilenameField = "AttachmentFilenameField"- show AttachmentIsObsoleteField = "AttachmentIsObsoleteField"- show AttachmentIsPatchField = "AttachmentIsPatchField"- show AttachmentIsPrivateField = "AttachmentIsPrivateField"- show AttachmentMimetypeField = "AttachmentMimetypeField"- show BlocksField = "BlocksField"- show BugIdField = "BugIdField"- show CcField = "CcField"- show CcListAccessibleField = "CcListAccessibleField"- show ClassificationField = "ClassificationField"- show CommentField = "CommentField"- show CommentIsPrivateField = "CommentIsPrivateField"- show CommentTagsField = "CommentTagsField"- show CommenterField = "CommenterField"- show ComponentField = "ComponentField"- show ContentField = "ContentField"- show CreationDateField = "CreationDateField"- show DaysElapsedField = "DaysElapsedField"- show DependsOnField = "DependsOnField"- show EverConfirmedField = "EverConfirmedField"- show FlagRequesteeField = "FlagRequesteeField"- show FlagSetterField = "FlagSetterField"- show FlagsField = "FlagsField"- show GroupField = "GroupField"- show KeywordsField = "KeywordsField"- show ChangedField = "ChangedField"- show CommentCountField = "CommentCountField"- show OperatingSystemField = "OperatingSystemField"- show HardwareField = "HardwareField"- show PriorityField = "PriorityField"- show ProductField = "ProductField"- show QaContactField = "QaContactField"- show ReporterField = "ReporterField"- show ReporterAccessibleField = "ReporterAccessibleField"- show ResolutionField = "ResolutionField"- show RestrictCommentsField = "RestrictCommentsField"- show SeeAlsoField = "SeeAlsoField"- show SeverityField = "SeverityField"- show StatusField = "StatusField"- show WhiteboardField = "WhiteboardField"- show SummaryField = "SummaryField"- show TagsField = "TagsField"- show TargetMilestoneField = "TargetMilestoneField"- show TimeSinceAssigneeTouchedField = "TimeSinceAssigneeTouchedField"- show BugURLField = "BugURLField"- show VersionField = "VersionField"- show VotesField = "VotesField"- show (CustomField name) = "CustomField " ++ show name---- | Provides a human-readable name for a 'Field'.-fieldName :: Field a -> T.Text-fieldName AliasField = "Alias"-fieldName AssignedToField = "Assigned to"-fieldName AttachmentCreatorField = "Attachment creator"-fieldName AttachmentDataField = "Attachment data"-fieldName AttachmentDescriptionField = "Attachment description"-fieldName AttachmentFilenameField = "Attachment filename"-fieldName AttachmentIsObsoleteField = "Attachment is obsolete"-fieldName AttachmentIsPatchField = "Attachment is patch"-fieldName AttachmentIsPrivateField = "Attachment is private"-fieldName AttachmentMimetypeField = "Attachment MIME type"-fieldName BlocksField = "Blocks"-fieldName BugIdField = "BugId"-fieldName CcField = "CC"-fieldName CcListAccessibleField = "CC list is accessible"-fieldName ClassificationField = "Classification"-fieldName CommentField = "Comment"-fieldName CommentIsPrivateField = "Comment is private"-fieldName CommentTagsField = "Comment tags"-fieldName CommenterField = "Commenter"-fieldName ComponentField = "Component"-fieldName ContentField = "Content"-fieldName CreationDateField = "Creation date"-fieldName DaysElapsedField = "Days elapsed"-fieldName DependsOnField = "Depends on"-fieldName EverConfirmedField = "Ever confirmed"-fieldName FlagRequesteeField = "Flag requestee"-fieldName FlagSetterField = "Flag setter"-fieldName FlagsField = "Flags"-fieldName GroupField = "Group"-fieldName KeywordsField = "Keywords"-fieldName ChangedField = "Changed"-fieldName CommentCountField = "Comment count"-fieldName OperatingSystemField = "Operating system"-fieldName HardwareField = "Hardware"-fieldName PriorityField = "Priority"-fieldName ProductField = "Product"-fieldName QaContactField = "QA contact"-fieldName ReporterField = "Reporter"-fieldName ReporterAccessibleField = "Reporter accessible"-fieldName ResolutionField = "Resolution"-fieldName RestrictCommentsField = "Restrict comments"-fieldName SeeAlsoField = "See also"-fieldName SeverityField = "Severity"-fieldName StatusField = "Status"-fieldName WhiteboardField = "Whiteboard"-fieldName SummaryField = "Summary"-fieldName TagsField = "Tags"-fieldName TargetMilestoneField = "Target milestone"-fieldName TimeSinceAssigneeTouchedField = "Time since assignee touched"-fieldName BugURLField = "Bug URL"-fieldName VersionField = "Version"-fieldName VotesField = "Votes"-fieldName (CustomField name) = T.concat ["Custom field \"", T.pack (show name), "\""]--searchFieldName :: Field a -> T.Text-searchFieldName AliasField = "alias"-searchFieldName AssignedToField = "assigned_to"-searchFieldName AttachmentCreatorField = "attachments.submitter"-searchFieldName AttachmentDataField = "attach_data.thedata"-searchFieldName AttachmentDescriptionField = "attachments.description"-searchFieldName AttachmentFilenameField = "attachments.filename"-searchFieldName AttachmentIsObsoleteField = "attachments.isobsolete"-searchFieldName AttachmentIsPatchField = "attachments.ispatch"-searchFieldName AttachmentIsPrivateField = "attachments.isprivate"-searchFieldName AttachmentMimetypeField = "attachments.mimetype"-searchFieldName BlocksField = "blocked"-searchFieldName BugIdField = "bug_id"-searchFieldName CcField = "cc"-searchFieldName CcListAccessibleField = "cclist_accessible"-searchFieldName ClassificationField = "classification"-searchFieldName CommentField = "longdesc"-searchFieldName CommentIsPrivateField = "longdescs.isprivate"-searchFieldName CommentTagsField = "comment_tag"-searchFieldName CommenterField = "commenter"-searchFieldName ComponentField = "component"-searchFieldName ContentField = "content"-searchFieldName CreationDateField = "creation_ts"-searchFieldName DaysElapsedField = "days_elapsed"-searchFieldName DependsOnField = "dependson"-searchFieldName EverConfirmedField = "everconfirmed"-searchFieldName FlagRequesteeField = "requestees.login_name"-searchFieldName FlagSetterField = "setters.login_name"-searchFieldName FlagsField = "flagtypes.name"-searchFieldName GroupField = "bug_group"-searchFieldName KeywordsField = "keywords"-searchFieldName ChangedField = "delta_ts"-searchFieldName CommentCountField = "longdescs.count"-searchFieldName OperatingSystemField = "op_sys"-searchFieldName HardwareField = "rep_platform"-searchFieldName PriorityField = "priority"-searchFieldName ProductField = "product"-searchFieldName QaContactField = "qa_contact"-searchFieldName ReporterField = "reporter"-searchFieldName ReporterAccessibleField = "reporter_accessible"-searchFieldName ResolutionField = "resolution"-searchFieldName RestrictCommentsField = "restrict_comments"-searchFieldName SeeAlsoField = "see_also"-searchFieldName SeverityField = "bug_severity"-searchFieldName StatusField = "bug_status"-searchFieldName WhiteboardField = "status_whiteboard"-searchFieldName SummaryField = "short_desc"-searchFieldName TagsField = "tag"-searchFieldName TargetMilestoneField = "target_milestone"-searchFieldName TimeSinceAssigneeTouchedField = "owner_idle_time"-searchFieldName BugURLField = "bug_file_loc"-searchFieldName VersionField = "version"-searchFieldName VotesField = "votes"-searchFieldName (CustomField name) = name---- | A Bugzilla user.-data User = User- { userId :: !UserId- , userEmail :: Maybe UserEmail- , userName :: T.Text- , userRealName :: T.Text- } deriving (Eq, Ord, Show)--instance FromJSON User where- parseJSON (Object v) =- User <$> v .: "id"- <*> v .:? "email"- <*> v .: "name"- <*> v .: "real_name"- parseJSON _ = mzero--newtype UserList = UserList [User]- deriving (Eq, Show)--instance FromJSON UserList where- parseJSON (Object v) = UserList <$> v .: "users"- parseJSON _ = mzero---- | Flags, which may be set on an attachment or on a bug directly.-data Flag = Flag- { flagId :: !FlagId- , flagTypeId :: !FlagType- , flagName :: T.Text- , flagSetter :: UserEmail- , flagStatus :: T.Text- , flagCreationDate :: UTCTime- , flagModificationDate :: UTCTime- , flagRequestee :: Maybe UserEmail- } deriving (Eq, Ord, Show)--instance FromJSON Flag where- parseJSON (Object v) =- Flag <$> v .: "id"- <*> v .: "type_id"- <*> v .: "name"- <*> v .: "setter"- <*> v .: "status"- <*> v .: "creation_date"- <*> v .: "modification_date"- <*> v .:? "requestee"- parseJSON _ = mzero---- | An external bug type-data ExternalType = ExternalType- { externalTypeDescription :: T.Text- , externalTypeUrl :: T.Text- , externalTypeId :: Int- , externalTypeType :: T.Text- , externalTypeFullUrl :: T.Text- } deriving (Eq, Ord, Show)--instance FromJSON ExternalType where- parseJSON (Object v) =- ExternalType <$> v .: "description"- <*> v .: "url"- <*> v .: "id"- <*> v .: "type"- <*> v .: "full_url"- parseJSON _ = mzero---- | An external bug.-data ExternalBug = ExternalBug- { externalDescription :: T.Text- , externalBzId :: Int- , externalPriority :: T.Text- , externalBugId :: T.Text- , externalStatus :: T.Text- , externalId :: Int- , externalType :: ExternalType- } deriving (Eq, Ord, Show)--instance FromJSON ExternalBug where- parseJSON (Object v) =- ExternalBug <$> v .: "ext_description"- <*> v .: "ext_bz_id"- <*> v .: "ext_priority"- <*> v .: "ext_bz_bug_id"- <*> v .: "ext_status"- <*> v .: "id"- <*> v .: "type"- parseJSON _ = mzero---- | A Bugzilla bug.-data Bug = Bug- { bugId :: !BugId- , bugAlias :: Maybe [T.Text]- , bugAssignedTo :: UserEmail- , bugAssignedToDetail :: User- , bugBlocks :: [BugId]- , bugCc :: [UserEmail]- , bugCcDetail :: [User]- , bugClassification :: T.Text- , bugComponent :: [T.Text]- , bugCreationTime :: UTCTime- , bugCreator :: UserEmail- , bugCreatorDetail :: User- , bugDependsOn :: [BugId]- , bugDupeOf :: Maybe BugId- , bugFlags :: Maybe [Flag]- , bugGroups :: [T.Text]- , bugIsCcAccessible :: Bool- , bugIsConfirmed :: Bool- , bugIsCreatorAccessible :: Bool- , bugIsOpen :: Bool- , bugKeywords :: [T.Text]- , bugLastChangeTime :: UTCTime- , bugOpSys :: T.Text- , bugPlatform :: T.Text- , bugPriority :: T.Text- , bugProduct :: T.Text- , bugQaContact :: UserEmail- , bugResolution :: T.Text- , bugSeeAlso :: [T.Text]- , bugSeverity :: T.Text- , bugStatus :: T.Text- , bugSummary :: T.Text- , bugTargetMilestone :: T.Text- , bugUrl :: T.Text- , bugVersion :: [T.Text]- , bugWhiteboard :: T.Text- , bugCustomFields ::-#if MIN_VERSION_aeson(2,0,0)- M.KeyMap T.Text-#else- M.HashMap T.Text T.Text-#endif-, bugExternalBugs :: Maybe [ExternalBug]- } deriving (Eq, Show)--instance FromJSON Bug where- parseJSON (Object v) =- Bug <$> v .: "id"- <*> v .:? "alias"- <*> v .: "assigned_to"- <*> v .: "assigned_to_detail"- <*> v .: "blocks"- <*> v .: "cc"- <*> v .: "cc_detail"- <*> v .: "classification"- <*> v .: "component"- <*> v .: "creation_time"- <*> v .: "creator"- <*> v .: "creator_detail"- <*> v .: "depends_on"- <*> v .:? "dupe_of"- <*> v .:? "flags"- <*> v .: "groups"- <*> v .: "is_cc_accessible"- <*> v .: "is_confirmed"- <*> v .: "is_creator_accessible"- <*> v .: "is_open"- <*> v .: "keywords"- <*> v .: "last_change_time"- <*> v .: "op_sys"- <*> v .: "platform"- <*> v .: "priority"- <*> v .: "product"- <*> v .: "qa_contact"- <*> v .: "resolution"- <*> v .: "see_also"- <*> v .: "severity"- <*> v .: "status"- <*> v .: "summary"- <*> v .: "target_milestone"- <*> v .: "url"- <*> v .: "version"- <*> v .: "whiteboard"- <*> pure (customFields v)- <*> v .:? "external_bugs"- parseJSON _ = mzero--customFields :: Object ->-#if MIN_VERSION_aeson(2,0,0)- M.KeyMap T.Text-#else- M.HashMap T.Text T.Text-#endif-customFields = M.map stringifyCustomFields- . M.filterWithKey filterCustomFields- where- stringifyCustomFields :: Value -> T.Text- stringifyCustomFields (String t) = t- stringifyCustomFields v = T.concat- . TL.toChunks- . TLB.toLazyText- . encodeToTextBuilder- . toJSON- $ v-- filterCustomFields k _ = "cf_" `T.isPrefixOf` toText k-#if !MIN_VERSION_aeson(2,0,0)- where toText = id-#endif--newtype BugList = BugList [Bug]- deriving (Eq, Show)--instance FromJSON BugList where- parseJSON (Object v) = BugList <$> v .: "bugs"- parseJSON _ = mzero--newtype BugIdList = BugIdList [BugId]- deriving (Eq, Show)--instance FromJSON BugIdList where- parseJSON (Object v) = do- bugs <- v .: "bugs"- bugIds <- mapM (.: "id") bugs- return $ BugIdList bugIds- parseJSON _ = mzero---- | An attachment to a bug.-data Attachment = Attachment- { attachmentId :: !AttachmentId- , attachmentBugId :: !BugId- , attachmentFileName :: T.Text- , attachmentSummary :: T.Text- , attachmentCreator :: UserEmail- , attachmentIsPrivate :: Bool- , attachmentIsObsolete :: Bool- , attachmentIsPatch :: Bool- , attachmentFlags :: [Flag]- , attachmentCreationTime :: UTCTime- , attachmentLastChangeTime :: UTCTime- , attachmentContentType :: T.Text- , attachmentSize :: !Int- , attachmentData :: T.Text- } deriving (Eq, Show)--instance FromJSON Attachment where- parseJSON (Object v) =- Attachment <$> v .: "id"- <*> v .: "bug_id"- <*> v .: "file_name"- <*> v .: "summary"- <*> v .: "creator"- <*> (fromNumericBool <$> v .: "is_private")- <*> (fromNumericBool <$> v .: "is_obsolete")- <*> (fromNumericBool <$> v .: "is_patch")- <*> v .: "flags"- <*> v .: "creation_time"- <*> v .: "last_change_time"- <*> v .: "content_type"- <*> v .: "size"- <*> v .: "data"- parseJSON _ = mzero--fromNumericBool :: Int -> Bool-fromNumericBool 0 = False-fromNumericBool _ = True--newtype AttachmentList = AttachmentList [Attachment]- deriving (Eq, Show)--instance FromJSON AttachmentList where- parseJSON (Object v) = do- attachmentsVal <- v .: "attachments"- bugsVal <- v .: "bugs"- case (attachmentsVal, bugsVal) of- (Object (M.toList -> [(_, as)]), _) -> AttachmentList . (:[]) <$> parseJSON as- (_, Object (M.toList -> [(_, as)])) -> AttachmentList <$> parseJSON as- _ -> mzero- parseJSON _ = mzero---- | A bug comment. To display these the way Bugzilla does, you'll--- need to call 'getUser' and use the 'userRealName' for each user.-data Comment = Comment- { commentId :: !CommentId- , commentBugId :: !BugId- , commentAttachmentId :: Maybe AttachmentId- , commentCount :: !Int- , commentText :: T.Text- , commentCreator :: UserEmail- , commentCreationTime :: UTCTime- , commentIsPrivate :: Bool- } deriving (Eq, Show)--instance FromJSON Comment where- parseJSON (Object v) =- Comment <$> v .: "id"- <*> v .: "bug_id"- <*> v .: "attachment_id"- <*> v .: "count"- <*> v .: "text"- <*> v .: "creator"- <*> v .: "creation_time"- <*> v .: "is_private"- parseJSON _ = mzero--newtype CommentList = CommentList [Comment]- deriving (Eq, Show)--instance FromJSON CommentList where- parseJSON (Object v) = do- bugsVal <- v .: "bugs"- case bugsVal of- Object (M.toList -> [(_, cs)]) ->- do comments <- withObject "comments" (.: "comments") cs- withArray "comment list" (\a -> CommentList <$> parseJSON (addCount a)) comments- _ -> mzero- parseJSON _ = mzero---- Note that we make the (possibly unwise) assumption that Bugzilla--- returns the comments in order. If it turns out that's not true, we--- can always sort by their 'id' to ensure correct results.-addCount :: V.Vector Value -> Value-addCount vs = Array $ V.zipWith addCount' (V.enumFromN 0 $ V.length vs) vs- where- addCount' :: Int -> Value -> Value- addCount' c (Object v) = Object $ M.insert "count" (Number $ fromIntegral c) v- addCount' _ v = v---- | History information for a bug.-data History = History- { historyBugId :: !BugId- , historyEvents :: [HistoryEvent]- } deriving (Eq, Show)--instance FromJSON History where- parseJSON (Object v) = do- bugsVal <- v .: "bugs"- case bugsVal of- Array (V.toList -> [history]) ->- withObject "history"- (\h -> History <$> h .: "id"- <*> parseHistoryEvents h)- history- _ -> mzero- parseJSON _ = mzero--parseHistoryEvents :: Object -> Parser [HistoryEvent]-parseHistoryEvents h = do- events <- h .: "history"- withArray "event list" (parseJSON . addCount) events---- | An event in a bug's history.-data HistoryEvent = HistoryEvent- { historyEventId :: EventId -- ^ A sequential event id.- , historyEventTime :: UTCTime -- ^ When the event occurred.- , historyEventUser :: UserEmail -- ^ Which user was responsible.- , historyEventChanges :: [Change] -- ^ All the changes which are- -- part of this event.- } deriving (Eq, Show)--instance FromJSON HistoryEvent where- parseJSON (Object v) =- HistoryEvent <$> v .: "count"- <*> v .: "when"- <*> v .: "who"- <*> v .: "changes"- parseJSON _ = mzero---- | A single change which is part of an event. Different constructors--- are used according to the type of the field. The 'Modification'--- describes the value of the field before and after the change.-data Change- = TextFieldChange (Field T.Text) (Modification T.Text)- | ListFieldChange (Field [T.Text]) (Modification [T.Text])- | IntFieldChange (Field Int) (Modification Int)- | TimeFieldChange (Field UTCTime) (Modification UTCTime)- | BoolFieldChange (Field Bool) (Modification Bool)- deriving (Eq, Show)--instance FromJSON Change where- parseJSON (Object v) = do- changedField <- v .: "field_name"- case changedField of- "alias" -> ListFieldChange AliasField <$> parseModification v- "assigned_to" -> TextFieldChange AssignedToField <$> parseModification v- "attachments.submitter" -> TextFieldChange AttachmentCreatorField <$> parseModification v- "attach_data.thedata" -> TextFieldChange AttachmentDataField <$> parseModification v- "attachments.description"-> TextFieldChange AttachmentDescriptionField <$> parseModification v- "attachments.filename" -> TextFieldChange AttachmentFilenameField <$> parseModification v- "attachments.isobsolete" -> BoolFieldChange AttachmentIsObsoleteField <$> parseModification v- "attachments.ispatch" -> BoolFieldChange AttachmentIsPatchField <$> parseModification v- "attachments.isprivate" -> BoolFieldChange AttachmentIsPrivateField <$> parseModification v- "attachments.mimetype" -> TextFieldChange AttachmentMimetypeField <$> parseModification v- "blocks" -> IntFieldChange BlocksField <$> parseModification v- "bug_id" -> IntFieldChange BugIdField <$> parseModification v- "cc" -> TextFieldChange CcField <$> parseModification v- "is_cc_accessible" -> BoolFieldChange CcListAccessibleField <$> parseModification v- "classification" -> TextFieldChange ClassificationField <$> parseModification v- "component" -> ListFieldChange ComponentField <$> parseModification v- "content" -> TextFieldChange ContentField <$> parseModification v- "creation_time" -> TimeFieldChange CreationDateField <$> parseModification v- "days_elapsed" -> IntFieldChange DaysElapsedField <$> parseModification v- "depends_on" -> IntFieldChange DependsOnField <$> parseModification v- "everconfirmed" -> BoolFieldChange EverConfirmedField <$> parseModification v- "flagtypes.name" -> TextFieldChange FlagsField <$> parseModification v- "bug_group" -> TextFieldChange GroupField <$> parseModification v- "keywords" -> ListFieldChange KeywordsField <$> parseModification v- "op_sys" -> TextFieldChange OperatingSystemField <$> parseModification v- "platform" -> TextFieldChange HardwareField <$> parseModification v- "priority" -> TextFieldChange PriorityField <$> parseModification v- "product" -> TextFieldChange ProductField <$> parseModification v- "qa_contact" -> TextFieldChange QaContactField <$> parseModification v- "reporter" -> TextFieldChange ReporterField <$> parseModification v- "reporter_accessible" -> BoolFieldChange ReporterAccessibleField <$> parseModification v- "resolution" -> TextFieldChange ResolutionField <$> parseModification v- "restrict_comments" -> BoolFieldChange RestrictCommentsField <$> parseModification v- "see_also" -> TextFieldChange SeeAlsoField <$> parseModification v- "severity" -> TextFieldChange SeverityField <$> parseModification v- "status" -> TextFieldChange StatusField <$> parseModification v- "whiteboard" -> TextFieldChange WhiteboardField <$> parseModification v- "summary" -> TextFieldChange SummaryField <$> parseModification v- "tag" -> TextFieldChange TagsField <$> parseModification v- "target_milestone" -> TextFieldChange TargetMilestoneField <$> parseModification v- "url" -> TextFieldChange BugURLField <$> parseModification v- "version" -> TextFieldChange VersionField <$> parseModification v- "votes" -> TextFieldChange VotesField <$> parseModification v- name -> TextFieldChange (CustomField name) <$> parseModification v- parseJSON _ = mzero---- | A description of how a field changed during a 'HistoryEvent'.-data (Eq a, Show a) => Modification a = Modification- { modRemoved :: Maybe a- , modAdded :: Maybe a- , modAttachmentId :: Maybe AttachmentId- } deriving (Eq, Show)--parseModification :: (FromJSON a, Eq b, Show b, ToModification a b) => Object -> Parser (Modification b)-parseModification v = Modification <$> (toMod =<< v .: "removed")- <*> (toMod =<< v .: "added")- <*> v .:? "attachment_id"--class ToModification a b | b -> a where toMod :: a -> Parser (Maybe b)-instance ToModification T.Text T.Text where toMod = return . Just-instance ToModification UTCTime UTCTime where toMod = return . Just--instance ToModification T.Text Int where- toMod v | v == "" = return Nothing- | otherwise = case TR.decimal v of- Left _ -> mzero- Right (i, _) -> return $ Just i--instance ToModification T.Text Bool where- toMod v | v == "0" = return $ Just False- | v == "1" = return $ Just True- | otherwise = mzero--instance ToModification T.Text [T.Text] where- toMod v = return . Just $ T.splitOn ", " v
− src/Web/Bugzilla/RedHat/Search.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}---- | A modified version of Web.Bugzilla.Search to support--- the list fields in Red Hat's modified bugzilla API.--module Web.Bugzilla.RedHat.Search-(- -- * Search operators- (.==.)-, (./=.)-, (.<.)-, (.<=.)-, (.>.)-, (.>=.)-, (.=~.)-, (./=~.)-, equalsAny-, contains-, containsCase-, containsAny-, containsAll-, changedBefore-, changedAfter-, changedSince-, changedUntil-, changedRange-, changedFrom-, changedTo-, changedBy-, contentMatches-, isEmpty-, isNotEmpty-, (.&&.)-, (.||.)-, not'-- -- * Search expressions-, Field (..)-, SearchExpression-, evalSearchExpr-) where--import qualified Data.Text as T-import Data.Time.Clock (UTCTime(..))--import Web.Bugzilla.RedHat.Internal.Search-import Web.Bugzilla.RedHat.Internal.Types--(.==.) :: FieldType a => Field a -> a -> SearchExpression-(.==.) = (Term .) . BinaryOp "equals"-infix 4 .==.--(./=.) :: FieldType a => Field a -> a -> SearchExpression-(./=.) = (Term .) . BinaryOp "notequals"-infix 4 ./=.--(.<.) :: FieldType a => Field a -> a -> SearchExpression-(.<.) = (Term .) . BinaryOp "lessthan"-infix 4 .<.--(.<=.) :: FieldType a => Field a -> a -> SearchExpression-(.<=.) = (Term .) . BinaryOp "lessthaneq"-infix 4 .<=.--(.>.) :: FieldType a => Field a -> a -> SearchExpression-(.>.) = (Term .) . BinaryOp "greaterthan"-infix 4 .>.--(.>=.) :: FieldType a => Field a -> a -> SearchExpression-(.>=.) = (Term .) . BinaryOp "greaterthaneq"-infix 4 .>=.--(.=~.) :: FieldType a => Field a -> a -> SearchExpression-(.=~.) = (Term .) . BinaryOp "regexp"--(./=~.) :: FieldType a => Field a -> a -> SearchExpression-(./=~.) = (Term .) . BinaryOp "notregexp"--equalsAny :: FieldType a => Field a -> [a] -> SearchExpression-equalsAny = (Term .) . BinaryOp "anyexact"--contains :: Field T.Text -> T.Text -> SearchExpression-contains = (Term .) . BinaryOp "substring"--containsCase :: Field T.Text -> T.Text -> SearchExpression-containsCase = (Term .) . BinaryOp "casesubstring"--containsAny :: Field T.Text -> [T.Text] -> SearchExpression-containsAny = (Term .) . BinaryOp "anywordssubstr"--containsAll :: Field T.Text -> [T.Text] -> SearchExpression-containsAll = (Term .) . BinaryOp "allwordssubstr"--changedBefore :: FieldType a => Field a -> UTCTime -> SearchExpression-changedBefore = (Term .) . BinaryOp "changedbefore"--changedAfter :: FieldType a => Field a -> UTCTime -> SearchExpression-changedAfter = (Term .) . BinaryOp "changedafter"---- | Filter bug changed since UTCTime-changedSince :: UTCTime -> SearchExpression-changedSince ts = Term $ EqTerm (CustomField "chfieldfrom") ts---- | Filter bug changed until UTCTime-changedUntil :: UTCTime -> SearchExpression-changedUntil ts = Term $ EqTerm (CustomField "chfieldto") ts---- | Filter bug changed in range-changedRange :: UTCTime -> UTCTime -> SearchExpression-changedRange from to = changedSince from .&&. changedUntil to--changedFrom :: FieldType a => Field a -> a -> SearchExpression-changedFrom = (Term .) . BinaryOp "changedfrom"--changedTo :: FieldType a => Field a -> a -> SearchExpression-changedTo = (Term .) . BinaryOp "changedto"--changedBy :: FieldType a => Field a -> UserEmail -> SearchExpression-changedBy = (Term .) . BinaryOp "changedby"--contentMatches :: T.Text -> SearchExpression-contentMatches = Term . BinaryOp "matches" ContentField--isEmpty :: FieldType a => Field a -> SearchExpression-isEmpty = Term . UnaryOp "isempty"--isNotEmpty :: FieldType a => Field a -> SearchExpression-isNotEmpty = Term . UnaryOp "isnotempty"--(.&&.) :: SearchExpression -> SearchExpression -> SearchExpression-(.&&.) (And as) (And bs) = And (as ++ bs)-(.&&.) (And as) a = And (as ++ [a])-(.&&.) a (And as) = And (a:as)-(.&&.) a b = And [a, b]-infixr 3 .&&.--(.||.) :: SearchExpression -> SearchExpression -> SearchExpression-(.||.) (Or as) (Or bs) = Or (as ++ bs)-(.||.) a (Or as) = Or (a:as)-(.||.) (Or as) a = Or (as ++ [a])-(.||.) a b = Or [a, b]-infixr 2 .||.--not' :: SearchExpression -> SearchExpression-not' (Not a) = a-not' a = Not a
+ src/Web/RedHatBugzilla.hs view
@@ -0,0 +1,249 @@+{-# LANGUAGE OverloadedStrings #-}++-- | This package is designed to provide an easy-to-use, typesafe+-- interface to querying Bugzilla from Haskell.+--+-- A modified version of Web.Bugzilla to support+-- the list fields in Red Hat's modified bugzilla API.+--+-- A very simple program using this package might look like this:+--+-- > let session = anonymousSession "https://bugzilla.redhat.com"+-- > user = "me@example.org"+-- > query = AssignedToField .==. user .&&.+-- > FlagRequesteeField .==. user .&&.+-- > (FlagsField `contains` "review" .||. FlagsField `contains` "feedback")+-- > bugs <- searchBugs session query+-- > mapM_ (putStrLn . show . bugSummary) bugs+--+-- There's a more in-depth demo program included with the+-- source code to this package.++module Web.RedHatBugzilla+( -- * Connecting to Bugzilla+ apikeySession+, anonymousSession++, BugzillaServer+, BugzillaSession (..)+, BugzillaApiKey (..)++ -- * Querying Bugzilla+, searchBugs+, searchBugsAll+, searchBugs'+, searchBugsWithLimit+, searchBugsAllWithLimit+, searchBugsWithLimit'+, getBug+, getBugAll+, getAttachment+, getAttachments+, getComments+, getHistory+, searchUsers+, getUser+, getUserById+, newBzRequest+, sendBzRequest+, intAsText++, Request+, BugId+, AttachmentId+, CommentId+, UserId+, EventId+, FlagId+, FlagType+, UserEmail+, Field (..)+, User (..)+, Flag (..)+, Bug (..)+, ExternalBug (..)+, ExternalType (..)+, Attachment (..)+, Comment (..)+, History (..)+, HistoryEvent (..)+, Change (..)+, Modification (..)+, fieldName++, BugzillaException (..)+) where++import Control.Exception (throw)+import Data.Aeson (FromJSON)+import qualified Data.Text as T++import Web.RedHatBugzilla.Internal.Network+import Web.RedHatBugzilla.Internal.Search+import Web.RedHatBugzilla.Internal.Types++-- | Creates a 'BugzillaSession' using the provided api key.+apikeySession :: BugzillaServer -> BugzillaApiKey -> BugzillaSession+apikeySession = ApiKeySession++-- | Creates an anonymous 'BugzillaSession'. Note that some content+-- will be hidden by Bugzilla when you make queries in this state.+anonymousSession :: BugzillaServer -> BugzillaSession+anonymousSession = AnonymousSession++intAsText :: Int -> T.Text+intAsText = T.pack . show++-- | Searches Bugzilla and returns a list of 'Bug's. The 'SearchExpression'+-- can be constructed conveniently using the operators in "Web.Bugzilla.Search".+searchBugs :: BugzillaSession -> SearchExpression -> IO [Bug]+searchBugs session search = do+ BugList bugs <- doSearchBugs session search Nothing Nothing+ return bugs++-- | Similar to 'searchBugs', but return _all fields.+searchBugsAll :: BugzillaSession -> SearchExpression -> IO [Bug]+searchBugsAll session search = do+ BugList bugs <- doSearchBugs session search (Just "_all") Nothing+ return bugs++-- | Like 'searchBugs', but returns a list of 'BugId's. You can+-- retrieve the 'Bug' for each 'BugId' using 'getBug'. The combination+-- of 'searchBugs'' and 'getBug' is much less efficient than+-- 'searchBugs'. 'searchBugs'' is suitable for cases where you won't need to call+-- 'getBug' most of the time - for example, polling to determine whether the+-- set of bugs returned by a query has changed.+searchBugs' :: BugzillaSession -> SearchExpression -> IO [BugId]+searchBugs' session search = do+ BugIdList bugs <- doSearchBugs session search (Just "id") Nothing+ return bugs++doSearchBugs :: FromJSON a => BugzillaSession -> SearchExpression -> Maybe T.Text -> Maybe (Int, Int) -> IO a+doSearchBugs session search includeField limits = do+ let fieldsQuery = case includeField of+ Nothing -> []+ Just field -> [("include_fields", Just field)]+ limitQuery = case limits of+ Nothing -> []+ Just (limit, offset) -> [("limit", Just $ intAsText limit),+ ("offset", Just $ intAsText offset)]+ searchQuery = evalSearchExpr search+ req = newBzRequest session ["bug"] (limitQuery ++ fieldsQuery ++ searchQuery)+ sendBzRequest req++-- | Search Bugzilla and returns a limited number of results. You can+-- call this repeatedly and use 'offset' to retrieve the results of+-- a large query incrementally. Note that most Bugzillas won't+-- return all of the results for a very large query by default, but+-- you can request this by calling 'searchBugsWithLimit' with 0 for+-- the limit.+searchBugsWithLimit :: BugzillaSession+ -> Int -- ^ The maximum number of results to return.+ -> Int -- ^ The offset from the first result to start from.+ -> SearchExpression+ -> IO [Bug]+searchBugsWithLimit session limit offset search = do+ BugList bugs <- doSearchBugs session search Nothing (Just (limit, offset))+ return bugs++-- | Similar to 'searchBugsWithLimit', but return _all fields.+searchBugsAllWithLimit :: BugzillaSession+ -> Int -- ^ The maximum number of results to return.+ -> Int -- ^ The offset from the first result to start from.+ -> SearchExpression+ -> IO [Bug]+searchBugsAllWithLimit session limit offset search = do+ BugList bugs <- doSearchBugs session search (Just "_all") (Just (limit, offset))+ return bugs++-- | Like 'searchBugsWithLimit', but returns a list of 'BugId's. See+-- 'searchBugs'' for more discussion.+searchBugsWithLimit' :: BugzillaSession+ -> Int -- ^ The maximum number of results to return.+ -> Int -- ^ The offset from the first result to start from.+ -> SearchExpression+ -> IO [BugId]+searchBugsWithLimit' session limit offset search = do+ BugIdList bugs <- doSearchBugs session search (Just "id") (Just (limit, offset))+ return bugs++-- | Retrieve a bug by bug number.+getBug :: BugzillaSession -> BugId -> IO (Maybe Bug)+getBug session bid = getBugIncludeFields session bid []++-- | Retrieve all bug field by bug number+getBugAll :: BugzillaSession -> BugId -> IO (Maybe Bug)+getBugAll session bid = getBugIncludeFields session bid ["_all"]++-- | Retrieve a bug by bug number with fields+getBugIncludeFields :: BugzillaSession -> BugId -> [T.Text] -> IO (Maybe Bug)+getBugIncludeFields session bid includeFields = do+ let req = newBzRequest session ["bug", intAsText bid] query+ (BugList bugs) <- sendBzRequest req+ case bugs of+ [bug] -> return $ Just bug+ [] -> return Nothing+ _ -> throw $ BugzillaUnexpectedValue+ "Request for a single bug returned multiple bugs"+ where+ query = map (\f -> ("include_fields", Just f)) includeFields++-- | Retrieve a bug by attachment number.+getAttachment :: BugzillaSession -> AttachmentId -> IO (Maybe Attachment)+getAttachment session aid = do+ let req = newBzRequest session ["bug", "attachment", intAsText aid] []+ (AttachmentList as) <- sendBzRequest req+ case as of+ [a] -> return $ Just a+ [] -> return Nothing+ _ -> throw $ BugzillaUnexpectedValue+ "Request for a single attachment returned multiple attachments"++-- | Get all attachments for a bug.+getAttachments :: BugzillaSession -> BugId -> IO [Attachment]+getAttachments session bid = do+ let req = newBzRequest session ["bug", intAsText bid, "attachment"] []+ (AttachmentList as) <- sendBzRequest req+ return as++-- | Get all comments for a bug.+getComments :: BugzillaSession -> BugId -> IO [Comment]+getComments session bid = do+ let req = newBzRequest session ["bug", intAsText bid, "comment"] []+ (CommentList as) <- sendBzRequest req+ return as++-- | Get the history for a bug.+getHistory :: BugzillaSession -> BugId -> IO History+getHistory session bid = do+ let req = newBzRequest session ["bug", intAsText bid, "history"] []+ sendBzRequest req++-- | Search user names and emails using a substring search.+searchUsers :: BugzillaSession -> T.Text -> IO [User]+searchUsers session text = do+ let req = newBzRequest session ["user"] [("match", Just text)]+ (UserList users) <- sendBzRequest req+ return users++-- | Get a user by email.+getUser :: BugzillaSession -> UserEmail -> IO (Maybe User)+getUser session user = do+ let req = newBzRequest session ["user", user] []+ (UserList users) <- sendBzRequest req+ case users of+ [u] -> return $ Just u+ [] -> return Nothing+ _ -> throw $ BugzillaUnexpectedValue+ "Request for a single user returned multiple users"++-- | Get a user by user ID.+getUserById :: BugzillaSession -> UserId -> IO (Maybe User)+getUserById session uid = do+ let req = newBzRequest session ["user", intAsText uid] []+ (UserList users) <- sendBzRequest req+ case users of+ [u] -> return $ Just u+ [] -> return Nothing+ _ -> throw $ BugzillaUnexpectedValue+ "Request for a single user returned multiple users"
+ src/Web/RedHatBugzilla/Internal/Network.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Web.RedHatBugzilla.Internal.Network+( BugzillaServer+, BugzillaApiKey (..)+, BugzillaSession (..)+, BugzillaException (..)+, QueryPart+, Request+, requestUrl+, newBzRequest+, sendBzRequest+) where++import Blaze.ByteString.Builder (toByteString)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>), (<*>))+#endif+import Control.Exception (Exception, throw)+import Control.Monad (mzero)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Resource (runResourceT)+import Data.Aeson+import Data.Maybe (fromMaybe)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Typeable+import Network.HTTP.Simple (defaultRequest, httpLBS, parseRequest)+import Network.HTTP.Conduit (Request(..), Response(..), host, path, port,+ queryString, requestHeaders, secure)+import Network.HTTP.Types.URI (QueryText, encodePathSegments, renderQueryText)++type BugzillaServer = T.Text++newtype BugzillaApiKey = BugzillaApiKey T.Text++-- | A session for Bugzilla queries. Use 'anonymousSession' and+-- 'loginSession', as appropriate, to create one.+data BugzillaSession = AnonymousSession BugzillaServer+ | ApiKeySession BugzillaServer BugzillaApiKey++bzServer :: BugzillaSession -> BugzillaServer+bzServer (AnonymousSession svr) = svr+bzServer (ApiKeySession svr _) = svr++data BugzillaException+ = BugzillaJSONParseError String+ | BugzillaAPIError Int String+ | BugzillaUnexpectedValue String+ deriving (Show, Typeable)++instance Exception BugzillaException++type QueryPart = (T.Text, Maybe T.Text)++requestUrl :: Request -> B.ByteString+requestUrl req = "https://" <> host req <> path req <> queryString req++sslRequest :: Request+sslRequest =+ defaultRequest {+ secure = True,+ port = 443+ }++newBzRequest :: BugzillaSession -> [T.Text] -> QueryText -> Request+newBzRequest session methodParts query =+ let req =+ baseRequest {+ path = toByteString $ encodePathSegments $ "rest" : methodParts,+ queryString = toByteString $ renderQueryText True query+ }+ in case session of+ ApiKeySession _ (BugzillaApiKey key) ->+ req { requestHeaders = [("Authorization",+ "Bearer " <> TE.encodeUtf8 key)] }+ _ -> req+ where+ -- Try to parse the bzServer first, if it has a scheme then use it as the base request,+ -- otherwise force a secure ssl request.+ baseRequest :: Request+ baseRequest = fromMaybe (sslRequest { host = serverBytes }) (parseRequest serverStr)+ serverBytes = TE.encodeUtf8 serverTxt+ serverStr = T.unpack serverTxt+ serverTxt = bzServer session++data BzError = BzError Int String+ deriving (Eq, Show)++instance FromJSON BzError where+ parseJSON (Object v) = BzError <$> v .: "code"+ <*> v .: "message"+ parseJSON _ = mzero++handleError :: String -> BL.ByteString -> IO b+handleError parseError body = do+ let mError = eitherDecode body+ case mError of+ Left _ -> throw $ BugzillaJSONParseError parseError+ Right (BzError code msg) -> throw $ BugzillaAPIError code msg++sendBzRequest :: FromJSON a => Request -> IO a+sendBzRequest req = runResourceT $ do+ response <- liftIO $ httpLBS req+ let mResult = eitherDecode $ responseBody response+ case mResult of+ Left msg -> liftIO $ handleError msg (responseBody response)+ Right decoded -> return decoded
+ src/Web/RedHatBugzilla/Internal/Search.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Operators which can be used to construct queries for Bugzilla.+-- These operators are intended to be typesafe: you should not be+-- able to construct a query that causes Bugzilla to return an+-- error. If you *are* able to construct an erroneous query, please+-- report a bug.+module Web.RedHatBugzilla.Internal.Search+( FieldType+, SearchTerm (..)+, SearchExpression (..)+, evalSearchExpr+) where++import Data.List+import qualified Data.Text as T+import Data.Time.Clock (UTCTime(..))+import Data.Time.ISO8601 (formatISO8601)++import Web.RedHatBugzilla.Internal.Network+import Web.RedHatBugzilla.Internal.Types++class FieldType a where fvAsText :: a -> T.Text++instance FieldType T.Text where fvAsText = id+instance FieldType Int where fvAsText = T.pack . show+instance FieldType UTCTime where fvAsText = T.pack . formatISO8601++instance FieldType Bool where+ fvAsText True = "true"+ fvAsText False = "false"++instance FieldType a => FieldType [a] where+ fvAsText = T.intercalate "," . map fvAsText++data SearchTerm where+ UnaryOp :: FieldType a => T.Text -> Field a -> SearchTerm+ BinaryOp :: (FieldType a, FieldType b) => T.Text -> Field a -> b -> SearchTerm+ EqTerm :: (FieldType a, FieldType b) => Field a -> b -> SearchTerm++-- | A Boolean expression which can be used to query Bugzilla.+data SearchExpression+ = And [SearchExpression]+ | Or [SearchExpression]+ | Not SearchExpression+ | Term SearchTerm++taggedQueryPart :: Int -> Char -> T.Text -> QueryPart+taggedQueryPart t k v = (T.cons k . T.pack . show $ t, Just v)++termQuery :: FieldType b => Int -> Field a -> T.Text -> b -> [QueryPart]+termQuery t f o v = [taggedQueryPart t 'f' (searchFieldName f),+ taggedQueryPart t 'o' o,+ taggedQueryPart t 'v' (fvAsText v)]++evalSearchTerm :: Int -> SearchTerm -> [QueryPart]+evalSearchTerm t (UnaryOp op field) = termQuery t field op ("" :: T.Text)+evalSearchTerm t (BinaryOp op field val) = termQuery t field op val+evalSearchTerm _ (EqTerm field val) = [(searchFieldName field, Just . fvAsText $ val)]++evalSearchExpr :: SearchExpression -> [QueryPart]+evalSearchExpr e = snd $ evalSearchExpr' 1 e+ where+ evalExprGroup :: Int -> [SearchExpression] -> (Int, [QueryPart])+ evalExprGroup t es =+ let (subExprT, subExprQs) = foldl' evalSubExpr (t + 1, []) es+ qs = taggedQueryPart t 'f' "OP" :+ taggedQueryPart subExprT 'f' "CP" :+ subExprQs+ in (subExprT + 1, qs)++ evalSubExpr :: (Int, [QueryPart]) -> SearchExpression -> (Int, [QueryPart])+ evalSubExpr (t, qs) expr = let (nextT, qs') = evalSearchExpr' t expr+ in (nextT, qs ++ qs')++ evalSearchExpr' :: Int -> SearchExpression -> (Int, [QueryPart])+ evalSearchExpr' t (And es) = evalExprGroup t es++ evalSearchExpr' t (Or es) =+ let (groupT, groupQs) = evalExprGroup t es+ qs = taggedQueryPart t 'j' "OR" : groupQs+ in (groupT + 1, qs)++ evalSearchExpr' t (Not es) =+ let (groupT, groupQs) = evalSearchExpr' t es+ qs = taggedQueryPart t 'n' "1" : groupQs+ in (groupT + 1, qs)++ evalSearchExpr' t (Term term) = (t + 1, evalSearchTerm t term)
+ src/Web/RedHatBugzilla/Internal/Types.hs view
@@ -0,0 +1,747 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}++module Web.RedHatBugzilla.Internal.Types+( BugId+, AttachmentId+, CommentId+, UserId+, EventId+, FlagId+, FlagType+, UserEmail+, Field (..)+, User (..)+, UserList (..)+, Flag (..)+, ExternalType (..)+, ExternalBug (..)+, Bug (..)+, BugList (..)+, BugIdList (..)+, Attachment (..)+, AttachmentList (..)+, Comment (..)+, CommentList (..)+, History (..)+, HistoryEvent (..)+, Change (..)+, Modification (..)+, fieldName+, searchFieldName+) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (pure, (<$>), (<*>))+#endif+import Control.Monad (mzero)+import Data.Aeson+#if MIN_VERSION_aeson(1,0,0)+import Data.Aeson.Text+#else+import Data.Aeson.Encode+#endif+import Data.Aeson.Types+#if MIN_VERSION_aeson(2,0,0)+import Data.Aeson.Key+import qualified Data.Aeson.KeyMap as M+#else+import qualified Data.HashMap.Strict as M+#endif+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB+import qualified Data.Text.Read as TR+import qualified Data.Vector as V+import Data.Time.Clock (UTCTime(..))++type BugId = Int+type AttachmentId = Int+type CommentId = Int+type UserId = Int+type EventId = Int+type FlagId = Int+type FlagType = Int+type UserEmail = T.Text++-- | A field which you can search by using 'Web.Bugzilla.searchBugs' or track+-- changes to using 'Web.Bugzilla.getHistory'. To get a human-readable name for+-- a field, use 'fieldName'.+data Field a where+ AliasField :: Field [T.Text] -- Alias+ AssignedToField :: Field UserEmail -- Assignee+ AttachmentCreatorField :: Field UserEmail -- Attachment creator+ AttachmentDataField :: Field T.Text -- Attachment data+ AttachmentDescriptionField :: Field T.Text -- Attachment description+ AttachmentFilenameField :: Field T.Text -- Attachment filename+ AttachmentIsObsoleteField :: Field Bool -- Attachment is obsolete+ AttachmentIsPatchField :: Field Bool -- Attachment is patch+ AttachmentIsPrivateField :: Field Bool -- Attachment is private+ AttachmentMimetypeField :: Field T.Text -- Attachment mime type+ BlocksField :: Field Int -- Blocks+ BugIdField :: Field Int -- Bug ID+ CcField :: Field UserEmail -- CC+ CcListAccessibleField :: Field Bool -- CC list accessible+ ClassificationField :: Field T.Text -- Classification+ CommentField :: Field T.Text -- Comment+ CommentIsPrivateField :: Field T.Text -- Comment is private+ CommentTagsField :: Field T.Text -- Comment Tags+ CommenterField :: Field UserEmail -- Commenter+ ComponentField :: Field [T.Text] -- Component+ ContentField :: Field T.Text -- Content+ CreationDateField :: Field UTCTime -- Creation date+ DaysElapsedField :: Field Int -- Days since bug changed+ DependsOnField :: Field Int -- Depends on+ EverConfirmedField :: Field Bool -- Ever confirmed+ FlagRequesteeField :: Field UserEmail -- Flag Requestee+ FlagSetterField :: Field UserEmail -- Flag Setter+ FlagsField :: Field T.Text -- Flags+ GroupField :: Field T.Text -- Group+ KeywordsField :: Field [T.Text] -- Keywords+ ChangedField :: Field UTCTime -- Changed+ CommentCountField :: Field Int -- Number of Comments+ OperatingSystemField :: Field T.Text -- OS+ HardwareField :: Field T.Text -- Hardware+ PriorityField :: Field T.Text -- Priority+ ProductField :: Field T.Text -- Product+ QaContactField :: Field UserEmail -- QA Contact+ ReporterField :: Field UserEmail -- Reporter+ ReporterAccessibleField :: Field Bool -- Reporter accessible+ ResolutionField :: Field T.Text -- Resolution+ RestrictCommentsField :: Field Bool -- Restrict Comments+ SeeAlsoField :: Field T.Text -- See Also+ SeverityField :: Field T.Text -- Severity+ StatusField :: Field T.Text -- Status+ WhiteboardField :: Field T.Text -- Whiteboard+ SummaryField :: Field T.Text -- Summary+ TagsField :: Field T.Text -- Tags+ TargetMilestoneField :: Field T.Text -- Target Milestone+ TimeSinceAssigneeTouchedField :: Field Int -- Time Since Assignee Touched+ BugURLField :: Field T.Text -- URL+ VersionField :: Field T.Text -- Version+ VotesField :: Field T.Text -- Votes+ CustomField :: T.Text -> Field T.Text -- (Custom fields)++instance Eq (Field a) where+ (CustomField a) == (CustomField b) = a == b+ (CustomField _) == _ = False+ _ == (CustomField _) = False+ a == b = searchFieldName a == searchFieldName b++instance Show (Field a) where+ show AliasField = "AliasField"+ show AssignedToField = "AssignedToField"+ show AttachmentCreatorField = "AttachmentCreatorField"+ show AttachmentDataField = "AttachmentDataField"+ show AttachmentDescriptionField = "AttachmentDescriptionField"+ show AttachmentFilenameField = "AttachmentFilenameField"+ show AttachmentIsObsoleteField = "AttachmentIsObsoleteField"+ show AttachmentIsPatchField = "AttachmentIsPatchField"+ show AttachmentIsPrivateField = "AttachmentIsPrivateField"+ show AttachmentMimetypeField = "AttachmentMimetypeField"+ show BlocksField = "BlocksField"+ show BugIdField = "BugIdField"+ show CcField = "CcField"+ show CcListAccessibleField = "CcListAccessibleField"+ show ClassificationField = "ClassificationField"+ show CommentField = "CommentField"+ show CommentIsPrivateField = "CommentIsPrivateField"+ show CommentTagsField = "CommentTagsField"+ show CommenterField = "CommenterField"+ show ComponentField = "ComponentField"+ show ContentField = "ContentField"+ show CreationDateField = "CreationDateField"+ show DaysElapsedField = "DaysElapsedField"+ show DependsOnField = "DependsOnField"+ show EverConfirmedField = "EverConfirmedField"+ show FlagRequesteeField = "FlagRequesteeField"+ show FlagSetterField = "FlagSetterField"+ show FlagsField = "FlagsField"+ show GroupField = "GroupField"+ show KeywordsField = "KeywordsField"+ show ChangedField = "ChangedField"+ show CommentCountField = "CommentCountField"+ show OperatingSystemField = "OperatingSystemField"+ show HardwareField = "HardwareField"+ show PriorityField = "PriorityField"+ show ProductField = "ProductField"+ show QaContactField = "QaContactField"+ show ReporterField = "ReporterField"+ show ReporterAccessibleField = "ReporterAccessibleField"+ show ResolutionField = "ResolutionField"+ show RestrictCommentsField = "RestrictCommentsField"+ show SeeAlsoField = "SeeAlsoField"+ show SeverityField = "SeverityField"+ show StatusField = "StatusField"+ show WhiteboardField = "WhiteboardField"+ show SummaryField = "SummaryField"+ show TagsField = "TagsField"+ show TargetMilestoneField = "TargetMilestoneField"+ show TimeSinceAssigneeTouchedField = "TimeSinceAssigneeTouchedField"+ show BugURLField = "BugURLField"+ show VersionField = "VersionField"+ show VotesField = "VotesField"+ show (CustomField name) = "CustomField " ++ show name++-- | Provides a human-readable name for a 'Field'.+fieldName :: Field a -> T.Text+fieldName AliasField = "Alias"+fieldName AssignedToField = "Assigned to"+fieldName AttachmentCreatorField = "Attachment creator"+fieldName AttachmentDataField = "Attachment data"+fieldName AttachmentDescriptionField = "Attachment description"+fieldName AttachmentFilenameField = "Attachment filename"+fieldName AttachmentIsObsoleteField = "Attachment is obsolete"+fieldName AttachmentIsPatchField = "Attachment is patch"+fieldName AttachmentIsPrivateField = "Attachment is private"+fieldName AttachmentMimetypeField = "Attachment MIME type"+fieldName BlocksField = "Blocks"+fieldName BugIdField = "BugId"+fieldName CcField = "CC"+fieldName CcListAccessibleField = "CC list is accessible"+fieldName ClassificationField = "Classification"+fieldName CommentField = "Comment"+fieldName CommentIsPrivateField = "Comment is private"+fieldName CommentTagsField = "Comment tags"+fieldName CommenterField = "Commenter"+fieldName ComponentField = "Component"+fieldName ContentField = "Content"+fieldName CreationDateField = "Creation date"+fieldName DaysElapsedField = "Days elapsed"+fieldName DependsOnField = "Depends on"+fieldName EverConfirmedField = "Ever confirmed"+fieldName FlagRequesteeField = "Flag requestee"+fieldName FlagSetterField = "Flag setter"+fieldName FlagsField = "Flags"+fieldName GroupField = "Group"+fieldName KeywordsField = "Keywords"+fieldName ChangedField = "Changed"+fieldName CommentCountField = "Comment count"+fieldName OperatingSystemField = "Operating system"+fieldName HardwareField = "Hardware"+fieldName PriorityField = "Priority"+fieldName ProductField = "Product"+fieldName QaContactField = "QA contact"+fieldName ReporterField = "Reporter"+fieldName ReporterAccessibleField = "Reporter accessible"+fieldName ResolutionField = "Resolution"+fieldName RestrictCommentsField = "Restrict comments"+fieldName SeeAlsoField = "See also"+fieldName SeverityField = "Severity"+fieldName StatusField = "Status"+fieldName WhiteboardField = "Whiteboard"+fieldName SummaryField = "Summary"+fieldName TagsField = "Tags"+fieldName TargetMilestoneField = "Target milestone"+fieldName TimeSinceAssigneeTouchedField = "Time since assignee touched"+fieldName BugURLField = "Bug URL"+fieldName VersionField = "Version"+fieldName VotesField = "Votes"+fieldName (CustomField name) = T.concat ["Custom field \"", T.pack (show name), "\""]++searchFieldName :: Field a -> T.Text+searchFieldName AliasField = "alias"+searchFieldName AssignedToField = "assigned_to"+searchFieldName AttachmentCreatorField = "attachments.submitter"+searchFieldName AttachmentDataField = "attach_data.thedata"+searchFieldName AttachmentDescriptionField = "attachments.description"+searchFieldName AttachmentFilenameField = "attachments.filename"+searchFieldName AttachmentIsObsoleteField = "attachments.isobsolete"+searchFieldName AttachmentIsPatchField = "attachments.ispatch"+searchFieldName AttachmentIsPrivateField = "attachments.isprivate"+searchFieldName AttachmentMimetypeField = "attachments.mimetype"+searchFieldName BlocksField = "blocked"+searchFieldName BugIdField = "bug_id"+searchFieldName CcField = "cc"+searchFieldName CcListAccessibleField = "cclist_accessible"+searchFieldName ClassificationField = "classification"+searchFieldName CommentField = "longdesc"+searchFieldName CommentIsPrivateField = "longdescs.isprivate"+searchFieldName CommentTagsField = "comment_tag"+searchFieldName CommenterField = "commenter"+searchFieldName ComponentField = "component"+searchFieldName ContentField = "content"+searchFieldName CreationDateField = "creation_ts"+searchFieldName DaysElapsedField = "days_elapsed"+searchFieldName DependsOnField = "dependson"+searchFieldName EverConfirmedField = "everconfirmed"+searchFieldName FlagRequesteeField = "requestees.login_name"+searchFieldName FlagSetterField = "setters.login_name"+searchFieldName FlagsField = "flagtypes.name"+searchFieldName GroupField = "bug_group"+searchFieldName KeywordsField = "keywords"+searchFieldName ChangedField = "delta_ts"+searchFieldName CommentCountField = "longdescs.count"+searchFieldName OperatingSystemField = "op_sys"+searchFieldName HardwareField = "rep_platform"+searchFieldName PriorityField = "priority"+searchFieldName ProductField = "product"+searchFieldName QaContactField = "qa_contact"+searchFieldName ReporterField = "reporter"+searchFieldName ReporterAccessibleField = "reporter_accessible"+searchFieldName ResolutionField = "resolution"+searchFieldName RestrictCommentsField = "restrict_comments"+searchFieldName SeeAlsoField = "see_also"+searchFieldName SeverityField = "bug_severity"+searchFieldName StatusField = "bug_status"+searchFieldName WhiteboardField = "status_whiteboard"+searchFieldName SummaryField = "short_desc"+searchFieldName TagsField = "tag"+searchFieldName TargetMilestoneField = "target_milestone"+searchFieldName TimeSinceAssigneeTouchedField = "owner_idle_time"+searchFieldName BugURLField = "bug_file_loc"+searchFieldName VersionField = "version"+searchFieldName VotesField = "votes"+searchFieldName (CustomField name) = name++-- | A Bugzilla user.+data User = User+ { userId :: !UserId+ , userEmail :: Maybe UserEmail+ , userName :: T.Text+ , userRealName :: T.Text+ } deriving (Eq, Ord, Show)++instance FromJSON User where+ parseJSON (Object v) =+ User <$> v .: "id"+ <*> v .:? "email"+ <*> v .: "name"+ <*> v .: "real_name"+ parseJSON _ = mzero++newtype UserList = UserList [User]+ deriving (Eq, Show)++instance FromJSON UserList where+ parseJSON (Object v) = UserList <$> v .: "users"+ parseJSON _ = mzero++-- | Flags, which may be set on an attachment or on a bug directly.+data Flag = Flag+ { flagId :: !FlagId+ , flagTypeId :: !FlagType+ , flagName :: T.Text+ , flagSetter :: UserEmail+ , flagStatus :: T.Text+ , flagCreationDate :: UTCTime+ , flagModificationDate :: UTCTime+ , flagRequestee :: Maybe UserEmail+ } deriving (Eq, Ord, Show)++instance FromJSON Flag where+ parseJSON (Object v) =+ Flag <$> v .: "id"+ <*> v .: "type_id"+ <*> v .: "name"+ <*> v .: "setter"+ <*> v .: "status"+ <*> v .: "creation_date"+ <*> v .: "modification_date"+ <*> v .:? "requestee"+ parseJSON _ = mzero++-- | An external bug type+data ExternalType = ExternalType+ { externalTypeDescription :: T.Text+ , externalTypeUrl :: T.Text+ , externalTypeId :: Int+ , externalTypeType :: T.Text+ , externalTypeFullUrl :: T.Text+ } deriving (Eq, Ord, Show)++instance FromJSON ExternalType where+ parseJSON (Object v) =+ ExternalType <$> v .: "description"+ <*> v .: "url"+ <*> v .: "id"+ <*> v .: "type"+ <*> v .: "full_url"+ parseJSON _ = mzero++-- | An external bug.+data ExternalBug = ExternalBug+ { externalDescription :: T.Text+ , externalBzId :: Int+ , externalPriority :: T.Text+ , externalBugId :: T.Text+ , externalStatus :: T.Text+ , externalId :: Int+ , externalType :: ExternalType+ } deriving (Eq, Ord, Show)++instance FromJSON ExternalBug where+ parseJSON (Object v) =+ ExternalBug <$> v .: "ext_description"+ <*> v .: "ext_bz_id"+ <*> v .: "ext_priority"+ <*> v .: "ext_bz_bug_id"+ <*> v .: "ext_status"+ <*> v .: "id"+ <*> v .: "type"+ parseJSON _ = mzero++-- | A Bugzilla bug.+data Bug = Bug+ { bugId :: !BugId+ , bugAlias :: Maybe [T.Text]+ , bugAssignedTo :: UserEmail+ , bugAssignedToDetail :: User+ , bugBlocks :: [BugId]+ , bugCc :: [UserEmail]+ , bugCcDetail :: [User]+ , bugClassification :: T.Text+ , bugComponent :: [T.Text]+ , bugCreationTime :: UTCTime+ , bugCreator :: UserEmail+ , bugCreatorDetail :: User+ , bugDependsOn :: [BugId]+ , bugDupeOf :: Maybe BugId+ , bugFlags :: Maybe [Flag]+ , bugGroups :: [T.Text]+ , bugIsCcAccessible :: Bool+ , bugIsConfirmed :: Bool+ , bugIsCreatorAccessible :: Bool+ , bugIsOpen :: Bool+ , bugKeywords :: [T.Text]+ , bugLastChangeTime :: UTCTime+ , bugOpSys :: T.Text+ , bugPlatform :: T.Text+ , bugPriority :: T.Text+ , bugProduct :: T.Text+ , bugQaContact :: UserEmail+ , bugResolution :: T.Text+ , bugSeeAlso :: [T.Text]+ , bugSeverity :: T.Text+ , bugStatus :: T.Text+ , bugSummary :: T.Text+ , bugTargetMilestone :: T.Text+ , bugUrl :: T.Text+ , bugVersion :: [T.Text]+ , bugWhiteboard :: T.Text+ , bugCustomFields ::+#if MIN_VERSION_aeson(2,0,0)+ M.KeyMap T.Text+#else+ M.HashMap T.Text T.Text+#endif+, bugExternalBugs :: Maybe [ExternalBug]+ } deriving (Eq, Show)++instance FromJSON Bug where+ parseJSON (Object v) =+ Bug <$> v .: "id"+ <*> v .:? "alias"+ <*> v .: "assigned_to"+ <*> v .: "assigned_to_detail"+ <*> v .: "blocks"+ <*> v .: "cc"+ <*> v .: "cc_detail"+ <*> v .: "classification"+ <*> v .: "component"+ <*> v .: "creation_time"+ <*> v .: "creator"+ <*> v .: "creator_detail"+ <*> v .: "depends_on"+ <*> v .:? "dupe_of"+ <*> v .:? "flags"+ <*> v .: "groups"+ <*> v .: "is_cc_accessible"+ <*> v .: "is_confirmed"+ <*> v .: "is_creator_accessible"+ <*> v .: "is_open"+ <*> v .: "keywords"+ <*> v .: "last_change_time"+ <*> v .: "op_sys"+ <*> v .: "platform"+ <*> v .: "priority"+ <*> v .: "product"+ <*> v .: "qa_contact"+ <*> v .: "resolution"+ <*> v .: "see_also"+ <*> v .: "severity"+ <*> v .: "status"+ <*> v .: "summary"+ <*> v .: "target_milestone"+ <*> v .: "url"+ <*> v .: "version"+ <*> v .: "whiteboard"+ <*> pure (customFields v)+ <*> v .:? "external_bugs"+ parseJSON _ = mzero++customFields :: Object ->+#if MIN_VERSION_aeson(2,0,0)+ M.KeyMap T.Text+#else+ M.HashMap T.Text T.Text+#endif+customFields = M.map stringifyCustomFields+ . M.filterWithKey filterCustomFields+ where+ stringifyCustomFields :: Value -> T.Text+ stringifyCustomFields (String t) = t+ stringifyCustomFields v = T.concat+ . TL.toChunks+ . TLB.toLazyText+ . encodeToTextBuilder+ . toJSON+ $ v++ filterCustomFields k _ = "cf_" `T.isPrefixOf` toText k+#if !MIN_VERSION_aeson(2,0,0)+ where toText = id+#endif++newtype BugList = BugList [Bug]+ deriving (Eq, Show)++instance FromJSON BugList where+ parseJSON (Object v) = BugList <$> v .: "bugs"+ parseJSON _ = mzero++newtype BugIdList = BugIdList [BugId]+ deriving (Eq, Show)++instance FromJSON BugIdList where+ parseJSON (Object v) = do+ bugs <- v .: "bugs"+ bugIds <- mapM (.: "id") bugs+ return $ BugIdList bugIds+ parseJSON _ = mzero++-- | An attachment to a bug.+data Attachment = Attachment+ { attachmentId :: !AttachmentId+ , attachmentBugId :: !BugId+ , attachmentFileName :: T.Text+ , attachmentSummary :: T.Text+ , attachmentCreator :: UserEmail+ , attachmentIsPrivate :: Bool+ , attachmentIsObsolete :: Bool+ , attachmentIsPatch :: Bool+ , attachmentFlags :: [Flag]+ , attachmentCreationTime :: UTCTime+ , attachmentLastChangeTime :: UTCTime+ , attachmentContentType :: T.Text+ , attachmentSize :: !Int+ , attachmentData :: T.Text+ } deriving (Eq, Show)++instance FromJSON Attachment where+ parseJSON (Object v) =+ Attachment <$> v .: "id"+ <*> v .: "bug_id"+ <*> v .: "file_name"+ <*> v .: "summary"+ <*> v .: "creator"+ <*> (fromNumericBool <$> v .: "is_private")+ <*> (fromNumericBool <$> v .: "is_obsolete")+ <*> (fromNumericBool <$> v .: "is_patch")+ <*> v .: "flags"+ <*> v .: "creation_time"+ <*> v .: "last_change_time"+ <*> v .: "content_type"+ <*> v .: "size"+ <*> v .: "data"+ parseJSON _ = mzero++fromNumericBool :: Int -> Bool+fromNumericBool 0 = False+fromNumericBool _ = True++newtype AttachmentList = AttachmentList [Attachment]+ deriving (Eq, Show)++instance FromJSON AttachmentList where+ parseJSON (Object v) = do+ attachmentsVal <- v .: "attachments"+ bugsVal <- v .: "bugs"+ case (attachmentsVal, bugsVal) of+ (Object (M.toList -> [(_, as)]), _) -> AttachmentList . (:[]) <$> parseJSON as+ (_, Object (M.toList -> [(_, as)])) -> AttachmentList <$> parseJSON as+ _ -> mzero+ parseJSON _ = mzero++-- | A bug comment. To display these the way Bugzilla does, you'll+-- need to call 'getUser' and use the 'userRealName' for each user.+data Comment = Comment+ { commentId :: !CommentId+ , commentBugId :: !BugId+ , commentAttachmentId :: Maybe AttachmentId+ , commentCount :: !Int+ , commentText :: T.Text+ , commentCreator :: UserEmail+ , commentCreationTime :: UTCTime+ , commentIsPrivate :: Bool+ } deriving (Eq, Show)++instance FromJSON Comment where+ parseJSON (Object v) =+ Comment <$> v .: "id"+ <*> v .: "bug_id"+ <*> v .: "attachment_id"+ <*> v .: "count"+ <*> v .: "text"+ <*> v .: "creator"+ <*> v .: "creation_time"+ <*> v .: "is_private"+ parseJSON _ = mzero++newtype CommentList = CommentList [Comment]+ deriving (Eq, Show)++instance FromJSON CommentList where+ parseJSON (Object v) = do+ bugsVal <- v .: "bugs"+ case bugsVal of+ Object (M.toList -> [(_, cs)]) ->+ do comments <- withObject "comments" (.: "comments") cs+ withArray "comment list" (\a -> CommentList <$> parseJSON (addCount a)) comments+ _ -> mzero+ parseJSON _ = mzero++-- Note that we make the (possibly unwise) assumption that Bugzilla+-- returns the comments in order. If it turns out that's not true, we+-- can always sort by their 'id' to ensure correct results.+addCount :: V.Vector Value -> Value+addCount vs = Array $ V.zipWith addCount' (V.enumFromN 0 $ V.length vs) vs+ where+ addCount' :: Int -> Value -> Value+ addCount' c (Object v) = Object $ M.insert "count" (Number $ fromIntegral c) v+ addCount' _ v = v++-- | History information for a bug.+data History = History+ { historyBugId :: !BugId+ , historyEvents :: [HistoryEvent]+ } deriving (Eq, Show)++instance FromJSON History where+ parseJSON (Object v) = do+ bugsVal <- v .: "bugs"+ case bugsVal of+ Array (V.toList -> [history]) ->+ withObject "history"+ (\h -> History <$> h .: "id"+ <*> parseHistoryEvents h)+ history+ _ -> mzero+ parseJSON _ = mzero++parseHistoryEvents :: Object -> Parser [HistoryEvent]+parseHistoryEvents h = do+ events <- h .: "history"+ withArray "event list" (parseJSON . addCount) events++-- | An event in a bug's history.+data HistoryEvent = HistoryEvent+ { historyEventId :: EventId -- ^ A sequential event id.+ , historyEventTime :: UTCTime -- ^ When the event occurred.+ , historyEventUser :: UserEmail -- ^ Which user was responsible.+ , historyEventChanges :: [Change] -- ^ All the changes which are+ -- part of this event.+ } deriving (Eq, Show)++instance FromJSON HistoryEvent where+ parseJSON (Object v) =+ HistoryEvent <$> v .: "count"+ <*> v .: "when"+ <*> v .: "who"+ <*> v .: "changes"+ parseJSON _ = mzero++-- | A single change which is part of an event. Different constructors+-- are used according to the type of the field. The 'Modification'+-- describes the value of the field before and after the change.+data Change+ = TextFieldChange (Field T.Text) (Modification T.Text)+ | ListFieldChange (Field [T.Text]) (Modification [T.Text])+ | IntFieldChange (Field Int) (Modification Int)+ | TimeFieldChange (Field UTCTime) (Modification UTCTime)+ | BoolFieldChange (Field Bool) (Modification Bool)+ deriving (Eq, Show)++instance FromJSON Change where+ parseJSON (Object v) = do+ changedField <- v .: "field_name"+ case changedField of+ "alias" -> ListFieldChange AliasField <$> parseModification v+ "assigned_to" -> TextFieldChange AssignedToField <$> parseModification v+ "attachments.submitter" -> TextFieldChange AttachmentCreatorField <$> parseModification v+ "attach_data.thedata" -> TextFieldChange AttachmentDataField <$> parseModification v+ "attachments.description"-> TextFieldChange AttachmentDescriptionField <$> parseModification v+ "attachments.filename" -> TextFieldChange AttachmentFilenameField <$> parseModification v+ "attachments.isobsolete" -> BoolFieldChange AttachmentIsObsoleteField <$> parseModification v+ "attachments.ispatch" -> BoolFieldChange AttachmentIsPatchField <$> parseModification v+ "attachments.isprivate" -> BoolFieldChange AttachmentIsPrivateField <$> parseModification v+ "attachments.mimetype" -> TextFieldChange AttachmentMimetypeField <$> parseModification v+ "blocks" -> IntFieldChange BlocksField <$> parseModification v+ "bug_id" -> IntFieldChange BugIdField <$> parseModification v+ "cc" -> TextFieldChange CcField <$> parseModification v+ "is_cc_accessible" -> BoolFieldChange CcListAccessibleField <$> parseModification v+ "classification" -> TextFieldChange ClassificationField <$> parseModification v+ "component" -> ListFieldChange ComponentField <$> parseModification v+ "content" -> TextFieldChange ContentField <$> parseModification v+ "creation_time" -> TimeFieldChange CreationDateField <$> parseModification v+ "days_elapsed" -> IntFieldChange DaysElapsedField <$> parseModification v+ "depends_on" -> IntFieldChange DependsOnField <$> parseModification v+ "everconfirmed" -> BoolFieldChange EverConfirmedField <$> parseModification v+ "flagtypes.name" -> TextFieldChange FlagsField <$> parseModification v+ "bug_group" -> TextFieldChange GroupField <$> parseModification v+ "keywords" -> ListFieldChange KeywordsField <$> parseModification v+ "op_sys" -> TextFieldChange OperatingSystemField <$> parseModification v+ "platform" -> TextFieldChange HardwareField <$> parseModification v+ "priority" -> TextFieldChange PriorityField <$> parseModification v+ "product" -> TextFieldChange ProductField <$> parseModification v+ "qa_contact" -> TextFieldChange QaContactField <$> parseModification v+ "reporter" -> TextFieldChange ReporterField <$> parseModification v+ "reporter_accessible" -> BoolFieldChange ReporterAccessibleField <$> parseModification v+ "resolution" -> TextFieldChange ResolutionField <$> parseModification v+ "restrict_comments" -> BoolFieldChange RestrictCommentsField <$> parseModification v+ "see_also" -> TextFieldChange SeeAlsoField <$> parseModification v+ "severity" -> TextFieldChange SeverityField <$> parseModification v+ "status" -> TextFieldChange StatusField <$> parseModification v+ "whiteboard" -> TextFieldChange WhiteboardField <$> parseModification v+ "summary" -> TextFieldChange SummaryField <$> parseModification v+ "tag" -> TextFieldChange TagsField <$> parseModification v+ "target_milestone" -> TextFieldChange TargetMilestoneField <$> parseModification v+ "url" -> TextFieldChange BugURLField <$> parseModification v+ "version" -> TextFieldChange VersionField <$> parseModification v+ "votes" -> TextFieldChange VotesField <$> parseModification v+ name -> TextFieldChange (CustomField name) <$> parseModification v+ parseJSON _ = mzero++-- | A description of how a field changed during a 'HistoryEvent'.+data (Eq a, Show a) => Modification a = Modification+ { modRemoved :: Maybe a+ , modAdded :: Maybe a+ , modAttachmentId :: Maybe AttachmentId+ } deriving (Eq, Show)++parseModification :: (FromJSON a, Eq b, Show b, ToModification a b) => Object -> Parser (Modification b)+parseModification v = Modification <$> (toMod =<< v .: "removed")+ <*> (toMod =<< v .: "added")+ <*> v .:? "attachment_id"++class ToModification a b | b -> a where toMod :: a -> Parser (Maybe b)+instance ToModification T.Text T.Text where toMod = return . Just+instance ToModification UTCTime UTCTime where toMod = return . Just++instance ToModification T.Text Int where+ toMod v | v == "" = return Nothing+ | otherwise = case TR.decimal v of+ Left _ -> mzero+ Right (i, _) -> return $ Just i++instance ToModification T.Text Bool where+ toMod v | v == "0" = return $ Just False+ | v == "1" = return $ Just True+ | otherwise = mzero++instance ToModification T.Text [T.Text] where+ toMod v = return . Just $ T.splitOn ", " v
+ src/Web/RedHatBugzilla/Search.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE OverloadedStrings #-}++-- | A modified version of Web.Bugzilla.Search to support+-- the list fields in Red Hat's modified bugzilla API.++module Web.RedHatBugzilla.Search+(+ -- * Search operators+ (.==.)+, (./=.)+, (.<.)+, (.<=.)+, (.>.)+, (.>=.)+, (.=~.)+, (./=~.)+, equalsAny+, contains+, containsCase+, containsAny+, containsAll+, changedBefore+, changedAfter+, changedSince+, changedUntil+, changedRange+, changedFrom+, changedTo+, changedBy+, contentMatches+, isEmpty+, isNotEmpty+, (.&&.)+, (.||.)+, not'++ -- * Search expressions+, Field (..)+, SearchExpression+, evalSearchExpr+) where++import qualified Data.Text as T+import Data.Time.Clock (UTCTime(..))++import Web.RedHatBugzilla.Internal.Search+import Web.RedHatBugzilla.Internal.Types++(.==.) :: FieldType a => Field a -> a -> SearchExpression+(.==.) = (Term .) . BinaryOp "equals"+infix 4 .==.++(./=.) :: FieldType a => Field a -> a -> SearchExpression+(./=.) = (Term .) . BinaryOp "notequals"+infix 4 ./=.++(.<.) :: FieldType a => Field a -> a -> SearchExpression+(.<.) = (Term .) . BinaryOp "lessthan"+infix 4 .<.++(.<=.) :: FieldType a => Field a -> a -> SearchExpression+(.<=.) = (Term .) . BinaryOp "lessthaneq"+infix 4 .<=.++(.>.) :: FieldType a => Field a -> a -> SearchExpression+(.>.) = (Term .) . BinaryOp "greaterthan"+infix 4 .>.++(.>=.) :: FieldType a => Field a -> a -> SearchExpression+(.>=.) = (Term .) . BinaryOp "greaterthaneq"+infix 4 .>=.++(.=~.) :: FieldType a => Field a -> a -> SearchExpression+(.=~.) = (Term .) . BinaryOp "regexp"++(./=~.) :: FieldType a => Field a -> a -> SearchExpression+(./=~.) = (Term .) . BinaryOp "notregexp"++equalsAny :: FieldType a => Field a -> [a] -> SearchExpression+equalsAny = (Term .) . BinaryOp "anyexact"++contains :: Field T.Text -> T.Text -> SearchExpression+contains = (Term .) . BinaryOp "substring"++containsCase :: Field T.Text -> T.Text -> SearchExpression+containsCase = (Term .) . BinaryOp "casesubstring"++containsAny :: Field T.Text -> [T.Text] -> SearchExpression+containsAny = (Term .) . BinaryOp "anywordssubstr"++containsAll :: Field T.Text -> [T.Text] -> SearchExpression+containsAll = (Term .) . BinaryOp "allwordssubstr"++changedBefore :: FieldType a => Field a -> UTCTime -> SearchExpression+changedBefore = (Term .) . BinaryOp "changedbefore"++changedAfter :: FieldType a => Field a -> UTCTime -> SearchExpression+changedAfter = (Term .) . BinaryOp "changedafter"++-- | Filter bug changed since UTCTime+changedSince :: UTCTime -> SearchExpression+changedSince ts = Term $ EqTerm (CustomField "chfieldfrom") ts++-- | Filter bug changed until UTCTime+changedUntil :: UTCTime -> SearchExpression+changedUntil ts = Term $ EqTerm (CustomField "chfieldto") ts++-- | Filter bug changed in range+changedRange :: UTCTime -> UTCTime -> SearchExpression+changedRange from to = changedSince from .&&. changedUntil to++changedFrom :: FieldType a => Field a -> a -> SearchExpression+changedFrom = (Term .) . BinaryOp "changedfrom"++changedTo :: FieldType a => Field a -> a -> SearchExpression+changedTo = (Term .) . BinaryOp "changedto"++changedBy :: FieldType a => Field a -> UserEmail -> SearchExpression+changedBy = (Term .) . BinaryOp "changedby"++contentMatches :: T.Text -> SearchExpression+contentMatches = Term . BinaryOp "matches" ContentField++isEmpty :: FieldType a => Field a -> SearchExpression+isEmpty = Term . UnaryOp "isempty"++isNotEmpty :: FieldType a => Field a -> SearchExpression+isNotEmpty = Term . UnaryOp "isnotempty"++(.&&.) :: SearchExpression -> SearchExpression -> SearchExpression+(.&&.) (And as) (And bs) = And (as ++ bs)+(.&&.) (And as) a = And (as ++ [a])+(.&&.) a (And as) = And (a:as)+(.&&.) a b = And [a, b]+infixr 3 .&&.++(.||.) :: SearchExpression -> SearchExpression -> SearchExpression+(.||.) (Or as) (Or bs) = Or (as ++ bs)+(.||.) a (Or as) = Or (a:as)+(.||.) (Or as) a = Or (as ++ [a])+(.||.) a b = Or [a, b]+infixr 2 .||.++not' :: SearchExpression -> SearchExpression+not' (Not a) = a+not' a = Not a
test/Spec.hs view
@@ -4,7 +4,7 @@ module Main (main) where import Test.Hspec-import Web.Bugzilla.RedHat.Search+import Web.RedHatBugzilla.Search main :: IO () main = hspec spec