-
Notifications
You must be signed in to change notification settings - Fork 5
Generic scope ID extraction and remote-scope API client #115
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
Changes from 2 commits
e210fda
cd3bd5b
5ec9da9
61e4270
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 |
|---|---|---|
|
|
@@ -2,11 +2,11 @@ package cmd | |
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/DevExpGBB/gh-devlake/internal/devlake" | ||
| "github.com/DevExpGBB/gh-devlake/internal/prompt" | ||
| ) | ||
|
|
||
|
|
@@ -101,14 +101,15 @@ func runScopeDelete(cmd *cobra.Command, args []string) error { | |
| } | ||
| var entries []scopeEntry | ||
| var labels []string | ||
| def := FindConnectionDef(selectedPlugin) | ||
| for _, s := range resp.Scopes { | ||
| id := s.Scope.ID | ||
| if id == "" { | ||
| id = strconv.Itoa(s.Scope.GithubID) | ||
| var id string | ||
| if def != nil && def.ScopeIDField != "" { | ||
| id = devlake.ExtractScopeID(s.RawScope, def.ScopeIDField) | ||
| } | ||
|
Comment on lines
105
to
113
|
||
| name := s.Scope.FullName | ||
| name := s.ScopeFullName() | ||
| if name == "" { | ||
| name = s.Scope.Name | ||
| name = s.ScopeName() | ||
| } | ||
| label := fmt.Sprintf("[%s] %s", id, name) | ||
| entries = append(entries, scopeEntry{id: id, label: label}) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,6 @@ package cmd | |
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
| "text/tabwriter" | ||
|
|
||
|
|
@@ -110,15 +109,16 @@ func runScopeList(cmd *cobra.Command, args []string) error { | |
| // JSON output path | ||
| if outputJSON { | ||
| items := make([]scopeListItem, len(resp.Scopes)) | ||
| def := FindConnectionDef(selectedPlugin) | ||
| for i, s := range resp.Scopes { | ||
| scopeID := s.Scope.ID | ||
| if scopeID == "" { | ||
| scopeID = strconv.Itoa(s.Scope.GithubID) | ||
| var scopeID string | ||
| if def != nil && def.ScopeIDField != "" { | ||
| scopeID = devlake.ExtractScopeID(s.RawScope, def.ScopeIDField) | ||
| } | ||
|
||
| items[i] = scopeListItem{ | ||
| ID: scopeID, | ||
| Name: s.Scope.Name, | ||
| FullName: s.Scope.FullName, | ||
| Name: s.ScopeName(), | ||
| FullName: s.ScopeFullName(), | ||
| } | ||
| } | ||
| return printJSON(items) | ||
|
|
@@ -132,12 +132,13 @@ func runScopeList(cmd *cobra.Command, args []string) error { | |
| w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0) | ||
| fmt.Fprintln(w, "Scope ID\tName\tFull Name") | ||
| fmt.Fprintln(w, strings.Repeat("\u2500", 10)+"\t"+strings.Repeat("\u2500", 20)+"\t"+strings.Repeat("\u2500", 30)) | ||
| def := FindConnectionDef(selectedPlugin) | ||
| for _, s := range resp.Scopes { | ||
| scopeID := s.Scope.ID | ||
| if scopeID == "" { | ||
| scopeID = strconv.Itoa(s.Scope.GithubID) | ||
| var scopeID string | ||
| if def != nil && def.ScopeIDField != "" { | ||
| scopeID = devlake.ExtractScopeID(s.RawScope, def.ScopeIDField) | ||
| } | ||
| fmt.Fprintf(w, "%s\t%s\t%s\n", scopeID, s.Scope.Name, s.Scope.FullName) | ||
| fmt.Fprintf(w, "%s\t%s\t%s\n", scopeID, s.ScopeName(), s.ScopeFullName()) | ||
| } | ||
| w.Flush() | ||
| fmt.Println() | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,10 @@ | ||||||
| package devlake | ||||||
|
|
||||||
| import ( | ||||||
| "encoding/json" | ||||||
| "strconv" | ||||||
| ) | ||||||
|
|
||||||
| // ScopeConfig represents a DevLake scope configuration (e.g., DORA settings). | ||||||
| type ScopeConfig struct { | ||||||
| ID int `json:"id,omitempty"` | ||||||
|
|
@@ -46,18 +51,67 @@ type ScopeBatchRequest struct { | |||||
|
|
||||||
| // ScopeListWrapper wraps a scope object as returned by the DevLake GET scopes API. | ||||||
| // The API nests each scope inside a "scope" key: { "scope": { ... } }. | ||||||
| // RawScope preserves the full plugin-specific payload for generic ID extraction. | ||||||
| type ScopeListWrapper struct { | ||||||
| Scope ScopeListEntry `json:"scope"` | ||||||
| } | ||||||
|
|
||||||
| // ScopeListEntry represents a scope object returned inside the wrapper. | ||||||
| // ID fields vary by plugin (githubId for GitHub, id for Copilot), so we | ||||||
| // capture both and resolve in the caller. | ||||||
| type ScopeListEntry struct { | ||||||
| GithubID int `json:"githubId,omitempty"` | ||||||
| ID string `json:"id,omitempty"` | ||||||
| Name string `json:"name"` | ||||||
| FullName string `json:"fullName,omitempty"` | ||||||
| RawScope json.RawMessage `json:"scope"` | ||||||
| } | ||||||
|
|
||||||
| // ScopeName returns the display name from the raw scope JSON (checks "fullName" then "name"). | ||||||
| func (w *ScopeListWrapper) ScopeName() string { | ||||||
| var m map[string]json.RawMessage | ||||||
| if err := json.Unmarshal(w.RawScope, &m); err != nil { | ||||||
| return "" | ||||||
| } | ||||||
| for _, key := range []string{"fullName", "name"} { | ||||||
| if v, ok := m[key]; ok { | ||||||
|
Comment on lines
+74
to
+81
|
||||||
| var s string | ||||||
| if err := json.Unmarshal(v, &s); err == nil { | ||||||
|
||||||
| if err := json.Unmarshal(v, &s); err == nil { | |
| if err := json.Unmarshal(v, &s); err == nil && s != "" { |
Copilot
AI
Mar 5, 2026
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.
ExtractScopeID() only attempts to decode numeric IDs as int64. This will fail for plugins that return unsigned IDs (e.g., uint64) or numbers larger than MaxInt64, causing scope ID extraction to silently return "". Consider unmarshaling into json.Number (via a Decoder with UseNumber) or trying uint64 in addition to int64 so all numeric ID fields are supported.
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.
In this loop,
ScopeFullName()is called multiple times (scopeName := w.ScopeFullName()and laterfullName := w.ScopeFullName()). Since these helpers unmarshal the raw JSON each call, this does redundant work. Consider callingScopeFullName()once per scope (or just useScopeName()for the display name since it already prefersfullName) and reuse the value for repo tracking.