Launchpad bridge: fetch comments.

Cyril Roelandt created

Change summary

bridge/launchpad/import.go        | 46 ++++++++++++++++++++++++-
bridge/launchpad/launchpad_api.go | 59 ++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+), 3 deletions(-)

Detailed changes

bridge/launchpad/import.go 🔗

@@ -44,15 +44,18 @@ func (li *launchpadImporter) ImportAll(repo *cache.RepoCache) error {
 	}
 
 	for _, lpBug := range lpBugs {
+		var b *cache.BugCache
+		var err error
+
 		lpBugID := fmt.Sprintf("%d", lpBug.ID)
-		_, err := repo.ResolveBugCreateMetadata(keyLaunchpadID, lpBugID)
+		b, err = repo.ResolveBugCreateMetadata(keyLaunchpadID, lpBugID)
 		if err != nil && err != bug.ErrBugNotExist {
 			return err
 		}
 
 		if err == bug.ErrBugNotExist {
 			createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt)
-			_, err := repo.NewBugRaw(
+			b, err = repo.NewBugRaw(
 				li.makePerson(lpBug.Owner),
 				createdAt.Unix(),
 				lpBug.Title,
@@ -70,6 +73,45 @@ func (li *launchpadImporter) ImportAll(repo *cache.RepoCache) error {
 			fmt.Println("TODO: Update bug")
 		}
 
+		/* Handle messages */
+		if len(lpBug.Messages) == 0 {
+			return errors.Wrapf(err, "failed to fetch comments for bug #%s", lpBugID)
+		}
+
+		// The Launchpad API returns the bug description as the first
+		// comment, so skip it.
+		for _, lpMessage := range lpBug.Messages[1:] {
+			_, err := b.ResolveTargetWithMetadata(keyLaunchpadID, lpMessage.ID)
+			if err != nil && err != cache.ErrNoMatchingOp {
+				return errors.Wrapf(err, "failed to fetch comments for bug #%s", lpBugID)
+			}
+
+			// If this comment already exists, we are probably
+			// updating an existing bug. We do not want to duplicate
+			// the comments, so let us just skip this one.
+			// TODO: Can Launchpad comments be edited?
+			if err == nil {
+				continue
+			}
+
+			// This is a new comment, we can add it.
+			createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt)
+			err = b.AddCommentRaw(
+				li.makePerson(lpMessage.Owner),
+				createdAt.Unix(),
+				lpMessage.Content,
+				nil,
+				map[string]string{
+					keyLaunchpadID: lpMessage.ID,
+				})
+			if err != nil {
+				return errors.Wrapf(err, "failed to add comment to bug #%s", lpBugID)
+			}
+		}
+		err = b.CommitAsNeeded()
+		if err != nil {
+			return err
+		}
 	}
 	return nil
 }

bridge/launchpad/launchpad_api.go 🔗

@@ -5,7 +5,6 @@ package launchpad
  * 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
@@ -78,6 +77,15 @@ type LPBug struct {
 	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 {
@@ -91,6 +99,11 @@ type launchpadAnswer struct {
 	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
 }
@@ -113,6 +126,7 @@ func (lapi *launchpadAPI) SearchTasks(project string) ([]LPBug, error) {
 	}
 	queryParams := url.Values{}
 	queryParams.Add("ws.op", "searchTasks")
+	queryParams.Add("order_by", "-date_last_updated")
 	for _, validStatus := range validStatuses {
 		queryParams.Add("status", validStatus)
 	}
@@ -175,5 +189,48 @@ func (lapi *launchpadAPI) queryBug(url string) (LPBug, error) {
 		return bug, err
 	}
 
+	/* Fetch messages */
+	messagesCollectionLink := fmt.Sprintf("%s/bugs/%d/messages", apiRoot, bug.ID)
+	messages, err := lapi.queryMessages(messagesCollectionLink)
+	if err != nil {
+		return bug, err
+	}
+	bug.Messages = messages
+
 	return bug, nil
 }
+
+func (lapi *launchpadAPI) queryMessages(messagesURL string) ([]LPMessage, error) {
+	var messages []LPMessage
+
+	for {
+		req, err := http.NewRequest("GET", messagesURL, nil)
+		if err != nil {
+			return nil, err
+		}
+
+		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
+}