Repository navigation
Add GitLab source and webhook support for TaskSpawners #1721
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
base: main
Are you sure you want to change the base?
Changes from all commits
c49b8ef
3ce94cf
eaa8472
3f8149b
eab8eda
224e4ae
2ba1124
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package v1alpha2 | ||
|
|
||
| // TrackerSource is the provider-neutral view of a code-host source on a | ||
| // TaskSpawner. Polling sources (githubIssues, githubPullRequests, gitlab) and | ||
| // webhook sources (githubWebhook, gitlabWebhook) all project onto it, so code | ||
| // that only needs the provider, the repository, or the reporting | ||
| // configuration does not branch on the concrete source. | ||
| type TrackerSource struct { | ||
| // Provider is the code host, using the Workspace provider values. | ||
| Provider string | ||
| // Webhook is true for event-driven sources and false for polling sources. | ||
| Webhook bool | ||
| // Repo scopes the source to a repository other than the Workspace's: the | ||
| // GitHub "owner/repo" override or restriction, or the GitLab project path. | ||
| // Empty means the Workspace repository. | ||
| Repo string | ||
| // PriorityLabels orders discovered items for polling sources that support it. | ||
| PriorityLabels []string | ||
| // Comments is the status-comment configuration, or nil when comment | ||
| // reporting is off. The deprecated GitHub reporting.enabled flag maps to | ||
| // PerTask comments. | ||
| Comments *CommentsReporting | ||
| // Checks is the GitHub Check Run configuration, or nil. | ||
| Checks *GitHubChecksReporting | ||
| } | ||
|
|
||
| // CommentMode returns the effective comment mode: the configured one, or | ||
| // PerTask when comments are enabled without an explicit mode. | ||
| func (t TrackerSource) CommentMode() CommentMode { | ||
| if t.Comments != nil && t.Comments.Mode != "" { | ||
| return t.Comments.Mode | ||
| } | ||
| return CommentModePerTask | ||
| } | ||
|
|
||
| // Tracker returns the code-host view of the configured source. ok is false | ||
| // when the source is not backed by a code host (cron, jira, linear, generic | ||
| // webhook, slack) or when no source is set. | ||
| func (w When) Tracker() (TrackerSource, bool) { | ||
| switch { | ||
| case w.GitHubIssues != nil: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When a Prompt for AI agents |
||
| return githubTracker(w.GitHubIssues.Repo, w.GitHubIssues.PriorityLabels, w.GitHubIssues.Reporting, false), true | ||
| case w.GitHubPullRequests != nil: | ||
| return githubTracker(w.GitHubPullRequests.Repo, w.GitHubPullRequests.PriorityLabels, w.GitHubPullRequests.Reporting, false), true | ||
| case w.GitHubWebhook != nil: | ||
| return githubTracker(w.GitHubWebhook.Repository, nil, w.GitHubWebhook.Reporting, true), true | ||
| case w.GitLab != nil: | ||
| return gitlabTracker(w.GitLab.Project, w.GitLab.Reporting, false), true | ||
| case w.GitLabWebhook != nil: | ||
| return gitlabTracker(w.GitLabWebhook.Project, w.GitLabWebhook.Reporting, true), true | ||
| } | ||
| return TrackerSource{}, false | ||
| } | ||
|
|
||
| // PollInterval returns the configured poll interval of the polling source, or | ||
| // "" when the source uses the default or is not polled. | ||
| func (w When) PollInterval() string { | ||
| switch { | ||
| case w.GitHubIssues != nil: | ||
| return w.GitHubIssues.PollInterval | ||
| case w.GitHubPullRequests != nil: | ||
| return w.GitHubPullRequests.PollInterval | ||
| case w.Jira != nil: | ||
| return w.Jira.PollInterval | ||
| case w.GitLab != nil: | ||
| return w.GitLab.PollInterval | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| func githubTracker(repo string, priorityLabels []string, reporting *GitHubReporting, webhook bool) TrackerSource { | ||
| t := TrackerSource{Provider: WorkspaceProviderGitHub, Webhook: webhook, Repo: repo, PriorityLabels: priorityLabels} | ||
| if reporting == nil { | ||
| return t | ||
| } | ||
| t.Comments = reporting.Comments | ||
| if t.Comments == nil && reporting.Enabled { | ||
| t.Comments = &CommentsReporting{Mode: CommentModePerTask} | ||
| } | ||
| t.Checks = reporting.Checks | ||
| return t | ||
| } | ||
|
|
||
| func gitlabTracker(project string, reporting *GitLabReporting, webhook bool) TrackerSource { | ||
| t := TrackerSource{Provider: WorkspaceProviderGitLab, Webhook: webhook, Repo: project} | ||
| if reporting != nil { | ||
| t.Comments = reporting.Comments | ||
| } | ||
| return t | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package v1alpha2 | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| // whenFieldTrackers records, for every When field, whether it is a code-host | ||
| // source. Adding a field to When without listing it here fails the test, so a | ||
| // new provider cannot be forgotten in Tracker(). | ||
| var whenFieldTrackers = map[string]bool{ | ||
| "GitHubIssues": true, | ||
| "GitHubPullRequests": true, | ||
| "GitHubWebhook": true, | ||
| "GitLab": true, | ||
| "GitLabWebhook": true, | ||
| "Cron": false, | ||
| "Jira": false, | ||
| "LinearWebhook": false, | ||
| "GenericWebhook": false, | ||
| "Slack": false, | ||
| } | ||
|
|
||
| func TestWhenTrackerCoversEveryField(t *testing.T) { | ||
| whenType := reflect.TypeOf(When{}) | ||
| for i := 0; i < whenType.NumField(); i++ { | ||
| field := whenType.Field(i) | ||
| wantTracker, listed := whenFieldTrackers[field.Name] | ||
| if !listed { | ||
| t.Fatalf("When.%s is not classified in whenFieldTrackers; decide whether Tracker() must cover it", field.Name) | ||
| } | ||
| var when When | ||
| reflect.ValueOf(&when).Elem().Field(i).Set(reflect.New(field.Type.Elem())) | ||
| if _, ok := when.Tracker(); ok != wantTracker { | ||
| t.Errorf("When{%s}.Tracker() ok = %v, want %v", field.Name, ok, wantTracker) | ||
| } | ||
| } | ||
| if len(whenFieldTrackers) != whenType.NumField() { | ||
| t.Errorf("whenFieldTrackers lists %d fields, When has %d", len(whenFieldTrackers), whenType.NumField()) | ||
| } | ||
| } | ||
|
|
||
| func TestWhenTracker(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| when When | ||
| want TrackerSource | ||
| }{ | ||
| { | ||
| name: "github issues with deprecated enabled flag", | ||
| when: When{GitHubIssues: &GitHubIssues{Repo: "org/upstream", PriorityLabels: []string{"p0"}, Reporting: &GitHubReporting{Enabled: true}}}, | ||
| want: TrackerSource{Provider: WorkspaceProviderGitHub, Repo: "org/upstream", PriorityLabels: []string{"p0"}, Comments: &CommentsReporting{Mode: CommentModePerTask}}, | ||
| }, | ||
| { | ||
| name: "github pull requests with sticky comments and checks", | ||
| when: When{GitHubPullRequests: &GitHubPullRequests{Reporting: &GitHubReporting{ | ||
| Comments: &CommentsReporting{Mode: CommentModeSticky}, | ||
| Checks: &GitHubChecksReporting{Name: "kelos"}, | ||
| }}}, | ||
| want: TrackerSource{Provider: WorkspaceProviderGitHub, Comments: &CommentsReporting{Mode: CommentModeSticky}, Checks: &GitHubChecksReporting{Name: "kelos"}}, | ||
| }, | ||
| { | ||
| name: "github webhook restriction", | ||
| when: When{GitHubWebhook: &GitHubWebhook{Repository: "org/repo"}}, | ||
| want: TrackerSource{Provider: WorkspaceProviderGitHub, Webhook: true, Repo: "org/repo"}, | ||
| }, | ||
| { | ||
| name: "gitlab project with comments", | ||
| when: When{GitLab: &GitLab{Project: "group/repo", Reporting: &GitLabReporting{Comments: &CommentsReporting{}}}}, | ||
| want: TrackerSource{Provider: WorkspaceProviderGitLab, Repo: "group/repo", Comments: &CommentsReporting{}}, | ||
| }, | ||
| { | ||
| name: "gitlab webhook without reporting", | ||
| when: When{GitLabWebhook: &GitLabWebhook{Project: "group/repo"}}, | ||
| want: TrackerSource{Provider: WorkspaceProviderGitLab, Webhook: true, Repo: "group/repo"}, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got, ok := tt.when.Tracker() | ||
| if !ok { | ||
| t.Fatal("Tracker() ok = false, want true") | ||
| } | ||
| if !reflect.DeepEqual(got, tt.want) { | ||
| t.Errorf("Tracker() = %+v, want %+v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestTrackerSourceCommentMode(t *testing.T) { | ||
| if got := (TrackerSource{}).CommentMode(); got != CommentModePerTask { | ||
| t.Errorf("CommentMode() without comments = %q, want PerTask", got) | ||
| } | ||
| if got := (TrackerSource{Comments: &CommentsReporting{}}).CommentMode(); got != CommentModePerTask { | ||
| t.Errorf("CommentMode() with empty mode = %q, want PerTask", got) | ||
| } | ||
| if got := (TrackerSource{Comments: &CommentsReporting{Mode: CommentModeSticky}}).CommentMode(); got != CommentModeSticky { | ||
| t.Errorf("CommentMode() = %q, want Sticky", got) | ||
| } | ||
| } | ||
|
|
||
| func TestWhenPollInterval(t *testing.T) { | ||
| tests := map[string]When{ | ||
| "2m": {GitHubIssues: &GitHubIssues{PollInterval: "2m"}}, | ||
| "3m": {GitHubPullRequests: &GitHubPullRequests{PollInterval: "3m"}}, | ||
| "4m": {Jira: &Jira{PollInterval: "4m"}}, | ||
| "30s": {GitLab: &GitLab{PollInterval: "30s"}}, | ||
| "": {GitHubWebhook: &GitHubWebhook{}}, | ||
| } | ||
| for want, when := range tests { | ||
| if got := when.PollInterval(); got != want { | ||
| t.Errorf("PollInterval() = %q, want %q", got, want) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: For webhook sources, an empty
RepositoryorProjectmeans unrestricted delivery, not the Workspace repository. Clarify this contract so future callers do not treat an empty webhook restriction as a workspace fallback.Prompt for AI agents