Skip to content

Commit 097829e

Browse files
authored
fix merge of full db id
1 parent 1799fed commit 097829e

2 files changed

Lines changed: 75 additions & 168 deletions

File tree

pkg/github/issues.go

Lines changed: 48 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
7048
const (
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-
181148
type 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

pkg/github/issues_test.go

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,27 +1313,29 @@ func Test_CreateIssue(t *testing.T) {
13131313
}),
13141314
mockedGQLClient: githubv4mock.NewMockedHTTPClient(
13151315
githubv4mock.NewQueryMatcher(
1316-
issueFieldMetadataQuery{},
1316+
issueFieldWriteMetadataQuery{},
13171317
map[string]any{
13181318
"owner": githubv4.String("owner"),
13191319
"repo": githubv4.String("repo"),
13201320
},
13211321
githubv4mock.DataResponse(map[string]any{
13221322
"repository": map[string]any{
13231323
"issueFields": map[string]any{
1324-
"nodes": []map[string]any{
1325-
{
1326-
"databaseId": 101,
1327-
"name": "Priority",
1328-
"dataType": "single_select",
1329-
"options": []map[string]any{
1330-
{"databaseId": 9001, "name": "P1"},
1324+
"nodes": []any{
1325+
map[string]any{
1326+
"__typename": "IssueFieldSingleSelect",
1327+
"fullDatabaseId": "101",
1328+
"name": "Priority",
1329+
"dataType": "single_select",
1330+
"options": []any{
1331+
map[string]any{"fullDatabaseId": "9001", "name": "P1"},
13311332
},
13321333
},
1333-
{
1334-
"databaseId": 102,
1335-
"name": "Customer",
1336-
"dataType": "text",
1334+
map[string]any{
1335+
"__typename": "IssueFieldText",
1336+
"fullDatabaseId": "102",
1337+
"name": "Customer",
1338+
"dataType": "text",
13371339
},
13381340
},
13391341
},
@@ -2776,25 +2778,27 @@ func Test_UpdateIssue(t *testing.T) {
27762778
}),
27772779
mockedGQLClient: githubv4mock.NewMockedHTTPClient(
27782780
githubv4mock.NewQueryMatcher(
2779-
issueFieldMetadataQuery{},
2781+
issueFieldWriteMetadataQuery{},
27802782
map[string]any{
27812783
"owner": githubv4.String("owner"),
27822784
"repo": githubv4.String("repo"),
27832785
},
27842786
githubv4mock.DataResponse(map[string]any{
27852787
"repository": map[string]any{
27862788
"issueFields": map[string]any{
2787-
"nodes": []map[string]any{
2788-
{
2789-
"databaseId": 101,
2790-
"name": "Priority",
2791-
"dataType": "single_select",
2792-
"options": []map[string]any{{"databaseId": 9001, "name": "P1"}},
2789+
"nodes": []any{
2790+
map[string]any{
2791+
"__typename": "IssueFieldSingleSelect",
2792+
"fullDatabaseId": "101",
2793+
"name": "Priority",
2794+
"dataType": "single_select",
2795+
"options": []any{map[string]any{"fullDatabaseId": "9001", "name": "P1"}},
27932796
},
2794-
{
2795-
"databaseId": 102,
2796-
"name": "Customer",
2797-
"dataType": "text",
2797+
map[string]any{
2798+
"__typename": "IssueFieldText",
2799+
"fullDatabaseId": "102",
2800+
"name": "Customer",
2801+
"dataType": "text",
27982802
},
27992803
},
28002804
},

0 commit comments

Comments
 (0)