@@ -45,28 +45,6 @@ type IssueWriteFieldInput struct {
4545 FieldOptionName string
4646}
4747
48- type issueFieldMetadataOption struct {
49- DatabaseID githubv4.Int `graphql:"databaseId"`
50- Name githubv4.String
51- }
52-
53- type issueFieldMetadataNode struct {
54- DatabaseID githubv4.Int `graphql:"databaseId"`
55- Name githubv4.String
56- DataType githubv4.String
57- SingleSelectField struct {
58- Options []issueFieldMetadataOption `graphql:"options"`
59- } `graphql:"... on IssueFieldSingleSelect"`
60- }
61-
62- type issueFieldMetadataQuery struct {
63- Repository struct {
64- IssueFields struct {
65- Nodes []issueFieldMetadataNode
66- } `graphql:"issueFields(first: 100)"`
67- } `graphql:"repository(owner: $owner, name: $repo)"`
68- }
69-
7048const (
7149 IssueClosedStateReasonCompleted IssueClosedStateReason = "COMPLETED"
7250 IssueClosedStateReasonDuplicate IssueClosedStateReason = "DUPLICATE"
@@ -135,14 +113,6 @@ func getCloseStateReason(stateReason string) IssueClosedStateReason {
135113 }
136114}
137115
138- // IssueWriteFieldInput is a user-supplied issue field assignment: a field name and a string value.
139- // The value is forwarded directly to the REST API — for single-select fields it must be the
140- // option name (e.g. "P1"), not an option ID. Field ID resolution is done internally via GQL.
141- type IssueWriteFieldInput struct {
142- FieldName string
143- Value string
144- }
145-
146116// issueFieldWriteMetadataNode queries only the fields needed to resolve a write: the field's
147117// fullDatabaseId (BigInt scalar, returned as string) plus its name and data type for validation.
148118// shurcooL/githubv4 cannot use interface-level fragments at union top-level, so we repeat
@@ -168,16 +138,13 @@ type issueFieldWriteMetadataNode struct {
168138 FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
169139 Name githubv4.String
170140 DataType githubv4.String
141+ Options []struct {
142+ FullDatabaseID githubv4.String `graphql:"fullDatabaseId"`
143+ Name githubv4.String
144+ }
171145 } `graphql:"... on IssueFieldSingleSelect"`
172146}
173147
174- // issueFieldWriteMetadata holds the resolved name, database ID, and data type for a single field.
175- type issueFieldWriteMetadata struct {
176- DatabaseID int64
177- Name string
178- DataType string
179- }
180-
181148type issueFieldWriteMetadataQuery struct {
182149 Repository struct {
183150 IssueFields struct {
@@ -186,101 +153,6 @@ type issueFieldWriteMetadataQuery struct {
186153 } `graphql:"repository(owner: $owner, name: $repo)"`
187154}
188155
189- func optionalIssueWriteFields (args map [string ]any ) ([]IssueWriteFieldInput , error ) {
190- raw , exists := args ["issue_fields" ]
191- if ! exists {
192- return nil , nil
193- }
194-
195- var inputMaps []map [string ]any
196- switch v := raw .(type ) {
197- case []any :
198- for _ , item := range v {
199- m , ok := item .(map [string ]any )
200- if ! ok {
201- return nil , fmt .Errorf ("each issue_fields item must be an object" )
202- }
203- inputMaps = append (inputMaps , m )
204- }
205- case []map [string ]any :
206- inputMaps = v
207- default :
208- return nil , fmt .Errorf ("issue_fields must be an array" )
209- }
210-
211- out := make ([]IssueWriteFieldInput , 0 , len (inputMaps ))
212- for _ , m := range inputMaps {
213- fieldName , err := RequiredParam [string ](m , "field_name" )
214- if err != nil || strings .TrimSpace (fieldName ) == "" {
215- return nil , fmt .Errorf ("field_name is required for each issue_fields item" )
216- }
217- value , err := RequiredParam [string ](m , "value" )
218- if err != nil {
219- return nil , fmt .Errorf ("issue_fields item %q: value is required" , fieldName )
220- }
221- out = append (out , IssueWriteFieldInput {FieldName : fieldName , Value : value })
222- }
223- return out , nil
224- }
225-
226- func resolveIssueWriteFieldValues (ctx context.Context , gqlClient * githubv4.Client , owner , repo string , inputs []IssueWriteFieldInput ) ([]* github.IssueRequestFieldValue , error ) {
227- if len (inputs ) == 0 {
228- return nil , nil
229- }
230-
231- ctxWithFeatures := ghcontext .WithGraphQLFeatures (ctx , "issue_fields" , "repo_issue_fields" )
232- var query issueFieldWriteMetadataQuery
233- vars := map [string ]any {
234- "owner" : githubv4 .String (owner ),
235- "repo" : githubv4 .String (repo ),
236- }
237- if err := gqlClient .Query (ctxWithFeatures , & query , vars ); err != nil {
238- return nil , fmt .Errorf ("failed to query issue field metadata: %w" , err )
239- }
240-
241- // Build name → metadata map from the GQL response.
242- byName := make (map [string ]issueFieldWriteMetadata , len (query .Repository .IssueFields .Nodes ))
243- for _ , node := range query .Repository .IssueFields .Nodes {
244- var name , dataType , fullDBID string
245- switch string (node .TypeName ) {
246- case "IssueFieldText" :
247- name , dataType , fullDBID = string (node .IssueFieldText .Name ), string (node .IssueFieldText .DataType ), string (node .IssueFieldText .FullDatabaseID )
248- case "IssueFieldNumber" :
249- name , dataType , fullDBID = string (node .IssueFieldNumber .Name ), string (node .IssueFieldNumber .DataType ), string (node .IssueFieldNumber .FullDatabaseID )
250- case "IssueFieldDate" :
251- name , dataType , fullDBID = string (node .IssueFieldDate .Name ), string (node .IssueFieldDate .DataType ), string (node .IssueFieldDate .FullDatabaseID )
252- case "IssueFieldSingleSelect" :
253- name , dataType , fullDBID = string (node .IssueFieldSingleSelect .Name ), string (node .IssueFieldSingleSelect .DataType ), string (node .IssueFieldSingleSelect .FullDatabaseID )
254- default :
255- continue
256- }
257- dbID , _ := strconv .ParseInt (fullDBID , 10 , 64 )
258- byName [strings .ToLower (strings .TrimSpace (name ))] = issueFieldWriteMetadata {
259- DatabaseID : dbID ,
260- Name : name ,
261- DataType : dataType ,
262- }
263- }
264-
265- resolved := make ([]* github.IssueRequestFieldValue , 0 , len (inputs ))
266- for _ , input := range inputs {
267- meta , ok := byName [strings .ToLower (strings .TrimSpace (input .FieldName ))]
268- if ! ok {
269- return nil , fmt .Errorf ("issue field %q was not found in %s/%s" , input .FieldName , owner , repo )
270- }
271- if meta .DatabaseID == 0 {
272- return nil , fmt .Errorf ("issue field %q is missing fullDatabaseId" , input .FieldName )
273- }
274- // For single-select the REST API expects the option name as a string value.
275- // For all other types, pass the value through as-is.
276- resolved = append (resolved , & github.IssueRequestFieldValue {
277- FieldID : meta .DatabaseID ,
278- Value : input .Value ,
279- })
280- }
281- return resolved , nil
282- }
283-
284156// IssueFieldRef resolves the name of an issue field across its concrete types.
285157// IssueFields is a union of IssueFieldDate, IssueFieldNumber, IssueFieldSingleSelect, IssueFieldText,
286158// so we have to ask for `name` on each member.
@@ -391,44 +263,75 @@ func resolveIssueRequestFieldValues(ctx context.Context, gqlClient *githubv4.Cli
391263 return nil , nil
392264 }
393265
394- query := issueFieldMetadataQuery {}
266+ ctxWithFeatures := ghcontext .WithGraphQLFeatures (ctx , "issue_fields" , "repo_issue_fields" )
267+ var query issueFieldWriteMetadataQuery
395268 vars := map [string ]any {
396269 "owner" : githubv4 .String (owner ),
397270 "repo" : githubv4 .String (repo ),
398271 }
399- if err := gqlClient .Query (ctx , & query , vars ); err != nil {
272+ if err := gqlClient .Query (ctxWithFeatures , & query , vars ); err != nil {
400273 return nil , fmt .Errorf ("failed to query issue fields metadata: %w" , err )
401274 }
402275
403- fieldByName := make (map [string ]issueFieldMetadataNode , len (query .Repository .IssueFields .Nodes ))
404- for _ , field := range query .Repository .IssueFields .Nodes {
405- fieldByName [strings .ToLower (strings .TrimSpace (string (field .Name )))] = field
276+ // Build name → node map, dispatching on concrete type to extract name.
277+ fieldByName := make (map [string ]issueFieldWriteMetadataNode , len (query .Repository .IssueFields .Nodes ))
278+ for _ , node := range query .Repository .IssueFields .Nodes {
279+ var name string
280+ switch string (node .TypeName ) {
281+ case "IssueFieldText" :
282+ name = string (node .IssueFieldText .Name )
283+ case "IssueFieldNumber" :
284+ name = string (node .IssueFieldNumber .Name )
285+ case "IssueFieldDate" :
286+ name = string (node .IssueFieldDate .Name )
287+ case "IssueFieldSingleSelect" :
288+ name = string (node .IssueFieldSingleSelect .Name )
289+ default :
290+ continue
291+ }
292+ fieldByName [strings .ToLower (strings .TrimSpace (name ))] = node
406293 }
407294
408295 resolved := make ([]* github.IssueRequestFieldValue , 0 , len (issueFields ))
409296 for _ , fieldInput := range issueFields {
410- field , ok := fieldByName [strings .ToLower (strings .TrimSpace (fieldInput .FieldName ))]
297+ node , ok := fieldByName [strings .ToLower (strings .TrimSpace (fieldInput .FieldName ))]
411298 if ! ok {
412299 return nil , fmt .Errorf ("issue field %q was not found in %s/%s" , fieldInput .FieldName , owner , repo )
413300 }
414301
415- fieldID := int64 (field .DatabaseID )
302+ var fullDatabaseIDStr , dataType string
303+ switch string (node .TypeName ) {
304+ case "IssueFieldText" :
305+ fullDatabaseIDStr = string (node .IssueFieldText .FullDatabaseID )
306+ dataType = string (node .IssueFieldText .DataType )
307+ case "IssueFieldNumber" :
308+ fullDatabaseIDStr = string (node .IssueFieldNumber .FullDatabaseID )
309+ dataType = string (node .IssueFieldNumber .DataType )
310+ case "IssueFieldDate" :
311+ fullDatabaseIDStr = string (node .IssueFieldDate .FullDatabaseID )
312+ dataType = string (node .IssueFieldDate .DataType )
313+ case "IssueFieldSingleSelect" :
314+ fullDatabaseIDStr = string (node .IssueFieldSingleSelect .FullDatabaseID )
315+ dataType = string (node .IssueFieldSingleSelect .DataType )
316+ }
317+
318+ fieldID := parseFullDatabaseID (fullDatabaseIDStr )
416319 if fieldID == 0 {
417- return nil , fmt .Errorf ("issue field %q is missing databaseId " , fieldInput .FieldName )
320+ return nil , fmt .Errorf ("issue field %q is missing fullDatabaseId " , fieldInput .FieldName )
418321 }
419322
420323 resolvedValue := fieldInput .Value
421324 if fieldInput .FieldOptionName != "" {
422- if ! strings .EqualFold (string ( field . DataType ) , "single_select" ) {
423- return nil , fmt .Errorf ("issue field %q is %q, so field_option_name cannot be used" , fieldInput .FieldName , field . DataType )
325+ if ! strings .EqualFold (dataType , "single_select" ) {
326+ return nil , fmt .Errorf ("issue field %q is %q, so field_option_name cannot be used" , fieldInput .FieldName , dataType )
424327 }
425328
426329 optionFound := false
427- for _ , option := range field . SingleSelectField .Options {
330+ for _ , option := range node . IssueFieldSingleSelect .Options {
428331 if strings .EqualFold (strings .TrimSpace (string (option .Name )), strings .TrimSpace (fieldInput .FieldOptionName )) {
429- optionID := int64 ( option .DatabaseID )
332+ optionID := parseFullDatabaseID ( string ( option .FullDatabaseID ) )
430333 if optionID == 0 {
431- return nil , fmt .Errorf ("issue field option %q on field %q is missing databaseId " , fieldInput .FieldOptionName , fieldInput .FieldName )
334+ return nil , fmt .Errorf ("issue field option %q on field %q is missing fullDatabaseId " , fieldInput .FieldOptionName , fieldInput .FieldName )
432335 }
433336 resolvedValue = optionID
434337 optionFound = true
0 commit comments