Merge remote-tracking branch 'origin' into pr-lookup-refactor

This commit is contained in:
Mislav Marohnić 2021-05-17 17:41:38 +02:00
commit bc3bb97c43
52 changed files with 1128 additions and 385 deletions

View file

@ -6,13 +6,14 @@ import (
)
func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
v := reflect.ValueOf(issue).Elem()
data := map[string]interface{}{}
for _, f := range fields {
switch f {
case "milestone":
if issue.Milestone.Title != "" {
data[f] = &issue.Milestone
data[f] = map[string]string{"title": issue.Milestone.Title}
} else {
data[f] = nil
}
@ -25,7 +26,6 @@ func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
case "projectCards":
data[f] = issue.ProjectCards.Nodes
default:
v := reflect.ValueOf(issue).Elem()
sf := fieldByName(v, f)
data[f] = sf.Interface()
}
@ -35,6 +35,7 @@ func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
}
func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
v := reflect.ValueOf(pr).Elem()
data := map[string]interface{}{}
for _, f := range fields {
@ -43,7 +44,7 @@ func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
data[f] = map[string]string{"name": pr.HeadRepository.Name}
case "milestone":
if pr.Milestone.Title != "" {
data[f] = &pr.Milestone
data[f] = map[string]string{"title": pr.Milestone.Title}
} else {
data[f] = nil
}
@ -75,7 +76,6 @@ func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
}
data[f] = &requests
default:
v := reflect.ValueOf(pr).Elem()
sf := fieldByName(v, f)
data[f] = sf.Interface()
}
@ -84,22 +84,6 @@ func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
return &data
}
func ExportIssues(issues []Issue, fields []string) *[]interface{} {
data := make([]interface{}, len(issues))
for i := range issues {
data[i] = issues[i].ExportData(fields)
}
return &data
}
func ExportPRs(prs []PullRequest, fields []string) *[]interface{} {
data := make([]interface{}, len(prs))
for i := range prs {
data[i] = prs[i].ExportData(fields)
}
return &data
}
func fieldByName(v reflect.Value, field string) reflect.Value {
return v.FieldByNameFunc(func(s string) bool {
return strings.EqualFold(field, s)

View file

@ -90,31 +90,6 @@ func TestIssue_ExportData(t *testing.T) {
}
}
func TestExportIssues(t *testing.T) {
issues := []Issue{
{Milestone: Milestone{Title: "hi"}},
{},
}
exported := ExportIssues(issues, []string{"milestone"})
buf := bytes.Buffer{}
enc := json.NewEncoder(&buf)
enc.SetIndent("", "\t")
require.NoError(t, enc.Encode(exported))
assert.Equal(t, heredoc.Doc(`
[
{
"milestone": {
"title": "hi"
}
},
{
"milestone": null
}
]
`), buf.String())
}
func TestPullRequest_ExportData(t *testing.T) {
tests := []struct {
name string

53
api/export_repo.go Normal file
View file

@ -0,0 +1,53 @@
package api
import (
"reflect"
)
func (repo *Repository) ExportData(fields []string) *map[string]interface{} {
v := reflect.ValueOf(repo).Elem()
data := map[string]interface{}{}
for _, f := range fields {
switch f {
case "parent":
data[f] = miniRepoExport(repo.Parent)
case "templateRepository":
data[f] = miniRepoExport(repo.TemplateRepository)
case "languages":
data[f] = repo.Languages.Edges
case "labels":
data[f] = repo.Labels.Nodes
case "assignableUsers":
data[f] = repo.AssignableUsers.Nodes
case "mentionableUsers":
data[f] = repo.MentionableUsers.Nodes
case "milestones":
data[f] = repo.Milestones.Nodes
case "projects":
data[f] = repo.Projects.Nodes
case "repositoryTopics":
var topics []RepositoryTopic
for _, n := range repo.RepositoryTopics.Nodes {
topics = append(topics, n.Topic)
}
data[f] = topics
default:
sf := fieldByName(v, f)
data[f] = sf.Interface()
}
}
return &data
}
func miniRepoExport(r *Repository) map[string]interface{} {
if r == nil {
return nil
}
return map[string]interface{}{
"id": r.ID,
"name": r.Name,
"owner": r.Owner,
}
}

View file

@ -91,7 +91,10 @@ func (p ProjectCards) ProjectNames() []string {
}
type Milestone struct {
Title string `json:"title"`
Number int `json:"number"`
Title string `json:"title"`
Description string `json:"description"`
DueOn *time.Time `json:"dueOn"`
}
type IssuesDisabledError struct {
@ -241,7 +244,6 @@ func IssueByNumber(client *Client, repo ghrepo.Interface, number int) (*Issue, e
id
title
state
closed
body
author {
login

View file

@ -16,25 +16,100 @@ import (
// Repository contains information about a GitHub repo
type Repository struct {
ID string
Name string
Description string
URL string
CloneURL string
CreatedAt time.Time
Owner RepositoryOwner
ID string
Name string
NameWithOwner string
Owner RepositoryOwner
Parent *Repository
TemplateRepository *Repository
Description string
HomepageURL string
OpenGraphImageURL string
UsesCustomOpenGraphImage bool
URL string
SSHURL string
MirrorURL string
SecurityPolicyURL string
IsPrivate bool
HasIssuesEnabled bool
HasWikiEnabled bool
ViewerPermission string
DefaultBranchRef BranchRef
CreatedAt time.Time
PushedAt *time.Time
UpdatedAt time.Time
Parent *Repository
IsBlankIssuesEnabled bool
IsSecurityPolicyEnabled bool
HasIssuesEnabled bool
HasProjectsEnabled bool
HasWikiEnabled bool
MergeCommitAllowed bool
SquashMergeAllowed bool
RebaseMergeAllowed bool
MergeCommitAllowed bool
RebaseMergeAllowed bool
SquashMergeAllowed bool
ForkCount int
StargazerCount int
Watchers struct {
TotalCount int `json:"totalCount"`
}
Issues struct {
TotalCount int `json:"totalCount"`
}
PullRequests struct {
TotalCount int `json:"totalCount"`
}
CodeOfConduct *CodeOfConduct
ContactLinks []ContactLink
DefaultBranchRef BranchRef
DeleteBranchOnMerge bool
DiskUsage int
FundingLinks []FundingLink
IsArchived bool
IsEmpty bool
IsFork bool
IsInOrganization bool
IsMirror bool
IsPrivate bool
IsTemplate bool
IsUserConfigurationRepository bool
LicenseInfo *RepositoryLicense
ViewerCanAdminister bool
ViewerDefaultCommitEmail string
ViewerDefaultMergeMethod string
ViewerHasStarred bool
ViewerPermission string
ViewerPossibleCommitEmails []string
ViewerSubscription string
RepositoryTopics struct {
Nodes []struct {
Topic RepositoryTopic
}
}
PrimaryLanguage *CodingLanguage
Languages struct {
Edges []struct {
Size int `json:"size"`
Node CodingLanguage `json:"node"`
}
}
IssueTemplates []IssueTemplate
PullRequestTemplates []PullRequestTemplate
Labels struct {
Nodes []IssueLabel
}
Milestones struct {
Nodes []Milestone
}
LatestRelease *RepositoryRelease
AssignableUsers struct {
Nodes []GitHubUser
}
MentionableUsers struct {
Nodes []GitHubUser
}
Projects struct {
Nodes []RepoProject
}
// pseudo-field that keeps track of host name of this repo
hostname string
@ -42,12 +117,76 @@ type Repository struct {
// RepositoryOwner is the owner of a GitHub repository
type RepositoryOwner struct {
Login string
ID string `json:"id"`
Login string `json:"login"`
}
type GitHubUser struct {
ID string `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
}
// BranchRef is the branch name in a GitHub repository
type BranchRef struct {
Name string
Name string `json:"name"`
}
type CodeOfConduct struct {
Key string `json:"key"`
Name string `json:"name"`
URL string `json:"url"`
}
type RepositoryLicense struct {
Key string `json:"key"`
Name string `json:"name"`
Nickname string `json:"nickname"`
}
type ContactLink struct {
About string `json:"about"`
Name string `json:"name"`
URL string `json:"url"`
}
type FundingLink struct {
Platform string `json:"platform"`
URL string `json:"url"`
}
type CodingLanguage struct {
Name string `json:"name"`
}
type IssueTemplate struct {
Name string `json:"name"`
Title string `json:"title"`
Body string `json:"body"`
About string `json:"about"`
}
type PullRequestTemplate struct {
Filename string `json:"filename"`
Body string `json:"body"`
}
type RepositoryTopic struct {
Name string `json:"name"`
}
type RepositoryRelease struct {
Name string `json:"name"`
TagName string `json:"tagName"`
URL string `json:"url"`
PublishedAt time.Time `json:"publishedAt"`
}
type IssueLabel struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Color string `json:"color"`
}
// RepoOwner is the login name of the owner
@ -65,11 +204,6 @@ func (r Repository) RepoHost() string {
return r.hostname
}
// IsFork is true when this repository has a parent repository
func (r Repository) IsFork() bool {
return r.Parent != nil
}
// ViewerCanPush is true when the requesting user has push access
func (r Repository) ViewerCanPush() bool {
switch r.ViewerPermission {
@ -305,16 +439,26 @@ type repositoryV3 struct {
NodeID string
Name string
CreatedAt time.Time `json:"created_at"`
CloneURL string `json:"clone_url"`
Owner struct {
Login string
}
}
// ForkRepo forks the repository on GitHub and returns the new repository
func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
func ForkRepo(client *Client, repo ghrepo.Interface, org string) (*Repository, error) {
path := fmt.Sprintf("repos/%s/forks", ghrepo.FullName(repo))
body := bytes.NewBufferString(`{}`)
params := map[string]interface{}{}
if org != "" {
params["organization"] = org
}
body := &bytes.Buffer{}
enc := json.NewEncoder(body)
if err := enc.Encode(params); err != nil {
return nil, err
}
result := repositoryV3{}
err := client.REST(repo.RepoHost(), "POST", path, body, &result)
if err != nil {
@ -324,7 +468,6 @@ func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
return &Repository{
ID: result.NodeID,
Name: result.Name,
CloneURL: result.CloneURL,
CreatedAt: result.CreatedAt,
Owner: RepositoryOwner{
Login: result.Owner.Login,
@ -707,9 +850,10 @@ func RepoResolveMetadataIDs(client *Client, repo ghrepo.Interface, input RepoRes
}
type RepoProject struct {
ID string
Name string
ResourcePath string
ID string `json:"id"`
Name string `json:"name"`
Number int `json:"number"`
ResourcePath string `json:"resourcePath"`
}
// RepoProjects fetches all open projects for a repository

View file

@ -144,9 +144,9 @@ func Test_RepoMetadata(t *testing.T) {
func Test_ProjectsToPaths(t *testing.T) {
expectedProjectPaths := []string{"OWNER/REPO/PROJECT_NUMBER", "ORG/PROJECT_NUMBER"}
projects := []RepoProject{
{"id1", "My Project", "/OWNER/REPO/projects/PROJECT_NUMBER"},
{"id2", "Org Project", "/orgs/ORG/projects/PROJECT_NUMBER"},
{"id3", "Project", "/orgs/ORG/projects/PROJECT_NUMBER_2"},
{ID: "id1", Name: "My Project", ResourcePath: "/OWNER/REPO/projects/PROJECT_NUMBER"},
{ID: "id2", Name: "Org Project", ResourcePath: "/orgs/ORG/projects/PROJECT_NUMBER"},
{ID: "id3", Name: "Project", ResourcePath: "/orgs/ORG/projects/PROJECT_NUMBER_2"},
}
projectNames := []string{"My Project", "Org Project"}

View file

@ -186,3 +186,133 @@ func PullRequestGraphQL(fields []string) string {
}
return strings.Join(q, ",")
}
var RepositoryFields = []string{
"id",
"name",
"nameWithOwner",
"owner",
"parent",
"templateRepository",
"description",
"homepageUrl",
"openGraphImageUrl",
"usesCustomOpenGraphImage",
"url",
"sshUrl",
"mirrorUrl",
"securityPolicyUrl",
"createdAt",
"pushedAt",
"updatedAt",
"isBlankIssuesEnabled",
"isSecurityPolicyEnabled",
"hasIssuesEnabled",
"hasProjectsEnabled",
"hasWikiEnabled",
"mergeCommitAllowed",
"squashMergeAllowed",
"rebaseMergeAllowed",
"forkCount",
"stargazerCount",
"watchers",
"issues",
"pullRequests",
"codeOfConduct",
"contactLinks",
"defaultBranchRef",
"deleteBranchOnMerge",
"diskUsage",
"fundingLinks",
"isArchived",
"isEmpty",
"isFork",
"isInOrganization",
"isMirror",
"isPrivate",
"isTemplate",
"isUserConfigurationRepository",
"licenseInfo",
"viewerCanAdminister",
"viewerDefaultCommitEmail",
"viewerDefaultMergeMethod",
"viewerHasStarred",
"viewerPermission",
"viewerPossibleCommitEmails",
"viewerSubscription",
"repositoryTopics",
"primaryLanguage",
"languages",
"issueTemplates",
"pullRequestTemplates",
"labels",
"milestones",
"latestRelease",
"assignableUsers",
"mentionableUsers",
"projects",
// "branchProtectionRules", // too complex to expose
// "collaborators", // does it make sense to expose without affiliation filter?
}
func RepositoryGraphQL(fields []string) string {
var q []string
for _, field := range fields {
switch field {
case "codeOfConduct":
q = append(q, "codeOfConduct{key,name,url}")
case "contactLinks":
q = append(q, "contactLinks{about,name,url}")
case "fundingLinks":
q = append(q, "fundingLinks{platform,url}")
case "licenseInfo":
q = append(q, "licenseInfo{key,name,nickname}")
case "owner":
q = append(q, "owner{id,login}")
case "parent":
q = append(q, "parent{id,name,owner{id,login}}")
case "templateRepository":
q = append(q, "templateRepository{id,name,owner{id,login}}")
case "repositoryTopics":
q = append(q, "repositoryTopics(first:100){nodes{topic{name}}}")
case "issueTemplates":
q = append(q, "issueTemplates{name,title,body,about}")
case "pullRequestTemplates":
q = append(q, "pullRequestTemplates{body,filename}")
case "labels":
q = append(q, "labels(first:100){nodes{id,color,name,description}}")
case "languages":
q = append(q, "languages(first:100){edges{size,node{name}}}")
case "primaryLanguage":
q = append(q, "primaryLanguage{name}")
case "latestRelease":
q = append(q, "latestRelease{publishedAt,tagName,name,url}")
case "milestones":
q = append(q, "milestones(first:100,states:OPEN){nodes{number,title,description,dueOn}}")
case "assignableUsers":
q = append(q, "assignableUsers(first:100){nodes{id,login,name}}")
case "mentionableUsers":
q = append(q, "mentionableUsers(first:100){nodes{id,login,name}}")
case "projects":
q = append(q, "projects(first:100,states:OPEN){nodes{id,name,number,body,resourcePath}}")
case "watchers":
q = append(q, "watchers{totalCount}")
case "issues":
q = append(q, "issues(states:OPEN){totalCount}")
case "pullRequests":
q = append(q, "pullRequests(states:OPEN){totalCount}")
case "defaultBranchRef":
q = append(q, "defaultBranchRef{name}")
default:
q = append(q, field)
}
}
return strings.Join(q, ",")
}