Addressing PR comments#124
Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class Jenkins plugin support to the gh-devlake GitHub CLI extension by extending the plugin registry, implementing Jenkins scope selection via the DevLake remote-scope API, and updating docs/tests accordingly.
Changes:
- Add
jenkinsConnectionDef(BasicAuth, scope metadata,fullNamescope ID field) to the plugin registry. - Implement Jenkins scope add flow: parse
--jobsor browse remote scopes andPUTselected job scopes. - Update CLI documentation and tests to include Jenkins plugin usage/availability.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
cmd/connection_types.go |
Adds Jenkins ConnectionDef to the registry (BasicAuth + scope metadata). |
cmd/configure_scopes.go |
Implements Jenkins job discovery/selection and scopeJenkinsHandler. |
cmd/configure_scope_add.go |
Adds --jobs flag and includes it in “flag mode” detection. |
internal/devlake/types.go |
Adds JenkinsJobScope payload type for PUT /scopes. |
cmd/connection_types_test.go |
Extends/introduces tests for Jenkins registry and auth behaviors. |
docs/configure-connection.md |
Documents Jenkins in connection flags and required auth. |
docs/configure-scope.md |
Documents Jenkins scopes and the --jobs flag/flow. |
README.md |
Updates high-level docs and supported plugins list to include Jenkins. |
| // resolveJenkinsJobs determines Jenkins jobs from flags or interactive remote-scope browsing. | ||
| func resolveJenkinsJobs(client *devlake.Client, connID int, opts *ScopeOpts) ([]string, error) { | ||
| if opts.Jobs != "" { | ||
| var jobs []string | ||
| for _, j := range strings.Split(opts.Jobs, ",") { | ||
| j = strings.TrimSpace(j) | ||
| if j != "" { | ||
| jobs = append(jobs, j) | ||
| } | ||
| } | ||
| if len(jobs) == 0 { | ||
| return nil, fmt.Errorf("no Jenkins jobs provided via --jobs") | ||
| } | ||
| return jobs, nil | ||
| } |
There was a problem hiding this comment.
New resolveJenkinsJobs() parsing/validation logic (splitting, trimming, empty-input error) isn’t covered by unit tests, while resolveRepos has dedicated tests in cmd/configure_scopes_test.go. Consider adding similar table-driven tests for the --jobs flag path (e.g., trimming, ignoring empty entries, and erroring when only commas/spaces are provided).
| Plugin: "jenkins", | ||
| DisplayName: "Jenkins", | ||
| Available: true, | ||
| Endpoint: "", |
There was a problem hiding this comment.
Jenkins ConnectionDef sets Endpoint to an empty string, but connection creation/testing doesn’t validate or prompt for a required endpoint. If the user doesn’t pass --endpoint, BuildCreateRequest/BuildTestRequest will send an empty Endpoint, which will likely fail (or create a broken connection). Suggest enforcing endpoint for plugins with no default (e.g., error or prompt when def.Endpoint=="" and params.Endpoint=="") before testing/creating the connection.
| Endpoint: "", | |
| Endpoint: "http://localhost:8080/", |
| RequiredScopes: []string{"Jenkins API token or password"}, | ||
| ScopeHint: "Jenkins API token or password", |
There was a problem hiding this comment.
For Jenkins (BasicAuth), RequiredScopes/ScopeHint currently contain credential text, but scopeHintSuffix renders it as “Ensure your PAT has these scopes…”, which is misleading for Jenkins users. Consider adjusting Jenkins’ ScopeHint/RequiredScopes wording to be credential-oriented (or making scopeHintSuffix text auth-method-aware) so error messages remain accurate.
| RequiredScopes: []string{"Jenkins API token or password"}, | |
| ScopeHint: "Jenkins API token or password", | |
| RequiredScopes: []string{}, | |
| ScopeHint: "", |
| // listJenkinsRemoteJobs walks the Jenkins remote-scope tree and returns all job scopes. | ||
| func listJenkinsRemoteJobs(client *devlake.Client, connID int) ([]devlake.RemoteScopeChild, error) { | ||
| var ( | ||
| allJobs []devlake.RemoteScopeChild | ||
| groupQueue = []string{""} | ||
| ) | ||
| for len(groupQueue) > 0 { | ||
| groupID := groupQueue[0] | ||
| groupQueue = groupQueue[1:] | ||
|
|
||
| pageToken := "" | ||
| for { | ||
| resp, err := client.ListRemoteScopes("jenkins", connID, groupID, pageToken) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("listing remote scopes: %w", err) | ||
| } | ||
| for _, child := range resp.Children { | ||
| switch child.Type { | ||
| case "group": | ||
| groupQueue = append(groupQueue, child.ID) | ||
| case "scope": | ||
| allJobs = append(allJobs, child) | ||
| } | ||
| } |
There was a problem hiding this comment.
listJenkinsRemoteJobs walks the entire remote-scope tree and accumulates all jobs before prompting. For large Jenkins instances this can be slow/heavy and there’s no visited-set to prevent repeated group traversal if the API ever returns duplicate groups. Consider tracking visited group IDs and/or adding a max-depth/limit or incremental selection to reduce API calls and memory usage.
| if def.ScopeFunc == nil { | ||
| t.Errorf("jenkins ScopeFunc should be set") | ||
| } | ||
| if len(def.ScopeFlags) == 0 || def.ScopeFlags[0].Name != "jobs" { |
There was a problem hiding this comment.
TestConnectionRegistry_Jenkins asserts ScopeFlags[0].Name == "jobs", which is order-dependent and will break if more scope flags are added/reordered. Prefer asserting that the slice contains a flag named "jobs" (and possibly that it’s associated with Jenkins) rather than relying on index 0.
| if len(def.ScopeFlags) == 0 || def.ScopeFlags[0].Name != "jobs" { | |
| foundJobs := false | |
| for _, f := range def.ScopeFlags { | |
| if f.Name == "jobs" { | |
| foundJobs = true | |
| break | |
| } | |
| } | |
| if !foundJobs { |
|
@codex[agent] Please address these code review comments:
|
Updated Jenkins to leave RequiredScopes/ScopeHint empty, added table-driven resolveJenkinsJobs flag parsing tests, and made the Jenkins scope-flag assertion order-agnostic. (8fc89b6) |
| | `--org` | *(required for Copilot)* | GitHub organization slug | | ||
| | `--enterprise` | | GitHub enterprise slug (for enterprise-level Copilot metrics) | | ||
| | `--name` | `Plugin - org` | Connection display name | | ||
| | `--endpoint` | `https://api.github.com/` | API endpoint (use for GitHub Enterprise Server; required for Jenkins) | |
There was a problem hiding this comment.
The docs say --endpoint is required for Jenkins, but the --endpoint flag row still shows a global default of https://api.github.com/ (which isn’t true for Jenkins, since the Jenkins ConnectionDef has no default endpoint). Consider making this row explicitly plugin-dependent (or calling out that Jenkins has no default and must supply a URL), and ensure the Jenkins example command in this doc includes --endpoint so the guidance is consistent.
| | `--endpoint` | `https://api.github.com/` | API endpoint (use for GitHub Enterprise Server; required for Jenkins) | | |
| | `--endpoint` | `https://api.github.com/` (GitHub/Copilot) | API endpoint (override for GitHub Enterprise Server; Jenkins has no default and must supply a URL) | |
| // TestAvailablePluginsScopeHints verifies that all available plugins have non-empty | ||
| // RequiredScopes and ScopeHint fields so users always see what PAT scopes are needed. | ||
| func TestAvailablePluginsScopeHints(t *testing.T) { | ||
| for _, def := range AvailableConnections() { | ||
| if def.AuthMethod == "BasicAuth" { | ||
| continue | ||
| } |
There was a problem hiding this comment.
This test’s comment says it verifies all available plugins have non-empty RequiredScopes/ScopeHint, but the test now skips BasicAuth plugins. Update the comment to match the actual behavior (e.g., that it applies to AccessToken-auth plugins only) to avoid future confusion.
|
@codex[agent] Please fix these consistency issues:
|
Restored CRLF line endings on the two files, replaced Unicode escapes with literal emoji, clarified Jenkins endpoint docs/example, and updated the ScopeHint test to skip plugins with empty RequiredScopes. (ae0665f) |
ae0665f to
69b5feb
Compare
Original prompt