Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 46 additions & 21 deletions pkg/github/issue_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"

ghcontext "github.com/github/github-mcp-server/pkg/context"
ghErrors "github.com/github/github-mcp-server/pkg/errors"
Expand All @@ -19,6 +20,7 @@ import (
// IssueField represents a repository issue field definition.
type IssueField struct {
ID string `json:"id"`
DatabaseID int64 `json:"database_id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
DataType string `json:"data_type"`
Expand All @@ -37,36 +39,42 @@ type IssueSingleSelectFieldOption struct {

// issueFieldNode is the GraphQL fragment for a single issue field in the IssueFields union.
// Only the fragment matching __typename is populated; read from the matching fragment.
// fullDatabaseId (BigInt scalar, returned as string) is fetched on each concrete type because
// shurcooL/githubv4 does not support interface fragments at the top level of a union.
type issueFieldNode struct {
TypeName githubv4.String `graphql:"__typename"`
IssueFieldText struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
ID githubv4.ID
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
} `graphql:"... on IssueFieldText"`
IssueFieldNumber struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
ID githubv4.ID
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
} `graphql:"... on IssueFieldNumber"`
IssueFieldDate struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
ID githubv4.ID
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
} `graphql:"... on IssueFieldDate"`
IssueFieldSingleSelect struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
Options []struct {
ID githubv4.ID
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
Name githubv4.String
Description githubv4.String
DataType githubv4.String
Visibility githubv4.String
Options []struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
Expand Down Expand Up @@ -200,6 +208,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
}
f = IssueField{
ID: fmt.Sprintf("%v", node.IssueFieldSingleSelect.ID),
DatabaseID: parseFullDatabaseID(string(node.IssueFieldSingleSelect.FullDatabaseID)),
Name: string(node.IssueFieldSingleSelect.Name),
Description: string(node.IssueFieldSingleSelect.Description),
DataType: string(node.IssueFieldSingleSelect.DataType),
Expand All @@ -209,6 +218,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
case "IssueFieldText":
f = IssueField{
ID: fmt.Sprintf("%v", node.IssueFieldText.ID),
DatabaseID: parseFullDatabaseID(string(node.IssueFieldText.FullDatabaseID)),
Name: string(node.IssueFieldText.Name),
Description: string(node.IssueFieldText.Description),
DataType: string(node.IssueFieldText.DataType),
Expand All @@ -217,6 +227,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
case "IssueFieldNumber":
f = IssueField{
ID: fmt.Sprintf("%v", node.IssueFieldNumber.ID),
DatabaseID: parseFullDatabaseID(string(node.IssueFieldNumber.FullDatabaseID)),
Name: string(node.IssueFieldNumber.Name),
Description: string(node.IssueFieldNumber.Description),
DataType: string(node.IssueFieldNumber.DataType),
Expand All @@ -225,6 +236,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
case "IssueFieldDate":
f = IssueField{
ID: fmt.Sprintf("%v", node.IssueFieldDate.ID),
DatabaseID: parseFullDatabaseID(string(node.IssueFieldDate.FullDatabaseID)),
Name: string(node.IssueFieldDate.Name),
Description: string(node.IssueFieldDate.Description),
DataType: string(node.IssueFieldDate.DataType),
Expand All @@ -237,3 +249,16 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
}
return fields
}

// parseFullDatabaseID converts a BigInt scalar string (e.g. "12345") to int64.
// Returns 0 if the string is empty or cannot be parsed.
func parseFullDatabaseID(s string) int64 {
if s == "" {
return 0
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0
}
return n
}
68 changes: 38 additions & 30 deletions pkg/github/issue_fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ func Test_ListIssueFields(t *testing.T) {
"issueFields": map[string]any{
"nodes": []any{
map[string]any{
"__typename": "IssueFieldText",
"id": "IFT_1",
"name": "DRI",
"description": "Directly responsible individual",
"dataType": "TEXT",
"visibility": "ORG_ONLY",
"__typename": "IssueFieldText",
"id": "IFT_1",
"fullDatabaseId": "42",
"name": "DRI",
"description": "Directly responsible individual",
"dataType": "TEXT",
"visibility": "ORG_ONLY",
},
},
},
Expand All @@ -89,6 +90,7 @@ func Test_ListIssueFields(t *testing.T) {
expectedFields: []IssueField{
{
ID: "IFT_1",
DatabaseID: 42,
Name: "DRI",
Description: "Directly responsible individual",
DataType: "TEXT",
Expand All @@ -107,12 +109,13 @@ func Test_ListIssueFields(t *testing.T) {
"issueFields": map[string]any{
"nodes": []any{
map[string]any{
"__typename": "IssueFieldSingleSelect",
"id": "IFSS_1",
"name": "Priority",
"description": "Level of importance",
"dataType": "SINGLE_SELECT",
"visibility": "ALL",
"__typename": "IssueFieldSingleSelect",
"id": "IFSS_1",
"fullDatabaseId": "99",
"name": "Priority",
"description": "Level of importance",
"dataType": "SINGLE_SELECT",
"visibility": "ALL",
"options": []any{
map[string]any{
"id": "OPT_1",
Expand All @@ -133,6 +136,7 @@ func Test_ListIssueFields(t *testing.T) {
expectedFields: []IssueField{
{
ID: "IFSS_1",
DatabaseID: 99,
Name: "Priority",
Description: "Level of importance",
DataType: "SINGLE_SELECT",
Expand Down Expand Up @@ -165,18 +169,19 @@ func Test_ListIssueFields(t *testing.T) {
"issueFields": map[string]any{
"nodes": []any{
map[string]any{
"__typename": "IssueFieldText",
"id": "IFT_1",
"name": "DRI",
"dataType": "TEXT",
"visibility": "ORG_ONLY",
"__typename": "IssueFieldText",
"id": "IFT_1",
"fullDatabaseId": "77",
"name": "DRI",
"dataType": "TEXT",
"visibility": "ORG_ONLY",
},
},
},
},
}),
expectedFields: []IssueField{
{ID: "IFT_1", Name: "DRI", DataType: "TEXT", Visibility: "ORG_ONLY"},
{ID: "IFT_1", DatabaseID: 77, Name: "DRI", DataType: "TEXT", Visibility: "ORG_ONLY"},
},
},
{
Expand All @@ -190,18 +195,19 @@ func Test_ListIssueFields(t *testing.T) {
"issueFields": map[string]any{
"nodes": []any{
map[string]any{
"__typename": "IssueFieldNumber",
"id": "IFN_1",
"name": "Engineering Staffing",
"dataType": "NUMBER",
"visibility": "ORG_ONLY",
"__typename": "IssueFieldNumber",
"id": "IFN_1",
"fullDatabaseId": "101",
"name": "Engineering Staffing",
"dataType": "NUMBER",
"visibility": "ORG_ONLY",
},
},
},
},
}),
expectedFields: []IssueField{
{ID: "IFN_1", Name: "Engineering Staffing", DataType: "NUMBER", Visibility: "ORG_ONLY"},
{ID: "IFN_1", DatabaseID: 101, Name: "Engineering Staffing", DataType: "NUMBER", Visibility: "ORG_ONLY"},
},
},
{
Expand All @@ -215,18 +221,19 @@ func Test_ListIssueFields(t *testing.T) {
"issueFields": map[string]any{
"nodes": []any{
map[string]any{
"__typename": "IssueFieldDate",
"id": "IFD_1",
"name": "Target Date",
"dataType": "DATE",
"visibility": "ORG_ONLY",
"__typename": "IssueFieldDate",
"id": "IFD_1",
"fullDatabaseId": "202",
"name": "Target Date",
"dataType": "DATE",
"visibility": "ORG_ONLY",
},
},
},
},
}),
expectedFields: []IssueField{
{ID: "IFD_1", Name: "Target Date", DataType: "DATE", Visibility: "ORG_ONLY"},
{ID: "IFD_1", DatabaseID: 202, Name: "Target Date", DataType: "DATE", Visibility: "ORG_ONLY"},
},
},
{
Expand Down Expand Up @@ -284,6 +291,7 @@ func Test_ListIssueFields(t *testing.T) {
require.Equal(t, len(tc.expectedFields), len(returnedFields))
for i, expected := range tc.expectedFields {
assert.Equal(t, expected.ID, returnedFields[i].ID)
assert.Equal(t, expected.DatabaseID, returnedFields[i].DatabaseID)
assert.Equal(t, expected.Name, returnedFields[i].Name)
assert.Equal(t, expected.DataType, returnedFields[i].DataType)
assert.Equal(t, expected.Visibility, returnedFields[i].Visibility)
Expand Down
Loading