Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/queries_issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ type ProjectCards struct {
}

type ProjectItems struct {
Nodes []*ProjectV2Item
Nodes []*ProjectV2Item
TotalCount int
}

type ProjectInfo struct {
Expand Down
10 changes: 6 additions & 4 deletions api/queries_projects_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ func ProjectsV2ItemsForIssue(client *Client, repo ghrepo.Interface, issue *Issue
Repository struct {
Issue struct {
ProjectItems struct {
Nodes []*projectV2Item
PageInfo struct {
TotalCount int
Nodes []*projectV2Item
PageInfo struct {
HasNextPage bool
EndCursor string
}
Expand Down Expand Up @@ -149,8 +150,9 @@ func ProjectsV2ItemsForPullRequest(client *Client, repo ghrepo.Interface, pr *Pu
Repository struct {
PullRequest struct {
ProjectItems struct {
Nodes []*projectV2Item
PageInfo struct {
TotalCount int
Nodes []*projectV2Item
PageInfo struct {
HasNextPage bool
EndCursor string
}
Expand Down
30 changes: 27 additions & 3 deletions internal/featuredetection/feature_detection.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/gh"
"github.com/hashicorp/go-version"
"golang.org/x/sync/errgroup"

ghauth "github.com/cli/go-gh/v2/pkg/auth"
Expand Down Expand Up @@ -205,12 +206,35 @@ func (d *detector) RepositoryFeatures() (RepositoryFeatures, error) {
return features, nil
}

const (
enterpriseProjectsV1Removed = "3.17.0"
)

func (d *detector) ProjectsV1() gh.ProjectsV1Support {
// Currently, projects v1 support is entirely dependent on the host. As this is deprecated in GHES,
// we will do feature detection on whether the GHES version has support.
if ghauth.IsEnterprise(d.host) {
if !ghauth.IsEnterprise(d.host) {
return gh.ProjectsV1Unsupported
}

hostVersion, hostVersionErr := resolveEnterpriseVersion(d.httpClient, d.host)
v1ProjectCutoffVersion, v1ProjectCutoffVersionErr := version.NewVersion(enterpriseProjectsV1Removed)

if hostVersionErr == nil && v1ProjectCutoffVersionErr == nil && hostVersion.LessThan(v1ProjectCutoffVersion) {
return gh.ProjectsV1Supported
}

return gh.ProjectsV1Unsupported
}

func resolveEnterpriseVersion(httpClient *http.Client, host string) (*version.Version, error) {
var metaResponse struct {
InstalledVersion string `json:"installed_version"`
}

apiClient := api.NewClientFromHTTP(httpClient)
err := apiClient.REST(host, "GET", "meta", nil, &metaResponse)
if err != nil {
return nil, err
}

return version.NewVersion(metaResponse.InstalledVersion)
}
74 changes: 63 additions & 11 deletions internal/featuredetection/feature_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,17 +373,69 @@ func TestRepositoryFeatures(t *testing.T) {
}

func TestProjectV1Support(t *testing.T) {
t.Parallel()
tests := []struct {
name string
hostname string
httpStubs func(*httpmock.Registry)
wantFeatures gh.ProjectsV1Support
}{
{
name: "github.com",
hostname: "github.com",
wantFeatures: gh.ProjectsV1Unsupported,
},
{
name: "ghec data residency (ghe.com)",
hostname: "stampname.ghe.com",
wantFeatures: gh.ProjectsV1Unsupported,
},
{
name: "GHE 3.16.0",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.16.0"}`),
)
},
wantFeatures: gh.ProjectsV1Supported,
},
{
name: "GHE 3.16.1",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.16.1"}`),
)
},
wantFeatures: gh.ProjectsV1Supported,
},
{
name: "GHE 3.17",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.17.0"}`),
)
},
wantFeatures: gh.ProjectsV1Unsupported,
},
}

t.Run("when the host is enterprise, project v1 is supported", func(t *testing.T) {
detector := detector{host: "my.ghes.com"}
isProjectV1Supported := detector.ProjectsV1()
require.Equal(t, gh.ProjectsV1Supported, isProjectV1Supported)
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
reg := &httpmock.Registry{}
if tt.httpStubs != nil {
tt.httpStubs(reg)
}
httpClient := &http.Client{}
httpmock.ReplaceTripper(httpClient, reg)

t.Run("when the host is not enterprise, project v1 is not supported", func(t *testing.T) {
detector := detector{host: "github.com"}
isProjectV1Supported := detector.ProjectsV1()
require.Equal(t, gh.ProjectsV1Unsupported, isProjectV1Supported)
})
detector := NewDetector(httpClient, tt.hostname)
require.Equal(t, tt.wantFeatures, detector.ProjectsV1())
})
}
}
20 changes: 17 additions & 3 deletions pkg/cmd/issue/view/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func viewRun(opts *ViewOptions) error {
opts.Detector = fd.NewDetector(cachedClient, baseRepo.RepoHost())
}

lookupFields.Add("projectItems")
projectsV1Support := opts.Detector.ProjectsV1()
if projectsV1Support == gh.ProjectsV1Supported {
lookupFields.Add("projectCards")
Expand Down Expand Up @@ -310,11 +311,24 @@ func issueAssigneeList(issue api.Issue) string {
}

func issueProjectList(issue api.Issue) string {
if len(issue.ProjectCards.Nodes) == 0 {
totalCount := issue.ProjectCards.TotalCount + issue.ProjectItems.TotalCount
count := len(issue.ProjectCards.Nodes) + len(issue.ProjectItems.Nodes)

if count == 0 {
return ""
}

projectNames := make([]string, 0, len(issue.ProjectCards.Nodes))
projectNames := make([]string, 0, count)

for _, project := range issue.ProjectItems.Nodes {
colName := project.Status.Name
if colName == "" {
colName = "No Status"
}
projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Title, colName))
}

// TODO: Remove v1 classic project logic when completely deprecated
for _, project := range issue.ProjectCards.Nodes {
colName := project.Column.Name
if colName == "" {
Expand All @@ -324,7 +338,7 @@ func issueProjectList(issue api.Issue) string {
}

list := strings.Join(projectNames, ", ")
if issue.ProjectCards.TotalCount > len(issue.ProjectCards.Nodes) {
if totalCount > count {
list += ", …"
}
return list
Expand Down
Loading
Loading