diff options
author | Cyril Roelandt <tipecaml@gmail.com> | 2018-12-08 03:58:51 +0100 |
---|---|---|
committer | Cyril Roelandt <tipecaml@gmail.com> | 2018-12-16 00:51:22 +0100 |
commit | d6ddf0ef5c64cdb5262bcaba8018e6345ea391a1 (patch) | |
tree | 059dc88d40ebf99a83ffea8f669f33df8cfbfbdc /bridge/launchpad/launchpad_api.go | |
parent | 63807382d345d93a939c8ffb8bed04fa2a840f66 (diff) | |
download | git-bug-d6ddf0ef5c64cdb5262bcaba8018e6345ea391a1.tar.gz |
Initial Launchpad bridge.
This a just a preview. Not all features are expected to work.
Diffstat (limited to 'bridge/launchpad/launchpad_api.go')
-rw-r--r-- | bridge/launchpad/launchpad_api.go | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/bridge/launchpad/launchpad_api.go b/bridge/launchpad/launchpad_api.go new file mode 100644 index 00000000..09e02bc5 --- /dev/null +++ b/bridge/launchpad/launchpad_api.go @@ -0,0 +1,178 @@ +package launchpad + +/* + * A wrapper around the Launchpad API. The documentation can be found at: + * https://launchpad.net/+apidoc/devel.html + * + * TODO: + * - Retrieve all messages associated to bugs + * - Retrieve bug status + * - Retrieve activity log + * - SearchTasks should yield bugs one by one + * + * TODO (maybe): + * - Authentication (this might help retrieving email adresses) + */ + +import ( + "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) + +func (owner *LPPerson) UnmarshalJSON(data []byte) error { + type LPPersonX LPPerson // Avoid infinite recursion + var ownerLink string + if err := json.Unmarshal(data, &ownerLink); err != nil { + return err + } + + // First, try to gather info about the bug owner using our cache. + if cachedPerson, hasKey := personCache[ownerLink]; hasKey { + *owner = cachedPerson + return nil + } + + // If the bug owner is not already known, we have to send a request. + req, err := http.NewRequest("GET", ownerLink, nil) + if err != nil { + return nil + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil + } + + defer resp.Body.Close() + + var p LPPersonX + if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { + return nil + } + *owner = LPPerson(p) + // Do not forget to update the cache. + personCache[ownerLink] = *owner + return nil +} + +// 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"` +} + +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 launchpadAPI struct { + client *http.Client +} + +func (lapi *launchpadAPI) Init() error { + lapi.client = &http.Client{} + return nil +} + +func (lapi *launchpadAPI) SearchTasks(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") + 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 + } + + defer resp.Body.Close() + + var result launchpadAnswer + + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + for _, bugEntry := range result.Entries { + bug, err := lapi.queryBug(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(url string) (LPBug, error) { + var bug LPBug + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return bug, err + } + + 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 + } + + return bug, nil +} |