Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2f64ac0

Browse files
Add discussion metadata fields to get_discussion tool (#1305)
* Add state metadata fields to get_discussion toolFixes#1303The get_discussion tool was missing important state metadata that'salready available in get_issue. Added four fields to provide completediscussion status information:- state: Current discussion state (OPEN/CLOSED)- isAnswered: Whether the discussion has an accepted answer- answeredAt: Timestamp when answer was provided- answerChosenAt: Timestamp when answer was selectedChanged GetDiscussion to return a map instead of github.Discussionstruct since the go-github library doesn't include all these fieldsin its type definition. This approach is consistent with otherfunctions in this codebase (ListDiscussions, GetDiscussionComments).All tests pass and linter checks pass.* Fix Discussion field mappings based on GitHub GraphQL APIChanges:- Replace 'State' (doesn't exist) with 'Closed' (Boolean)- Remove 'AnsweredAt' (doesn't exist)- Keep 'IsAnswered' (verified to exist in GitHub GraphQL API)- Use 'AnswerChosenAt' for answer timestampUpdated both implementation and tests to match actual GitHub GraphQL schema.All tests passing.---------Co-authored-by: tommaso-moro <tommaso-moro@github.com>
1 parent88a594e commit2f64ac0

File tree

2 files changed

+136
-100
lines changed

2 files changed

+136
-100
lines changed

‎pkg/github/discussions.go‎

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ type DiscussionFragment struct {
4444
}
4545

4646
typeNodeFragmentstruct {
47-
Number githubv4.Int
48-
Title githubv4.String
49-
CreatedAt githubv4.DateTime
50-
UpdatedAt githubv4.DateTime
51-
Authorstruct {
47+
Number githubv4.Int
48+
Title githubv4.String
49+
CreatedAt githubv4.DateTime
50+
UpdatedAt githubv4.DateTime
51+
Closed githubv4.Boolean
52+
IsAnswered githubv4.Boolean
53+
AnswerChosenAt*githubv4.DateTime
54+
Authorstruct {
5255
Login githubv4.String
5356
}
5457
Categorystruct {
@@ -294,12 +297,15 @@ func GetDiscussion(getGQLClient GetGQLClientFn, t translations.TranslationHelper
294297
varqstruct {
295298
Repositorystruct {
296299
Discussionstruct {
297-
Number githubv4.Int
298-
Title githubv4.String
299-
Body githubv4.String
300-
CreatedAt githubv4.DateTime
301-
URL githubv4.String`graphql:"url"`
302-
Categorystruct {
300+
Number githubv4.Int
301+
Title githubv4.String
302+
Body githubv4.String
303+
CreatedAt githubv4.DateTime
304+
Closed githubv4.Boolean
305+
IsAnswered githubv4.Boolean
306+
AnswerChosenAt*githubv4.DateTime
307+
URL githubv4.String`graphql:"url"`
308+
Categorystruct {
303309
Name githubv4.String
304310
}`graphql:"category"`
305311
}`graphql:"discussion(number: $discussionNumber)"`
@@ -314,17 +320,30 @@ func GetDiscussion(getGQLClient GetGQLClientFn, t translations.TranslationHelper
314320
returnmcp.NewToolResultError(err.Error()),nil
315321
}
316322
d:=q.Repository.Discussion
317-
discussion:=&github.Discussion{
318-
Number:github.Ptr(int(d.Number)),
319-
Title:github.Ptr(string(d.Title)),
320-
Body:github.Ptr(string(d.Body)),
321-
HTMLURL:github.Ptr(string(d.URL)),
322-
CreatedAt:&github.Timestamp{Time:d.CreatedAt.Time},
323-
DiscussionCategory:&github.DiscussionCategory{
324-
Name:github.Ptr(string(d.Category.Name)),
323+
324+
// Build response as map to include fields not present in go-github's Discussion struct.
325+
// The go-github library's Discussion type lacks isAnswered and answerChosenAt fields,
326+
// so we use map[string]interface{} for the response (consistent with other functions
327+
// like ListDiscussions and GetDiscussionComments).
328+
response:=map[string]interface{}{
329+
"number":int(d.Number),
330+
"title":string(d.Title),
331+
"body":string(d.Body),
332+
"url":string(d.URL),
333+
"closed":bool(d.Closed),
334+
"isAnswered":bool(d.IsAnswered),
335+
"createdAt":d.CreatedAt.Time,
336+
"category":map[string]interface{}{
337+
"name":string(d.Category.Name),
325338
},
326339
}
327-
out,err:=json.Marshal(discussion)
340+
341+
// Add optional timestamp fields if present
342+
ifd.AnswerChosenAt!=nil {
343+
response["answerChosenAt"]=d.AnswerChosenAt.Time
344+
}
345+
346+
out,err:=json.Marshal(response)
328347
iferr!=nil {
329348
returnnil,fmt.Errorf("failed to marshal discussion: %w",err)
330349
}

‎pkg/github/discussions_test.go‎

Lines changed: 97 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/json"
66
"net/http"
77
"testing"
8-
"time"
98

109
"github.com/github/github-mcp-server/internal/githubv4mock"
1110
"github.com/github/github-mcp-server/pkg/translations"
@@ -17,75 +16,89 @@ import (
1716

1817
var (
1918
discussionsGeneral= []map[string]any{
20-
{"number":1,"title":"Discussion 1 title","createdAt":"2023-01-01T00:00:00Z","updatedAt":"2023-01-01T00:00:00Z","author":map[string]any{"login":"user1"},"url":"https://github.com/owner/repo/discussions/1","category":map[string]any{"name":"General"}},
21-
{"number":3,"title":"Discussion 3 title","createdAt":"2023-03-01T00:00:00Z","updatedAt":"2023-02-01T00:00:00Z","author":map[string]any{"login":"user1"},"url":"https://github.com/owner/repo/discussions/3","category":map[string]any{"name":"General"}},
19+
{"number":1,"title":"Discussion 1 title","createdAt":"2023-01-01T00:00:00Z","updatedAt":"2023-01-01T00:00:00Z","closed":false,"isAnswered":false,"author":map[string]any{"login":"user1"},"url":"https://github.com/owner/repo/discussions/1","category":map[string]any{"name":"General"}},
20+
{"number":3,"title":"Discussion 3 title","createdAt":"2023-03-01T00:00:00Z","updatedAt":"2023-02-01T00:00:00Z","closed":false,"isAnswered":false,"author":map[string]any{"login":"user1"},"url":"https://github.com/owner/repo/discussions/3","category":map[string]any{"name":"General"}},
2221
}
2322
discussionsAll= []map[string]any{
2423
{
25-
"number":1,
26-
"title":"Discussion 1 title",
27-
"createdAt":"2023-01-01T00:00:00Z",
28-
"updatedAt":"2023-01-01T00:00:00Z",
29-
"author":map[string]any{"login":"user1"},
30-
"url":"https://github.com/owner/repo/discussions/1",
31-
"category":map[string]any{"name":"General"},
24+
"number":1,
25+
"title":"Discussion 1 title",
26+
"createdAt":"2023-01-01T00:00:00Z",
27+
"updatedAt":"2023-01-01T00:00:00Z",
28+
"closed":false,
29+
"isAnswered":false,
30+
"author":map[string]any{"login":"user1"},
31+
"url":"https://github.com/owner/repo/discussions/1",
32+
"category":map[string]any{"name":"General"},
3233
},
3334
{
34-
"number":2,
35-
"title":"Discussion 2 title",
36-
"createdAt":"2023-02-01T00:00:00Z",
37-
"updatedAt":"2023-02-01T00:00:00Z",
38-
"author":map[string]any{"login":"user2"},
39-
"url":"https://github.com/owner/repo/discussions/2",
40-
"category":map[string]any{"name":"Questions"},
35+
"number":2,
36+
"title":"Discussion 2 title",
37+
"createdAt":"2023-02-01T00:00:00Z",
38+
"updatedAt":"2023-02-01T00:00:00Z",
39+
"closed":false,
40+
"isAnswered":false,
41+
"author":map[string]any{"login":"user2"},
42+
"url":"https://github.com/owner/repo/discussions/2",
43+
"category":map[string]any{"name":"Questions"},
4144
},
4245
{
43-
"number":3,
44-
"title":"Discussion 3 title",
45-
"createdAt":"2023-03-01T00:00:00Z",
46-
"updatedAt":"2023-03-01T00:00:00Z",
47-
"author":map[string]any{"login":"user3"},
48-
"url":"https://github.com/owner/repo/discussions/3",
49-
"category":map[string]any{"name":"General"},
46+
"number":3,
47+
"title":"Discussion 3 title",
48+
"createdAt":"2023-03-01T00:00:00Z",
49+
"updatedAt":"2023-03-01T00:00:00Z",
50+
"closed":false,
51+
"isAnswered":false,
52+
"author":map[string]any{"login":"user3"},
53+
"url":"https://github.com/owner/repo/discussions/3",
54+
"category":map[string]any{"name":"General"},
5055
},
5156
}
5257

5358
discussionsOrgLevel= []map[string]any{
5459
{
55-
"number":1,
56-
"title":"Org Discussion 1 - Community Guidelines",
57-
"createdAt":"2023-01-15T00:00:00Z",
58-
"updatedAt":"2023-01-15T00:00:00Z",
59-
"author":map[string]any{"login":"org-admin"},
60-
"url":"https://github.com/owner/.github/discussions/1",
61-
"category":map[string]any{"name":"Announcements"},
60+
"number":1,
61+
"title":"Org Discussion 1 - Community Guidelines",
62+
"createdAt":"2023-01-15T00:00:00Z",
63+
"updatedAt":"2023-01-15T00:00:00Z",
64+
"closed":false,
65+
"isAnswered":false,
66+
"author":map[string]any{"login":"org-admin"},
67+
"url":"https://github.com/owner/.github/discussions/1",
68+
"category":map[string]any{"name":"Announcements"},
6269
},
6370
{
64-
"number":2,
65-
"title":"Org Discussion 2 - Roadmap 2023",
66-
"createdAt":"2023-02-20T00:00:00Z",
67-
"updatedAt":"2023-02-20T00:00:00Z",
68-
"author":map[string]any{"login":"org-admin"},
69-
"url":"https://github.com/owner/.github/discussions/2",
70-
"category":map[string]any{"name":"General"},
71+
"number":2,
72+
"title":"Org Discussion 2 - Roadmap 2023",
73+
"createdAt":"2023-02-20T00:00:00Z",
74+
"updatedAt":"2023-02-20T00:00:00Z",
75+
"closed":false,
76+
"isAnswered":false,
77+
"author":map[string]any{"login":"org-admin"},
78+
"url":"https://github.com/owner/.github/discussions/2",
79+
"category":map[string]any{"name":"General"},
7180
},
7281
{
73-
"number":3,
74-
"title":"Org Discussion 3 - Roadmap 2024",
75-
"createdAt":"2023-02-20T00:00:00Z",
76-
"updatedAt":"2023-02-20T00:00:00Z",
77-
"author":map[string]any{"login":"org-admin"},
78-
"url":"https://github.com/owner/.github/discussions/3",
79-
"category":map[string]any{"name":"General"},
82+
"number":3,
83+
"title":"Org Discussion 3 - Roadmap 2024",
84+
"createdAt":"2023-02-20T00:00:00Z",
85+
"updatedAt":"2023-02-20T00:00:00Z",
86+
"closed":false,
87+
"isAnswered":false,
88+
"author":map[string]any{"login":"org-admin"},
89+
"url":"https://github.com/owner/.github/discussions/3",
90+
"category":map[string]any{"name":"General"},
8091
},
8192
{
82-
"number":4,
83-
"title":"Org Discussion 4 - Roadmap 2025",
84-
"createdAt":"2023-02-20T00:00:00Z",
85-
"updatedAt":"2023-02-20T00:00:00Z",
86-
"author":map[string]any{"login":"org-admin"},
87-
"url":"https://github.com/owner/.github/discussions/4",
88-
"category":map[string]any{"name":"General"},
93+
"number":4,
94+
"title":"Org Discussion 4 - Roadmap 2025",
95+
"createdAt":"2023-02-20T00:00:00Z",
96+
"updatedAt":"2023-02-20T00:00:00Z",
97+
"closed":false,
98+
"isAnswered":false,
99+
"author":map[string]any{"login":"org-admin"},
100+
"url":"https://github.com/owner/.github/discussions/4",
101+
"category":map[string]any{"name":"General"},
89102
},
90103
}
91104

@@ -388,10 +401,10 @@ func Test_ListDiscussions(t *testing.T) {
388401
}
389402

390403
// Define the actual query strings that match the implementation
391-
qBasicNoOrder:="query($after:String$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
392-
qWithCategoryNoOrder:="query($after:String$categoryId:ID!$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
393-
qBasicWithOrder:="query($after:String$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
394-
qWithCategoryAndOrder:="query($after:String$categoryId:ID!$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
404+
qBasicNoOrder:="query($after:String$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
405+
qWithCategoryNoOrder:="query($after:String$categoryId:ID!$first:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
406+
qBasicWithOrder:="query($after:String$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
407+
qWithCategoryAndOrder:="query($after:String$categoryId:ID!$first:Int!$orderByDirection:OrderDirection!$orderByField:DiscussionOrderField!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussions(first: $first, after: $after, categoryId: $categoryId, orderBy: { field: $orderByField, direction: $orderByDirection }){nodes{number,title,createdAt,updatedAt,closed,isAnswered,answerChosenAt,author{login},category{name},url},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},totalCount}}}"
395408

396409
for_,tc:=rangetests {
397410
t.Run(tc.name,func(t*testing.T) {
@@ -484,7 +497,7 @@ func Test_GetDiscussion(t *testing.T) {
484497
assert.ElementsMatch(t,toolDef.InputSchema.Required, []string{"owner","repo","discussionNumber"})
485498

486499
// Use exact string query that matches implementation output
487-
qGetDiscussion:="query($discussionNumber:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussion(number: $discussionNumber){number,title,body,createdAt,url,category{name}}}}"
500+
qGetDiscussion:="query($discussionNumber:Int!$owner:String!$repo:String!){repository(owner: $owner, name: $repo){discussion(number: $discussionNumber){number,title,body,createdAt,closed,isAnswered,answerChosenAt,url,category{name}}}}"
488501

489502
vars:=map[string]interface{}{
490503
"owner":"owner",
@@ -495,31 +508,31 @@ func Test_GetDiscussion(t *testing.T) {
495508
namestring
496509
response githubv4mock.GQLResponse
497510
expectErrorbool
498-
expected*github.Discussion
511+
expectedmap[string]interface{}
499512
errContainsstring
500513
}{
501514
{
502515
name:"successful retrieval",
503516
response:githubv4mock.DataResponse(map[string]any{
504517
"repository":map[string]any{"discussion":map[string]any{
505-
"number":1,
506-
"title":"Test Discussion Title",
507-
"body":"This is a test discussion",
508-
"url":"https://github.com/owner/repo/discussions/1",
509-
"createdAt":"2025-04-25T12:00:00Z",
510-
"category":map[string]any{"name":"General"},
518+
"number":1,
519+
"title":"Test Discussion Title",
520+
"body":"This is a test discussion",
521+
"url":"https://github.com/owner/repo/discussions/1",
522+
"createdAt":"2025-04-25T12:00:00Z",
523+
"closed":false,
524+
"isAnswered":false,
525+
"category":map[string]any{"name":"General"},
511526
}},
512527
}),
513528
expectError:false,
514-
expected:&github.Discussion{
515-
HTMLURL:github.Ptr("https://github.com/owner/repo/discussions/1"),
516-
Number:github.Ptr(1),
517-
Title:github.Ptr("Test Discussion Title"),
518-
Body:github.Ptr("This is a test discussion"),
519-
CreatedAt:&github.Timestamp{Time:time.Date(2025,4,25,12,0,0,0,time.UTC)},
520-
DiscussionCategory:&github.DiscussionCategory{
521-
Name:github.Ptr("General"),
522-
},
529+
expected:map[string]interface{}{
530+
"number":float64(1),
531+
"title":"Test Discussion Title",
532+
"body":"This is a test discussion",
533+
"url":"https://github.com/owner/repo/discussions/1",
534+
"closed":false,
535+
"isAnswered":false,
523536
},
524537
},
525538
{
@@ -547,14 +560,18 @@ func Test_GetDiscussion(t *testing.T) {
547560
}
548561

549562
require.NoError(t,err)
550-
varoutgithub.Discussion
563+
varoutmap[string]interface{}
551564
require.NoError(t,json.Unmarshal([]byte(text),&out))
552-
assert.Equal(t,*tc.expected.HTMLURL,*out.HTMLURL)
553-
assert.Equal(t,*tc.expected.Number,*out.Number)
554-
assert.Equal(t,*tc.expected.Title,*out.Title)
555-
assert.Equal(t,*tc.expected.Body,*out.Body)
556-
// Check category label
557-
assert.Equal(t,*tc.expected.DiscussionCategory.Name,*out.DiscussionCategory.Name)
565+
assert.Equal(t,tc.expected["number"],out["number"])
566+
assert.Equal(t,tc.expected["title"],out["title"])
567+
assert.Equal(t,tc.expected["body"],out["body"])
568+
assert.Equal(t,tc.expected["url"],out["url"])
569+
assert.Equal(t,tc.expected["closed"],out["closed"])
570+
assert.Equal(t,tc.expected["isAnswered"],out["isAnswered"])
571+
// Check category is present
572+
category,ok:=out["category"].(map[string]interface{})
573+
require.True(t,ok)
574+
assert.Equal(t,"General",category["name"])
558575
})
559576
}
560577
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp