diff options
Diffstat (limited to 'bridge')
-rw-r--r-- | bridge/core/bridge.go | 10 | ||||
-rw-r--r-- | bridge/core/export.go | 101 | ||||
-rw-r--r-- | bridge/core/interfaces.go | 2 | ||||
-rw-r--r-- | bridge/github/export.go | 185 | ||||
-rw-r--r-- | bridge/github/export_test.go | 24 |
5 files changed, 231 insertions, 91 deletions
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index 1b960e0e..30e051be 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -297,21 +297,21 @@ func (b *Bridge) ImportAll(since time.Time) error { return importer.ImportAll(b.repo, since) } -func (b *Bridge) ExportAll(since time.Time) error { +func (b *Bridge) ExportAll(since time.Time) (<-chan ExportResult, error) { exporter := b.getExporter() if exporter == nil { - return ErrExportNotSupported + return nil, ErrExportNotSupported } err := b.ensureConfig() if err != nil { - return err + return nil, err } err = b.ensureInit() if err != nil { - return err + return nil, err } - return exporter.ExportAll(b.repo, since) + return exporter.ExportAll(b.repo, since), nil } diff --git a/bridge/core/export.go b/bridge/core/export.go new file mode 100644 index 00000000..51149430 --- /dev/null +++ b/bridge/core/export.go @@ -0,0 +1,101 @@ +package core + +import "fmt" + +type EventStatus int + +const ( + _ EventStatus = iota + EventStatusBug + EventStatusComment + EventStatusCommentEdition + EventStatusStatusChange + EventStatusTitleEdition + EventStatusLabelChange + EventStatusNothing +) + +type ExportResult struct { + Err error + Event EventStatus + ID string + Reason string +} + +func (er ExportResult) String() string { + switch er.Event { + case EventStatusBug: + return "new issue" + case EventStatusComment: + return "new comment" + case EventStatusCommentEdition: + return "updated comment" + case EventStatusStatusChange: + return "changed status" + case EventStatusTitleEdition: + return "changed title" + case EventStatusLabelChange: + return "changed label" + case EventStatusNothing: + return fmt.Sprintf("no event: %v", er.Reason) + default: + panic("unknown export result") + } +} + +func NewExportError(err error, reason string) ExportResult { + return ExportResult{ + Err: err, + Reason: reason, + } +} + +func NewExportNothing(id string, reason string) ExportResult { + return ExportResult{ + ID: id, + Reason: reason, + Event: EventStatusNothing, + } +} + +func NewExportBug(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusBug, + } +} + +func NewExportComment(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusComment, + } +} + +func NewExportCommentEdition(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusCommentEdition, + } +} + +func NewExportStatusChange(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusStatusChange, + } +} + +func NewExportLabelChange(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusLabelChange, + } +} + +func NewExportTitleEdition(id string) ExportResult { + return ExportResult{ + ID: id, + Event: EventStatusTitleEdition, + } +} diff --git a/bridge/core/interfaces.go b/bridge/core/interfaces.go index 37fdb3d7..5cc7006a 100644 --- a/bridge/core/interfaces.go +++ b/bridge/core/interfaces.go @@ -34,5 +34,5 @@ type Importer interface { type Exporter interface { Init(conf Configuration) error - ExportAll(repo *cache.RepoCache, since time.Time) error + ExportAll(repo *cache.RepoCache, since time.Time) <-chan ExportResult } diff --git a/bridge/github/export.go b/bridge/github/export.go index 00ba1585..40044ca1 100644 --- a/bridge/github/export.go +++ b/bridge/github/export.go @@ -26,13 +26,7 @@ var ( type githubExporter struct { conf core.Configuration - // number of exported bugs - exportedBugs int - - // number of exported labels - exportedLabels int - - // export only bugs taged with one of these origins + // export only bugs tagged with one of these origins onlyOrigins []string // cache identities clients @@ -81,7 +75,7 @@ func (ge *githubExporter) allowOrigin(origin string) bool { } // getIdentityClient return an identity github api v4 client -// if no client were found it will initilize it from the known tokens map and cache it for next use +// if no client were found it will initialize it from the known tokens map and cache it for next use func (ge *githubExporter) getIdentityClient(id string) (*githubv4.Client, error) { client, ok := ge.identityClient[id] if ok { @@ -103,60 +97,68 @@ func (ge *githubExporter) getIdentityClient(id string) (*githubv4.Client, error) } // ExportAll export all event made by the current user to Github -func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) error { - user, err := repo.GetUserIdentity() - if err != nil { - return err - } +func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) <-chan core.ExportResult { + out := make(chan core.ExportResult) - ge.identityToken[user.Id()] = ge.conf[keyToken] + go func(out chan<- core.ExportResult) { + defer close(out) - // get repository node id - ge.repositoryID, err = getRepositoryNodeID( - ge.conf[keyOwner], - ge.conf[keyProject], - ge.conf[keyToken], - ) - - if err != nil { - return err - } + user, err := repo.GetUserIdentity() + if err != nil { + out <- core.NewExportError(err, "") + return + } - allIdentitiesIds := []string{} - for id := range ge.identityToken { - allIdentitiesIds = append(allIdentitiesIds, id) - } + ge.identityToken[user.Id()] = ge.conf[keyToken] - allBugsIds := repo.AllBugsIds() + // get repository node id + ge.repositoryID, err = getRepositoryNodeID( + ge.conf[keyOwner], + ge.conf[keyProject], + ge.conf[keyToken], + ) - for _, id := range allBugsIds { - b, err := repo.ResolveBug(id) if err != nil { - return err + out <- core.NewExportError(err, ge.repositoryID) + return } - snapshot := b.Snapshot() - - // ignore issues created before since date - if snapshot.CreatedAt.Before(since) { - continue + var allIdentitiesIds []string + for id := range ge.identityToken { + allIdentitiesIds = append(allIdentitiesIds, id) } - if snapshot.HasAnyParticipant(allIdentitiesIds...) { - // try to export the bug and it associated events - if err := ge.exportBug(b, since); err != nil { - return err + allBugsIds := repo.AllBugsIds() + + for _, id := range allBugsIds { + b, err := repo.ResolveBug(id) + if err != nil { + out <- core.NewExportError(err, id) + return } - } - } + snapshot := b.Snapshot() - fmt.Printf("Successfully exported %d issues and %d labels to Github\n", ge.exportedBugs, ge.exportedLabels) - return nil + // ignore issues created before since date + if snapshot.CreatedAt.Before(since) { + out <- core.NewExportNothing(b.Id(), "bug created before the since date") + continue + } + + if snapshot.HasAnyActor(allIdentitiesIds...) { + // try to export the bug and it associated events + ge.exportBug(b, since, out) + } else { + out <- core.NewExportNothing(id, "not an actor") + } + } + }(out) + + return out } // exportBug publish bugs and related events -func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { +func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan<- core.ExportResult) { snapshot := b.Snapshot() var bugGithubID string @@ -174,8 +176,8 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // skip bug if origin is not allowed origin, ok := createOp.GetMetadata(keyOrigin) if ok && !ge.allowOrigin(origin) { - // TODO print a warn ? - return nil + out <- core.NewExportNothing(b.Id(), fmt.Sprintf("issue taged with origin: %s", origin)) + return } // get github bug ID @@ -187,6 +189,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { panic("expected to find github issue URL") } + out <- core.NewExportNothing(b.Id(), "bug already exported") // will be used to mark operation related to a bug as exported bugGithubID = githubID bugGithubURL = githubURL @@ -197,33 +200,41 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { if err != nil { // if bug is still not exported and we do not have the author stop the execution - fmt.Println("warning: skipping issue due to missing token for bug creator") + // fmt.Println("warning: skipping issue due to missing token for bug creator") // this is not an error, don't export bug - return nil + out <- core.NewExportNothing(b.Id(), fmt.Sprintf("missing author token")) + return } // create bug id, url, err := createGithubIssue(client, ge.repositoryID, createOp.Title, createOp.Message) if err != nil { - return errors.Wrap(err, "exporting github issue") + err := errors.Wrap(err, "exporting github issue") + out <- core.NewExportError(err, b.Id()) + return } - // incr exported bugs - ge.exportedBugs++ + out <- core.NewExportBug(b.Id()) hash, err := createOp.Hash() if err != nil { - return errors.Wrap(err, "comment hash") + err := errors.Wrap(err, "comment hash") + out <- core.NewExportError(err, b.Id()) + return } // mark bug creation operation as exported if err := markOperationAsExported(b, hash, id, url); err != nil { - return errors.Wrap(err, "marking operation as exported") + err := errors.Wrap(err, "marking operation as exported") + out <- core.NewExportError(err, b.Id()) + return } // commit operation to avoid creating multiple issues with multiple pushes if err := b.CommitAsNeeded(); err != nil { - return errors.Wrap(err, "bug commit") + err := errors.Wrap(err, "bug commit") + out <- core.NewExportError(err, b.Id()) + return } // cache bug github ID and URL @@ -234,7 +245,8 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // get createOp hash hash, err := createOp.Hash() if err != nil { - return err + out <- core.NewExportError(err, b.Id()) + return } bugCreationHash = hash.String() @@ -251,20 +263,23 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // get operation hash hash, err := op.Hash() if err != nil { - return errors.Wrap(err, "operation hash") + err := errors.Wrap(err, "operation hash") + out <- core.NewExportError(err, b.Id()) + return } // ignore imported (or exported) operations from github // cache the ID of already exported or imported issues and events from Github if id, ok := op.GetMetadata(keyGithubId); ok { ge.cachedIDs[hash.String()] = id + out <- core.NewExportNothing(hash.String(), "already exported operation") continue } opAuthor := op.GetAuthor() client, err := ge.getIdentityClient(opAuthor.Id()) if err != nil { - // don't export operation + out <- core.NewExportNothing(hash.String(), "missing operation author token") continue } @@ -276,9 +291,13 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // send operation to github id, url, err = addCommentGithubIssue(client, bugGithubID, opr.Message) if err != nil { - return errors.Wrap(err, "adding comment") + err := errors.Wrap(err, "adding comment") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportComment(hash.String()) + // cache comment id ge.cachedIDs[hash.String()] = id @@ -292,9 +311,13 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // case bug creation operation: we need to edit the Github issue if err := updateGithubIssueBody(client, bugGithubID, opr.Message); err != nil { - return errors.Wrap(err, "editing issue") + err := errors.Wrap(err, "editing issue") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportCommentEdition(hash.String()) + id = bugGithubID url = bugGithubURL @@ -308,9 +331,13 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { eid, eurl, err := editCommentGithubIssue(client, commentID, opr.Message) if err != nil { - return errors.Wrap(err, "editing comment") + err := errors.Wrap(err, "editing comment") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportCommentEdition(hash.String()) + // use comment id/url instead of issue id/url id = eid url = eurl @@ -319,27 +346,39 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { case *bug.SetStatusOperation: opr := op.(*bug.SetStatusOperation) if err := updateGithubIssueStatus(client, bugGithubID, opr.Status); err != nil { - return errors.Wrap(err, "editing status") + err := errors.Wrap(err, "editing status") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportStatusChange(hash.String()) + id = bugGithubID url = bugGithubURL case *bug.SetTitleOperation: opr := op.(*bug.SetTitleOperation) if err := updateGithubIssueTitle(client, bugGithubID, opr.Title); err != nil { - return errors.Wrap(err, "editing title") + err := errors.Wrap(err, "editing title") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportTitleEdition(hash.String()) + id = bugGithubID url = bugGithubURL case *bug.LabelChangeOperation: opr := op.(*bug.LabelChangeOperation) if err := ge.updateGithubIssueLabels(client, bugGithubID, opr.Added, opr.Removed); err != nil { - return errors.Wrap(err, "updating labels") + err := errors.Wrap(err, "updating labels") + out <- core.NewExportError(err, b.Id()) + return } + out <- core.NewExportLabelChange(hash.String()) + id = bugGithubID url = bugGithubURL @@ -349,16 +388,18 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time) error { // mark operation as exported if err := markOperationAsExported(b, hash, id, url); err != nil { - return errors.Wrap(err, "marking operation as exported") + err := errors.Wrap(err, "marking operation as exported") + out <- core.NewExportError(err, b.Id()) + return } // commit at each operation export to avoid exporting same events multiple times if err := b.CommitAsNeeded(); err != nil { - return errors.Wrap(err, "bug commit") + err := errors.Wrap(err, "bug commit") + out <- core.NewExportError(err, b.Id()) + return } } - - return nil } // getRepositoryNodeID request github api v3 to get repository node id @@ -391,7 +432,10 @@ func getRepositoryNodeID(owner, project, token string) (string, error) { }{} data, _ := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() + err = resp.Body.Close() + if err != nil { + return "", err + } err = json.Unmarshal(data, &aux) if err != nil { @@ -534,7 +578,6 @@ func (ge *githubExporter) getOrCreateGithubLabelID(gc *githubv4.Client, reposito return "", err } - ge.exportedLabels++ return labelID, nil } diff --git a/bridge/github/export_test.go b/bridge/github/export_test.go index ff8b70b8..827152b7 100644 --- a/bridge/github/export_test.go +++ b/bridge/github/export_test.go @@ -163,7 +163,7 @@ func testCases(repo *cache.RepoCache, identity *cache.IdentityCache) ([]*testCas func TestPushPull(t *testing.T) { // repo owner - user := os.Getenv("TEST_USER") + user := os.Getenv("GITHUB_TEST_USER") // token must have 'repo' and 'delete_repo' scopes token := os.Getenv("GITHUB_TOKEN_ADMIN") @@ -180,30 +180,24 @@ func TestPushPull(t *testing.T) { // set author identity author, err := backend.NewIdentity("test identity", "test@test.org") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = backend.SetUserIdentity(author) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer backend.Close() interrupt.RegisterCleaner(backend.Close) tests, err := testCases(backend, author) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // generate project name projectName := generateRepoName() // create target Github repository - if err := createRepository(projectName, token); err != nil { - t.Fatal(err) - } + err = createRepository(projectName, token) + require.NoError(t, err) + fmt.Println("created repository", projectName) // Make sure to remove the Github repository when the test end @@ -230,7 +224,9 @@ func TestPushPull(t *testing.T) { start := time.Now() // export all bugs - err = exporter.ExportAll(backend, time.Time{}) + for result := range exporter.ExportAll(backend, time.Time{}) { + require.NoError(t, result.Err) + } require.NoError(t, err) fmt.Printf("test repository exported in %f seconds\n", time.Since(start).Seconds()) |