Skip to content

Commit 754c64c

Browse files
authored
add support for fields in issue read
1 parent 1861a35 commit 754c64c

2 files changed

Lines changed: 141 additions & 20 deletions

File tree

pkg/github/issues_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,90 @@ func Test_IssueRead_IFC_InsidersMode(t *testing.T) {
392392
})
393393
}
394394

395+
func Test_GetIssue_FieldValues(t *testing.T) {
396+
// Verify that issue_field_values from the REST API are present in the returned object.
397+
serverTool := IssueRead(translations.NullTranslationHelper)
398+
399+
mockIssueWithFields := &github.Issue{
400+
Number: github.Ptr(99),
401+
Title: github.Ptr("Issue with field values"),
402+
Body: github.Ptr("body"),
403+
State: github.Ptr("open"),
404+
HTMLURL: github.Ptr("https://github.com/owner/repo/issues/99"),
405+
User: &github.User{
406+
Login: github.Ptr("testuser"),
407+
},
408+
IssueFieldValues: []*github.IssueFieldValue{
409+
{
410+
IssueFieldID: 1001,
411+
NodeID: "FV_node_1",
412+
DataType: "single_select",
413+
Value: "High",
414+
SingleSelectOption: &github.IssueFieldValueSingleSelectOption{
415+
ID: 42,
416+
Name: "High",
417+
Color: "red",
418+
},
419+
},
420+
{
421+
IssueFieldID: 1002,
422+
NodeID: "FV_node_2",
423+
DataType: "text",
424+
Value: "some text value",
425+
},
426+
},
427+
}
428+
429+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
430+
GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssueWithFields),
431+
})
432+
433+
cache := stubRepoAccessCache(nil, 15*time.Minute)
434+
flags := stubFeatureFlags(map[string]bool{"lockdown-mode": false})
435+
deps := BaseDeps{
436+
Client: mustNewGHClient(t, mockedClient),
437+
GQLClient: defaultGQLClient,
438+
RepoAccessCache: cache,
439+
Flags: flags,
440+
}
441+
handler := serverTool.Handler(deps)
442+
443+
request := createMCPRequest(map[string]any{
444+
"method": "get",
445+
"owner": "owner",
446+
"repo": "repo",
447+
"issue_number": float64(99),
448+
})
449+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
450+
require.NoError(t, err)
451+
require.NotNil(t, result)
452+
453+
textContent := getTextResult(t, result)
454+
455+
var returnedIssue MinimalIssue
456+
err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
457+
require.NoError(t, err)
458+
459+
require.Len(t, returnedIssue.IssueFieldValues, 2, "expected two issue field values")
460+
461+
first := returnedIssue.IssueFieldValues[0]
462+
assert.Equal(t, int64(1001), first.IssueFieldID)
463+
assert.Equal(t, "FV_node_1", first.NodeID)
464+
assert.Equal(t, "single_select", first.DataType)
465+
assert.Equal(t, "High", first.Value)
466+
require.NotNil(t, first.SingleSelectOption)
467+
assert.Equal(t, int64(42), first.SingleSelectOption.ID)
468+
assert.Equal(t, "High", first.SingleSelectOption.Name)
469+
assert.Equal(t, "red", first.SingleSelectOption.Color)
470+
471+
second := returnedIssue.IssueFieldValues[1]
472+
assert.Equal(t, int64(1002), second.IssueFieldID)
473+
assert.Equal(t, "FV_node_2", second.NodeID)
474+
assert.Equal(t, "text", second.DataType)
475+
assert.Equal(t, "some text value", second.Value)
476+
assert.Nil(t, second.SingleSelectOption)
477+
}
478+
395479
func Test_AddIssueComment(t *testing.T) {
396480
// Verify tool definition once
397481
serverTool := AddIssueComment(translations.NullTranslationHelper)

pkg/github/minimal_types.go

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -201,28 +201,45 @@ type MinimalReactions struct {
201201
Eyes int `json:"eyes"`
202202
}
203203

204+
// MinimalIssueFieldValueSingleSelectOption is the trimmed output type for a single-select option of an issue field value.
205+
type MinimalIssueFieldValueSingleSelectOption struct {
206+
ID int64 `json:"id"`
207+
Name string `json:"name"`
208+
Color string `json:"color"`
209+
}
210+
211+
// MinimalIssueFieldValue is the trimmed output type for a custom field value attached to an issue.
212+
type MinimalIssueFieldValue struct {
213+
IssueFieldID int64 `json:"issue_field_id"`
214+
NodeID string `json:"node_id"`
215+
DataType string `json:"data_type"`
216+
Value any `json:"value"`
217+
SingleSelectOption *MinimalIssueFieldValueSingleSelectOption `json:"single_select_option,omitempty"`
218+
}
219+
204220
// MinimalIssue is the trimmed output type for issue objects to reduce verbosity.
205221
type MinimalIssue struct {
206-
Number int `json:"number"`
207-
Title string `json:"title"`
208-
Body string `json:"body,omitempty"`
209-
State string `json:"state"`
210-
StateReason string `json:"state_reason,omitempty"`
211-
Draft bool `json:"draft,omitempty"`
212-
Locked bool `json:"locked,omitempty"`
213-
HTMLURL string `json:"html_url,omitempty"`
214-
User *MinimalUser `json:"user,omitempty"`
215-
AuthorAssociation string `json:"author_association,omitempty"`
216-
Labels []string `json:"labels,omitempty"`
217-
Assignees []string `json:"assignees,omitempty"`
218-
Milestone string `json:"milestone,omitempty"`
219-
Comments int `json:"comments,omitempty"`
220-
Reactions *MinimalReactions `json:"reactions,omitempty"`
221-
CreatedAt string `json:"created_at,omitempty"`
222-
UpdatedAt string `json:"updated_at,omitempty"`
223-
ClosedAt string `json:"closed_at,omitempty"`
224-
ClosedBy string `json:"closed_by,omitempty"`
225-
IssueType string `json:"issue_type,omitempty"`
222+
Number int `json:"number"`
223+
Title string `json:"title"`
224+
Body string `json:"body,omitempty"`
225+
State string `json:"state"`
226+
StateReason string `json:"state_reason,omitempty"`
227+
Draft bool `json:"draft,omitempty"`
228+
Locked bool `json:"locked,omitempty"`
229+
HTMLURL string `json:"html_url,omitempty"`
230+
User *MinimalUser `json:"user,omitempty"`
231+
AuthorAssociation string `json:"author_association,omitempty"`
232+
Labels []string `json:"labels,omitempty"`
233+
Assignees []string `json:"assignees,omitempty"`
234+
Milestone string `json:"milestone,omitempty"`
235+
Comments int `json:"comments,omitempty"`
236+
Reactions *MinimalReactions `json:"reactions,omitempty"`
237+
CreatedAt string `json:"created_at,omitempty"`
238+
UpdatedAt string `json:"updated_at,omitempty"`
239+
ClosedAt string `json:"closed_at,omitempty"`
240+
ClosedBy string `json:"closed_by,omitempty"`
241+
IssueType string `json:"issue_type,omitempty"`
242+
IssueFieldValues []MinimalIssueFieldValue `json:"issue_field_values,omitempty"`
226243
}
227244

228245
// MinimalIssuesResponse is the trimmed output for a paginated list of issues.
@@ -400,6 +417,26 @@ func convertToMinimalIssue(issue *github.Issue) MinimalIssue {
400417
m.IssueType = issueType.GetName()
401418
}
402419

420+
for _, fv := range issue.IssueFieldValues {
421+
if fv == nil {
422+
continue
423+
}
424+
mfv := MinimalIssueFieldValue{
425+
IssueFieldID: fv.IssueFieldID,
426+
NodeID: fv.NodeID,
427+
DataType: fv.DataType,
428+
Value: fv.Value,
429+
}
430+
if opt := fv.SingleSelectOption; opt != nil {
431+
mfv.SingleSelectOption = &MinimalIssueFieldValueSingleSelectOption{
432+
ID: opt.ID,
433+
Name: opt.Name,
434+
Color: opt.Color,
435+
}
436+
}
437+
m.IssueFieldValues = append(m.IssueFieldValues, mfv)
438+
}
439+
403440
if r := issue.Reactions; r != nil {
404441
m.Reactions = &MinimalReactions{
405442
TotalCount: r.GetTotalCount(),

0 commit comments

Comments
 (0)