diff --git a/cmd_airtable.go b/cmd_airtable.go index 03992fd3e..ba6778ba7 100644 --- a/cmd_airtable.go +++ b/cmd_airtable.go @@ -3,9 +3,10 @@ package main import ( "encoding/json" "reflect" + "sort" "time" - airtable "github.com/fabioberger/airtable-go" + "github.com/brianloveswords/airtable" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -67,15 +68,17 @@ func airtableSync(opts *airtableOptions) error { issues.filterByTargets(opts.Targets) logger().Debug("fetch db entries", zap.Int("count", len(issues))) - at, err := airtable.New(opts.AirtableToken, opts.AirtableBaseID) - if err != nil { - return err + at := airtable.Client{ + APIKey: opts.AirtableToken, + BaseID: opts.AirtableBaseID, + Limiter: airtable.RateLimiter(5), } + table := at.Table(opts.AirtableTableName) alreadyInAirtable := map[string]bool{} records := []airtableRecord{} - if err := at.ListRecords(opts.AirtableTableName, &records); err != nil { + if err := table.List(&records, &airtable.Options{}); err != nil { return err } logger().Debug("fetched airtable records", zap.Int("count", len(records))) @@ -95,16 +98,17 @@ func airtableSync(opts *airtableOptions) error { r := issue.ToAirtableRecord() r.Fields.Labels = []string{} r.Fields.Assignees = []string{} - if err := at.CreateRecord(opts.AirtableTableName, r); err != nil { + if err := table.Create(&r); err != nil { return err } + records = append(records, r) } // update/destroy existing ones for _, record := range records { if issue, found := issues[record.Fields.URL]; !found { logger().Debug("destroying airtable record", zap.String("URL", record.Fields.URL)) - if err := at.DestroyRecord(opts.AirtableTableName, record.ID); err != nil { + if err := table.Delete(&record); err != nil { return errors.Wrap(err, "failed to destroy record") } } else { @@ -117,13 +121,17 @@ func airtableSync(opts *airtableOptions) error { } 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 { + record.Fields = issue.ToAirtableRecord().Fields + if err := table.Update(&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 { + record.Fields.Labels = []string{} + record.Fields.Assignees = []string{} + if typedErr, ok := err.(airtable.ErrClientRequest); ok { + record.Fields.Errors = typedErr.Err.Error() + } else { + record.Fields.Errors = err.Error() + } + if err := table.Update(&record); err != nil { logger().Error("failed to update record without slices", zap.String("URL", issue.URL), zap.Error(err)) } } @@ -171,6 +179,7 @@ func (i Issue) ToAirtableRecord() airtableRecord { RepoURL: i.RepoURL, Body: i.Body, State: i.State, + Errors: "", }, } } @@ -187,9 +196,21 @@ type airtableIssue struct { Type string Labels []string Assignees []string + Errors string } func (ai airtableIssue) Equals(other airtableIssue) bool { + sameSlice := func(a, b []string) bool { + if a == nil { + a = []string{} + } + if b == nil { + b = []string{} + } + sort.Strings(a) + sort.Strings(b) + return reflect.DeepEqual(a, b) + } 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() && @@ -199,23 +220,7 @@ func (ai airtableIssue) Equals(other airtableIssue) bool { 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, - "Provider": a.Provider, - "State": a.State, - "Body": a.Body, - "RepoURL": a.RepoURL, - "Type": a.Type, - "Labels": a.Labels, - "Assignees": a.Assignees, - "Errors": "", - } + sameSlice(ai.Labels, other.Labels) && + sameSlice(ai.Assignees, other.Assignees) && + ai.Errors == other.Errors } diff --git a/go.mod b/go.mod index dee72222b..dcebaa88d 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ require ( cloud.google.com/go v0.29.0 // indirect github.com/BurntSushi/toml v0.3.1 // indirect github.com/awalterschulze/gographviz v0.0.0-20180927133620-e69668a01397 + github.com/brianloveswords/airtable v0.0.0-20180329193050-a39294038dd9 github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // indirect github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect - github.com/fabioberger/airtable-go v3.1.0+incompatible // indirect github.com/go-chi/chi v3.3.3+incompatible - github.com/go-chi/docgen v1.0.2 // indirect + github.com/go-chi/docgen v1.0.2 github.com/go-chi/render v1.0.1 github.com/go-sql-driver/mysql v1.4.0 // indirect github.com/google/go-cmp v0.2.0 // indirect @@ -24,7 +24,9 @@ require ( github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.2.1 + github.com/uber-go/atomic v1.3.2 // indirect github.com/xanzy/go-gitlab v0.11.1 + go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect go.uber.org/zap v1.9.1 golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 // indirect golang.org/x/net v0.0.0-20181003013248-f5e5bdd77824 // indirect diff --git a/go.sum b/go.sum index 1f00db3c5..d7d8a3699 100644 --- a/go.sum +++ b/go.sum @@ -4,14 +4,14 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/awalterschulze/gographviz v0.0.0-20180927133620-e69668a01397 h1:SzTF/aqwdSBijZUpXvw1ditdkwfU+ONMyocC2CyRtrM= github.com/awalterschulze/gographviz v0.0.0-20180927133620-e69668a01397/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/brianloveswords/airtable v0.0.0-20180329193050-a39294038dd9 h1:2oAZVcirE7xEkkA3lQqEB6ElATe5r60zwQoeTQ64WNI= +github.com/brianloveswords/airtable v0.0.0-20180329193050-a39294038dd9/go.mod h1:jXij3SzY3HghKUmTjQYGRIXyCRBkFK+0wztOaI6a9Bk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo= github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/fabioberger/airtable-go v3.1.0+incompatible h1:n5dw+HWBc+hytrVL75xe94EGt7FtNFGDII1tNoWTCAE= -github.com/fabioberger/airtable-go v3.1.0+incompatible/go.mod h1:EoKuSh7EefzhMCyVr6iXPlgFzDgHyZCZ3E5Sg8Cy9GM= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= @@ -69,12 +69,16 @@ github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M= github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/xanzy/go-gitlab v0.11.1 h1:kgVxG9YFerbzMnLuwcFio5nwdv/5bCDHgm7WviT+EEE= github.com/xanzy/go-gitlab v0.11.1/go.mod h1:CRKHkvFWNU6C3AEfqLWjnCNnAs4nj8Zk95rX2S3X6Mw= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 h1:d9qaMM+ODpCq+9We41//fu/sHsTnXcrqd1en3x+GKy4= +go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=