Skip to content

Commit 282ac3f

Browse files
zwickCopilot
andcommitted
Add singular Issue Field project updates
Support name-based attached Issue Field updates for singular Project items while preserving existing read and standard field behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c94f3ce-c04a-482f-830b-ab85abc3f6e4
1 parent a387c39 commit 282ac3f

6 files changed

Lines changed: 700 additions & 217 deletions

File tree

pkg/github/granular_tools_test.go

Lines changed: 39 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,27 @@ func TestGranularUnresolveReviewThread(t *testing.T) {
17461746
}
17471747

17481748
func TestGranularSetIssueFields(t *testing.T) {
1749+
t.Run("mutation selects only issue identity", func(t *testing.T) {
1750+
transport := &sequencedGraphQLTransport{
1751+
t: t,
1752+
responses: []func(capturedGraphQLRequest) (int, string){
1753+
func(req capturedGraphQLRequest) (int, string) {
1754+
assert.Contains(t, req.Query, "issue{id,url}")
1755+
assert.NotContains(t, req.Query, "issueFieldValues")
1756+
assert.NotContains(t, req.Query, "number")
1757+
return http.StatusOK, `{"data":{"setIssueFieldValue":{"issue":{"id":"ISSUE_123","url":"https://github.com/owner/repo/issues/5"}}}}`
1758+
},
1759+
},
1760+
}
1761+
_, err := SetIssueFieldValues(context.Background(), githubv4.NewClient(&http.Client{Transport: transport}), SetIssueFieldValueInput{
1762+
IssueID: githubv4.ID("ISSUE_123"),
1763+
IssueFields: []IssueFieldCreateOrUpdateInput{{
1764+
FieldID: githubv4.ID("FIELD_1"), TextValue: githubv4.NewString("hello"),
1765+
}},
1766+
})
1767+
require.NoError(t, err)
1768+
})
1769+
17491770
t.Run("successful set with text value", func(t *testing.T) {
17501771
matchers := []githubv4mock.Matcher{
17511772
// Mock the issue ID query
@@ -1770,29 +1791,7 @@ func TestGranularSetIssueFields(t *testing.T) {
17701791
),
17711792
// Mock the setIssueFieldValue mutation
17721793
githubv4mock.NewMutationMatcher(
1773-
struct {
1774-
SetIssueFieldValue struct {
1775-
Issue struct {
1776-
ID githubv4.ID
1777-
Number githubv4.Int
1778-
URL githubv4.String
1779-
}
1780-
IssueFieldValues []struct {
1781-
TextValue struct {
1782-
Value string
1783-
} `graphql:"... on IssueFieldTextValue"`
1784-
SingleSelectValue struct {
1785-
Name string
1786-
} `graphql:"... on IssueFieldSingleSelectValue"`
1787-
DateValue struct {
1788-
Value string
1789-
} `graphql:"... on IssueFieldDateValue"`
1790-
NumberValue struct {
1791-
Value float64
1792-
} `graphql:"... on IssueFieldNumberValue"`
1793-
}
1794-
} `graphql:"setIssueFieldValue(input: $input)"`
1795-
}{},
1794+
setIssueFieldValueMutation{},
17961795
SetIssueFieldValueInput{
17971796
IssueID: githubv4.ID("ISSUE_123"),
17981797
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -1806,9 +1805,8 @@ func TestGranularSetIssueFields(t *testing.T) {
18061805
githubv4mock.DataResponse(map[string]any{
18071806
"setIssueFieldValue": map[string]any{
18081807
"issue": map[string]any{
1809-
"id": "ISSUE_123",
1810-
"number": 5,
1811-
"url": "https://github.com/owner/repo/issues/5",
1808+
"id": "ISSUE_123",
1809+
"url": "https://github.com/owner/repo/issues/5",
18121810
},
18131811
},
18141812
}),
@@ -1945,29 +1943,7 @@ func TestGranularSetIssueFields(t *testing.T) {
19451943
}),
19461944
),
19471945
githubv4mock.NewMutationMatcher(
1948-
struct {
1949-
SetIssueFieldValue struct {
1950-
Issue struct {
1951-
ID githubv4.ID
1952-
Number githubv4.Int
1953-
URL githubv4.String
1954-
}
1955-
IssueFieldValues []struct {
1956-
TextValue struct {
1957-
Value string
1958-
} `graphql:"... on IssueFieldTextValue"`
1959-
SingleSelectValue struct {
1960-
Name string
1961-
} `graphql:"... on IssueFieldSingleSelectValue"`
1962-
DateValue struct {
1963-
Value string
1964-
} `graphql:"... on IssueFieldDateValue"`
1965-
NumberValue struct {
1966-
Value float64
1967-
} `graphql:"... on IssueFieldNumberValue"`
1968-
}
1969-
} `graphql:"setIssueFieldValue(input: $input)"`
1970-
}{},
1946+
setIssueFieldValueMutation{},
19711947
SetIssueFieldValueInput{
19721948
IssueID: githubv4.ID("ISSUE_123"),
19731949
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -1982,9 +1958,8 @@ func TestGranularSetIssueFields(t *testing.T) {
19821958
githubv4mock.DataResponse(map[string]any{
19831959
"setIssueFieldValue": map[string]any{
19841960
"issue": map[string]any{
1985-
"id": "ISSUE_123",
1986-
"number": 5,
1987-
"url": "https://github.com/owner/repo/issues/5",
1961+
"id": "ISSUE_123",
1962+
"url": "https://github.com/owner/repo/issues/5",
19881963
},
19891964
},
19901965
}),
@@ -2059,29 +2034,7 @@ func TestGranularSetIssueFields(t *testing.T) {
20592034
}),
20602035
),
20612036
githubv4mock.NewMutationMatcher(
2062-
struct {
2063-
SetIssueFieldValue struct {
2064-
Issue struct {
2065-
ID githubv4.ID
2066-
Number githubv4.Int
2067-
URL githubv4.String
2068-
}
2069-
IssueFieldValues []struct {
2070-
TextValue struct {
2071-
Value string
2072-
} `graphql:"... on IssueFieldTextValue"`
2073-
SingleSelectValue struct {
2074-
Name string
2075-
} `graphql:"... on IssueFieldSingleSelectValue"`
2076-
DateValue struct {
2077-
Value string
2078-
} `graphql:"... on IssueFieldDateValue"`
2079-
NumberValue struct {
2080-
Value float64
2081-
} `graphql:"... on IssueFieldNumberValue"`
2082-
}
2083-
} `graphql:"setIssueFieldValue(input: $input)"`
2084-
}{},
2037+
setIssueFieldValueMutation{},
20852038
SetIssueFieldValueInput{
20862039
IssueID: githubv4.ID("ISSUE_123"),
20872040
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -2096,9 +2049,8 @@ func TestGranularSetIssueFields(t *testing.T) {
20962049
githubv4mock.DataResponse(map[string]any{
20972050
"setIssueFieldValue": map[string]any{
20982051
"issue": map[string]any{
2099-
"id": "ISSUE_123",
2100-
"number": 5,
2101-
"url": "https://github.com/owner/repo/issues/5",
2052+
"id": "ISSUE_123",
2053+
"url": "https://github.com/owner/repo/issues/5",
21022054
},
21032055
},
21042056
}),
@@ -2173,29 +2125,7 @@ func TestGranularSetIssueFields(t *testing.T) {
21732125
}),
21742126
),
21752127
githubv4mock.NewMutationMatcher(
2176-
struct {
2177-
SetIssueFieldValue struct {
2178-
Issue struct {
2179-
ID githubv4.ID
2180-
Number githubv4.Int
2181-
URL githubv4.String
2182-
}
2183-
IssueFieldValues []struct {
2184-
TextValue struct {
2185-
Value string
2186-
} `graphql:"... on IssueFieldTextValue"`
2187-
SingleSelectValue struct {
2188-
Name string
2189-
} `graphql:"... on IssueFieldSingleSelectValue"`
2190-
DateValue struct {
2191-
Value string
2192-
} `graphql:"... on IssueFieldDateValue"`
2193-
NumberValue struct {
2194-
Value float64
2195-
} `graphql:"... on IssueFieldNumberValue"`
2196-
}
2197-
} `graphql:"setIssueFieldValue(input: $input)"`
2198-
}{},
2128+
setIssueFieldValueMutation{},
21992129
SetIssueFieldValueInput{
22002130
IssueID: githubv4.ID("ISSUE_123"),
22012131
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -2210,9 +2140,8 @@ func TestGranularSetIssueFields(t *testing.T) {
22102140
githubv4mock.DataResponse(map[string]any{
22112141
"setIssueFieldValue": map[string]any{
22122142
"issue": map[string]any{
2213-
"id": "ISSUE_123",
2214-
"number": 5,
2215-
"url": "https://github.com/owner/repo/issues/5",
2143+
"id": "ISSUE_123",
2144+
"url": "https://github.com/owner/repo/issues/5",
22162145
},
22172146
},
22182147
}),
@@ -2264,29 +2193,7 @@ func TestGranularSetIssueFields(t *testing.T) {
22642193
}),
22652194
),
22662195
githubv4mock.NewMutationMatcher(
2267-
struct {
2268-
SetIssueFieldValue struct {
2269-
Issue struct {
2270-
ID githubv4.ID
2271-
Number githubv4.Int
2272-
URL githubv4.String
2273-
}
2274-
IssueFieldValues []struct {
2275-
TextValue struct {
2276-
Value string
2277-
} `graphql:"... on IssueFieldTextValue"`
2278-
SingleSelectValue struct {
2279-
Name string
2280-
} `graphql:"... on IssueFieldSingleSelectValue"`
2281-
DateValue struct {
2282-
Value string
2283-
} `graphql:"... on IssueFieldDateValue"`
2284-
NumberValue struct {
2285-
Value float64
2286-
} `graphql:"... on IssueFieldNumberValue"`
2287-
}
2288-
} `graphql:"setIssueFieldValue(input: $input)"`
2289-
}{},
2196+
setIssueFieldValueMutation{},
22902197
SetIssueFieldValueInput{
22912198
IssueID: githubv4.ID("ISSUE_123"),
22922199
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -2302,9 +2209,8 @@ func TestGranularSetIssueFields(t *testing.T) {
23022209
githubv4mock.DataResponse(map[string]any{
23032210
"setIssueFieldValue": map[string]any{
23042211
"issue": map[string]any{
2305-
"id": "ISSUE_123",
2306-
"number": 5,
2307-
"url": "https://github.com/owner/repo/issues/5",
2212+
"id": "ISSUE_123",
2213+
"url": "https://github.com/owner/repo/issues/5",
23082214
},
23092215
},
23102216
}),
@@ -2356,29 +2262,7 @@ func TestGranularSetIssueFields(t *testing.T) {
23562262
}),
23572263
),
23582264
githubv4mock.NewMutationMatcher(
2359-
struct {
2360-
SetIssueFieldValue struct {
2361-
Issue struct {
2362-
ID githubv4.ID
2363-
Number githubv4.Int
2364-
URL githubv4.String
2365-
}
2366-
IssueFieldValues []struct {
2367-
TextValue struct {
2368-
Value string
2369-
} `graphql:"... on IssueFieldTextValue"`
2370-
SingleSelectValue struct {
2371-
Name string
2372-
} `graphql:"... on IssueFieldSingleSelectValue"`
2373-
DateValue struct {
2374-
Value string
2375-
} `graphql:"... on IssueFieldDateValue"`
2376-
NumberValue struct {
2377-
Value float64
2378-
} `graphql:"... on IssueFieldNumberValue"`
2379-
}
2380-
} `graphql:"setIssueFieldValue(input: $input)"`
2381-
}{},
2265+
setIssueFieldValueMutation{},
23822266
SetIssueFieldValueInput{
23832267
IssueID: githubv4.ID("ISSUE_123"),
23842268
IssueFields: []IssueFieldCreateOrUpdateInput{
@@ -2392,9 +2276,8 @@ func TestGranularSetIssueFields(t *testing.T) {
23922276
githubv4mock.DataResponse(map[string]any{
23932277
"setIssueFieldValue": map[string]any{
23942278
"issue": map[string]any{
2395-
"id": "ISSUE_123",
2396-
"number": 5,
2397-
"url": "https://github.com/owner/repo/issues/5",
2279+
"id": "ISSUE_123",
2280+
"url": "https://github.com/owner/repo/issues/5",
23982281
},
23992282
},
24002283
}),

pkg/github/issues_granular.go

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,27 @@ type IssueFieldCreateOrUpdateInput struct {
12631263
Suggest *githubv4.Boolean `json:"suggest,omitempty"`
12641264
}
12651265

1266+
type setIssueFieldValueMutation struct {
1267+
SetIssueFieldValue struct {
1268+
Issue struct {
1269+
ID githubv4.ID
1270+
URL githubv4.String
1271+
}
1272+
} `graphql:"setIssueFieldValue(input: $input)"`
1273+
}
1274+
1275+
// SetIssueFieldValues updates Issue Field values and returns the updated issue.
1276+
func SetIssueFieldValues(ctx context.Context, gqlClient *githubv4.Client, input SetIssueFieldValueInput) (MinimalResponse, error) {
1277+
var mutation setIssueFieldValueMutation
1278+
if err := gqlClient.Mutate(ctx, &mutation, input, nil); err != nil {
1279+
return MinimalResponse{}, err
1280+
}
1281+
return MinimalResponse{
1282+
ID: fmt.Sprintf("%v", mutation.SetIssueFieldValue.Issue.ID),
1283+
URL: string(mutation.SetIssueFieldValue.Issue.URL),
1284+
}, nil
1285+
}
1286+
12661287
// GranularSetIssueFields creates a tool to set issue field values on an issue using GraphQL.
12671288
func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.ServerTool {
12681289
st := NewTool(
@@ -1486,31 +1507,6 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
14861507
return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to get issue", err), nil, nil
14871508
}
14881509

1489-
// Execute the setIssueFieldValue mutation
1490-
var mutation struct {
1491-
SetIssueFieldValue struct {
1492-
Issue struct {
1493-
ID githubv4.ID
1494-
Number githubv4.Int
1495-
URL githubv4.String
1496-
}
1497-
IssueFieldValues []struct {
1498-
TextValue struct {
1499-
Value string
1500-
} `graphql:"... on IssueFieldTextValue"`
1501-
SingleSelectValue struct {
1502-
Name string
1503-
} `graphql:"... on IssueFieldSingleSelectValue"`
1504-
DateValue struct {
1505-
Value string
1506-
} `graphql:"... on IssueFieldDateValue"`
1507-
NumberValue struct {
1508-
Value float64
1509-
} `graphql:"... on IssueFieldNumberValue"`
1510-
}
1511-
} `graphql:"setIssueFieldValue(input: $input)"`
1512-
}
1513-
15141510
mutationInput := SetIssueFieldValueInput{
15151511
IssueID: issueID,
15161512
IssueFields: issueFields,
@@ -1519,14 +1515,12 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
15191515
// The rationale and suggest input fields on IssueFieldCreateOrUpdateInput
15201516
// are gated behind the update_issue_suggestions GraphQL feature flag.
15211517
ctxWithFeatures := ghcontext.WithGraphQLFeatures(ctx, "update_issue_suggestions")
1522-
if err := gqlClient.Mutate(ctxWithFeatures, &mutation, mutationInput, nil); err != nil {
1518+
response, err := SetIssueFieldValues(ctxWithFeatures, gqlClient, mutationInput)
1519+
if err != nil {
15231520
return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to set issue field values", err), nil, nil
15241521
}
15251522

1526-
r, err := json.Marshal(MinimalResponse{
1527-
ID: fmt.Sprintf("%v", mutation.SetIssueFieldValue.Issue.ID),
1528-
URL: string(mutation.SetIssueFieldValue.Issue.URL),
1529-
})
1523+
r, err := json.Marshal(response)
15301524
if err != nil {
15311525
return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil
15321526
}

0 commit comments

Comments
 (0)