Skip to content

Commit cee4014

Browse files
authored
feat(issue-fields): expose fullDatabaseId (BigInt) in list_issue_fields
- Add DatabaseID (int64) to IssueField struct, populated from fullDatabaseId BigInt scalar (returned as string) on all 4 concrete GQL union types - Repeat fullDatabaseId per union fragment (shurcooL/githubv4 cannot use interface-level fragments at union top-level) - Add parseFullDatabaseID helper to parse BigInt string to int64 - Update tests to assert DatabaseID is populated from fullDatabaseId
1 parent 1add5fe commit cee4014

2 files changed

Lines changed: 84 additions & 51 deletions

File tree

pkg/github/issue_fields.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"strconv"
78

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

3840
// issueFieldNode is the GraphQL fragment for a single issue field in the IssueFields union.
3941
// Only the fragment matching __typename is populated; read from the matching fragment.
42+
// fullDatabaseId (BigInt scalar, returned as string) is fetched on each concrete type because
43+
// shurcooL/githubv4 does not support interface fragments at the top level of a union.
4044
type issueFieldNode struct {
4145
TypeName githubv4.String `graphql:"__typename"`
4246
IssueFieldText struct {
43-
ID githubv4.ID
44-
Name githubv4.String
45-
Description githubv4.String
46-
DataType githubv4.String
47-
Visibility githubv4.String
47+
ID githubv4.ID
48+
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
49+
Name githubv4.String
50+
Description githubv4.String
51+
DataType githubv4.String
52+
Visibility githubv4.String
4853
} `graphql:"... on IssueFieldText"`
4954
IssueFieldNumber struct {
50-
ID githubv4.ID
51-
Name githubv4.String
52-
Description githubv4.String
53-
DataType githubv4.String
54-
Visibility githubv4.String
55+
ID githubv4.ID
56+
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
57+
Name githubv4.String
58+
Description githubv4.String
59+
DataType githubv4.String
60+
Visibility githubv4.String
5561
} `graphql:"... on IssueFieldNumber"`
5662
IssueFieldDate struct {
57-
ID githubv4.ID
58-
Name githubv4.String
59-
Description githubv4.String
60-
DataType githubv4.String
61-
Visibility githubv4.String
63+
ID githubv4.ID
64+
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
65+
Name githubv4.String
66+
Description githubv4.String
67+
DataType githubv4.String
68+
Visibility githubv4.String
6269
} `graphql:"... on IssueFieldDate"`
6370
IssueFieldSingleSelect struct {
64-
ID githubv4.ID
65-
Name githubv4.String
66-
Description githubv4.String
67-
DataType githubv4.String
68-
Visibility githubv4.String
69-
Options []struct {
71+
ID githubv4.ID
72+
FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
73+
Name githubv4.String
74+
Description githubv4.String
75+
DataType githubv4.String
76+
Visibility githubv4.String
77+
Options []struct {
7078
ID githubv4.ID
7179
Name githubv4.String
7280
Description githubv4.String
@@ -200,6 +208,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
200208
}
201209
f = IssueField{
202210
ID: fmt.Sprintf("%v", node.IssueFieldSingleSelect.ID),
211+
DatabaseID: parseFullDatabaseID(string(node.IssueFieldSingleSelect.FullDatabaseID)),
203212
Name: string(node.IssueFieldSingleSelect.Name),
204213
Description: string(node.IssueFieldSingleSelect.Description),
205214
DataType: string(node.IssueFieldSingleSelect.DataType),
@@ -209,6 +218,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
209218
case "IssueFieldText":
210219
f = IssueField{
211220
ID: fmt.Sprintf("%v", node.IssueFieldText.ID),
221+
DatabaseID: parseFullDatabaseID(string(node.IssueFieldText.FullDatabaseID)),
212222
Name: string(node.IssueFieldText.Name),
213223
Description: string(node.IssueFieldText.Description),
214224
DataType: string(node.IssueFieldText.DataType),
@@ -217,6 +227,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
217227
case "IssueFieldNumber":
218228
f = IssueField{
219229
ID: fmt.Sprintf("%v", node.IssueFieldNumber.ID),
230+
DatabaseID: parseFullDatabaseID(string(node.IssueFieldNumber.FullDatabaseID)),
220231
Name: string(node.IssueFieldNumber.Name),
221232
Description: string(node.IssueFieldNumber.Description),
222233
DataType: string(node.IssueFieldNumber.DataType),
@@ -225,6 +236,7 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
225236
case "IssueFieldDate":
226237
f = IssueField{
227238
ID: fmt.Sprintf("%v", node.IssueFieldDate.ID),
239+
DatabaseID: parseFullDatabaseID(string(node.IssueFieldDate.FullDatabaseID)),
228240
Name: string(node.IssueFieldDate.Name),
229241
Description: string(node.IssueFieldDate.Description),
230242
DataType: string(node.IssueFieldDate.DataType),
@@ -237,3 +249,16 @@ func issueFieldsFromNodes(nodes []issueFieldNode) []IssueField {
237249
}
238250
return fields
239251
}
252+
253+
// parseFullDatabaseID converts a BigInt scalar string (e.g. "12345") to int64.
254+
// Returns 0 if the string is empty or cannot be parsed.
255+
func parseFullDatabaseID(s string) int64 {
256+
if s == "" {
257+
return 0
258+
}
259+
n, err := strconv.ParseInt(s, 10, 64)
260+
if err != nil {
261+
return 0
262+
}
263+
return n
264+
}

pkg/github/issue_fields_test.go

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,13 @@ func Test_ListIssueFields(t *testing.T) {
7575
"issueFields": map[string]any{
7676
"nodes": []any{
7777
map[string]any{
78-
"__typename": "IssueFieldText",
79-
"id": "IFT_1",
80-
"name": "DRI",
81-
"description": "Directly responsible individual",
82-
"dataType": "TEXT",
83-
"visibility": "ORG_ONLY",
78+
"__typename": "IssueFieldText",
79+
"id": "IFT_1",
80+
"fullDatabaseId": "42",
81+
"name": "DRI",
82+
"description": "Directly responsible individual",
83+
"dataType": "TEXT",
84+
"visibility": "ORG_ONLY",
8485
},
8586
},
8687
},
@@ -89,6 +90,7 @@ func Test_ListIssueFields(t *testing.T) {
8990
expectedFields: []IssueField{
9091
{
9192
ID: "IFT_1",
93+
DatabaseID: 42,
9294
Name: "DRI",
9395
Description: "Directly responsible individual",
9496
DataType: "TEXT",
@@ -107,12 +109,13 @@ func Test_ListIssueFields(t *testing.T) {
107109
"issueFields": map[string]any{
108110
"nodes": []any{
109111
map[string]any{
110-
"__typename": "IssueFieldSingleSelect",
111-
"id": "IFSS_1",
112-
"name": "Priority",
113-
"description": "Level of importance",
114-
"dataType": "SINGLE_SELECT",
115-
"visibility": "ALL",
112+
"__typename": "IssueFieldSingleSelect",
113+
"id": "IFSS_1",
114+
"fullDatabaseId": "99",
115+
"name": "Priority",
116+
"description": "Level of importance",
117+
"dataType": "SINGLE_SELECT",
118+
"visibility": "ALL",
116119
"options": []any{
117120
map[string]any{
118121
"id": "OPT_1",
@@ -133,6 +136,7 @@ func Test_ListIssueFields(t *testing.T) {
133136
expectedFields: []IssueField{
134137
{
135138
ID: "IFSS_1",
139+
DatabaseID: 99,
136140
Name: "Priority",
137141
Description: "Level of importance",
138142
DataType: "SINGLE_SELECT",
@@ -165,18 +169,19 @@ func Test_ListIssueFields(t *testing.T) {
165169
"issueFields": map[string]any{
166170
"nodes": []any{
167171
map[string]any{
168-
"__typename": "IssueFieldText",
169-
"id": "IFT_1",
170-
"name": "DRI",
171-
"dataType": "TEXT",
172-
"visibility": "ORG_ONLY",
172+
"__typename": "IssueFieldText",
173+
"id": "IFT_1",
174+
"fullDatabaseId": "77",
175+
"name": "DRI",
176+
"dataType": "TEXT",
177+
"visibility": "ORG_ONLY",
173178
},
174179
},
175180
},
176181
},
177182
}),
178183
expectedFields: []IssueField{
179-
{ID: "IFT_1", Name: "DRI", DataType: "TEXT", Visibility: "ORG_ONLY"},
184+
{ID: "IFT_1", DatabaseID: 77, Name: "DRI", DataType: "TEXT", Visibility: "ORG_ONLY"},
180185
},
181186
},
182187
{
@@ -190,18 +195,19 @@ func Test_ListIssueFields(t *testing.T) {
190195
"issueFields": map[string]any{
191196
"nodes": []any{
192197
map[string]any{
193-
"__typename": "IssueFieldNumber",
194-
"id": "IFN_1",
195-
"name": "Engineering Staffing",
196-
"dataType": "NUMBER",
197-
"visibility": "ORG_ONLY",
198+
"__typename": "IssueFieldNumber",
199+
"id": "IFN_1",
200+
"fullDatabaseId": "101",
201+
"name": "Engineering Staffing",
202+
"dataType": "NUMBER",
203+
"visibility": "ORG_ONLY",
198204
},
199205
},
200206
},
201207
},
202208
}),
203209
expectedFields: []IssueField{
204-
{ID: "IFN_1", Name: "Engineering Staffing", DataType: "NUMBER", Visibility: "ORG_ONLY"},
210+
{ID: "IFN_1", DatabaseID: 101, Name: "Engineering Staffing", DataType: "NUMBER", Visibility: "ORG_ONLY"},
205211
},
206212
},
207213
{
@@ -215,18 +221,19 @@ func Test_ListIssueFields(t *testing.T) {
215221
"issueFields": map[string]any{
216222
"nodes": []any{
217223
map[string]any{
218-
"__typename": "IssueFieldDate",
219-
"id": "IFD_1",
220-
"name": "Target Date",
221-
"dataType": "DATE",
222-
"visibility": "ORG_ONLY",
224+
"__typename": "IssueFieldDate",
225+
"id": "IFD_1",
226+
"fullDatabaseId": "202",
227+
"name": "Target Date",
228+
"dataType": "DATE",
229+
"visibility": "ORG_ONLY",
223230
},
224231
},
225232
},
226233
},
227234
}),
228235
expectedFields: []IssueField{
229-
{ID: "IFD_1", Name: "Target Date", DataType: "DATE", Visibility: "ORG_ONLY"},
236+
{ID: "IFD_1", DatabaseID: 202, Name: "Target Date", DataType: "DATE", Visibility: "ORG_ONLY"},
230237
},
231238
},
232239
{
@@ -284,6 +291,7 @@ func Test_ListIssueFields(t *testing.T) {
284291
require.Equal(t, len(tc.expectedFields), len(returnedFields))
285292
for i, expected := range tc.expectedFields {
286293
assert.Equal(t, expected.ID, returnedFields[i].ID)
294+
assert.Equal(t, expected.DatabaseID, returnedFields[i].DatabaseID)
287295
assert.Equal(t, expected.Name, returnedFields[i].Name)
288296
assert.Equal(t, expected.DataType, returnedFields[i].DataType)
289297
assert.Equal(t, expected.Visibility, returnedFields[i].Visibility)

0 commit comments

Comments
 (0)