Skip to content

Commit

Permalink
internal/gaby: connect labeler to gaby
Browse files Browse the repository at this point in the history
Add the labeler as part of the gaby main program.

For #64.

Change-Id: I935888d46daf25018345c605bdcc0bf9b758159b
Reviewed-on: https://go-review.googlesource.com/c/oscar/+/639735
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
jba committed Jan 2, 2025
1 parent 7a94449 commit 85d2049
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 3 deletions.
3 changes: 3 additions & 0 deletions internal/gaby/github_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ func (g *Gaby) handleGitHubIssueEvent(ctx context.Context, event *github.Webhook
if err := g.fixGitHubIssue(ctx, project, event.Issue.Number); err != nil {
return false, err
}
if err := g.labeler.LabelIssue(ctx, project, event.Issue.Number); err != nil {
return false, err
}
return true, nil
}

Expand Down
5 changes: 5 additions & 0 deletions internal/gaby/github_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"golang.org/x/oscar/internal/docs"
"golang.org/x/oscar/internal/github"
"golang.org/x/oscar/internal/httprr"
"golang.org/x/oscar/internal/labels"
"golang.org/x/oscar/internal/llm"
"golang.org/x/oscar/internal/related"
"golang.org/x/oscar/internal/secret"
Expand Down Expand Up @@ -140,6 +141,7 @@ func testGaby(t *testing.T, secret secret.DB) *Gaby {

vdb := storage.MemVectorDB(db, lg, "vecs")
emb := llm.QuoteEmbedder()
cgen := llm.EchoContentGenerator()

rp := related.New(lg, db, gh, vdb, dc, "related")
rp.EnableProject(testProject)
Expand All @@ -152,6 +154,8 @@ func testGaby(t *testing.T, secret secret.DB) *Gaby {
// No fixes yet.
cf.EnableEdits()

lab := labels.New(lg, db, gh, cgen, "labels")

return &Gaby{
githubProjects: []string{testProject, testProject2},
github: gh,
Expand All @@ -163,6 +167,7 @@ func testGaby(t *testing.T, secret secret.DB) *Gaby {
docs: dc,
commentFixer: cf,
relatedPoster: rp,
labeler: lab,
}
}

Expand Down
31 changes: 29 additions & 2 deletions internal/gaby/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"golang.org/x/oscar/internal/gerrit"
"golang.org/x/oscar/internal/github"
"golang.org/x/oscar/internal/googlegroups"
"golang.org/x/oscar/internal/labels"
"golang.org/x/oscar/internal/llm"
"golang.org/x/oscar/internal/llmapp"
"golang.org/x/oscar/internal/overview"
Expand Down Expand Up @@ -110,6 +111,7 @@ type Gaby struct {
rulesPoster *rules.Poster // used to post rule violations
commentFixer *commentfix.Fixer // used to fix GitHub comments
overview *overview.Client // used to generate and post overviews
labeler *labels.Labeler // used to assign labels to issues
}

func main() {
Expand Down Expand Up @@ -226,6 +228,20 @@ func main() {
}
g.rulesPoster = rulep

labeler := labels.New(g.slog, g.db, g.github, ai, "gabyhelp")
for _, proj := range g.githubProjects {
// TODO: support other projects.
if proj != "golang/go" {
continue
}
labeler.EnableProject(proj)
}
labeler.EnableLabels()
if !slices.Contains(autoApprovePkgs, "labels") {
labeler.RequireApproval()
}
g.labeler = labeler

// Named functions to retrieve latest Watcher times.
watcherLatests := map[string]func() timed.DBTime{
github.DocWatcherID: docs.LatestFunc(g.github),
Expand All @@ -239,6 +255,7 @@ func main() {
"gerritlinks fix": cf.Latest,
"related": rp.Latest,
"rules": rulep.Latest,
"labeler": labeler.Latest,
}

// Install a metric that observes the latest values of the watchers each time metrics are sampled.
Expand All @@ -254,7 +271,7 @@ func main() {
select {}
}

var validApprovalPkgs = []string{"commentfix", "related", "rules"}
var validApprovalPkgs = []string{"commentfix", "related", "rules", "labels"}

// parseApprovalPkgs parses a comma-separated list of package names,
// checking that the packages are valid.
Expand Down Expand Up @@ -693,10 +710,12 @@ func (g *Gaby) syncAndRunAll(ctx context.Context) (errs []error) {
}

if flags.enablechanges {
// Changes can run in any order.
// Changes can run in almost any order; the labeler should
// run before anything that uses labels.
// Write all changes to the action log.
check(g.fixAllComments(ctx))
check(g.postAllRelated(ctx))
check(g.labelAll(ctx))
check(g.postAllRules(ctx))
// Apply all actions.
actions.Run(ctx, g.slog, g.db)
Expand All @@ -716,6 +735,7 @@ const (
gabyFixCommentLock = "gabyfixcommentaction"
gabyPostRelatedLock = "gabyrelatedaction"
gabyPostRulesLock = "gabyrulesaction"
gabyLabelLock = "gabylabelaction"
)

func (g *Gaby) syncGitHubIssues(ctx context.Context) error {
Expand Down Expand Up @@ -803,6 +823,13 @@ func (g *Gaby) postAllRules(ctx context.Context) error {
return g.rulesPoster.Run(ctx)
}

func (g *Gaby) labelAll(ctx context.Context) error {
g.db.Lock(gabyLabelLock)
defer g.db.Unlock(gabyLabelLock)

return g.labeler.Run(ctx)
}

// localCron simulates Cloud Scheduler by fetching our server's /cron endpoint once per minute.
func (g *Gaby) localCron() {
for ; ; time.Sleep(1 * time.Minute) {
Expand Down
30 changes: 29 additions & 1 deletion internal/labels/labeler.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,34 @@ func (l *Labeler) Run(ctx context.Context) error {
return nil
}

// LabelIssue labels a single issue.
//
// It follows the same logic as [Labeler.Run] for a single issue, except
// that it does not rely on or modify the Labeler's watcher.
// This means that [Labeler.LabelIssue] can be called on any issue without
// affecting the starting point of future calls to [Labeler.Run].
//
// It requires that there be a database entry for the given issue.
func (l *Labeler) LabelIssue(ctx context.Context, project string, issue int64) error {
e := lookupIssueEvent(project, issue, l.github)
if e == nil {
return fmt.Errorf("labels.Labeler.LabelIssue(project=%s, issue=%d): event not found", project, issue)
}
_, err := l.logLabelIssue(ctx, e)
return err
}

// lookupIssueEvent returns the first event for the "/issues" API with
// the given ID in the database, or nil if not found.
func lookupIssueEvent(project string, issue int64, gh *github.Client) *github.Event {
for event := range gh.Events(project, issue, issue) {
if event.API == "/issues" {
return event
}
}
return nil
}

// logLabelIssue logs an action to post an issue for the event.
// advance is true if the event should be considered to have been
// handled by this or a previous run function, indicating
Expand All @@ -179,7 +207,7 @@ func (l *Labeler) logLabelIssue(ctx context.Context, e *github.Event) (advance b
return false, nil
}
// If an action has already been logged for this event, do nothing.
// we don't need a lock. [actions.before] will lock to avoid multiple log entries.
// We don't need a lock. [actions.before] will lock to avoid multiple log entries.
if _, ok := actions.Get(l.db, l.actionKind, logKey(e)); ok {
l.slog.Info("labels.Labeler already logged", "name", l.name, "project", e.Project, "issue", e.Issue, "event", e)
// If labeling is enabled, we can advance the watcher because
Expand Down

0 comments on commit 85d2049

Please sign in to comment.