Merge branch 'trunk' into trunk
This commit is contained in:
commit
a491578751
10 changed files with 208 additions and 10 deletions
|
|
@ -139,6 +139,25 @@ func (pr *PullRequest) ExportData(fields []string) map[string]interface{} {
|
|||
}
|
||||
}
|
||||
data[f] = &requests
|
||||
case "closingIssuesReferences":
|
||||
items := make([]map[string]interface{}, 0, len(pr.ClosingIssuesReferences.Nodes))
|
||||
for _, n := range pr.ClosingIssuesReferences.Nodes {
|
||||
items = append(items, map[string]interface{}{
|
||||
|
||||
"id": n.ID,
|
||||
"number": n.Number,
|
||||
"url": n.URL,
|
||||
"repository": map[string]interface{}{
|
||||
"id": n.Repository.ID,
|
||||
"name": n.Repository.Name,
|
||||
"owner": map[string]interface{}{
|
||||
"id": n.Repository.Owner.ID,
|
||||
"login": n.Repository.Owner.Login,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
data[f] = items
|
||||
default:
|
||||
sf := fieldByName(v, f)
|
||||
data[f] = sf.Interface()
|
||||
|
|
|
|||
|
|
@ -245,6 +245,70 @@ func TestPullRequest_ExportData(t *testing.T) {
|
|||
}
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "linked issues",
|
||||
fields: []string{"closingIssuesReferences"},
|
||||
inputJSON: heredoc.Doc(`
|
||||
{ "closingIssuesReferences": { "nodes": [
|
||||
{
|
||||
"id": "I_123",
|
||||
"number": 123,
|
||||
"url": "https://github.com/cli/cli/issues/123",
|
||||
"repository": {
|
||||
"id": "R_123",
|
||||
"name": "cli",
|
||||
"owner": {
|
||||
"id": "O_123",
|
||||
"login": "cli"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "I_456",
|
||||
"number": 456,
|
||||
"url": "https://github.com/cli/cli/issues/456",
|
||||
"repository": {
|
||||
"id": "R_456",
|
||||
"name": "cli",
|
||||
"owner": {
|
||||
"id": "O_456",
|
||||
"login": "cli"
|
||||
}
|
||||
}
|
||||
}
|
||||
] } }
|
||||
`),
|
||||
outputJSON: heredoc.Doc(`
|
||||
{ "closingIssuesReferences": [
|
||||
{
|
||||
"id": "I_123",
|
||||
"number": 123,
|
||||
"repository": {
|
||||
"id": "R_123",
|
||||
"name": "cli",
|
||||
"owner": {
|
||||
"id": "O_123",
|
||||
"login": "cli"
|
||||
}
|
||||
},
|
||||
"url": "https://github.com/cli/cli/issues/123"
|
||||
},
|
||||
{
|
||||
"id": "I_456",
|
||||
"number": 456,
|
||||
"repository": {
|
||||
"id": "R_456",
|
||||
"name": "cli",
|
||||
"owner": {
|
||||
"id": "O_456",
|
||||
"login": "cli"
|
||||
}
|
||||
},
|
||||
"url": "https://github.com/cli/cli/issues/456"
|
||||
}
|
||||
] }
|
||||
`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ type PullRequest struct {
|
|||
Reviews PullRequestReviews
|
||||
LatestReviews PullRequestReviews
|
||||
ReviewRequests ReviewRequests
|
||||
|
||||
ClosingIssuesReferences ClosingIssuesReferences
|
||||
}
|
||||
|
||||
type StatusCheckRollupNode struct {
|
||||
|
|
@ -107,6 +109,26 @@ type CommitStatusCheckRollup struct {
|
|||
Contexts CheckContexts
|
||||
}
|
||||
|
||||
type ClosingIssuesReferences struct {
|
||||
Nodes []struct {
|
||||
ID string
|
||||
Number int
|
||||
URL string
|
||||
Repository struct {
|
||||
ID string
|
||||
Name string
|
||||
Owner struct {
|
||||
ID string
|
||||
Login string
|
||||
}
|
||||
}
|
||||
}
|
||||
PageInfo struct {
|
||||
HasNextPage bool
|
||||
EndCursor string
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/graphql/reference/enums#checkrunstate
|
||||
type CheckRunState string
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,25 @@ var prCommits = shortenQuery(`
|
|||
}
|
||||
`)
|
||||
|
||||
var prClosingIssuesReferences = shortenQuery(`
|
||||
closingIssuesReferences(first: 100) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
url,
|
||||
repository {
|
||||
id,
|
||||
name,
|
||||
owner {
|
||||
id,
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo{hasNextPage,endCursor}
|
||||
}
|
||||
`)
|
||||
|
||||
var autoMergeRequest = shortenQuery(`
|
||||
autoMergeRequest {
|
||||
authorEmail,
|
||||
|
|
@ -287,6 +306,7 @@ var PullRequestFields = append(sharedIssuePRFields,
|
|||
"baseRefName",
|
||||
"baseRefOid",
|
||||
"changedFiles",
|
||||
"closingIssuesReferences",
|
||||
"commits",
|
||||
"deletions",
|
||||
"files",
|
||||
|
|
@ -366,6 +386,8 @@ func IssueGraphQL(fields []string) string {
|
|||
q = append(q, StatusCheckRollupGraphQLWithoutCountByState(""))
|
||||
case "statusCheckRollupWithCountByState": // pseudo-field
|
||||
q = append(q, StatusCheckRollupGraphQLWithCountByState())
|
||||
case "closingIssuesReferences":
|
||||
q = append(q, prClosingIssuesReferences)
|
||||
default:
|
||||
q = append(q, field)
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/briandowns/spinner v1.18.1
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3
|
||||
github.com/charmbracelet/huh v0.6.1-0.20250409210615-c5906631cbb5
|
||||
github.com/charmbracelet/huh v0.7.0
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc
|
||||
github.com/cli/go-gh/v2 v2.12.0
|
||||
github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -110,20 +110,28 @@ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4p
|
|||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3 h1:hx6E25SvI2WiZdt/gxINcYBnHD7PE2Vr9auqwg5B05g=
|
||||
github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3/go.mod h1:ihVqv4/YOY5Fweu1cxajuQrwJFh3zU4Ukb4mHVNjq3s=
|
||||
github.com/charmbracelet/huh v0.6.1-0.20250409210615-c5906631cbb5 h1:uOnMxWghHfEYm2DPMeIHHAEirV/TduBVC9ZRXGcX9Q8=
|
||||
github.com/charmbracelet/huh v0.6.1-0.20250409210615-c5906631cbb5/go.mod h1:xl27E/xNaX3WwdkqpvBwjJcGWhupkU52CWLC5hReBTw=
|
||||
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
|
||||
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc h1:nFRtCfZu/zkltd2lsLUPlVNv3ej/Atod9hcdbRZtlys=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
|
||||
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
|
||||
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
// Wait for prompt to appear
|
||||
_, err := console.ExpectString("Choose:")
|
||||
_, err := console.ExpectString("Input a number between 1 and 3:")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Select option 1
|
||||
|
|
@ -57,7 +57,7 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
// Wait for prompt to appear
|
||||
_, err := console.ExpectString("Select a number")
|
||||
_, err := console.ExpectString("Input a number between 0 and 3:")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Select options 1 and 2
|
||||
|
|
@ -134,6 +134,11 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
passwordValue, err := p.Password("Enter password")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dummyPassword, passwordValue)
|
||||
|
||||
// Ensure the dummy password is not printed to the screen,
|
||||
// asserting that echo mode is disabled.
|
||||
_, err = console.ExpectString(" \r\n\r\n")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Confirm", func(t *testing.T) {
|
||||
|
|
@ -192,6 +197,11 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
authValue, err := p.AuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dummyAuthToken, authValue)
|
||||
|
||||
// Ensure the dummy password is not printed to the screen,
|
||||
// asserting that echo mode is disabled.
|
||||
_, err = console.ExpectString(" \r\n\r\n")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("AuthToken - blank input returns error", func(t *testing.T) {
|
||||
|
|
@ -220,6 +230,11 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
authValue, err := p.AuthToken()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dummyAuthTokenForAfterFailure, authValue)
|
||||
|
||||
// Ensure the dummy password is not printed to the screen,
|
||||
// asserting that echo mode is disabled.
|
||||
_, err = console.ExpectString(" \r\n\r\n")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("ConfirmDeletion", func(t *testing.T) {
|
||||
|
|
@ -325,7 +340,7 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Expect a notice to enter something valid since blank is disallowed.
|
||||
_, err = console.ExpectString("invalid input. please try again")
|
||||
_, err = console.ExpectString("Invalid: must be between 1 and 1")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send a 1 to select to open the editor. This will immediately exit
|
||||
|
|
@ -352,7 +367,7 @@ func TestAccessiblePrompter(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Expect a notice to enter something valid since blank is disallowed.
|
||||
_, err = console.ExpectString("invalid input. please try again")
|
||||
_, err = console.ExpectString("Invalid: must be between 1 and 1")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send a 1 to select to open the editor since skip is invalid and
|
||||
|
|
|
|||
|
|
@ -137,10 +137,12 @@ func (p *accessiblePrompter) Input(prompt, defaultValue string) (string, error)
|
|||
|
||||
func (p *accessiblePrompter) Password(prompt string) (string, error) {
|
||||
var result string
|
||||
// EchoMode(huh.EchoModePassword) doesn't have any effect in accessible mode.
|
||||
// EchoModePassword is not used as password masking is unsupported in huh.
|
||||
// EchoModeNone and EchoModePassword have the same effect of hiding user input.
|
||||
form := p.newForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
EchoMode(huh.EchoModeNone).
|
||||
Title(prompt).
|
||||
Value(&result),
|
||||
),
|
||||
|
|
@ -171,9 +173,12 @@ func (p *accessiblePrompter) Confirm(prompt string, defaultValue bool) (bool, er
|
|||
|
||||
func (p *accessiblePrompter) AuthToken() (string, error) {
|
||||
var result string
|
||||
// EchoModeNone and EchoModePassword both result in disabling echo mode
|
||||
// as password masking is outside of VT100 spec.
|
||||
form := p.newForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
EchoMode(huh.EchoModeNone).
|
||||
Title("Paste your authentication token:").
|
||||
// Note: if this validation fails, the prompt loops.
|
||||
Validate(func(input string) error {
|
||||
|
|
@ -183,8 +188,6 @@ func (p *accessiblePrompter) AuthToken() (string, error) {
|
|||
return nil
|
||||
}).
|
||||
Value(&result),
|
||||
// This doesn't have any effect in accessible mode.
|
||||
// EchoMode(huh.EchoModePassword),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -245,6 +245,11 @@ func (f *finder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, err
|
|||
return preloadPrComments(httpClient, f.baseRefRepo, pr)
|
||||
})
|
||||
}
|
||||
if fields.Contains("closingIssuesReferences") {
|
||||
g.Go(func() error {
|
||||
return preloadPrClosingIssuesReferences(httpClient, f.baseRefRepo, pr)
|
||||
})
|
||||
}
|
||||
if fields.Contains("statusCheckRollup") {
|
||||
g.Go(func() error {
|
||||
return preloadPrChecks(httpClient, f.baseRefRepo, pr)
|
||||
|
|
@ -458,6 +463,45 @@ func preloadPrComments(client *http.Client, repo ghrepo.Interface, pr *api.PullR
|
|||
return nil
|
||||
}
|
||||
|
||||
func preloadPrClosingIssuesReferences(client *http.Client, repo ghrepo.Interface, pr *api.PullRequest) error {
|
||||
if !pr.ClosingIssuesReferences.PageInfo.HasNextPage {
|
||||
return nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Node struct {
|
||||
PullRequest struct {
|
||||
ClosingIssuesReferences api.ClosingIssuesReferences `graphql:"closingIssuesReferences(first: 100, after: $endCursor)"`
|
||||
} `graphql:"...on PullRequest"`
|
||||
} `graphql:"node(id: $id)"`
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"id": githubv4.ID(pr.ID),
|
||||
"endCursor": githubv4.String(pr.ClosingIssuesReferences.PageInfo.EndCursor),
|
||||
}
|
||||
|
||||
gql := api.NewClientFromHTTP(client)
|
||||
|
||||
for {
|
||||
var query response
|
||||
err := gql.Query(repo.RepoHost(), "closingIssuesReferences", &query, variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.ClosingIssuesReferences.Nodes = append(pr.ClosingIssuesReferences.Nodes, query.Node.PullRequest.ClosingIssuesReferences.Nodes...)
|
||||
|
||||
if !query.Node.PullRequest.ClosingIssuesReferences.PageInfo.HasNextPage {
|
||||
break
|
||||
}
|
||||
variables["endCursor"] = githubv4.String(query.Node.PullRequest.ClosingIssuesReferences.PageInfo.EndCursor)
|
||||
}
|
||||
|
||||
pr.ClosingIssuesReferences.PageInfo.HasNextPage = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func preloadPrChecks(client *http.Client, repo ghrepo.Interface, pr *api.PullRequest) error {
|
||||
if len(pr.StatusCheckRollup.Nodes) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func TestJSONFields(t *testing.T) {
|
|||
"changedFiles",
|
||||
"closed",
|
||||
"closedAt",
|
||||
"closingIssuesReferences",
|
||||
"comments",
|
||||
"commits",
|
||||
"createdAt",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue