Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add more airtable fields + error handling #98

Merged
merged 1 commit into from
Oct 6, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 96 additions & 39 deletions cmd_airtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"encoding/json"
"reflect"
"time"

airtable "github.com/fabioberger/airtable-go"
Expand Down Expand Up @@ -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")
}
Expand All @@ -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,
}
}