mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-24 11:25:00 +03:00
5ca326af83
bridge/core: add ImportResult objects to stream import events bridge/core: launchpad support asynchronous import bridge/github: cancellable export and import functions bridge/gitlab: cancellable export and import functions commands: bridge pull/push gracefull kill bridge/github: fix github import bridge/github: use simple context for imports bridge/core: name parameters in interfaces github/core: Add EventError to export and import events types bridge/gitlab: add context support in gitlab requests functions bridge/gitlab: remove imported events count from importer logic bridge/github: remove imported events count from importer logic bridge/github: add context support in query and muration requets bridge/github: fix bug duplicate editions after multiple calls bridge/core: import import and export events String methods bridge/gitlab: fix error handling in note import events commands/bridge: Add statistics about imports and exports bridge/gitlab: properly handle context cancellation bridge/github: improve error handling bridge: break iterators on context cancel or timeout bridge: add context timeout support bridge: improve event formating and error handling commands: handle interrupt and switch cases bridge/github: add export mutation timeouts bridge: fix race condition bug in the github and gitlab importers bridge/github: improve context error handling
205 lines
4.7 KiB
Go
205 lines
4.7 KiB
Go
package launchpad
|
|
|
|
/*
|
|
* A wrapper around the Launchpad API. The documentation can be found at:
|
|
* https://launchpad.net/+apidoc/devel.html
|
|
*
|
|
* TODO:
|
|
* - Retrieve bug status
|
|
* - Retrieve activity log
|
|
* - SearchTasks should yield bugs one by one
|
|
*
|
|
* TODO (maybe):
|
|
* - Authentication (this might help retrieving email adresses)
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
const apiRoot = "https://api.launchpad.net/devel"
|
|
|
|
// Person describes a person on Launchpad (a bug owner, a message author, ...).
|
|
type LPPerson struct {
|
|
Name string `json:"display_name"`
|
|
Login string `json:"name"`
|
|
}
|
|
|
|
// Caching all the LPPerson we know.
|
|
// The keys are links to an owner page, such as
|
|
// https://api.launchpad.net/devel/~login
|
|
var personCache = make(map[string]LPPerson)
|
|
|
|
// LPBug describes a Launchpad bug.
|
|
type LPBug struct {
|
|
Title string `json:"title"`
|
|
ID int `json:"id"`
|
|
Owner LPPerson `json:"owner_link"`
|
|
Description string `json:"description"`
|
|
CreatedAt string `json:"date_created"`
|
|
Messages []LPMessage
|
|
}
|
|
|
|
// LPMessage describes a comment on a bug report
|
|
type LPMessage struct {
|
|
Content string `json:"content"`
|
|
CreatedAt string `json:"date_created"`
|
|
Owner LPPerson `json:"owner_link"`
|
|
ID string `json:"self_link"`
|
|
}
|
|
|
|
type launchpadBugEntry struct {
|
|
BugLink string `json:"bug_link"`
|
|
SelfLink string `json:"self_link"`
|
|
}
|
|
|
|
type launchpadAnswer struct {
|
|
Entries []launchpadBugEntry `json:"entries"`
|
|
Start int `json:"start"`
|
|
NextLink string `json:"next_collection_link"`
|
|
}
|
|
|
|
type launchpadMessageAnswer struct {
|
|
Entries []LPMessage `json:"entries"`
|
|
NextLink string `json:"next_collection_link"`
|
|
}
|
|
|
|
type launchpadAPI struct {
|
|
client *http.Client
|
|
}
|
|
|
|
func (lapi *launchpadAPI) Init() error {
|
|
lapi.client = &http.Client{
|
|
Timeout: defaultTimeout,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (lapi *launchpadAPI) SearchTasks(ctx context.Context, project string) ([]LPBug, error) {
|
|
var bugs []LPBug
|
|
|
|
// First, let us build the URL. Not all statuses are included by
|
|
// default, so we have to explicitely enumerate them.
|
|
validStatuses := [13]string{
|
|
"New", "Incomplete", "Opinion", "Invalid",
|
|
"Won't Fix", "Expired", "Confirmed", "Triaged",
|
|
"In Progress", "Fix Committed", "Fix Released",
|
|
"Incomplete (with response)", "Incomplete (without response)",
|
|
}
|
|
queryParams := url.Values{}
|
|
queryParams.Add("ws.op", "searchTasks")
|
|
queryParams.Add("order_by", "-date_last_updated")
|
|
for _, validStatus := range validStatuses {
|
|
queryParams.Add("status", validStatus)
|
|
}
|
|
lpURL := fmt.Sprintf("%s/%s?%s", apiRoot, project, queryParams.Encode())
|
|
|
|
for {
|
|
req, err := http.NewRequest("GET", lpURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := lapi.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result launchpadAnswer
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
|
_ = resp.Body.Close()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, bugEntry := range result.Entries {
|
|
bug, err := lapi.queryBug(ctx, bugEntry.BugLink)
|
|
if err == nil {
|
|
bugs = append(bugs, bug)
|
|
}
|
|
}
|
|
|
|
// Launchpad only returns 75 results at a time. We get the next
|
|
// page and run another query, unless there is no other page.
|
|
lpURL = result.NextLink
|
|
if lpURL == "" {
|
|
break
|
|
}
|
|
}
|
|
|
|
return bugs, nil
|
|
}
|
|
|
|
func (lapi *launchpadAPI) queryBug(ctx context.Context, url string) (LPBug, error) {
|
|
var bug LPBug
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return bug, err
|
|
}
|
|
req = req.WithContext(ctx)
|
|
|
|
resp, err := lapi.client.Do(req)
|
|
if err != nil {
|
|
return bug, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&bug); err != nil {
|
|
return bug, err
|
|
}
|
|
|
|
/* Fetch messages */
|
|
messagesCollectionLink := fmt.Sprintf("%s/bugs/%d/messages", apiRoot, bug.ID)
|
|
messages, err := lapi.queryMessages(ctx, messagesCollectionLink)
|
|
if err != nil {
|
|
return bug, err
|
|
}
|
|
bug.Messages = messages
|
|
|
|
return bug, nil
|
|
}
|
|
|
|
func (lapi *launchpadAPI) queryMessages(ctx context.Context, messagesURL string) ([]LPMessage, error) {
|
|
var messages []LPMessage
|
|
|
|
for {
|
|
req, err := http.NewRequest("GET", messagesURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req = req.WithContext(ctx)
|
|
|
|
resp, err := lapi.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result launchpadMessageAnswer
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
|
_ = resp.Body.Close()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
messages = append(messages, result.Entries...)
|
|
|
|
// Launchpad only returns 75 results at a time. We get the next
|
|
// page and run another query, unless there is no other page.
|
|
messagesURL = result.NextLink
|
|
if messagesURL == "" {
|
|
break
|
|
}
|
|
}
|
|
return messages, nil
|
|
}
|