diff --git a/cmd_airtable.go b/cmd_airtable.go index f16ce958a..a1fcd2950 100644 --- a/cmd_airtable.go +++ b/cmd_airtable.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "reflect" "time" airtable "github.com/fabioberger/airtable-go" @@ -79,10 +80,30 @@ func airtableSync(opts *airtableOptions) error { } logger().Debug("fetched airtable records", zap.Int("count", len(records))) + // create new records for _, record := range records { alreadyInAirtable[record.Fields.URL] = true + } + for _, issue := range issues { + if issue.Hidden { + continue + } + if _, found := alreadyInAirtable[issue.URL]; found { + continue + } + logger().Debug("creating airtable record without slices", zap.String("URL", issue.URL)) + r := issue.ToAirtableRecord() + r.Fields.Labels = []string{} + r.Fields.Assignees = []string{} + if err := at.CreateRecord(opts.AirtableTableName, r); err != nil { + return err + } + } + + // update/destroy existing ones + for _, record := range records { if issue, found := issues[record.Fields.URL]; !found { - logger().Debug("destroying airtable record", zap.String("ID", record.Fields.URL)) + logger().Debug("destroying airtable record", zap.String("URL", record.Fields.URL)) if err := at.DestroyRecord(opts.AirtableTableName, record.ID); err != nil { return errors.Wrap(err, "failed to destroy record") } @@ -95,69 +116,105 @@ func airtableSync(opts *airtableOptions) error { continue } - logger().Debug("updating airtable record", zap.String("ID", issue.URL)) - if err := at.UpdateRecord(opts.AirtableTableName, record.ID, issue.ToAirtableRecord().Fields.Map(), &record); err != nil { - return errors.Wrap(err, "failed to update record") + logger().Debug("updating airtable record", zap.String("URL", issue.URL)) + m := issue.ToAirtableRecord().Fields.Map() + if err := at.UpdateRecord(opts.AirtableTableName, record.ID, m, &record); err != nil { + logger().Warn("failed to update record, retrying without slices", zap.String("URL", issue.URL), zap.Error(err)) + m["Labels"] = []string{} + m["Assignees"] = []string{} + m["Errors"] = err.Error() + if err := at.UpdateRecord(opts.AirtableTableName, record.ID, m, &record); err != nil { + logger().Error("failed to update record without slices", zap.String("URL", issue.URL), zap.Error(err)) + } } } } - for _, issue := range issues { - if issue.Hidden { - continue - } - if _, found := alreadyInAirtable[issue.URL]; found { - continue - } - logger().Debug("creating airtable record", zap.String("ID", issue.URL)) - if err := at.CreateRecord(opts.AirtableTableName, issue.ToAirtableRecord()); err != nil { - return err - } - } return nil } +type airtableRecord struct { + ID string `json:"id,omitempty"` + Fields airtableIssue `json:"fields,omitempty"` +} + +func (ai airtableIssue) String() string { + out, _ := json.Marshal(ai) + return string(out) +} + func (i Issue) ToAirtableRecord() airtableRecord { + typ := "issue" + if i.IsPR { + typ = "pull-request" + } + labels := []string{} + for _, label := range i.Labels { + labels = append(labels, label.Name) + } + assignees := []string{} + for _, assignee := range i.Assignees { + assignees = append(assignees, assignee.Username) + } + return airtableRecord{ ID: "", Fields: airtableIssue{ - URL: i.URL, - Created: i.CreatedAt, - Updated: i.UpdatedAt, - Title: i.Title, + URL: i.URL, + Created: i.CreatedAt, + Updated: i.UpdatedAt, + Title: i.Title, + Type: typ, + Labels: labels, + Assignees: assignees, + Provider: string(i.Provider), + RepoURL: i.RepoURL, + Body: i.Body, + State: i.State, }, } } -type airtableRecord struct { - ID string `json:"id,omitempty"` - Fields airtableIssue `json:"fields,omitempty"` -} - type airtableIssue struct { - URL string - Created time.Time - Updated time.Time - Title string + URL string + Created time.Time + Updated time.Time + Title string + Provider string + State string + Body string + RepoURL string + Type string + Labels []string + Assignees []string } func (ai airtableIssue) Equals(other airtableIssue) bool { return ai.URL == other.URL && ai.Created.Truncate(time.Millisecond).UTC() == other.Created.Truncate(time.Millisecond).UTC() && ai.Updated.Truncate(time.Millisecond).UTC() == other.Updated.Truncate(time.Millisecond).UTC() && - ai.Title == other.Title -} - -func (ai airtableIssue) String() string { - out, _ := json.Marshal(ai) - return string(out) + ai.Title == other.Title && + ai.Provider == other.Provider && + ai.State == other.State && + ai.Body == other.Body && + ai.RepoURL == other.RepoURL && + ai.Type == other.Type && + reflect.DeepEqual(ai.Labels, other.Labels) && + reflect.DeepEqual(ai.Assignees, other.Assignees) } func (a airtableIssue) Map() map[string]interface{} { return map[string]interface{}{ - "URL": a.URL, - "Created": a.Created, - "Updated": a.Updated, - "Title": a.Title, + "URL": a.URL, + "Created": a.Created, + "Updated": a.Updated, + "Title": a.Title, + "Provider": a.Provider, + "State": a.State, + "Body": a.Body, + "RepoURL": a.RepoURL, + "Type": a.Type, + "Labels": a.Labels, + "Assignees": a.Assignees, } }