Skip to content

Addressing PR comments#123

Merged
ewega merged 4 commits into
mainfrom
claude/add-jira-support-cli
Mar 5, 2026
Merged

Addressing PR comments#123
ewega merged 4 commits into
mainfrom
claude/add-jira-support-cli

Conversation

@Claude

@Claude Claude AI commented Mar 5, 2026

Copy link
Copy Markdown
Contributor
Original prompt

This section details on the original issue you should resolve

<issue_title>Jira plugin support</issue_title>
<issue_description>## Summary

Add Jira as a supported DevOps tool in the CLI. Jira is the most popular issue tracker globally (~75% enterprise adoption), providing change lead time and issue resolution data for DORA metrics.

Upstream Plugin

jira in incubator-devlake/backend/plugins/jira/

Property Value
Plugin slug jira
Auth MultiAuth (AccessToken for Jira Cloud, BasicAuth for Jira Server)
Scope type Boards (BoardId uint64)
Scope ID field boardId
Default endpoint User-provided (e.g., https://your-domain.atlassian.net/)
Remote-scope API Yes
Connection test Yes

Dependencies

Blocked by:

Changes

  • Add ConnectionDef for jira in connectionRegistry:
    • AuthMethod: "AccessToken" (Jira Cloud; BasicAuth for Server can be added later)
    • Endpoint: "" (user must provide)
    • SupportsTest: true
    • TokenPrompt: "Jira API Token"
    • EnvVarNames: []string{"JIRA_TOKEN", "JIRA_API_TOKEN"}
    • EnvFileKeys: []string{"JIRA_TOKEN", "JIRA_API_TOKEN"}
    • ScopeIDField: "boardId"
    • ScopeFunc: scopeJiraHandler
  • Implement scopeJiraHandler:
    • Use client.ListRemoteScopes("jira", connID, "", "") to list boards
    • Let user select boards interactively or via --board-ids flag
    • PUT selected boards as scopes
  • Add JiraBoardScope struct (or build dynamically from remote-scope data)
  • Set Available: true

Acceptance Criteria

  • gh devlake configure connection add --plugin jira --endpoint https://mysite.atlassian.net creates a Jira connection
  • Jira appears in interactive plugin picker
  • Jira scopes list boards for selection
  • Connection test works against Jira API
  • go build ./..., go test ./..., go vet ./... pass</issue_description>

Comments on the Issue (you are @claude[agent] in this section)

@ewega ## Agent Context for Implementation

This repo uses a declarative plugin registry pattern. Here's what you need to do:

1. Add ConnectionDef entry to cmd/connection_types.go

Add a new entry to connectionRegistry slice. Use the existing github entry as a template. Key fields for Jira:

  • Plugin: "jira"
  • DisplayName: "Jira"
  • Available: true
  • Endpoint: "" (user must provide, e.g. https://your-domain.atlassian.net/)
  • SupportsTest: true
  • AuthMethod: "AccessToken" (Jira Cloud uses API tokens)
  • TokenPrompt: "Jira API token"
  • EnvVarNames: []string{"JIRA_TOKEN", "JIRA_API_TOKEN"}
  • EnvFileKeys: []string{"JIRA_TOKEN", "JIRA_API_TOKEN"}
  • ScopeIDField: "boardId"
  • ScopeFunc: scopeJiraHandler

2. Implement scopeJiraHandler in cmd/configure_scopes.go

Follow the pattern of scopeGitHubHandler. Use client.ListRemoteScopes("jira", connID, "", "") to list boards. Let user select interactively via prompt.SelectMulti. PUT selected boards as scopes.

3. Key conventions

  • Use doGet[T], doPost[T] generic helpers from internal/devlake/client.go — never raw HTTP
  • Error wrapping: fmt.Errorf("context: %w", err)
  • Terminal output: \n before emoji-prefixed steps, 3-space indent for sub-items
  • Imports: stdlib → external → internal, separated by blank lines
  • Tests: add entries to cmd/connection_types_test.go verifying the new registry entry

4. Do NOT

  • Add switch/case branches on plugin names outside connectionRegistry
  • Modify existing plugin entries
  • Change any files in internal/devlake/ unless adding new types needed for Jira scope responses</comment_new>

@Claude Claude AI assigned Claude and ewega Mar 5, 2026
@Claude Claude AI changed the title [WIP] Add Jira as a supported DevOps tool in CLI Add Jira plugin support Mar 5, 2026
@ewega ewega marked this pull request as ready for review March 5, 2026 12:32
Copilot AI review requested due to automatic review settings March 5, 2026 12:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Jira as a supported DevLake plugin in this GitHub CLI extension, enabling Jira board-based scope selection for DORA metrics workflows.

Changes:

  • Added a jira ConnectionDef entry to the plugin registry (auth, env var token resolution, scope metadata).
  • Implemented scopeJiraHandler to list Jira remote scopes (boards), prompt multi-select, and PUT selected board scopes.
  • Introduced a JiraBoardScope payload type and added a registry-focused unit test for the Jira entry.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
cmd/connection_types.go Registers the Jira plugin in connectionRegistry with auth/scope metadata.
cmd/configure_scopes.go Adds Jira scope handler to fetch remote boards, prompt selection, and upsert scopes.
internal/devlake/types.go Adds JiraBoardScope struct for PUT /scopes payloads.
cmd/connection_types_test.go Adds TestJiraConnectionDef to validate the Jira registry entry fields.
Comments suppressed due to low confidence (1)

cmd/connection_types.go:255

  • PR description says interactive connection creation “prompts for endpoint”, but the current connection-add flow does not prompt for endpoint at all; it only uses the registry default or the --endpoint flag. With Jira’s registry default set to empty, interactive users won’t be asked and requests will go out with an empty endpoint.
		Endpoint:         "", // user must provide (e.g., https://your-domain.atlassian.net/)
		SupportsTest:     true,

Comment thread cmd/connection_types.go
Plugin: "jira",
DisplayName: "Jira",
Available: true,
Endpoint: "", // user must provide (e.g., https://your-domain.atlassian.net/)

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endpoint is set to an empty string for the Jira ConnectionDef, but configure connection add does not prompt for an endpoint when the registry default is empty (it only uses --endpoint overrides). This will send create/test requests with an empty endpoint in interactive mode (and in flag mode if the user forgets --endpoint). Consider either providing a sensible default endpoint, or adding plugin-specific validation/prompting so Jira requires an endpoint before calling Test/Create.

This issue also appears on line 254 of the same file.

Suggested change
Endpoint: "", // user must provide (e.g., https://your-domain.atlassian.net/)
Endpoint: "https://your-domain.atlassian.net/", // user must provide (e.g., https://your-domain.atlassian.net/)

Copilot uses AI. Check for mistakes.
Comment thread cmd/connection_types.go Outdated
Comment on lines +256 to +259
AuthMethod: "AccessToken", // Jira Cloud uses API tokens
RateLimitPerHour: 0, // uses default 4500
RequiredScopes: []string{"read:jira-work", "read:jira-user"},
ScopeHint: "read:jira-work, read:jira-user",

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jira entry sets TokenPrompt to an API token, but RequiredScopes/ScopeHint are OAuth-style scopes (read:jira-work, read:jira-user) and are surfaced as “PAT scopes” in error messages and token resolution output. If this plugin is intended to use Atlassian API tokens, consider updating RequiredScopes/ScopeHint to user guidance that matches API-token auth (or adjusting the hint rendering so non-PAT auth methods don’t present scope-based instructions).

Suggested change
AuthMethod: "AccessToken", // Jira Cloud uses API tokens
RateLimitPerHour: 0, // uses default 4500
RequiredScopes: []string{"read:jira-work", "read:jira-user"},
ScopeHint: "read:jira-work, read:jira-user",
AuthMethod: "AccessToken", // Jira Cloud uses API tokens (no explicit scopes)
RateLimitPerHour: 0, // uses default 4500
// Jira API tokens do not use OAuth/PAT scopes; permissions come from the user account.

Copilot uses AI. Check for mistakes.
Comment thread cmd/configure_scopes.go Outdated
Comment on lines +391 to +395
// Extract boards from remote-scope response
var boardOptions []string
boardMap := make(map[string]*devlake.RemoteScopeChild)
for i := range remoteScopes.Children {
child := &remoteScopes.Children[i]

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ListRemoteScopes can return a NextPageToken, but this handler only fetches the first page. For Jira instances with many boards, the picker will silently omit boards beyond the first page. Consider looping while NextPageToken != "" and appending additional Children before building boardOptions.

Suggested change
// Extract boards from remote-scope response
var boardOptions []string
boardMap := make(map[string]*devlake.RemoteScopeChild)
for i := range remoteScopes.Children {
child := &remoteScopes.Children[i]
// Aggregate all pages of remote scopes
allChildren := remoteScopes.Children
nextToken := remoteScopes.NextPageToken
for nextToken != "" {
page, err := client.ListRemoteScopes("jira", connID, nextToken, "")
if err != nil {
return nil, fmt.Errorf("failed to list Jira boards (page token %s): %w", nextToken, err)
}
allChildren = append(allChildren, page.Children...)
nextToken = page.NextPageToken
}
// Extract boards from remote-scope response
var boardOptions []string
boardMap := make(map[string]*devlake.RemoteScopeChild)
for i := range allChildren {
child := &allChildren[i]

Copilot uses AI. Check for mistakes.
Comment thread cmd/configure_scopes.go
Comment on lines +398 to +402
if child.ID != "" {
label = fmt.Sprintf("%s (ID: %s)", child.Name, child.ID)
}
boardOptions = append(boardOptions, label)
boardMap[label] = child

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boardMap is keyed by the rendered label string. If child.ID is empty (or if two boards somehow render to the same label), later entries will overwrite earlier ones and selection may map to the wrong board. Consider keying the map by a stable unique value (e.g., child.ID) and keeping a separate label→ID mapping (or include a disambiguating suffix when IDs are missing).

Copilot uses AI. Check for mistakes.
Comment thread cmd/configure_scopes.go Outdated
Comment on lines +423 to +424
var boardID uint64
if _, err := fmt.Sscanf(child.ID, "%d", &boardID); err != nil {

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing boardID via fmt.Sscanf(child.ID, "%d", &boardID) is relatively brittle and less explicit than using strconv.ParseUint(child.ID, 10, 64) (which also gives clearer errors and avoids format-string parsing). Consider switching to ParseUint and handling empty IDs explicitly.

Suggested change
var boardID uint64
if _, err := fmt.Sscanf(child.ID, "%d", &boardID); err != nil {
if child.ID == "" {
fmt.Printf(" ⚠️ Skipping board %q: empty ID\n", child.Name)
continue
}
boardID, err := strconv.ParseUint(child.ID, 10, 64)
if err != nil {

Copilot uses AI. Check for mistakes.
@ewega

ewega commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

@claude[agent] Please address these code review comments:

  1. Remove misleading RequiredScopes/ScopeHint — Jira API tokens don't use OAuth/PAT scopes. Clear RequiredScopes to []string{} and ScopeHint to "" so error messages don't misleadingly tell users to "ensure your PAT has these scopes."

  2. Add pagination for board listingListRemoteScopes can return a NextPageToken. Loop while NextPageToken != "" and append additional Children before building boardOptions. Otherwise large Jira instances will silently miss boards.

  3. Use strconv.ParseUint for board ID parsing — Replace fmt.Sscanf(child.ID, "%d", &boardID) with strconv.ParseUint(child.ID, 10, 64) for clearer errors. Also handle empty child.ID explicitly with a warning.

@Claude

Claude AI commented Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

@claude[agent] Please address these code review comments:

  1. Remove misleading RequiredScopes/ScopeHint — Jira API tokens don't use OAuth/PAT scopes. Clear RequiredScopes to []string{} and ...

All three items addressed in commit 0317238:

  1. Removed RequiredScopes/ScopeHint — Cleared to empty []string{} and "" with comment explaining Jira API tokens don't use OAuth scopes
  2. Added pagination — Loop fetches all pages while NextPageToken != "", aggregating boards before building picker options
  3. Switched to strconv.ParseUint — Replaced fmt.Sscanf with strconv.ParseUint(child.ID, 10, 64) and added explicit empty ID check

@Claude Claude AI changed the title Add Jira plugin support Addressing PR comments Mar 5, 2026
@ewega ewega requested a review from Copilot March 5, 2026 12:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread cmd/configure_scopes.go
for nextToken != "" {
page, err := client.ListRemoteScopes("jira", connID, "", nextToken)
if err != nil {
return nil, fmt.Errorf("failed to list Jira boards (page token %s): %w", nextToken, err)

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error path includes the raw nextToken value in the returned error. Pagination tokens can be long/opaque and may contain sensitive or internal data; echoing them can leak into logs and makes errors noisy. Consider omitting the token entirely or only logging a truncated/quoted form (e.g., %q with a length cap).

Suggested change
return nil, fmt.Errorf("failed to list Jira boards (page token %s): %w", nextToken, err)
return nil, fmt.Errorf("failed to list Jira boards during pagination: %w", err)

Copilot uses AI. Check for mistakes.
Comment thread cmd/configure_scopes.go
Comment on lines +408 to +415
if child.Type == "scope" {
label := child.Name
if child.ID != "" {
label = fmt.Sprintf("%s (ID: %s)", child.Name, child.ID)
}
boardOptions = append(boardOptions, label)
boardMap[label] = child
}

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boards with empty/invalid IDs are still shown in the selection list, but are later skipped during scope creation, which can lead to a confusing flow where the user selects items and then hits "no valid boards to add". Filter out children with missing/invalid IDs before building boardOptions (or validate the selection and reprompt) so only addable boards are presented.

Suggested change
if child.Type == "scope" {
label := child.Name
if child.ID != "" {
label = fmt.Sprintf("%s (ID: %s)", child.Name, child.ID)
}
boardOptions = append(boardOptions, label)
boardMap[label] = child
}
if child.Type != "scope" {
continue
}
// Only expose boards with non-empty, valid numeric IDs so that
// any selection the user makes can be added as a scope.
if child.ID == "" {
fmt.Printf(" ⚠️ Skipping board %q in menu: empty ID\n", child.Name)
continue
}
if _, err := strconv.ParseUint(child.ID, 10, 64); err != nil {
fmt.Printf(" ⚠️ Skipping board %q in menu: invalid ID %q\n", child.Name, child.ID)
continue
}
label := fmt.Sprintf("%s (ID: %s)", child.Name, child.ID)
boardOptions = append(boardOptions, label)
boardMap[label] = child

Copilot uses AI. Check for mistakes.
@ewega

ewega commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

@claude[agent] Please fix these items:

  1. Add Jira to README Supported Plugins table — Add a row for Jira in the supported plugins table in README.md. Follow the existing pattern. Mark it as available with the appropriate token type info.

  2. Update TestAvailablePluginsScopeHints — Change your apiTokenPlugins map-based skip to if len(def.RequiredScopes) == 0 { continue }. This is more generic and handles both Jira and Jenkins (which also has empty scopes).

@Claude

Claude AI commented Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

@claude[agent] Please fix these items:

  1. Add Jira to README Supported Plugins table — Add a row for Jira in the supported plugins table in README.md. Follow the existing pattern. Mark it as av...

Both items addressed in commit 30842f9:

  1. Added Jira to README — New row in Supported Plugins table: "Jira | ✅ Available | Boards, issues, sprints (change lead time, cycle time) | API token (permissions from user account)"
  2. Simplified test — Replaced apiTokenPlugins map with if len(def.RequiredScopes) == 0 { continue } for a more generic approach that handles any plugin with empty scopes

Claude AI and others added 4 commits March 5, 2026 16:43
- Add Jira ConnectionDef entry to connectionRegistry with AccessToken auth
- Implement scopeJiraHandler using ListRemoteScopes API to fetch boards
- Add JiraBoardScope type for board scope payloads
- Add comprehensive test coverage for Jira plugin configuration
- All tests pass (go test ./..., go vet ./...)

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
- Remove misleading RequiredScopes and ScopeHint (Jira API tokens don't use OAuth scopes)
- Add pagination support for ListRemoteScopes to fetch all boards across pages
- Replace fmt.Sscanf with strconv.ParseUint for clearer error handling
- Handle empty board IDs explicitly
- Update tests to allow empty scopes for API token-based plugins

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
- Add Jira row to Supported Plugins table in README.md
- Simplify TestAvailablePluginsScopeHints to check RequiredScopes length instead of maintaining plugin-specific map
- More generic approach handles both Jira and any future API token-based plugins

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@ewega ewega force-pushed the claude/add-jira-support-cli branch from 30842f9 to 68e5522 Compare March 5, 2026 13:47
@ewega ewega merged commit 6232766 into main Mar 5, 2026
10 checks passed
@ewega ewega deleted the claude/add-jira-support-cli branch March 25, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Jira plugin support

3 participants