@@ -70,33 +70,36 @@ type projectFieldsQueryUser struct {
7070 } `graphql:"user(login: $owner)"`
7171}
7272
73- // projectFieldsConnection is a paginated list of project fields. We select `id`
74- // to discriminate the union variant and `databaseId` for the numeric ID REST needs.
73+ type projectV2FieldSelection struct {
74+ ID githubv4.ID
75+ DatabaseID githubv4.Int `graphql:"databaseId"`
76+ Name githubv4.String
77+ DataType githubv4.String
78+ }
79+
80+ type projectV2SingleSelectOptionSelection struct {
81+ ID githubv4.String
82+ Name githubv4.String
83+ }
84+
85+ type projectV2SingleSelectFieldSelection struct {
86+ ID githubv4.ID
87+ DatabaseID githubv4.Int `graphql:"databaseId"`
88+ Name githubv4.String
89+ DataType githubv4.String
90+ Options []projectV2SingleSelectOptionSelection
91+ }
92+
93+ // projectFieldsNode selects `id` to discriminate the union variant and
94+ // `databaseId` for the numeric ID REST needs.
95+ type projectFieldsNode struct {
96+ ProjectV2Field projectV2FieldSelection `graphql:"... on ProjectV2Field"`
97+ ProjectV2IterationField projectV2FieldSelection `graphql:"... on ProjectV2IterationField"`
98+ ProjectV2SingleSelectField projectV2SingleSelectFieldSelection `graphql:"... on ProjectV2SingleSelectField"`
99+ }
100+
75101type projectFieldsConnection struct {
76- Nodes []struct {
77- ProjectV2Field struct {
78- ID githubv4.ID
79- DatabaseID githubv4.Int `graphql:"databaseId"`
80- Name githubv4.String
81- DataType githubv4.String
82- } `graphql:"... on ProjectV2Field"`
83- ProjectV2IterationField struct {
84- ID githubv4.ID
85- DatabaseID githubv4.Int `graphql:"databaseId"`
86- Name githubv4.String
87- DataType githubv4.String
88- } `graphql:"... on ProjectV2IterationField"`
89- ProjectV2SingleSelectField struct {
90- ID githubv4.ID
91- DatabaseID githubv4.Int `graphql:"databaseId"`
92- Name githubv4.String
93- DataType githubv4.String
94- Options []struct {
95- ID githubv4.String
96- Name githubv4.String
97- }
98- } `graphql:"... on ProjectV2SingleSelectField"`
99- }
102+ Nodes []projectFieldsNode
100103 PageInfo PageInfoFragment
101104}
102105
@@ -117,52 +120,77 @@ type projectFieldsWithIssueFieldsQueryUser struct {
117120}
118121
119122type projectFieldsWithIssueFieldsConnection struct {
120- Nodes []struct {
121- ProjectV2Field struct {
122- ID githubv4.ID
123- DatabaseID githubv4.Int `graphql:"databaseId"`
124- Name githubv4.String
125- DataType githubv4.String
126- IsIssueField githubv4.Boolean
127- IssueField projectIssueFieldMetadata
128- } `graphql:"... on ProjectV2Field"`
129- ProjectV2IterationField struct {
130- ID githubv4.ID
131- DatabaseID githubv4.Int `graphql:"databaseId"`
132- Name githubv4.String
133- DataType githubv4.String
134- } `graphql:"... on ProjectV2IterationField"`
135- ProjectV2SingleSelectField struct {
136- ID githubv4.ID
137- DatabaseID githubv4.Int `graphql:"databaseId"`
138- Name githubv4.String
139- DataType githubv4.String
140- IsIssueField githubv4.Boolean
141- IssueField projectIssueFieldMetadata
142- Options []struct {
143- ID githubv4.String
144- Name githubv4.String
145- }
146- } `graphql:"... on ProjectV2SingleSelectField"`
147- }
123+ Nodes []projectFieldsWithIssueFieldsNode
148124 PageInfo PageInfoFragment
149125}
150126
127+ type projectFieldsWithIssueFieldsNode struct {
128+ ProjectV2Field struct {
129+ projectV2FieldSelection
130+ IsIssueField githubv4.Boolean
131+ IssueField projectIssueFieldMetadata
132+ } `graphql:"... on ProjectV2Field"`
133+ ProjectV2IterationField projectV2FieldSelection `graphql:"... on ProjectV2IterationField"`
134+ ProjectV2SingleSelectField struct {
135+ projectV2SingleSelectFieldSelection
136+ IsIssueField githubv4.Boolean
137+ IssueField projectIssueFieldMetadata
138+ } `graphql:"... on ProjectV2SingleSelectField"`
139+ }
140+
141+ func projectFieldsQueryVariables (owner string , projectNumber int , after * githubv4.String ) map [string ]any {
142+ vars := map [string ]any {
143+ "owner" : githubv4 .String (owner ),
144+ "projectNumber" : githubv4 .Int (int32 (projectNumber )), //nolint:gosec // Project numbers are small
145+ "first" : githubv4 .Int (resolverFieldsPageSize ),
146+ "after" : (* githubv4 .String )(nil ),
147+ }
148+ if after != nil {
149+ vars ["after" ] = after
150+ }
151+ return vars
152+ }
153+
154+ func resolvedProjectField (node projectFieldsNode ) (ResolvedField , bool ) {
155+ switch {
156+ case node .ProjectV2SingleSelectField .ID != nil :
157+ options := make ([]ResolvedFieldOption , 0 , len (node .ProjectV2SingleSelectField .Options ))
158+ for _ , option := range node .ProjectV2SingleSelectField .Options {
159+ options = append (options , ResolvedFieldOption {ID : string (option .ID ), Name : string (option .Name )})
160+ }
161+ return ResolvedField {
162+ ID : fmt .Sprintf ("%d" , node .ProjectV2SingleSelectField .DatabaseID ),
163+ NodeID : fmt .Sprintf ("%v" , node .ProjectV2SingleSelectField .ID ),
164+ Name : string (node .ProjectV2SingleSelectField .Name ),
165+ DataType : string (node .ProjectV2SingleSelectField .DataType ),
166+ Options : options ,
167+ }, true
168+ case node .ProjectV2IterationField .ID != nil :
169+ return ResolvedField {
170+ ID : fmt .Sprintf ("%d" , node .ProjectV2IterationField .DatabaseID ),
171+ NodeID : fmt .Sprintf ("%v" , node .ProjectV2IterationField .ID ),
172+ Name : string (node .ProjectV2IterationField .Name ),
173+ DataType : string (node .ProjectV2IterationField .DataType ),
174+ }, true
175+ case node .ProjectV2Field .ID != nil :
176+ return ResolvedField {
177+ ID : fmt .Sprintf ("%d" , node .ProjectV2Field .DatabaseID ),
178+ NodeID : fmt .Sprintf ("%v" , node .ProjectV2Field .ID ),
179+ Name : string (node .ProjectV2Field .Name ),
180+ DataType : string (node .ProjectV2Field .DataType ),
181+ }, true
182+ default :
183+ return ResolvedField {}, false
184+ }
185+ }
186+
151187// listAllProjectFields fetches every field on a project, paginating as needed.
152188func listAllProjectFields (ctx context.Context , gqlClient * githubv4.Client , owner , ownerType string , projectNumber int ) ([]ResolvedField , error ) {
153189 all := []ResolvedField {}
154190 var after * githubv4.String
155191
156192 for {
157- vars := map [string ]any {
158- "owner" : githubv4 .String (owner ),
159- "projectNumber" : githubv4 .Int (int32 (projectNumber )), //nolint:gosec // Project numbers are small
160- "first" : githubv4 .Int (resolverFieldsPageSize ),
161- "after" : (* githubv4 .String )(nil ),
162- }
163- if after != nil {
164- vars ["after" ] = after
165- }
193+ vars := projectFieldsQueryVariables (owner , projectNumber , after )
166194
167195 var conn projectFieldsConnection
168196 if ownerType == "org" {
@@ -179,34 +207,9 @@ func listAllProjectFields(ctx context.Context, gqlClient *githubv4.Client, owner
179207 conn = q .User .ProjectV2 .Fields
180208 }
181209
182- for _ , n := range conn .Nodes {
183- switch {
184- case n .ProjectV2SingleSelectField .ID != nil :
185- opts := make ([]ResolvedFieldOption , 0 , len (n .ProjectV2SingleSelectField .Options ))
186- for _ , o := range n .ProjectV2SingleSelectField .Options {
187- opts = append (opts , ResolvedFieldOption {ID : string (o .ID ), Name : string (o .Name )})
188- }
189- all = append (all , ResolvedField {
190- ID : fmt .Sprintf ("%d" , n .ProjectV2SingleSelectField .DatabaseID ),
191- NodeID : fmt .Sprintf ("%v" , n .ProjectV2SingleSelectField .ID ),
192- Name : string (n .ProjectV2SingleSelectField .Name ),
193- DataType : string (n .ProjectV2SingleSelectField .DataType ),
194- Options : opts ,
195- })
196- case n .ProjectV2IterationField .ID != nil :
197- all = append (all , ResolvedField {
198- ID : fmt .Sprintf ("%d" , n .ProjectV2IterationField .DatabaseID ),
199- NodeID : fmt .Sprintf ("%v" , n .ProjectV2IterationField .ID ),
200- Name : string (n .ProjectV2IterationField .Name ),
201- DataType : string (n .ProjectV2IterationField .DataType ),
202- })
203- case n .ProjectV2Field .ID != nil :
204- all = append (all , ResolvedField {
205- ID : fmt .Sprintf ("%d" , n .ProjectV2Field .DatabaseID ),
206- NodeID : fmt .Sprintf ("%v" , n .ProjectV2Field .ID ),
207- Name : string (n .ProjectV2Field .Name ),
208- DataType : string (n .ProjectV2Field .DataType ),
209- })
210+ for _ , node := range conn .Nodes {
211+ if field , ok := resolvedProjectField (node ); ok {
212+ all = append (all , field )
210213 }
211214 }
212215
@@ -234,15 +237,7 @@ func listAllProjectFieldsWithIssueFieldMetadata(ctx context.Context, gqlClient *
234237 ctx = ghcontext .WithGraphQLFeatures (ctx , "issue_fields" )
235238
236239 for {
237- vars := map [string ]any {
238- "owner" : githubv4 .String (owner ),
239- "projectNumber" : githubv4 .Int (int32 (projectNumber )), //nolint:gosec // Project numbers are small
240- "first" : githubv4 .Int (resolverFieldsPageSize ),
241- "after" : (* githubv4 .String )(nil ),
242- }
243- if after != nil {
244- vars ["after" ] = after
245- }
240+ vars := projectFieldsQueryVariables (owner , projectNumber , after )
246241
247242 var conn projectFieldsWithIssueFieldsConnection
248243 if ownerType == "org" {
@@ -259,47 +254,28 @@ func listAllProjectFieldsWithIssueFieldMetadata(ctx context.Context, gqlClient *
259254 conn = q .User .ProjectV2 .Fields
260255 }
261256
262- for _ , n := range conn .Nodes {
263- switch {
264- case n .ProjectV2SingleSelectField .ID != nil :
265- opts := make ([]ResolvedFieldOption , 0 , len (n .ProjectV2SingleSelectField .Options ))
266- for _ , o := range n .ProjectV2SingleSelectField .Options {
267- opts = append (opts , ResolvedFieldOption {ID : string (o .ID ), Name : string (o .Name )})
268- }
269- issueFieldNodeID := ""
270- if n .ProjectV2SingleSelectField .IsIssueField {
271- opts = opts [:0 ]
272- issueFieldNodeID = issueFieldNodeIDForType (n .ProjectV2SingleSelectField .DataType , n .ProjectV2SingleSelectField .IssueField )
273- for _ , o := range n .ProjectV2SingleSelectField .IssueField .SingleSelect .Options {
274- opts = append (opts , ResolvedFieldOption {ID : fmt .Sprintf ("%v" , o .ID ), Name : string (o .Name )})
275- }
257+ for _ , node := range conn .Nodes {
258+ field , ok := resolvedProjectField (projectFieldsNode {
259+ ProjectV2Field : node .ProjectV2Field .projectV2FieldSelection ,
260+ ProjectV2IterationField : node .ProjectV2IterationField ,
261+ ProjectV2SingleSelectField : node .ProjectV2SingleSelectField .projectV2SingleSelectFieldSelection ,
262+ })
263+ if ! ok {
264+ continue
265+ }
266+
267+ if field .DataType == "SINGLE_SELECT" && bool (node .ProjectV2SingleSelectField .IsIssueField ) {
268+ field .IsIssueField = true
269+ field .IssueFieldNodeID = issueFieldNodeIDForType (field .DataType , node .ProjectV2SingleSelectField .IssueField )
270+ field .Options = field .Options [:0 ]
271+ for _ , option := range node .ProjectV2SingleSelectField .IssueField .SingleSelect .Options {
272+ field .Options = append (field .Options , ResolvedFieldOption {ID : fmt .Sprintf ("%v" , option .ID ), Name : string (option .Name )})
276273 }
277- all = append (all , ResolvedField {
278- ID : fmt .Sprintf ("%d" , n .ProjectV2SingleSelectField .DatabaseID ),
279- NodeID : fmt .Sprintf ("%v" , n .ProjectV2SingleSelectField .ID ),
280- Name : string (n .ProjectV2SingleSelectField .Name ),
281- DataType : string (n .ProjectV2SingleSelectField .DataType ),
282- Options : opts ,
283- IsIssueField : bool (n .ProjectV2SingleSelectField .IsIssueField ),
284- IssueFieldNodeID : issueFieldNodeID ,
285- })
286- case n .ProjectV2IterationField .ID != nil :
287- all = append (all , ResolvedField {
288- ID : fmt .Sprintf ("%d" , n .ProjectV2IterationField .DatabaseID ),
289- NodeID : fmt .Sprintf ("%v" , n .ProjectV2IterationField .ID ),
290- Name : string (n .ProjectV2IterationField .Name ),
291- DataType : string (n .ProjectV2IterationField .DataType ),
292- })
293- case n .ProjectV2Field .ID != nil :
294- all = append (all , ResolvedField {
295- ID : fmt .Sprintf ("%d" , n .ProjectV2Field .DatabaseID ),
296- NodeID : fmt .Sprintf ("%v" , n .ProjectV2Field .ID ),
297- Name : string (n .ProjectV2Field .Name ),
298- DataType : string (n .ProjectV2Field .DataType ),
299- IsIssueField : bool (n .ProjectV2Field .IsIssueField ),
300- IssueFieldNodeID : issueFieldNodeIDForType (n .ProjectV2Field .DataType , n .ProjectV2Field .IssueField ),
301- })
274+ } else if bool (node .ProjectV2Field .IsIssueField ) {
275+ field .IsIssueField = true
276+ field .IssueFieldNodeID = issueFieldNodeIDForType (field .DataType , node .ProjectV2Field .IssueField )
302277 }
278+ all = append (all , field )
303279 }
304280
305281 if ! bool (conn .PageInfo .HasNextPage ) {
@@ -328,12 +304,18 @@ func issueFieldSchemaUnavailable(err error) bool {
328304 return true
329305 }
330306 }
307+ for _ , typeName := range []string {"IssueFieldText" , "IssueFieldNumber" , "IssueFieldDate" , "IssueFieldSingleSelect" } {
308+ if strings .Contains (message , fmt .Sprintf ("No such type %s, so it can't be a fragment condition" , typeName )) ||
309+ strings .Contains (message , fmt .Sprintf (`Unknown type "%s".` , typeName )) {
310+ return true
311+ }
312+ }
331313 return false
332314}
333315
334- func issueFieldNodeIDForType (dataType githubv4. String , metadata projectIssueFieldMetadata ) string {
316+ func issueFieldNodeIDForType (dataType string , metadata projectIssueFieldMetadata ) string {
335317 var id githubv4.ID
336- switch strings .ToUpper (string ( dataType ) ) {
318+ switch strings .ToUpper (dataType ) {
337319 case "TEXT" :
338320 id = metadata .Text .ID
339321 case "NUMBER" :
@@ -349,13 +331,6 @@ func issueFieldNodeIDForType(dataType githubv4.String, metadata projectIssueFiel
349331 return fmt .Sprintf ("%v" , id )
350332}
351333
352- // resolveProjectFieldByName resolves a field by display name. Returns a
353- // structured error on not-found, ambiguous, or wrong-data-type (when
354- // expectedDataType is set) so the agent can self-correct.
355- func resolveProjectFieldByName (ctx context.Context , gqlClient * githubv4.Client , owner , ownerType string , projectNumber int , fieldName , expectedDataType string ) (* ResolvedField , error ) {
356- return resolveProjectFieldByNameWithLister (ctx , gqlClient , owner , ownerType , projectNumber , fieldName , expectedDataType , listAllProjectFields )
357- }
358-
359334func resolveProjectFieldForUpdateByName (ctx context.Context , gqlClient * githubv4.Client , owner , ownerType string , projectNumber int , fieldName , expectedDataType string ) (* ResolvedField , error ) {
360335 return resolveProjectFieldByNameWithLister (ctx , gqlClient , owner , ownerType , projectNumber , fieldName , expectedDataType , listAllProjectFieldsForUpdate )
361336}
0 commit comments